unit TeeODS;
{$I TeeDefs.inc}

interface

uses
  Classes, Graphics,
  TeEngine, Chart, TeeStore;

type
  // OpenDocument SpreadSheet format
  TSeriesDataODS=class(TSeriesData)
  public
    Procedure SaveToStream(AStream:TStream); override;
  end;

implementation

uses
  SysUtils, System.Zip, TeeConst;

{ TSeriesDataODS }

Procedure TSeriesDataODS.SaveToStream(AStream:TStream);

  function ODSColor(const AColor:TColor):String;
  begin
    result:=IntToStr(AColor);
  end;

  function BeginRow:String;
  begin
    result:='<table:table-row>';
  end;

  function EndRow:String;
  begin
    result:='</table:table-row>';
  end;

  function WriteText(const S:String):String;
  begin
    result:='<table:table-cell office:value-type="string">'+
               '<text:p>'+S+'</text:p>'+
            '</table:table-cell>';
  end;

  function WriteNull:String;
  begin
    result:=WriteText('');
  end;

  function WriteDouble(const V:Double):String;
  var S : String;
  begin
    S:=FloatToStr(V);

    result:='<table:table-cell office:value-type="float" office:value="'+S+'">'+
               '<text:p>'+S+'</text:p>'+
            '</table:table-cell>';
  end;

