//****************************************************************
// Read/write Open Document Format (spreadsheet)
// Author:  Ruslan V. Neborak
// e-mail:  avemey@tut.by
// URL:     http://avemey.com
// License: zlib
// Last update: 2012.03.31
//----------------------------------------------------------------
{
 Copyright (C) 2012 Ruslan Neborak

  This software is provided 'as-is', without any express or implied
 warranty. In no event will the authors be held liable for any damages
 arising from the use of this software.

 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it
 freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.

    2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.

    3. This notice may not be removed or altered from any source
    distribution.
}
//****************************************************************


unit zeodfs;

interface

{$I zexml.inc}
{$I compver.inc}

{$IFDEF FPC}
  {$mode objfpc}{$H+}
{$ENDIF}

uses
  SysUtils, classes, zsspxml, zexmlss, zesavecommon, graphics
  {$IFDEF FPC},zipper{$ENDIF};

//Сохраняет незапакованный документ в формате Open Document
function SaveXmlssToODFSPath(var XMLSS: TZEXMLSS; PathName: string; const SheetsNumbers:array of integer;
                         const SheetsNames: array of string; TextConverter: TAnsiToCPConverter; CodePageName: string; BOM: ansistring = ''): integer; overload;

{$IFDEF FPC}
function SaveXmlssToODFS(var XMLSS: TZEXMLSS; FileName: string; const SheetsNumbers:array of integer;
                         const SheetsNames: array of string; TextConverter: TAnsiToCPConverter; CodePageName: string; BOM: ansistring = ''): integer; overload;
{$ENDIF}
implementation

uses Types;

const
  ZETag_StyleFontFace       = 'style:font-face';      //style:font-face
  ZETag_Attr_StyleName      = 'style:name';           //style:name

//BooleanToStr для ODF //TODO: потом заменить
function ODFBoolToStr(value: boolean): string;
begin
  if (value) then
    result := 'true'
  else
    result := 'false';
end;

//Добавляет атрибуты для тэга office:document-content
procedure GenODContentAttr(Attr: TZAttributesH);
begin
  Attr.Add('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0', false);
  Attr.Add('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0', false);
  Attr.Add('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0', false);
  Attr.Add('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0', false);
  Attr.Add('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0', false);
  Attr.Add('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0', false);
  Attr.Add('xmlns:xlink', 'http://www.w3.org/1999/xlink', false);
  Attr.Add('xmlns:dc', 'http://purl.org/dc/elements/1.1/', false);
  Attr.Add('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0', false);
  Attr.Add('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0', false);
  Attr.Add('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0', false);
  Attr.Add('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0', false);
  Attr.Add('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0', false);
  Attr.Add('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0', false);
  Attr.Add('xmlns:math', 'http://www.w3.org/1998/Math/MathML', false);
  Attr.Add('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0', false);
  Attr.Add('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0', false);
  Attr.Add('xmlns:ooo', 'http://openoffice.org/2004/office', false);
  Attr.Add('xmlns:ooow', 'http://openoffice.org/2004/writer', false);
  Attr.Add('xmlns:oooc', 'http://openoffice.org/2004/calc', false);
  Attr.Add('xmlns:dom', 'http://www.w3.org/2001/xml-events', false);
  Attr.Add('xmlns:xforms', 'http://www.w3.org/2002/xforms', false);
  Attr.Add('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema', false);
  Attr.Add('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance', false);
  Attr.Add('xmlns:rpt', 'http://openoffice.org/2005/report', false);
  Attr.Add('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2', false);
  Attr.Add('xmlns:xhtml', 'http://www.w3.org/1999/xhtml', false);
  Attr.Add('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#', false);
  Attr.Add('xmlns:tableooo', 'http://openoffice.org/2009/table', false);
  Attr.Add('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0', false);
  Attr.Add('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0', false);
  Attr.Add('xmlns:css3t', 'http://www.w3.org/TR/css3-text/', false);
  Attr.Add('office:version', '1.2', false);
end; //GenODContentAttr

//добавляет атрибуты для тэга office:document-meta
procedure GenODMetaAttr(Attr: TZAttributesH);
begin
  Attr.Add('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0', false);
  Attr.Add('xmlns:xlink', 'http://www.w3.org/1999/xlink', false);
  Attr.Add('xmlns:dc', 'http://purl.org/dc/elements/1.1/', false);
  Attr.Add('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0', false);
  Attr.Add('xmlns:ooo', 'http://openoffice.org/2004/office', false);
  Attr.Add('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#', false);
  Attr.Add('office:version', '1.2', false);
end; //GenODMetaAttr