var Col : Integer;

  function WriteHeaderSeries(const ASeries:TChartSeries):String;
  var tt : Integer;
  begin
    result:='';

    if tfLabel in IFormat then
    begin
      result:=result+WriteText(TeeMsg_Text);
      Inc(Col);
    end;

    if tfColor in IFormat then
    begin
      result:=result+WriteText(TeeMsg_Colors);
      Inc(Col);
    end;

    if tfNoMandatory in IFormat then
    begin
      result:=result+WriteText(ASeries.NotMandatoryValueList.Name);
      Inc(Col);
    end;

    for tt:=1 to ASeries.ValuesList.Count-1 do
    begin
      result:=result+WriteText(ASeries.ValuesList[tt].Name);
      Inc(Col);
    end;
  end;

  function WriteSeries(Row:Integer; const ASeries:TChartSeries):String;

    function WriteValue(const AList:TChartValueList):String;
    var tmp : String;
    begin
      if UseSeriesFormat then
         tmp:=ASeries.ValueFormat
      else
         tmp:=ValueFormat;

      if tmp='' then
         result:=WriteDouble(AList.Value[Row])
      else
         // Trick, use format string to "round" value:
         result:=WriteDouble(StrToFloat(ValueToString(ASeries,AList.Value[Row])));
    end;

  var tt : Integer;
  begin
    result:='';

    if (ASeries.Count-1)<Row then
    begin
      if tfLabel in IFormat then
      begin
        result:=result+WriteText('');
        Inc(Col);
      end;

      if tfColor in IFormat then
      begin
        result:=result+WriteText('');
        Inc(Col);
      end;

      if tfNoMandatory in IFormat then
      begin
        result:=result+WriteNull;
        Inc(Col);
      end;

      for tt:=1 to ASeries.ValuesList.Count-1 do
      begin
        result:=result+WriteNull;
        Inc(Col);
      end;
    end
    else
    begin
      if tfLabel in IFormat then
      begin
        result:=result+WriteText(ASeries.Labels[Row]);
        Inc(Col);
      end;

      if tfColor in IFormat then
      begin
        result:=result+WriteText(ODSColor(ASeries.ValueColor[Row]));
        Inc(Col);
      end;

      if tfNoMandatory in IFormat then
      begin
        result:=result+WriteValue(ASeries.NotMandatoryValueList);
        Inc(Col);
      end;

      if ASeries.IsNull(Row) then
      for tt:=1 to ASeries.ValuesList.Count-1 do
      begin
        result:=result+WriteNull;
        Inc(Col);
      end
      else
      begin
        result:=result+WriteValue(ASeries.MandatoryValueList);

        Inc(Col);

        for tt:=2 to ASeries.ValuesList.Count-1 do
        begin
          result:=result+WriteValue(ASeries.ValuesList[tt]);
          Inc(Col);
        end;
      end;
    end;
  end;

  function SeriesData:String;
  var t : Integer;
      Row : Integer;
  begin
    result:='';

    if IncludeHeader then
    begin
      Col:=0;

      result:=result+BeginRow;

      if IncludeIndex then
      begin
        result:=result+WriteText(TeeMsg_Index);
        Inc(Col);
      end;

      if Assigned(Series) then
         result:=result+WriteHeaderSeries(Series)
      else
      for t:=0 to Chart.SeriesCount-1 do
          result:=result+WriteHeaderSeries(Chart[t]);

      result:=result+EndRow;
    end;

    for Row:=0 to MaxSeriesCount-1 do
    begin
      Col:=0;

      result:=result+BeginRow;

      if IncludeIndex then
      begin
        result:=result+WriteDouble(Row);
        Inc(Col);
      end;

      if Assigned(Series) then
         result:=result+WriteSeries(Row,Series)
      else
      for t:=0 to Chart.SeriesCount-1 do
          result:=result+WriteSeries(Row,Chart[t]);

      result:=result+EndRow;
    end;
  end;

  function Content:TStream;
  begin
    result:=TStringStream.Create(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
'<office:document-content xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" '+
'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '+
'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" '+
'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" '+
'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" '+
'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" '+
'xmlns:xlink="http://www.w3.org/1999/xlink" '+
'xmlns:dc="http://purl.org/dc/elements/1.1/" '+
'xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" '+
'xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" '+
'xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" office:version="1.2">'+
'<office:font-face-decls><style:font-face style:name="Calibri" svg:font-family="Calibri"/>'+
'</office:font-face-decls><office:automatic-styles>'+
'<style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="N0"/>'+
'<style:style style:name="co1" style:family="table-column">'+
'<style:table-column-properties fo:break-before="auto" style:column-width="1.69333333333333cm"/>'+
'</style:style>'+
'<style:style style:name="ro1" style:family="table-row">'+
'<style:table-row-properties style:row-height="15pt" style:use-optimal-row-height="true" fo:break-before="auto"/>'+
'</style:style>'+
'<style:style style:name="ta1" style:family="table" style:master-page-name="mp1">'+
'<style:table-properties table:display="true" style:writing-mode="lr-tb"/>'+
'</style:style>'+
'</office:automatic-styles>'+
'<office:body><office:spreadsheet>'+
'<table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false"/>'+

'<table:table table:name="'+Chart.Name+'">'+
'<table:table-column table:number-columns-repeated="16384"/>'+

   SeriesData+

'</table:table>'+
'</office:spreadsheet>'+
'</office:body>'+
'</office:document-content>'

   );
  end;

  function DateStr:String;
  begin
    result:=FormatDateTime('yyyy-mm-dd"T"hh:mm:ss"Z"',Now);
  end;

  function Meta:TStream;
  begin
    result:=TStringStream.Create(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
'<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '+
'xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" '+
'xmlns:dc="http://purl.org/dc/elements/1.1/" '+
'xmlns:xlink="http://www.w3.org/1999/xlink" office:version="1.2">'+
'<office:meta><meta:generator>Steema.com TeeChart</meta:generator>'+
'<meta:initial-creator>Steema Software</meta:initial-creator>'+
'<dc:creator>Steema Sofware</dc:creator>'+
'<meta:creation-date>'+DateStr+'</meta:creation-date>'+
'<dc:date>'+DateStr+'</dc:date></office:meta></office:document-meta>'
    );
  end;

  function Manifest:TStream;
  begin
    result:=TStringStream.Create('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
'<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">'+
'<manifest:file-entry manifest:full-path="/" '+
'manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>'+
//'<manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>'+
'<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>'+
'<manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/>'+
'</manifest:manifest>');
  end;

  function Styles:TStream;
  begin
    result:=TStringStream.Create('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
            '<office:document-styles xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" '+
            'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '+
            'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" '+
            'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" '+
            'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" '+
            'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" '+
            'xmlns:xlink="http://www.w3.org/1999/xlink" '+
            'xmlns:dc="http://purl.org/dc/elements/1.1/" '+
            'xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" '+
            'xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" '+
            'xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" office:version="1.2">'+

            '<office:font-face-decls>'+
              '<style:font-face style:name="Calibri" svg:font-family="Calibri"/>'+
            '</office:font-face-decls>'+
            '<office:styles>'+
              '<number:number-style style:name="N0"><number:number number:min-integer-digits="1"/></number:number-style>'+
              '<style:style style:name="Default" style:family="table-cell" style:data-style-name="N0">'+
              '<style:table-cell-properties style:vertical-align="automatic" fo:background-color="transparent"/>'+
              '<style:text-properties fo:color="#000000" style:font-name="Calibri" '+
                   'style:font-name-asian="Calibri" style:font-name-complex="Calibri" fo:font-size="11pt" '+
                   'style:font-size-asian="11pt" style:font-size-complex="11pt"/>'+
              '</style:style>'+
              '</office:styles>'+
              '<office:automatic-styles>'+
                '<style:page-layout style:name="pm1">'+
                   '<style:page-layout-properties fo:margin-top="0.3in" '+
                          'fo:margin-bottom="0.3in" fo:margin-left="0.7in" '+
                          'fo:margin-right="0.7in" style:table-centering="none" '+
                          'style:print="objects charts drawings"/>'+
                   '<style:header-style>'+
                      '<style:header-footer-properties fo:min-height="0.45in" '+
                          'fo:margin-left="0.7in" fo:margin-right="0.7in" '+
                          'fo:margin-bottom="0in"/>'+
                   '</style:header-style>'+
                   '<style:footer-style>'+
                      '<style:header-footer-properties fo:min-height="0.45in" '+
                          'fo:margin-left="0.7in" fo:margin-right="0.7in" '+
                          'fo:margin-top="0in"/>'+
                   '</style:footer-style>'+
                '</style:page-layout>'+
              '</office:automatic-styles>'+
              '<office:master-styles>'+
                '<style:master-page style:name="mp1" style:page-layout-name="pm1">'+
                '<style:header/>'+
                  '<style:header-left style:display="false"/>'+
                '<style:footer/>'+
                  '<style:footer-left style:display="false"/>'+
                '</style:master-page>'+
              '</office:master-styles>'+
            '</office:document-styles>'
    );
  end;

  function Settings:TStream;
  begin
    result:=TStringStream.Create(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
'<office:document-settings xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" '+
'xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" '+
'xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" '+
'xmlns:db="urn:oasis:names:tc:opendocument:xmlns:database:1.0" '+
'xmlns:dc="http://purl.org/dc/elements/1.1/" '+
'xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" '+
'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" '+
'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" '+
'xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" '+
'xmlns:grddl="http://www.w3.org/2003/g/data-view#" '+
'xmlns:math="http://www.w3.org/1998/Math/MathML" '+
'xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" '+
'xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" '+
'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '+
'xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" '+
'xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" '+
'xmlns:smil="urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" '+
'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" '+
'xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" '+
'xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" '+
'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" '+
'xmlns:xforms="http://www.w3.org/2002/xforms" '+
'xmlns:xhtml="http://www.w3.org/1999/xhtml" '+
'xmlns:xlink="http://www.w3.org/1999/xlink" office:version="1.2"/>'
    );
  end;

  function MimeType:TStream;
  begin
    result:=TStringStream.Create('application/vnd.oasis.opendocument.spreadsheet');
  end;

  procedure AddStream(const z:TZipFile; const AStream:TStream; const AFileName:String);
  begin
    try
      z.Add(AStream,AFileName);
    finally
      AStream.Free;
    end;
  end;

var z : TZipFile;
begin
  z:=TZipFile.Create;
  try
    z.Open(AStream,TZipMode.zmWrite);

    AddStream(z,Content,'content.xml');
    AddStream(z,Meta,'meta.xml');
    AddStream(z,MimeType,'mimetype');
    //AddStream(z,Styles,'styles.xml');
    AddStream(z,Settings,'settings.xml');
    AddStream(z,Manifest,'META-INF\manifest.xml');
  finally
    z.Free;
  end;
end;

end.