//добавляет атрибуты для тэга office:document-styles (styles.xml)
procedure GenODStylesAttr(Attr: TZAttributesH);
begin
  Attr.Add('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
  Attr.Add('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
  Attr.Add('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
  Attr.Add('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
  Attr.Add('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
  Attr.Add('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
  Attr.Add('xmlns:xlink', 'http://www.w3.org/1999/xlink');
  Attr.Add('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
  Attr.Add('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
  Attr.Add('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
  Attr.Add('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
  Attr.Add('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
  Attr.Add('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
  Attr.Add('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
  Attr.Add('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
  Attr.Add('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
  Attr.Add('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
  Attr.Add('xmlns:ooo', 'http://openoffice.org/2004/office');
  Attr.Add('xmlns:ooow', 'http://openoffice.org/2004/writer'); //???
  Attr.Add('xmlns:oooc', 'http://openoffice.org/2004/calc');
  Attr.Add('xmlns:dom', 'http://www.w3.org/2001/xml-events');
  Attr.Add('xmlns:rpt', 'http://openoffice.org/2005/report');
  Attr.Add('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
  Attr.Add('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
  Attr.Add('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
  Attr.Add('xmlns:tableooo', 'http://openoffice.org/2009/table');
  Attr.Add('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
  Attr.Add('office:version', '1.2');
end; //GenODStylesAttr

//<office:font-face-decls> ... </office:font-face-decls>
procedure ZEWriteFontFaceDecls(_xml: TZsspXMLWriterH);
begin
  _xml.WriteTagNode('office:font-face-decls', true, true, true);
  _xml.Attributes.Add(ZETag_Attr_StyleName, 'Arial', true);
  _xml.Attributes.Add('svg:font-family', 'Arial', true);
  _xml.Attributes.Add('style:font-family-generic', 'swiss', true);
  _xml.Attributes.Add('style:font-pitch', 'variable', true);
  _xml.WriteEmptyTag(ZETag_StyleFontFace, true, false);

  _xml.Attributes.ItemsByNum[0] := 'Mangal';
  _xml.Attributes.ItemsByNum[1] := 'Mangal';
  _xml.Attributes.ItemsByNum[2] := 'system';
  _xml.WriteEmptyTag(ZETag_StyleFontFace, true, false);

  _xml.Attributes.ItemsByNum[0] := 'Tahoma';
  _xml.Attributes.ItemsByNum[1] := 'Tahoma';
  _xml.WriteEmptyTag(ZETag_StyleFontFace, true, false);
  _xml.Attributes.Clear();
  _xml.WriteEndTagNode(); //office:font-face-decls
end; //WriteFontFaceDecls

//Переводит стиль границы в строку для ODF
//INPUT
//      BStyle: TZBorderStyle - стиль границы
function ZEODFBorderStyleTostr(BStyle: TZBorderStyle): string;
var
  s: string;

begin
  result := '';
  if (Assigned(BStyle)) then
  begin
    //не забыть потом толщину подправить {tut}
    case (BStyle.Weight) of
      0: result := '0pt';
      1: result := '0.26pt';
      2: result := '2.49pt';
      3: result := '4pt';
      else
       result := '0.99pt';
    end;
    s := '';
    case (BStyle.LineStyle) of
      ZENone: s := ' ';
      ZEContinuous: s := ' solid';
      ZEDot: s := ' dotted';
      ZEDash: s := ' dashed';
      ZEDashDot: s := ' ';
      ZEDashDotDot: s := ' ';
      ZESlantDashDot: s := ' ';
      ZEDouble: s := ' double';
    end;
    result := result + s + ' #' + ColorToHTMLHex(BStyle.Color);
  end;
end; //ZEODFBorderStyleTostr

//Записывает настройки стиля
//INPUT
//  var XMLSS: TZEXMLSS           - хранилище
//      _xml: TZsspXMLWriterH     - писатель
//      StyleNum: integer         - номер стиля
//      isDefaultStyle: boolean   - является-ли данный стиль стилем по-умолчанию
procedure ODFWriteTableStyle(var XMLSS: TZEXMLSS; _xml: TZsspXMLWriterH; StyleNum: integer; isDefaultStyle: boolean);
var
  b: boolean;
  s, satt: string;
  j, n: integer;

begin
{
     <attribute name="style:family"><value>table-cell</value>
     Доступны тэги:
        style:table-cell-properties
        style:paragraph-properties
        style:text-properties
}

  //Тэг style:table-cell-properties
  //Возможные атрибуты:
  //    style:vertical-align - выравниванние по вертикали (top | middle | bottom | automatic)
  //??  style:text-align-source - источник выравнивания текста (fix | value-type)
  //??  style:direction - направление символов в ячейке (ltr | ttb) слева-направо и сверху-вниз
  //??  style:glyph-orientation-vertical - ориентация глифа по вертикали
  //??  style:shadow - применяется эффект тени
  //    fo:background-color - цвет фона ячейки
  //    fo:border           - [
  //    fo:border-top       -
  //    fo:border-bottom    -   обрамление ячейки
  //    fo:border-left      -
  //    fo:border-right     -  ]
  //    style:diagonal-tl-br - диагональ верхний левый правый нижний
  //       style:diagonal-bl-tr-widths
  //    style:diagonal-bl-tr - диагональ нижний левый правый верхний угол
  //       style:diagonal-tl-br-widths
  //    style:border-line-width         -  [
  //    style:border-line-width-top     -
  //    style:border-line-width-bottom  -   толщина линии обрамления
  //    style:border-line-width-left    -
  //    style:border-line-width-right   -  ]
  //    fo:padding          - [
  //    fo:padding-top      -
  //    fo:padding-bottom   -  отступы
  //    fo:padding-left     -
  //    fo:padding-right    - ]
  //    fo:wrap-option  - свойство переноса по словам (no-wrap | wrap)
  //    style:rotation-angle - угол поворота (int >= 0)
  //??  style:rotation-align - выравнивание после поворота (none | bottom | top | center)
  //??  style:cell-protect - (none | hidden­and­protected ?? protected | formula­hidden)
  //??  style:print-content - выводить ли на печать содержимое ячейки (bool)
  //??  style:decimal-places - кол-во дробных разрядов
  //??  style:repeat-content - повторять-ли содержимое ячейки (bool)
  //    style:shrink-to-fit - подгонять ли содержимое по размеру, если текст не помещается (bool)

  _xml.Attributes.Clear();
  b := true;
  for j := 1 to 3 do
    if (not XMLSS.Styles[StyleNum].Border[j].IsEqual(XMLSS.Styles[StyleNum].Border[0])) then
    begin
      b := false;
      break;
    end;

  n := 0;
  if (b) then
  begin
    n := 4;
    if (not((XMLSS.Styles[StyleNum].Border[0].LineStyle = ZEContinuous) and (XMLSS.Styles[StyleNum].Border[0].Weight = 0))) then
    begin
      s := ZEODFBorderStyleTostr(XMLSS.Styles[StyleNum].Border[0]);
      _xml.Attributes.Add('fo:border', s);
    end;
  end;

  for j := n to 5 do
  begin
    case (j) of
      0: satt := 'fo:border-left';
      1: satt := 'fo:border-top';
      2: satt := 'fo:border-right';
      3: satt := 'fo:border-bottom';
      4: satt := 'style:diagonal-bl-tr';
      5: satt := 'style:diagonal-tl-br';
    end;
    if (not((XMLSS.Styles[StyleNum].Border[j].LineStyle = ZEContinuous) and (XMLSS.Styles[StyleNum].Border[j].Weight = 0))) then
    begin
      s := ZEODFBorderStyleTostr(XMLSS.Styles[StyleNum].Border[j]);
      _xml.Attributes.Add(satt, s);
    end;
  end;

  //Выравнивание по вертикали
  case (XMLSS.Styles[StyleNum].Alignment.Vertical) of
    ZVAutomatic: s := 'automatic';
    ZVTop: s := 'top';
    ZVBottom: s := 'bottom';
    ZVCenter: s := 'middle';
    ZVJustify, ZVDistributed, ZVJustifyDistributed: s := 'automatic'; //потом подумать
    //(top | middle | bottom | automatic)
  end;
  _xml.Attributes.Add('style:vertical-align', s);

  //Поворот текста
  if (XMLSS.Styles[StyleNum].Alignment.Rotate <> 0) then
    _xml.Attributes.Add('style:rotation-angle', IntToStr(XMLSS.Styles[StyleNum].Alignment.Rotate));

  //Цвет фона ячейки
  if ((XMLSS.Styles[StyleNum].BGColor <> XMLSS.Styles.DefaultStyle.BGColor) or (isDefaultStyle)) then
    if (XMLSS.Styles[StyleNum].BGColor <> clWindow) then
      _xml.Attributes.Add('fo:background-color', '#' + ColorToHTMLHex(XMLSS.Styles[StyleNum].BGColor));

  //style:shrink-to-fit
  if (XMLSS.Styles[StyleNum].Alignment.ShrinkToFit) then
    _xml.Attributes.Add('style:shrink-to-fit', ODFBoolToStr(true));

  _xml.WriteEmptyTag('style:table-cell-properties', true, true);

  //*************
  //Тэг style-paragraph-properties
  //Возможные атрибуты:
  //??  fo:line-height - фиксированная высота строки
  //??  style:line-height-at-least - минимальная высотка строки
  //??  style:line-spacing - межстрочный интервал
  //??  style:font-independent-line-spacing - независимый от шрифта межстрочный интервал (bool)
  //    fo:text-align - выравнивание текста (start | end | left | right | center | justify)
  //??  fo:text-align-last - выравнивание текста в последней строке (start | center | justify)
  //??  style:justify-single-word - выравнивать-ли последнее слово (bool)
  //??  fo:keep-together - не разрывать (auto | always)
  //??  fo:widows - int > 0
  //??  fo:orphans - int > 0
  //??  style:tab-stop-distance - int > 0
  //??  fo:hyphenation-keep
  //??  fo:hyphenation-ladder-count
  //

  _xml.Attributes.Clear();
  //Выравнивание по горизонтали
  case (XMLSS.Styles[StyleNum].Alignment.Horizontal) of
    ZHAutomatic: s := 'start';//'right';
    ZHLeft: s := 'start';//'left';
    ZHCenter: s := 'center';
    ZHRight: s := 'end';//'right';
    ZHFill: s := 'start';
    ZHJustify: s := 'justify';
    ZHCenterAcrossSelection: s := 'center';
    ZHDistributed: s := 'center';
    ZHJustifyDistributed: s := 'justify';
  end;
  _xml.Attributes.Add('fo:text-align', s);
  _xml.WriteEmptyTag('style:paragraph-properties', true, true);

  //*************
  //Тэг style:text-properties
  //Возможные атрибуты:
  //??  fo:font-variant - отображение текста прописныим буквами (normal | small-caps). Взаимоисключается с fo:text-transform
  //??  fo:text-transform - преобразование текста (none | lowercase | uppercase | capitalize)
  //    fo:color - цвет переднего плана текста
  //??  style:use-window-font-color - должен ли цвет переднего плана окна быть цветом фона для светлого фона и белый для тёмного фона (bool)
  //??  style:text-outline - показывать ли структуру текста или текст (bool)
  //    style:text-line-through-type - тип линии зачёркивания текста (none | single | double)
  //    style:text-line-through-style - (none | single | double) ??
  //??  style:text-line-through-width - толщина зачёркивания
  //??  style:text-line-through-color - цвет зачёркивания
  //??  style:text-line-through-text
  //??  style:text-line-through-text-style
  //??  style:text-position - (super | sub ?? percent)
  //     style:font-name          - [
  //     style:font-name-asian    - название шрифта
  //     style:font-name-complex  - ]
  //      fo:font-family            - [
  //      style:font-family-asian   -  семейство шрифтов
  //      style:font-family-complex - ]
  //      style:font-family-generic         - [
  //      style:font-family-generic-asian   - Группа семейства шрифтов (roman | swiss | modern | decorative | script | system)
  //      style:font-family-generic-complex - ]
  //    style:font-style-name         - [
  //    style:font-style-name-asian   - стиль шрифта
  //    style:font-style-name-complex - ]
  //??  style:font-pitch          - [
  //??  style:font-pitch-asian    - шаг шрифта (fixed | variable)
  //??  style:font-pitch-complex  - ]
  //??  style:font-charset          - [
  //??  style:font-charset-asian    - набор символов
  //??  style:font-charset-complex  - ]
  //    fo:font-size            - [
  //    style:font-size-asian   -  размер шрифта
  //    style:font-size-complex - ]
  //??  style:font-size-rel         - [
  //??  style:font-size-rel-asian   - масштаб шрифта
  //??  style:font-size-rel-complex - ]
  //??  style:script-type
  //??  fo:letter-spacing - межбуквенный интервал
  //??  fo:language         - [
  //??  fo:language-asian   - код языка
  //??  fo:language-complex - ]
  //??  fo:country            - [
  //??  style:country-asian   - код страны
  //??  style:country-complex - ]
  //    fo:font-style             - [
  //    style:font-style-asian    - стиль шрифта (normal | italic | oblique)
  //    style:font-style-complex  - ]
  //??  style:font-relief - рельефтность (выпуклый, высеченый, плоский) (none | embossed | engraved)
  //??  fo:text-shadow - тень
  //    style:text-underline-type - тип подчёркивания (none | single | double)
  //??  style:text-underline-style - стиль подчёркивания (none | solid | dotted | dash | long-dash | dot-dash | dot-dot-dash | wave)
  //??  style:text-underline-width - толщина подчёркивания (auto | norma | bold | thin | dash | medium | thick ?? int>0)
  //??  style:text-underline-color - цвет подчёркивания
  //    fo:font-weight            - [
  //    style:font-weight-asian   - жирность (normal | bold | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900)
  //    style:font-weight-complex - ]
  //??  style:text-underline-mode - режим подчёркивания слов (continuous | skip-white-space)
  //??  style:text-line-through-mode - режим зачёркивания слов (continuous | skip-white-space)
  //??  style:letter-kerning - кернинг (bool)
  //??  style:text-blinking - мигание текста (bool)
  //**  fo:background-color - цвет фона текста
  //??  style:text-combine - объединение текста (none | letters | lines)
  //??  style:text-combine-start-char
  //??  style:text-combine-end-char
  //??  *tyle:text-emphasize - вроде как для иероглифов выделение (none | accent | dot | circle | disc) + (above | below) (пример: "dot above")
  //??  style:text-scale - масштаб
  //??  style:text-rotation-angle - угол вращения текста (0 | 90 | 270)
  //??  style:text-rotation-scale - масштабирование при вращении (fixed | line-height)
  //??  fo:hyphenate - расстановка переносов
  //    text:display - показывать-ли текст (true - да, none - скрыть, condition - в зависимости от атрибута text:condition)
  _xml.Attributes.Clear();

  //style:font-name
  if ((XMLSS.Styles[StyleNum].Font.Name <> XMLSS.Styles.DefaultStyle.Font.Name) or (isDefaultStyle)) then
  begin
    s := XMLSS.Styles[StyleNum].Font.Name;
    _xml.Attributes.Add('style:font-name', s);
    _xml.Attributes.Add('style:font-name-asian', s);
    _xml.Attributes.Add('style:font-name-complex', s);
  end;

  //размер шрифта
  if ((XMLSS.Styles[StyleNum].Font.Size <> XMLSS.Styles.DefaultStyle.Font.Size) or (isDefaultStyle)) then
  begin
    s := IntToStr(XMLSS.Styles[StyleNum].Font.Size) + 'pt';
    _xml.Attributes.Add('fo:font-size', s);
    _xml.Attributes.Add('style:font-size-asian', s);
    _xml.Attributes.Add('style:font-size-complex', s);
  end;

  //Жирность
  if (fsBold in XMLSS.Styles[StyleNum].Font.Style) then
  begin
    s := 'bold';
    _xml.Attributes.Add('fo:font-weight', s);
    _xml.Attributes.Add('style:font-weight-asian', s);
    _xml.Attributes.Add('style:font-weight-complex', s);
  end;

  //перечёркнутый текст
  if (fsStrikeOut in XMLSS.Styles[StyleNum].Font.Style) then
    _xml.Attributes.Add('style:text-line-through-type', 'single'); //(none | single | double)

  //Подчёркнутый текст
  if (fsUnderline in XMLSS.Styles[StyleNum].Font.Style) then
    _xml.Attributes.Add('style:text-underline-type', 'single'); //(none | single | double)

  if (fsItalic in XMLSS.Styles[StyleNum].Font.Style) then
  begin
    s := 'italic';
    _xml.Attributes.Add('fo:font-style', s);
    _xml.Attributes.Add('style:font-style-asian', s);
    _xml.Attributes.Add('style:font-style-complex', s);
  end;

  _xml.WriteEmptyTag('style:text-properties', true, true);
end; //ODFWriteTableStyle

//Записывает в поток стили документа (styles.xml)
//INPUT
//  var XMLSS: TZEXMLSS                 - хранилище
//    Stream: TStream                   - поток для записи
//  const _pages: TZESaveIntArray       - массив страниц
//  const _names: TZESaveStrArray       - массив имён страниц
//    PageCount: integer                - кол-во страниц
//    TextConverter: TAnsiToCPConverter - конвертер из локальной кодировки в нужную
//    CodePageName: string              - название кодовой страници
//    BOM: ansistring                   - BOM
//RETURN
//      integer
function ODFCreateStyles(var XMLSS: TZEXMLSS; Stream: TStream; const _pages: TZESaveIntArray;
                          const _names: TZESaveStrArray; PageCount: integer; TextConverter: TAnsiToCPConverter; CodePageName: string; BOM: ansistring): integer;
var
  _xml: TZsspXMLWriterH; 

begin
  result := 0;
  _xml := nil;
  try
    _xml := TZsspXMLWriterH.Create();
    _xml.TabLength := 1;
    _xml.TextConverter := TextConverter;
    _xml.TabSymbol := ' ';
    if (not _xml.BeginSaveToStream(Stream)) then
    begin
      result := 2;
      exit;
    end;

    ZEWriteHeaderCommon(_xml, CodePageName, BOM);
    GenODStylesAttr(_xml.Attributes);
    _xml.WriteTagNode('office:document-styles', true, true, true);
    _xml.Attributes.Clear();
    ZEWriteFontFaceDecls(_xml);

    //office:styles
    _xml.Attributes.Clear();
    _xml.WriteTagNode('office:styles', true, true, true);

    //Стиль по-умолчанию
    _xml.Attributes.Clear();
    _xml.Attributes.Add('style:name', 'Default');
    _xml.Attributes.Add('style:family', 'table-cell');
    _xml.WriteTagNode('style:style', true, true, true);
    ODFWriteTableStyle(XMLSS, _xml, -1, true);
    _xml.WriteEndTagNode();

    _xml.WriteEndTagNode(); //office:styles

    _xml.WriteEndTagNode(); //office:document-styles
  finally
    if (Assigned(_xml)) then
      FreeAndNil(_xml);
  end;
end; //ODFCreateStyles

//Записывает в поток настройки (settings.xml)
//INPUT
//  var XMLSS: TZEXMLSS                 - хранилище
//    Stream: TStream                   - поток для записи
//  const _pages: TZESaveIntArray       - массив страниц
//  const _names: TZESaveStrArray       - массив имён страниц
//    PageCount: integer                - кол-во страниц
//    TextConverter: TAnsiToCPConverter - конвертер из локальной кодировки в нужную
//    CodePageName: string              - название кодовой страници
//    BOM: ansistring                   - BOM
//RETURN
//      integer
function ODFCreateSettings(var XMLSS: TZEXMLSS; Stream: TStream; const _pages: TZESaveIntArray;
                          const _names: TZESaveStrArray; PageCount: integer; TextConverter: TAnsiToCPConverter; CodePageName: string; BOM: ansistring): integer;
var
  _xml: TZsspXMLWriterH; 

begin
  result := 0;
  _xml := nil;
  try
    _xml := TZsspXMLWriterH.Create();
    _xml.TabLength := 1;
    _xml.TextConverter := TextConverter;
    _xml.TabSymbol := ' ';
    if (not _xml.BeginSaveToStream(Stream)) then
    begin
      result := 2;
      exit;
    end;

    ZEWriteHeaderCommon(_xml, CodePageName, BOM);
    _xml.Attributes.Add('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
    _xml.Attributes.Add('xmlns:xlink', 'http://www.w3.org/1999/xlink');
    _xml.Attributes.Add('xmlns:config', 'urn:oasis:names:tc:opendocument:xmlns:config:1.0');
    _xml.Attributes.Add('xmlns:ooo', 'http://openoffice.org/2004/office');
    _xml.Attributes.Add('office:version', '1.2');
    _xml.WriteTagNode('office:document-settings', true, true, true);
    _xml.Attributes.Clear();
    _xml.WriteTagNode('office:settings', true, true, true);
    //
    //
    _xml.WriteEndTagNode(); //office:settings
    _xml.WriteEndTagNode(); //office:document-settings
  finally
    if (Assigned(_xml)) then
      FreeAndNil(_xml);
  end;
end; //ODFCreateSettings

//Записывает в поток документ + автоматические стили (content.xml)
//INPUT
//  var XMLSS: TZEXMLSS                 - хранилище
//    Stream: TStream                   - поток для записи
//  const _pages: TZESaveIntArray       - массив страниц
//  const _names: TZESaveStrArray       - массив имён страниц
//    PageCount: integer                - кол-во страниц
//    TextConverter: TAnsiToCPConverter - конвертер из локальной кодировки в нужную
//    CodePageName: string              - название кодовой страници
//    BOM: ansistring                   - BOM
//RETURN
//      integer
function ODFCreateContent(var XMLSS: TZEXMLSS; Stream: TStream; const _pages: TZESaveIntArray;
                          const _names: TZESaveStrArray; PageCount: integer; TextConverter: TAnsiToCPConverter; CodePageName: string; BOM: ansistring): integer;
var
  _xml: TZsspXMLWriterH;
  ColumnStyle, RowStyle: array of array of integer;  //стили столбцов/строк
  i: integer;

  //Заголовок для content.xml
  procedure WriteHeader();
  var
    i, j: integer;
    kol: integer;
    n: integer;
    ColStyleNumber, RowStyleNumber: integer;
    
    //Стили для колонок
    procedure WriteColumnStyle(now_i, now_j, now_StyleNumber, count_i, count_j: integer);
    var
      i, j: integer;
      start_j: integer;
      b: boolean;
      s: string;

    begin
      if (ColumnStyle[now_i][now_j] > -1) then
        exit;
      _xml.Attributes.Clear();
      _xml.Attributes.Add('style:name', 'co' + IntToStr(now_StyleNumber));
      _xml.Attributes.Add('style:family', 'table-column');
      _xml.WriteTagNode('style:style', true, true, true);

      _xml.Attributes.Clear();
      //разрыв страницы (fo:break-before = auto | column | page)
      s := 'auto';
      if (XMLSS.Sheets[_pages[now_i]].Columns[now_j].Breaked) then
        s := 'column';
      _xml.Attributes.Add('fo:break-before', s);
      //Ширина колонки style:column-width
      _xml.Attributes.Add('style:column-width', ZEFloatSeparator(FormatFloat('0.###', XMLSS.Sheets[_pages[now_i]].Columns[now_j].WidthMM / 10)) + 'cm');
      _xml.WriteEmptyTag('style:table-column-properties', true);

      _xml.WriteEndTagNode(); //style:style
      start_j := now_j + 1;
      ColumnStyle[now_i][now_j] := now_StyleNumber;
      for i := now_i to count_i do
      begin
        for j := start_j to XMLSS.Sheets[_pages[i]].ColCount - 1{count_j} do
          if (ColumnStyle[i][j] = -1) then
          begin
            b := true;
            //style:column-width
            if (XMLSS.Sheets[_pages[i]].Columns[j].WidthPix <> XMLSS.Sheets[_pages[now_i]].Columns[now_j].WidthPix) then
              b := false;
            //fo:break-before
            if (XMLSS.Sheets[_pages[i]].Columns[j].Breaked <> XMLSS.Sheets[_pages[now_i]].Columns[now_j].Breaked) then
              b := false;

            if (b) then
              ColumnStyle[i][j] := now_StyleNumber;
          end;

        start_j := 0;
      end;
    end; //WriteColumnStyle

    //Стили для строк
    procedure WriteRowStyle(now_i, now_j, now_StyleNumber, count_i, count_j: integer);
    var
      i, j, k: integer;
      start_j: integer;
      b: boolean;
      s: string;

    begin
      if (RowStyle[now_i][now_j] > -1) then
        exit;
      _xml.Attributes.Clear();
      _xml.Attributes.Add('style:name', 'ro' + IntToStr(now_StyleNumber));
      _xml.Attributes.Add('style:family', 'table-row');
      _xml.WriteTagNode('style:style', true, true, true);

      _xml.Attributes.Clear();
      //разрыв страницы (fo:break-before = auto | column | page)
      s := 'auto';
      if (XMLSS.Sheets[_pages[now_i]].Rows[now_j].Breaked) then
        s := 'page';
      _xml.Attributes.Add('fo:break-before', s);
      //высота строки style:row-height
      _xml.Attributes.Add('style:row-height', ZEFloatSeparator(FormatFloat('0.###', XMLSS.Sheets[_pages[now_i]].Rows[now_j].HeightMM / 10)) + 'cm');
       //?? style:min-row-height
      //style:use-optimal-row-height - пересчитывать ли высоту, если содержимое ячеек изменилось
      if (abs(XMLSS.Sheets[_pages[now_i]].Rows[now_j].Height - XMLSS.Sheets[_pages[now_i]].DefaultRowHeight) < 0.001) then
        _xml.Attributes.Add('style:use-optimal-row-height', ODFBoolToStr(true));
      //fo:background-color - цвет фона
      k := XMLSS.Sheets[_pages[now_i]].Rows[now_j].StyleID;
      if (k > -1) then
        if (XMLSS.Styles.Count - 1 >= k) then
          if (XMLSS.Styles[k].BGColor <> XMLSS.Styles[-1].BGColor) then
            _xml.Attributes.Add('fo:background-color', '#' + ColorToHTMLHex(XMLSS.Styles[k].BGColor));

      //?? fo:keep-together - неразрывнеы строки (auto | always)
      _xml.WriteEmptyTag('style:table-row-properties', true);

      _xml.WriteEndTagNode(); //style:style
      start_j := now_j + 1;
      RowStyle[now_i][now_j] := now_StyleNumber;
      for i := now_i to count_i do
      begin
        for j := start_j to XMLSS.Sheets[_pages[i]].RowCount - 1{count_j} do
          if (RowStyle[i][j] = -1) then
          begin
            b := true;
            //style:row-height
            if (XMLSS.Sheets[_pages[i]].Rows[j].HeightPix <> XMLSS.Sheets[_pages[now_i]].Rows[now_j].HeightPix) then
              b := false;
            //fo:break-before
            if (XMLSS.Sheets[_pages[i]].Rows[j].Breaked <> XMLSS.Sheets[_pages[now_i]].Rows[now_j].Breaked) then
              b := false;

            if (b) then
              RowStyle[i][j] := now_StyleNumber;
          end;

        start_j := 0;
      end;
    end; //WriteRowStyle

  begin
    ZEWriteHeaderCommon(_xml, CodePageName, BOM);
    GenODContentAttr(_xml.Attributes);
    _xml.WriteTagNode('office:document-content', true, true, true);
    _xml.Attributes.Clear();
    _xml.WriteEmptyTag('office:scripts', true, false);  //потом на досуге можно подумать
    ZEWriteFontFaceDecls(_xml);

    ///********   Automatic Styles   ********///
    _xml.WriteTagNode('office:automatic-styles', true, true, true);
    //******* стили столбцов
    kol := High(_pages);
    SetLength(ColumnStyle, kol + 1);
    for i := 0 to kol do
    begin
      n := XMLSS.Sheets[_pages[i]].ColCount;
      SetLength(ColumnStyle[i], n);
      for j := 0 to n - 1 do
        ColumnStyle[i][j] := -1;
    end;
    ColStyleNumber := 0;
    for i := 0 to kol do
    begin
      n := XMLSS.Sheets[_pages[i]].ColCount;
      for j := 0 to n - 1 do
      begin
        WriteColumnStyle(i, j, ColStyleNumber, kol, n - 1);
        inc(ColStyleNumber);
      end;
    end;

    //******* стили строк
    SetLength(RowStyle, kol + 1);
    for i := 0 to kol do
    begin
      n := XMLSS.Sheets[_pages[i]].RowCount;
      SetLength(RowStyle[i], n);
      for j := 0 to n - 1 do
        RowStyle[i][j] := -1;
    end;
    RowStyleNumber := 0;
    for i := 0 to kol do
    begin
      n := XMLSS.Sheets[_pages[i]].RowCount - 1;
      for j := 0 to n - 1 do
      begin
        WriteRowStyle(i, j, RowStyleNumber, kol, n);
        inc(RowStyleNumber);
      end;
    end;

    //******* остальные стили
    for i := 0 to XMLSS.Styles.Count - 1 do
    begin
      _xml.Attributes.Clear();
      _xml.Attributes.Add('style:name', 'ce' + IntToStr(i));
      _xml.Attributes.Add('style:family', 'table-cell');
        //??style:parent-style-name = Default
      _xml.WriteTagNode('style:style', true, true, true);
      ODFWriteTableStyle(XMLSS, _xml, i, false);
      _xml.WriteEndTagNode(); //style:style
    end;

    _xml.WriteEndTagNode(); //office:automatic-styles
  end; //WriteHeader

  //<table:table> ... </table:table>
  //INPUT
  //      PageNum: integer    - номер страницы в хранилище
  //  const TableName: string - название страницы
  //      PageIndex: integer  - номер в массиве страниц
  procedure WriteODFTable(PageNum: integer; const TableName: string; PageIndex: integer);
  var
    b: boolean;
    i, j: integer;
    s, ss: string;
    k, t: integer;
    NumTopLeft: integer;  //Номер объединённой области, в которой ячейка является верхней левой
    NumArea: integer;     //Номер объединённой области, в которую входит ячейка
    isNotEmpty: boolean;
    _StyleID: integer;
    _CellData: string;

    //Выводит содержимое ячейки с учётом переноса строк
    procedure WriteTextP(xml: TZsspXMLWriterH; const CellData: string; const href: string = '');
    var
      s: string;
      i: integer;

    begin
      //ссылка <text:p><text:a xlink:href="http://google.com/" office:target-frame-name="_blank">Some_text</text:a></text:p>
      //Ссылка имеет больший приоритет
      if (length(href) > 0) then
      begin
        xml.Attributes.Clear();
        xml.WriteTagNode('text:p', true, false, true);
        xml.Attributes.Add('xlink:href', href);
        //office:target-frame-name='_blank' - открывать в новом фрейме
        xml.WriteTag('text:a', CellData, false, false, true);
        xml.WriteEndTagNode(); //text:p
      end else
      begin
        s := '';
        for i := 1 to length(CellData) do
        begin
          if CellData[i] = AnsiChar(#10) then
          begin
            xml.WriteTag('text:p', s, true, false, true);
            s := '';
          end else
            if (CellData[i] <> AnsiChar(#13)) then
              s := s + CellData[i];
        end;
        if (length(s) > 0) then
          xml.WriteTag('text:p', s, true, false, true);
      end;
    end; //WriteTextP

  begin
    _xml.Attributes.Clear();
    //Атрибуты для таблицы:
    //    table:name        - название таблицы
    //    table:style-name  - стиль таблицы
    //    table:protected   - признак защищённой таблицы (true/false)
    //?   table:protection-key - ХЭШ пароля (если таблица защищённая)
    //?   table:print       - является-ли таблица печатаемой (true - по-умолчанию)
    //?   table:display     - признак отображаемости таблицы (мощнее печати, true - по-умолчани.)
    //?   table:print-ranges - диапазон печати
    _xml.Attributes.Add('table:name', Tablename, true);
    b := XMLSS.Sheets[PageNum].Protect;
    if (b) then
      _xml.Attributes.Add('table:protected', ODFBoolToStr(b));
    _xml.WriteTagNode('table:table', true, true, true);

    //::::::: колонки :::::::::
    //table:table-column - описание колонок
    //Атрибуты
    //    table:number-columns-repeated   - кол-во столбцов, в которых повторяется описание столбца (кол-во - 1 последующих пропускать) - потом надо подумать {tut}
    //    table:style-name                - стиль столбца
    //    table:visibility                - видимость столбца (по-умолчанию visible);
    //    table:default-cell-style-name   - стиль ячеек по умолчанию (если не задан стиль строки и ячейки)
    for i := 0 to XMLSS.Sheets[PageNum].ColCount - 1 do
    begin
      _xml.Attributes.Clear();
      _xml.Attributes.Add('table:style-name', 'co' + IntToStr(ColumnStyle[PageIndex][i]));
      //Видимость: table:visibility (visible | collapse | filter)
      if (XMLSS.Sheets[PageNum].Columns[i].Hidden) then
        _xml.Attributes.Add('table:visibility', 'collapse'); //или всё-таки filter?
      s := 'Default';
      k := XMLSS.Sheets[PageNum].Columns[i].StyleID;
      if (k >= 0) then
        s := 'ce' + IntToStr(k);
      _xml.Attributes.Add('table:default-cell-style-name', s);
      //table:default-cell-style-name
      _xml.WriteEmptyTag('table:table-column', true);
    end;

    //::::::: строки :::::::::
    for i := 0 to XMLSS.Sheets[PageNum].RowCount - 1 do
    begin
      //table:table-row
      _xml.Attributes.Clear();
      _xml.Attributes.Add('table:style-name', 'ro' + IntToStr(RowStyle[PageIndex][i]));
      //?? table:number-rows-repeated - кол-во повторяемых строк
      // table:default-cell-style-name - стиль ячейки по-умолчанию
      k := XMLSS.Sheets[PageNum].Rows[i].StyleID;
      if (k >= 0) then
      begin
        s := 'ce' + IntToStr(k);
        _xml.Attributes.Add('table:default-cell-style-name', s);
      end;
      // table:visibility - видимость строки
      if (XMLSS.Sheets[PageNum].Rows[i].Hidden) then
        _xml.Attributes.Add('table:visibility', 'collapse'); 
      _xml.WriteTagNode('table:table-row', true, true, true);
      {ячейки}
      //**** пробегаем по всем ячейкам
      for j := 0 to XMLSS.Sheets[PageNum].ColCount - 1 do
      begin
        NumTopLeft := XMLSS.Sheets[PageNum].MergeCells.InLeftTopCorner(j, i);
        NumArea := XMLSS.Sheets[PageNum].MergeCells.InMergeRange(j, i);
        s := 'table:table-cell';
        _xml.Attributes.Clear();
        isNotEmpty := false;
        //Возможные атрибуты для ячейки:
        //    table:number-columns-repeated   - кол-во повторяемых ячеек
        //    table:number-columns-spanned    - кол-во объединённых столбцов
        //    table:number-rows-spanned       - кол-во объединённых строк
        //    table:style-name                - стиль ячейки
        //??  table:content-validation-name   - проводится ли в данной ячейке проверка правильности
        //    table:formula                   - формула
        //      office:value                  -  текущее числовое значение (для float | percentage | currency)
        //??    office:date-value             -  текущее значение даты
        //??    office:time-value             -  текущее значение времени
        //??    office:boolean-value          -  текущее логическое значение
        //      office:string-value           -  текущее строковое значение
        //     table:value-type               - тип значения в ячейке (float | percentage | currency,
        //                                        date, time, boolean, string
        //??     tableoffice:currency         - текущая денежная единица (только для currency)
        //     table:protected                - защищённость ячейки
        if ((NumTopLeft < 0) and (NumArea >= 0)) then //скрытая ячейка в объединённой области
        begin
          s := 'table:covered-table-cell';
        end else
        if (NumTopLeft >= 0) then   //объединённая ячейка (левая верхняя)
        begin
          t := XMLSS.Sheets[PageNum].MergeCells.Items[NumTopLeft].Right -
               XMLSS.Sheets[PageNum].MergeCells.Items[NumTopLeft].Left + 1;
          _xml.Attributes.Add('table:number-columns-spanned', IntToStr(t));
          t := XMLSS.Sheets[PageNum].MergeCells.Items[NumTopLeft].Bottom -
               XMLSS.Sheets[PageNum].MergeCells.Items[NumTopLeft].Top + 1;
          _xml.Attributes.Add('table:number-rows-spanned', IntToStr(t));
        end;

        //стиль
        _StyleID := XMLSS.Sheets[PageNum].Cell[j, i].CellStyle;
        if ((_StyleID >= 0) and (_StyleID < XMLSS.Styles.Count)) then
          _xml.Attributes.Add('table:style-name', 'ce' + IntToStr(_StyleID));

        //защита ячейки

        b := XMLSS.Styles[_StyleID].Protect;
        if (b) then
          _xml.Attributes.Add('table:protected', ODFBoolToStr(b));

        _CellData := XMLSS.Sheets[PageNum].Cell[j, i].Data;
        //table:value-type + office:some_type_value
        //  ZENumber -> float
        ss := '';
        case (XMLSS.Sheets[PageNum].Cell[j, i].CellType) of
          ZENumber:
            begin
              ss := 'float';
              _xml.Attributes.Add('office:value', ZEFloatSeparator(FormatFloat('0.#######', ZETryStrToFloat(_CellData))));
            end;
          ZEBoolean:
            begin
              ss := 'boolean';
              _xml.Attributes.Add('office:boolean-value', ODFBoolToStr(ZETryStrToBoolean(_CellData)));
            end;
          else
            // всё остальное считаем строкой (потом подправить, возможно, добавить новые типы)
            {ZEansistring ZEError ZEDateTime}
        end;
        if (length(ss) > 0) then
          _xml.Attributes.Add('office:value-type', ss);

        //формула  
        ss := XMLSS.Sheets[PageNum].Cell[j, i].Formula;
        if (Length(ss) > 0) then
          _xml.Attributes.Add('table:formula', ss);

        //Примечание
        //office:annotation
        //Атрибуты:
        //    office:display - отображать ли (true | false)
        //??  draw:style-name
        //??  draw:text-style-name
        //??  svg:width
        //??  svg:height
        //??  svg:x
        //??  svg:y
        //??  draw:caption-point-x
        //??  draw:caption-point-y
        if (XMLSS.Sheets[PageNum].Cell[j, i].ShowComment) then
        begin
          if (not isNotEmpty) then
            _xml.WriteTagNode(s, true, true, true);
          isNotEmpty := true;
          _xml.Attributes.Clear();
          b := XMLSS.Sheets[PageNum].Cell[j, i].AlwaysShowComment;
          if (b) then
            _xml.Attributes.Add('office:display', ODFBoolToStr(b));
          _xml.WriteTagNode('office:annotation', true, true, true);
          //автор примечания
          if (length(XMLSS.Sheets[PageNum].Cell[j, i].CommentAuthor) > 0) then
          begin
            _xml.Attributes.Clear();
            _xml.WriteTag('dc:creator', XMLSS.Sheets[PageNum].Cell[j, i].CommentAuthor, true, false, true);
          end;
          _xml.Attributes.Clear();
          WriteTextP(_xml, XMLSS.Sheets[PageNum].Cell[j, i].Comment);
          _xml.WriteEndTagNode(); //office:annotation
        end;

        //Содержимое ячейки
        if (Length(_CellData) > 0) then
        begin
          if (not isNotEmpty) then
            _xml.WriteTagNode(s, true, true, true);
          isNotEmpty := true;
          _xml.Attributes.Clear();
          WriteTextP(_xml, _CellData, XMLSS.Sheets[PageNum].Cell[j, i].Href);
        end;

        if (isNotEmpty) then
          _xml.WriteEndTagNode() //ячейка  table:table-cell | table:covered-table-cell
        else
          _xml.WriteEmptyTag(s, true, true);
      end;
      {/ячейки}
      _xml.WriteEndTagNode(); //table:table-row
    end;

    _xml.WriteEndTagNode(); //table:table
  end; //WriteODFTable

  //Сами таблицы
  procedure WriteBody();
  var
    i: integer;

  begin
    _xml.Attributes.Clear();
    _xml.WriteTagNode('office:body', true, true, true);
    _xml.WriteTagNode('office:spreadsheet', true, true);
    for i := Low(_pages) to High(_pages) do
      WriteODFTable(_pages[i], _names[i], i);
    _xml.WriteEndTagNode(); //office:spreadsheet
    _xml.WriteEndTagNode(); //office:body
  end; //WriteBody

begin
  result := 0;
  _xml := nil;
  try
    _xml := TZsspXMLWriterH.Create();
    _xml.TabLength := 1;
    _xml.TextConverter := TextConverter;
    _xml.TabSymbol := ' ';
    if (not _xml.BeginSaveToStream(Stream)) then
    begin
      result := 2;
      exit;
    end;
    WriteHeader();
    WriteBody();
    _xml.EndSaveTo();
  finally
    if (Assigned(_xml)) then
      FreeAndNil(_xml);
    for i := Low(ColumnStyle) to High(ColumnStyle) do
    begin
      SetLength(ColumnStyle[i], 0);
      ColumnStyle[i] := nil;
    end;
    SetLength(ColumnStyle, 0);
    for i := Low(RowStyle) to High(RowStyle) do
    begin
      SetLength(RowStyle[i], 0);
      RowStyle[i] := nil;
    end;
    RowStyle := nil;
  end;
end; //ODFCreateContent

//Записывает в поток метаинформацию (meta.xml)
//INPUT
//  var XMLSS: TZEXMLSS                 - хранилище
//    Stream: TStream                   - поток для записи
//    TextConverter: TAnsiToCPConverter - конвертер из локальной кодировки в нужную
//    CodePageName: string              - название кодовой страници
//    BOM: ansistring                   - BOM
//RETURN
//      integer
function ODFCreateMeta(var XMLSS: TZEXMLSS; Stream: TStream; TextConverter: TAnsiToCPConverter; CodePageName: string; BOM: ansistring): integer;
var
  _xml: TZsspXMLWriterH;    //писатель
  s: string;

begin
  result := 0;
  _xml := nil;
  try
    _xml := TZsspXMLWriterH.Create();
    _xml.TabLength := 1;
    _xml.TextConverter := TextConverter;
    _xml.TabSymbol := ' ';
    if (not _xml.BeginSaveToStream(Stream)) then
    begin
      result := 2;
      exit;
    end;

    ZEWriteHeaderCommon(_xml, CodePageName, BOM);
    GenODMetaAttr(_xml.Attributes);
    _xml.WriteTagNode('office:document-meta', true, true, true);
    _xml.Attributes.Clear();
    _xml.WriteTagNode('office:meta', true, true, true);
    //дата создания
    s := ZEDateToStr(XMLSS.DocumentProperties.Created);
    _xml.WriteTag('meta:creation-date', s, true, false, true);
    //Дата последнего редактирования пусть будет равна дате создания
    _xml.WriteTag('dc:date', s, true, false, true);

    {
    //Длительность редактирования PnYnMnDTnHnMnS
    //Пока не используется
    <meta:editing-duration>PT2M2S</meta:editing-duration>
    }

    //Кол-во циклов редактирования (каждый раз, когда сохраняется, нужно увеличивать на 1) > 0
    //Пока считаем, что документ создаётся только 1 раз
    //Потом можно будет добавить поле в хранилище
    _xml.WriteTag('meta:editing-cycles', '1', true, false, true);

    {
    //Статистика документа (кол-во страниц и др)
    //Пока не используется
    (*
    meta:page-count
    meta:table-count
    meta:image-count
    meta:cell-count
    meta:object-count
    *)
    <meta:document-statistic meta:table-count="3" meta:cell-count="7" meta:object-count="0"/>
    }
    //Генератор - какое приложение создало или редактировало документ
    //Потом надо добавить такое поле в хранилище
    {$IFDEF FPC}
    s := 'FPC';
    {$ELSE}
    s := 'DELPHI_or_CBUILDER';
    {$ENDIF}
    _xml.WriteTag('meta:generator', 'ZEXMLSSlib/0.0.3$' + s, true, false, true);

    _xml.WriteEndTagNode(); // office:meta
    _xml.WriteEndTagNode(); // office:document-meta

  finally
    if (Assigned(_xml)) then
      FreeAndNil(_xml);
  end;
end; //ODFCreateMeta

//Создаёт манифест
//INPUT
//      Stream: TStream                   - поток для записи
//      TextConverter: TAnsiToCPConverter - конвертер
//      CodePageName: string              - имя кодировки
//      BOM: ansistring                   - BOM
//RETURN
//      integer
function ODFCreateManifest(Stream: TStream; TextConverter: TAnsiToCPConverter; CodePageName: string; BOM: ansistring = ''): integer;
var
  _xml: TZsspXMLWriterH;    //писатель
  tag_name, att1, att2, s: string;

  procedure _writetag(const s1, s2: string);
  begin
    _xml.Attributes.Clear();
    _xml.Attributes.Add(att1, s1);
    _xml.Attributes.Add(att2, s2);
    _xml.WriteEmptyTag(tag_name);
  end;

begin
  _xml := nil;
  result := 0;
  try
    _xml := TZsspXMLWriterH.Create();
    _xml.TabLength := 1;
    _xml.TextConverter := TextConverter;
    _xml.TabSymbol := ' ';
    if (not _xml.BeginSaveToStream(Stream)) then
    begin
      result := 2;
      exit;
    end;

    ZEWriteHeaderCommon(_xml, CodePageName, BOM);
    _xml.Attributes.Clear();
    _xml.Attributes.Add('xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0');
    _xml.Attributes.Add('manifest:version', '1.2');
    _xml.WriteTagNode('manifest:manifest', true, true, true);

    tag_name := 'manifest:file-entry';
    att1 := 'manifest:media-type';
    att2 := 'manifest:full-path';

    _xml.Attributes.Clear();
    _xml.Attributes.Add(att1, 'application/vnd.oasis.opendocument.spreadsheet');
    _xml.Attributes.Add('manifest:version', '1.2');
    _xml.Attributes.Add(att2, '/');
    _xml.WriteEmptyTag(tag_name);
    s := 'text/xml';

    _writetag(s, 'meta.xml');
    _writetag(s, 'settings.xml');
    _writetag(s, 'content.xml');
    _writetag('image/png', 'Thumbnails/thumbnail.png');
    _writetag('', 'Configurations2/accelerator/current.xml');
    _writetag('application/vnd.sun.xml.ui.configuration', 'Configurations2/');
    _writetag(s, 'styles.xml');

    _xml.WriteEndTagNode(); //manifest:manifest
    _xml.EndSaveTo();
  finally
    if (Assigned(_xml)) then
      FreeAndNil(_xml);
  end;
end; //ODFCreateManifest

//Сохраняет незапакованный документ в формате Open Document
//INPUT
//  var XMLSS: TZEXMLSS                   - хранилище
//      PathName: string                  - путь к директории для сохранения (должна заканчиватся разделителем директории)
//  const SheetsNumbers:array of integer  - массив номеров страниц в нужной последовательности
//  const SheetsNames: array of string    - массив названий страниц
//                                          (количество элементов в двух массивах должны совпадать)
//      TextConverter: TAnsiToCPConverter - конвертер
//      CodePageName: string              - имя кодировки
//      BOM: ansistring                   - Byte Order Mark
//RETURN
//      integer
function SaveXmlssToODFSPath(var XMLSS: TZEXMLSS; PathName: string; const SheetsNumbers:array of integer;
                         const SheetsNames: array of string; TextConverter: TAnsiToCPConverter; CodePageName: string; BOM: ansistring = ''): integer; overload;
var
  _pages: TZESaveIntArray;      //номера страниц
  _names: TZESaveStrArray;      //названия страниц
  kol: integer;
  Stream: TStream;
  n: integer;
  s: string;

begin
  result := 0;
  Stream := nil;
  try
    n := length(PathName);
    if (n > 0) then
    begin
      if (PathName[n] <> PathDelim) then
        PathName := PathName + PathDelim;
    end;

    if (not DirectoryExists(PathName)) then
    begin
      result := 3;
      exit;
    end;

    if (not ZECheckTablesTitle(XMLSS, SheetsNumbers, SheetsNames, _pages, _names, kol)) then
    begin
      result := 2;
      exit;
    end;

    Stream := TFileStream.Create(PathName + 'content.xml', fmCreate);
    ODFCreateContent(XMLSS, Stream, _pages, _names, kol, TextConverter, CodePageName, BOM);
    FreeAndNil(Stream);

    Stream := TFileStream.Create(PathName + 'meta.xml', fmCreate);
    ODFCreateMeta(XMLSS, Stream, TextConverter, CodePageName, BOM);
    FreeAndNil(Stream);

    Stream := TFileStream.Create(PathName + 'settings.xml', fmCreate);
    ODFCreateSettings(XMLSS, Stream, _pages, _names, kol, TextConverter, CodePageName, BOM);
    FreeAndNil(Stream);

    Stream := TFileStream.Create(PathName + 'styles.xml', fmCreate);
    ODFCreateStyles(XMLSS, Stream, _pages, _names, kol, TextConverter, CodePageName, BOM);
    FreeAndNil(Stream);

    s := PathName + 'META-INF' + PathDelim;
    if (not DirectoryExists(s)) then
       ForceDirectories(s);

    Stream := TFileStream.Create(s + 'manifest.xml', fmCreate);
    ODFCreateManifest(Stream, TextConverter, CodePageName, BOM);
    FreeAndNil(Stream);

  finally
    ZESClearArrays(_pages, _names);
    if (Assigned(Stream)) then
      FreeAndNil(Stream);
  end;
end; //SaveXmlssToODFSPath

{$IFDEF FPC}
//Сохраняет документ в формате Open Document
//INPUT
//  var XMLSS: TZEXMLSS                   - хранилище
//      FileName: string                  - имя файла для сохранения
//  const SheetsNumbers:array of integer  - массив номеров страниц в нужной последовательности
//  const SheetsNames: array of string    - массив названий страниц
//                                          (количество элементов в двух массивах должны совпадать)
//      TextConverter: TAnsiToCPConverter - конвертер
//      CodePageName: string              - имя кодировки
//      BOM: ansistring                   - Byte Order Mark
//RETURN
//      integer
function SaveXmlssToODFS(var XMLSS: TZEXMLSS; FileName: string; const SheetsNumbers:array of integer;
                         const SheetsNames: array of string; TextConverter: TAnsiToCPConverter; CodePageName: string; BOM: ansistring = ''): integer; overload;
var
  _pages: TZESaveIntArray;      //номера страниц
  _names: TZESaveStrArray;      //названия страниц
  kol: integer;
  zip: TZipper;
  StreamC, StreamME, StreamS, StreamST, StreamMA: TStream;

begin
  zip := nil;
  StreamC := nil;
  StreamME := nil;
  StreamS := nil;
  StreamST := nil;
  StreamMA := nil;
  try

    if (not ZECheckTablesTitle(XMLSS, SheetsNumbers, SheetsNames, _pages, _names, kol)) then
    begin
      result := 2;
      exit;
    end;

    zip := TZipper.Create();

    StreamC := TMemoryStream.Create();
    ODFCreateContent(XMLSS, StreamC, _pages, _names, kol, TextConverter, CodePageName, BOM);

    StreamME := TMemoryStream.Create();
    ODFCreateMeta(XMLSS, StreamME, TextConverter, CodePageName, BOM);

    StreamS := TMemoryStream.Create();
    ODFCreateSettings(XMLSS, StreamS, _pages, _names, kol, TextConverter, CodePageName, BOM);

    StreamST := TMemoryStream.Create();
    ODFCreateStyles(XMLSS, StreamST, _pages, _names, kol, TextConverter, CodePageName, BOM);

    StreamMA := TMemoryStream.Create();
    ODFCreateManifest(StreamMA, TextConverter, CodePageName, BOM);

    zip.FileName := FileName;

    StreamC.Position := 0;
    StreamME.Position := 0;
    StreamS.Position := 0;
    StreamST.Position := 0;
    StreamMA.Position := 0;

    zip.Entries.AddFileEntry(StreamC, 'content.xml');
    zip.Entries.AddFileEntry(StreamME, 'meta.xml');
    zip.Entries.AddFileEntry(StreamS, 'settings.xml');
    zip.Entries.AddFileEntry(StreamST, 'styles.xml');
    zip.Entries.AddFileEntry(StreamMA, 'META-INF/manifest.xml');
    zip.ZipAllFiles();

  finally
    ZESClearArrays(_pages, _names);
    if (Assigned(zip)) then
      FreeAndNil(zip);
    if (Assigned(StreamC)) then
      FreeAndNil(StreamC);
    if (Assigned(StreamME)) then
      FreeAndNil(StreamME);
    if (Assigned(StreamS)) then
      FreeAndNil(StreamS);
    if (Assigned(StreamST)) then
      FreeAndNil(StreamST);
    if (Assigned(StreamMA)) then
      FreeAndNil(StreamMA);
  end;

end; //SaveXmlssToODFS

{$ENDIF}

end.
