diff --git a/examples/ImageInsert/ImageInsert.lpi b/examples/ImageInsert/ImageInsert.lpi new file mode 100644 index 0000000..e2490c5 --- /dev/null +++ b/examples/ImageInsert/ImageInsert.lpi @@ -0,0 +1,69 @@ + + + + + + + + + + + + + <UseAppBundle Value="False"/> + <ResourceType Value="res"/> + </General> + <i18n> + <EnableI18N LFM="False"/> + </i18n> + <VersionInfo> + <StringTable ProductVersion=""/> + </VersionInfo> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + </local> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="fpOdf"/> + </Item1> + </RequiredPackages> + <Units Count="1"> + <Unit0> + <Filename Value="ImageInsert.lpr"/> + <IsPartOfProject Value="True"/> + <UnitName Value="ImageInsert"/> + </Unit0> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target> + <Filename Value="ImageInsert"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/examples/ImageInsert/ImageInsert.lpr b/examples/ImageInsert/ImageInsert.lpr new file mode 100644 index 0000000..8ad4a93 --- /dev/null +++ b/examples/ImageInsert/ImageInsert.lpr @@ -0,0 +1,95 @@ +{ fpOdf "Image Insert" Example + + Copyright (c) 2013-2019 Daniel F.Gaspary + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +} + +program ImageInsert; + +uses + classes, odf_types, base64; + +const + cStyle = 'Standard'; + + cInputFile = '/tmp/image1.png'; + cOutputFile = '/tmp/output.fodt'; + +function EncodeStreamBase64(AInputStream: TStream):String; +var + Outstream : TStringStream; + Encoder : TBase64EncodingStream; +begin + Outstream:=TStringStream.Create(''); + try + Encoder:=TBase64EncodingStream.create(outstream); + try + Encoder.CopyFrom(AInputStream, AInputStream.Size); + finally + Encoder.Free; + end; + Result:=Outstream.DataString; + finally + Outstream.free; + end; +end; + +var + doc: TOdfTextDocument; + p: TOdfParagraph; + eDrawFrame, eDrawImage, eBinaryData: TOdfElement; + + fs: TFileStream; + s: string; +begin + doc:=TOdfTextDocument.Create; + + doc.AddParagraph(cStyle).TextContent:='p1'; + p:=doc.AddParagraph(cStyle); + + eDrawFrame:=p.AppendOdfElement(oetDrawFrame); + + eDrawFrame.SetAttributes( + [oatDrawStyleName, oatDrawName, oatTextAnchorType, oatSvgWidth, oatSvgHeight, oatDrawZIndex], + ['fr1', 'Image1', 'paragraph', '5.80in', '3.6in', '0']); + + eDrawImage:=eDrawFrame.AppendOdfElement(oetDrawImage); + + eBinaryData:=eDrawImage.AppendOdfElement(oetOfficeBinaryData); + + try + fs:=TFileStream.Create(cInputFile, fmOpenRead); + s:=EncodeStreamBase64(fs); + finally + fs.Free; + end; + + eBinaryData.TextContent:=s; + + doc.AddParagraph(cStyle).TextContent:='p2'; + + try + doc.SaveToSingleXml(cOutputFile); + + finally + doc.Free; + end; +end. + diff --git a/examples/ImageInsert/ImageInsert.lps b/examples/ImageInsert/ImageInsert.lps new file mode 100644 index 0000000..a976f7e --- /dev/null +++ b/examples/ImageInsert/ImageInsert.lps @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectSession> + <Version Value="9"/> + <BuildModes Active="Default"/> + <Units Count="3"> + <Unit0> + <Filename Value="ImageInsert.lpr"/> + <IsPartOfProject Value="True"/> + <IsVisibleTab Value="True"/> + <TopLine Value="66"/> + <CursorPos X="25" Y="93"/> + <UsageCount Value="20"/> + <Loaded Value="True"/> + </Unit0> + <Unit1> + <Filename Value="../../odf_types.pas"/> + <EditorIndex Value="-1"/> + <TopLine Value="1333"/> + <CursorPos X="6" Y="1335"/> + <UsageCount Value="10"/> + </Unit1> + <Unit2> + <Filename Value="/usr/share/fpcsrc/2.7.1/packages/fcl-base/src/base64.pp"/> + <EditorIndex Value="-1"/> + <TopLine Value="148"/> + <UsageCount Value="10"/> + </Unit2> + </Units> + <JumpHistory Count="2" HistoryIndex="1"> + <Position1> + <Filename Value="ImageInsert.lpr"/> + <Caret Line="8" Column="26"/> + </Position1> + <Position2> + <Filename Value="ImageInsert.lpr"/> + <Caret Line="63" Column="33" TopLine="43"/> + </Position2> + </JumpHistory> + </ProjectSession> +</CONFIG> diff --git a/examples/TableInsert/TableInsert.lpi b/examples/TableInsert/TableInsert.lpi new file mode 100644 index 0000000..5b2f72c --- /dev/null +++ b/examples/TableInsert/TableInsert.lpi @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectOptions> + <Version Value="9"/> + <General> + <Flags> + <MainUnitHasCreateFormStatements Value="False"/> + <MainUnitHasTitleStatement Value="False"/> + </Flags> + <SessionStorage Value="InProjectDir"/> + <MainUnit Value="0"/> + <Title Value="TableInsert"/> + <UseAppBundle Value="False"/> + <ResourceType Value="res"/> + </General> + <i18n> + <EnableI18N LFM="False"/> + </i18n> + <VersionInfo> + <StringTable ProductVersion=""/> + </VersionInfo> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + </local> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="fpOdf"/> + </Item1> + </RequiredPackages> + <Units Count="1"> + <Unit0> + <Filename Value="TableInsert.lpr"/> + <IsPartOfProject Value="True"/> + <UnitName Value="TableInsert"/> + </Unit0> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target> + <Filename Value="TableInsert"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/examples/TableInsert/TableInsert.lpr b/examples/TableInsert/TableInsert.lpr new file mode 100644 index 0000000..99fad44 --- /dev/null +++ b/examples/TableInsert/TableInsert.lpr @@ -0,0 +1,126 @@ +{ fpOdf "Table Insert" Example + + Copyright (c) 2013-2019 Daniel F.Gaspary + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +} + +program TableInsert; + +uses + Classes, sysutils, odf_types; + +const + cStyle = 'Standard'; + cOutputFile = '/tmp/output.fodt'; + cTableStyle = 'Table1'; + cTableColumnStyle = 'Table1.A'; + cTableCellStyle = 'Table1.A'; + + cTableCellStyle2 = 'CellStyle2'; + + cRowCount = 3; + cColCount = 3; + + +var + doc: TOdfTextDocument; + p: TOdfParagraph; + e, vTable, vRow, vCell: TOdfElement; + + s, t: string; + + i, j: integer; + +procedure CreateStyles; +begin + e:=doc.CreateStyle(cTableStyle, sfvTble); + doc.AutomaticStyles.AppendChild(e); + e:=e.AppendOdfElement(oetStyleTableProperties, oatStyleWidth, '6.6931in'); + e.SetAttribute(oatTableAlign, 'margins'); + + e:=doc.CreateStyle(cTableColumnStyle, sfvTableColumn); + doc.AutomaticStyles.AppendChild(e); + e:=e.AppendOdfElement(oetStyleTableColumnProperties, oatStyleWidth, '3.346in'); + e.SetAttribute(oatStyleRelColumnWidth, '32767*'); + + e:=doc.CreateOdfElement(oetStyleDefaultStyle, oatStyleFamily, 'table'); + doc.Styles.AppendChild(e); + e:=e.AppendOdfElement(oetStyleTableProperties, oatTableBorderModel, 'collapsing'); + + e:=doc.CreateOdfElement(oetStyleDefaultStyle, oatStyleFamily, 'table-row'); + doc.Styles.AppendChild(e); + e:=e.AppendOdfElement(oetStyleTableRowProperties, oatFoKeepTogether, 'auto'); + + e:=doc.CreateStyle(cTableCellStyle2, sfvParagraph); + e.SetAttribute(oatStyleParentStyleName, cStyle); + doc.Styles.AppendChild(e); + e:=e.AppendOdfElement(oetStyleTextProperties, oatFoFontWeight, 'bold'); + e.SetAttributes([oatStyleFontWeightAsian, oatStyleFontWeightComplex], ['bold', 'bold']); +end; + +begin + doc:=TOdfTextDocument.Create; + + CreateStyles; + + doc.AddParagraph(cStyle).TextContent:='p1'; + p:=doc.AddParagraph(cStyle); + + //Create Table + vTable:=doc.CreateOdfElement(oetTableTable); + vTable.SetAttributes([oatTableName, oatTableStyleName], ['Table1', cTableStyle]); + + //Create table column description. + e:=vTable.AppendOdfElement(oetTableTableColumn, oatTableStyleName, cTableColumnStyle); + e.SetAttribute(oatTableNumberColumnsRepeated, IntToStr(cColCount)); + + //create rows and cells + for i:=1 to cRowCount do + begin + vRow:=vTable.AppendOdfElement(oetTableTableRow); + + for j:=1 to cColCount do + begin + vCell:=vRow.AppendOdfElement(oetTableTableCell, oatTableStyleName, cTableCellStyle); + vCell.SetAttribute(oatOfficeValueType, 'string'); + + if i=j + then + s:=cTableCellStyle2 + else + s:=cStyle; + + t:=Format('%d%d', [i,j]); + vCell.AppendOdfElement(oetTextP, oatTextStyleName, s).TextContent:=t; + end; + end; + + doc.Text.AppendChild(vTable); + + doc.AddParagraph(cStyle).TextContent:='p2'; + + try + doc.SaveToSingleXml(cOutputFile); + + finally + doc.Free; + end; +end. + diff --git a/examples/TableInsert/TableInsert.lps b/examples/TableInsert/TableInsert.lps new file mode 100644 index 0000000..2669014 --- /dev/null +++ b/examples/TableInsert/TableInsert.lps @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectSession> + <Version Value="9"/> + <BuildModes Active="Default"/> + <Units Count="3"> + <Unit0> + <Filename Value="TableInsert.lpr"/> + <IsPartOfProject Value="True"/> + <IsVisibleTab Value="True"/> + <CursorPos Y="66"/> + <UsageCount Value="20"/> + <Loaded Value="True"/> + </Unit0> + <Unit1> + <Filename Value="../../odf_types.pas"/> + <UnitName Value="odf_types"/> + <EditorIndex Value="-1"/> + <TopLine Value="1102"/> + <CursorPos X="22" Y="1114"/> + <UsageCount Value="10"/> + </Unit1> + <Unit2> + <Filename Value="../../incs/styles-decl.inc"/> + <EditorIndex Value="-1"/> + <TopLine Value="81"/> + <CursorPos X="33" Y="39"/> + <UsageCount Value="10"/> + </Unit2> + </Units> + <JumpHistory Count="4" HistoryIndex="3"> + <Position1> + <Filename Value="TableInsert.lpr"/> + <Caret Line="61" Column="11" TopLine="55"/> + </Position1> + <Position2> + <Filename Value="TableInsert.lpr"/> + <Caret Line="52" Column="45" TopLine="48"/> + </Position2> + <Position3> + <Filename Value="TableInsert.lpr"/> + <Caret Line="116" Column="47" TopLine="75"/> + </Position3> + <Position4> + <Filename Value="TableInsert.lpr"/> + <Caret Line="70" Column="52" TopLine="56"/> + </Position4> + </JumpHistory> + </ProjectSession> +</CONFIG> diff --git a/odf_types.pas b/odf_types.pas index 7b5d81e..3a2d136 100644 --- a/odf_types.pas +++ b/odf_types.pas @@ -3,7 +3,7 @@ fpOdf is a library used to help users to create and to modify OpenDocument Files(ODF) - Copyright (C) 2013-2019 Daniel F. Gaspary https://github.com/dgaspary + Copyright (C) 2013-2021 Daniel F. Gaspary https://github.com/dgaspary This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by @@ -41,7 +41,7 @@ interface uses - Classes, SysUtils, FileUtil, LazFileUtils, zipper, zstream, fgl, LazUTF8, Graphics, + Classes, SysUtils, FileUtil, LazFileUtils, zipper, zstream, fgl, LazUTF8, FPCanvas, { $Define ODF_LOGGING} @@ -249,6 +249,7 @@ TOdfElementTypeSet = class(specialize TFPGList<TElementType>) TOdfXmlFiles = ofManifestRdf .. High(TOdfFile); + const OdfXmlFileRoot: array[TOdfXmlFiles] of TElementType = ( @@ -384,6 +385,25 @@ TConfigConfigItemSet = class(TOdfElement) {$INCLUDE incs/styles-decl.inc} type + + TOdfColor = record + Red, + Green, + Blue: Byte; + end; + + { TOdfFont } + + TOdfFont = class(TFPCustomFont) + private + FColor: TOdfColor; + public + property Color: TOdfColor read FColor write FColor; + end; + + { TODO : Odf Font Style has different items than TFontStyle } + TOdfFontStyles = set of TOdfFontStyle; + TOdfNodeSet = TNodeSet; TOdfXPathNsResolver = class; @@ -392,6 +412,9 @@ TOdfXPathNsResolver = class; TOdfDocument = class private + FTempDir: string; + FRemoveTempDir: boolean; + FXmlDocument: TXMLDocument; //Document Root Elements (Ref: Table 7) @@ -406,7 +429,7 @@ TOdfDocument = class FMasterStyles: TDOMElement; FBody: TDOMElement; - FManifest: TDOMElement; + FManifest: TXMLDocument; FManifestRdf: TDOMElement; FXPathNsResolver: TOdfXPathNsResolver; @@ -419,6 +442,8 @@ TOdfDocument = class procedure FindOdfRootElements;virtual; procedure InitBodyContent; virtual; abstract; + procedure ResetRootElements; virtual; + function GetMimeType: TOdfMimetype; procedure SetCreationMeta; @@ -438,14 +463,21 @@ TOdfDocument = class constructor Create; destructor Destroy; override; + class function GetMimeType(ADoc: TXMLDocument): TOdfMimetype; + class function ElementOdfClassByType(et: TElementType): TOdfElementClass; class function ConvertOdfElement(AClass: TOdfElementClass; e: TDOMElement): TOdfElement; + class function CreateDocument(AMimeType: TOdfMimetype): TOdfDocument; + class function LoadFromFile(AFilename: string): TOdfDocument; class function LoadFromZipFile(AFilename, TempDir: string): TOdfDocument; - class function LoadFromSingleXml(AFilename: string): TOdfDocument; + + class function CreateFromXml(ADoc: TXMLDocument): TOdfDocument; + class function LoadFromSingleXml(AStream: TStream): TOdfDocument; overload; + class function LoadFromSingleXml(AFilename: string): TOdfDocument; overload; class procedure SaveToSingleXml(AOdf: TOdfDocument; AFilename: string); overload; @@ -461,7 +493,7 @@ TOdfDocument = class AParentStyle: TOdfStyleStyle): TOdfStyleStyle; - function CreateSpan(AText: string; FontStyles: TFontStyles): TSpan; + function CreateSpan(AText: string; FontStyles: TOdfFontStyles): TSpan; function SearchText(AText: string; AParent: TDOMElement; Accepted: TElementTypeArray; @@ -501,10 +533,13 @@ TOdfDocument = class function GetStyleElementByName(AStyleName: string): TDOMElement; - function GetStyleByProperties(AFamily: TStyleFamilyValue; - AFontSize: integer; AFontSizeUnit: string; - AFontStyle: TOdfFontStyle; AFontWeigth: TOdfFontWeight; - AStyleFamily: string): TStrings; + //Params with default values will be ignored in search. + //Example: 0 for AFontSize, '' for AStyleFamily and fwNone for AFontWeight + function GetStyleByProperties(AFontSize: integer; AFontSizeUnit: string; + AFontStyle: TOdfFontStyle; AFontWeight: TOdfFontWeight; + AFontUnderlineStyle: string): TStrings; + + function Clone: TOdfDocument; virtual; property Settings: TDOMElement read FSettings write FSettings; property Scripts: TDOMElement read FScripts write FScripts; @@ -513,8 +548,10 @@ TOdfDocument = class property AutomaticStyles: TDOMElement read FAutomaticStyles write FAutomaticStyles; property MasterStyles: TDOMElement read FMasterStyles write FMasterStyles; - property Manifest: TDOMElement read FManifest write FManifest; + property Manifest: TXMLDocument read FManifest write FManifest; property ManifestRdf: TDOMElement read FManifestRdf write FManifestRdf; + + property RemoveTempDir: boolean read FRemoveTempDir write FRemoveTempDir; end; @@ -540,12 +577,12 @@ TOdfParagraph = class(TOdfContent) private public - function AddSpan(AText: string; FontStyles: TFontStyles): TSpan;overload; - function AddSpan(AText: string; aFont: TFont;const doc: TOdfDocument): TSpan; + function AddSpan(AText: string; FontStyles: TOdfFontStyles): TSpan;overload; + function AddSpan(AText: string; aFont: TOdfFont;const doc: TOdfDocument): TSpan; overload; function AddSpan(AText: string; aStyle: string): TSpan;overload; - function AddBookmark(AText: string; FontStyles: TFontStyles;aBMName:string): TSpan; - function AddLink(AText: string; FontStyles: TFontStyles;aBMName:string): THyperLink; + function AddBookmark(AText: string; FontStyles: TOdfFontStyles;aBMName:string): TSpan; + function AddLink(AText: string; FontStyles: TOdfFontStyles;aBMName:string): THyperLink; //p1-7.4.15 { TODO : Text input can also be included on Span, oetTextH, etc } @@ -560,8 +597,8 @@ TSpan = class(TOdfContent) public class function CreateSpan(doc: TXMLDocument; AText: string): TSpan; - procedure SetStyle(fs: TFontStyles);overload; - procedure SetStyle(const doc: TOdfDocument; aFont: TFont); overload; + procedure SetStyle(fs: TOdfFontStyles);overload; + procedure SetStyle(const doc: TOdfDocument; aFont: TOdfFont); overload; procedure SetStyle(aStyleName: string);overload; end; @@ -593,6 +630,8 @@ TOdfTextDocument = class(TOdfDocument) FText: TDOMElement; procedure InitXmlDocument; override; + procedure ResetRootElements; override; + procedure InitBodyContent; override; Procedure GenerateAutoStyles; public @@ -928,14 +967,15 @@ constructor TOdfElementTypeSet.Create(EtArray: TElementTypeArray); { TOdfParagraph } -function TOdfParagraph.AddSpan(AText: string; FontStyles: TFontStyles): TSpan; +function TOdfParagraph.AddSpan(AText: string; FontStyles: TOdfFontStyles + ): TSpan; begin result:=TSpan.CreateSpan(self.OwnerDocument as TXMLDocument, AText); result.SetStyle(FontStyles); AppendChild(result); end; -function TOdfParagraph.AddSpan(AText: string; aFont: TFont; +function TOdfParagraph.AddSpan(AText: string; aFont: TOdfFont; const doc: TOdfDocument): TSpan; begin result:=TSpan.CreateSpan(self.OwnerDocument as TXMLDocument, AText); @@ -950,7 +990,7 @@ function TOdfParagraph.AddSpan(AText: string; aStyle: string): TSpan; AppendChild(result); end; -function TOdfParagraph.AddBookmark(AText: string; FontStyles: TFontStyles; +function TOdfParagraph.AddBookmark(AText: string; FontStyles: TOdfFontStyles; aBMName: string): TSpan; begin result := TBookMark.CreateBookmark(self.OwnerDocument as TXMLDocument,AText,aBMName); @@ -958,7 +998,7 @@ function TOdfParagraph.AddBookmark(AText: string; FontStyles: TFontStyles; AppendChild(result); end; -function TOdfParagraph.AddLink(AText: string; FontStyles: TFontStyles; +function TOdfParagraph.AddLink(AText: string; FontStyles: TOdfFontStyles; aBMName: string): THyperLink; begin result := THyperLink.CreateLink(self.OwnerDocument as TXMLDocument,AText,aBMName); @@ -1033,9 +1073,9 @@ procedure THyperLink.SetLink(aBMName: string); { TSpan } -procedure TSpan.SetStyle(fs: TFontStyles); +procedure TSpan.SetStyle(fs: TOdfFontStyles); var - lfs: TFontStyle; + lfs: TOdfFontStyle; lFsName: string; lFsVal: Integer; begin @@ -1048,7 +1088,7 @@ procedure TSpan.SetStyle(fs: TFontStyles); SetAttribute(oatTextStyleName,lFsName); end; -procedure TSpan.SetStyle(const doc: TOdfDocument; aFont: TFont); +procedure TSpan.SetStyle(const doc: TOdfDocument; aFont: TOdfFont); var lStyle: TOdfStyleStyle; lStyleprop: TOdfElement; @@ -1069,16 +1109,15 @@ procedure TSpan.SetStyle(const doc: TOdfDocument; aFont: TFont); if not assigned(lFontdcls) then begin lFontdcls := CreateOdfElement(oetStyleFontFace,oatStyleName,afont.Name); - TOdfElement(lFontdcls).SetAttribute(oatSvgFontFamily,afont.Name.QuotedString('''')); + TOdfElement(lFontdcls).SetAttribute(oatSvgFontFamily, QuotedStr(afont.Name)); FontFaceDecls.AppendChild(lFontdcls); end; lStyleprop.SetAttribute(oatStyleFontName,afont.Name) ; end; - if afont.Color <>clDefault then +// if afont.Color <>clDefault then { TODO : There is no "default" color for TFpColor.. } begin - RedGreenBlue(afont.Color,lR,lG,lB); - lStyleprop.SetAttribute(oatFoColor,'#'+IntToHex(Integer(RGBToColor(lb,lg,lr)),6)) ; - + with aFont.Color do + lStyleprop.SetAttribute(oatFoColor,'#'+Format('%2.2x%2.2x%2.2x', [Red, Green, Blue])) ; end; if afont.Size>0 then lStyleprop.SetAttribute(oatFoFontSize,inttostr(afont.Size)+'pt') ; @@ -1244,6 +1283,13 @@ procedure TOdfTextDocument.InitXmlDocument; MimeType:=omtText; end; +procedure TOdfTextDocument.ResetRootElements; +begin + inherited ResetRootElements; + + FText:=nil; +end; + procedure TOdfTextDocument.InitBodyContent; begin if Assigned(FText) @@ -1725,6 +1771,7 @@ function TOdfDocument.StylesUsed(AParent: TDomElement): TStrings; s+='//' + AParent.TagName + '//@' + OdfGetAttributeQName(att); end; + { TODO : Maybe the correct is to search starting from BODY element } if XPathSearch(s, FXmlDocument.DocumentElement, [], StylesFound) then begin @@ -1765,11 +1812,8 @@ class function TOdfDocument.ParseXmlFile(AStream: TStream): TXMLDocument; end; function TOdfDocument.GetMimeType: TOdfMimetype; -var - s: string; begin - s:=XmlDocument.DocumentElement.GetAttributeNS(GetURI(onsOffice), 'mimetype'); - result:=OdfGetMimeTypeByName(s); + Result:=GetMimeType(XmlDocument); end; class function TOdfDocument.LoadFromFile(AFilename: string): TOdfDocument; @@ -2043,18 +2087,25 @@ class procedure TOdfDocument.ReadPackage(ADir: String; AOdf: TOdfDocument); LoadXml('settings.xml'); AOdf.Settings:=MoveElem(vNsURI, 'settings'); - vDoc.Free; + LoadXml(IncludeTrailingPathDelimiter('META-INF') + 'manifest.xml'); - DeleteDirectory(ADir, false); + AOdf.Manifest:=vDoc; + vDoc:=nil; AOdf.InitBodyContent; end; procedure TOdfDocument.SetXmlDocument(AValue: TXMLDocument); begin - if assigned(FXmlDocument) then - FreeAndNil(FXmlDocument); - FXmlDocument:=AValue; + if Assigned(FXmlDocument) + then + begin + FreeAndNil(FXmlDocument); + + ResetRootElements; + end; + + FXmlDocument:=AValue; end; function OdfCreateManifestRdfFile: TXMLDocument; @@ -2138,9 +2189,9 @@ function OdfCreateManifestFile: TXMLDocument; AddFileEntry('manifest.rdf', 'application/rdf+xml'); AddFileEntry('styles.xml', 'text/xml'); AddFileEntry('meta.xml', 'text/xml'); - AddFileEntry('Thumbnails/thumbnail.png', 'image/png'); +{ AddFileEntry('Thumbnails/thumbnail.png', 'image/png'); AddFileEntry('Thumbnails/'); - +} { TODO : Configurations2 seems to be an OpenOffice specific directory. } AddFileEntry('Configurations2/accelerator/current.xml'); @@ -2190,7 +2241,7 @@ class procedure TOdfDocument.WritePackage(DestFile: String; if not OdfXPathSearch(vXPath, xmlDoc.DocumentElement, FoundElements) then - Raise Exception.Create('No Styles Found at destiny file.'); + Raise Exception.Create('No Styles Found at dOdfXPathSearchestiny file.'); if FoundElements.Count>0 then @@ -2239,7 +2290,13 @@ class procedure TOdfDocument.WritePackage(DestFile: String; nsSet:=[]; case f of ofManifestRdf : vDoc:=OdfCreateManifestRdfFile; - ofManifest : vDoc:=OdfCreateManifestFile; + ofManifest : begin + if Assigned(AOdf.Manifest) + then + vDoc:=AOdf.Manifest + else + vDoc:=OdfCreateManifestFile; + end else begin vDoc:=TXMLDocument.Create; @@ -2292,7 +2349,101 @@ class procedure TOdfDocument.WritePackage(DestFile: String; z.Entries.AddFileEntry(ATempDir + vFilename, vFilename); - vDoc.Free; + if (f<>ofManifest) // ?? or ( (f=ofManifest) and (AOdf.Manifest=nil)) + then + vDoc.Free; + end; + + procedure UpdateManifest(ASubDir, AFileName: string); + var + vNode: TDOMNode; + vElement: TDOMElement; + vMediaType: string; + begin + if not Assigned(AOdf.Manifest) + then + begin + { TODO : Create manifest } + end; + + ASubDir:=ExcludeTrailingPathDelimiter(ASubDir); + + vNode:=AOdf.Manifest.DocumentElement.FirstChild; + while Assigned(vNode) do + begin + if (vNode is TDOMElement) + then + begin + vElement:=(vNode as TDOMElement); + + if TOdfElement.SameType(vElement, oetManifestFileEntry) and + (TOdfElement(vElement).GetAttributeString(oatManifestFullPath) = ASubDir + '/' + AFileName) + then + break; + end; + + vElement:=nil; + + vNode:=vNode.NextSibling; + end; + + if vElement=nil + then + begin + vElement:=TOdfElement.CreateDomElement(oetManifestFileEntry, AOdf.Manifest, + oatManifestFullPath, ASubDir + '/' + AFileName); + + + { TODO : The Discovering of media type should have a dedicated procedure/classes } + vMediaType:=ExtractFileExt(AFileName); + if vMediaType='jpg' + then + vMediaType:='jpeg'; + + + if (vMediaType='jpeg') or (vMediaType='png') + then + vMediaType:='image/' + vMediaType; + + TOdfElement(vElement).SetAttribute(oatManifestMediaType, vMediaType); + + AOdf.Manifest.DocumentElement.AppendChild(vElement); + end; + + end; + + procedure AddFilesToZipper(ASubDir: string); + var + FileInfo : TSearchRec; + vBaseDir, vFile: string; + begin + vBaseDir:=IncludeTrailingPathDelimiter(AOdf.FTempDir); + + if (AOdf.FTempDir='') or (not DirectoryExistsUTF8(vBaseDir + ASubDir)) + then + exit; + + ASubDir:=IncludeTrailingPathDelimiter(ASubDir); + + if FindFirst (vBaseDir + ASubDir + '*',faAnyFile,FileInfo)=0 + then + begin + repeat + With FileInfo do + begin + if (Attr and faDirectory) = faDirectory + then + Continue; + + vFile:=vBaseDir + ASubDir + Name; + zfe:=z.Entries.AddFileEntry(vFile, ASubDir + Name); + //(zfe as TZipFileEntry).CompressionLevel:=Tcompressionlevel.clnone; + + UpdateManifest(ASubDir, Name); + end; + until FindNext(FileInfo)<>0; + FindClose(FileInfo); + end; end; begin @@ -2318,6 +2469,11 @@ class procedure TOdfDocument.WritePackage(DestFile: String; vBodyStyles:=AOdf.StylesUsed(AOdf.Body); + { TODO : List and extract filenames using XPath, avoiding hardcoded directories. + Example Element: <draw:image xlink:href="Pictures/12345.jpg" } + AddFilesToZipper('Pictures'); + AddFilesToZipper('Thumbnails'); + for f in TOdfXmlFiles do begin WriteFileToDisk; @@ -2490,10 +2646,34 @@ procedure TOdfDocument.FindOdfRootElements; FBody:=OdfGetElement(oetOfficeBody,e); end; end; + +procedure TOdfDocument.ResetRootElements; +begin + FMeta:=nil; + FSettings:=nil; + FScripts:=nil; + FFontFaceDecls:=nil; + FStyles:=nil; + FAutomaticStyles:=nil; + FMasterStyles:=nil; + + FBody:=nil; + + if Assigned(FManifest) + then + FManifest.Free; + + FManifest:=nil; + FManifestRdf:=nil; +end; + constructor TOdfDocument.Create; begin inherited Create; + FTempDir:=''; + FRemoveTempDir:=true; + InitXmlDocument; end; @@ -2502,9 +2682,25 @@ destructor TOdfDocument.Destroy; FXPathNsResolver.Free; FXmlDocument.Free; + if Assigned(FManifest) + then + FManifest.Free; + + if FRemoveTempDir and DirectoryExistsUTF8(FTempDir) + then + RemoveDirUTF8(FTempDir); + inherited Destroy; end; +class function TOdfDocument.GetMimeType(ADoc: TXMLDocument): TOdfMimetype; +var + s: string; +begin + s:=ADoc.DocumentElement.GetAttributeNS(GetURI(onsOffice), 'mimetype'); + result:=OdfGetMimeTypeByName(s); +end; + class function TOdfDocument.ElementOdfClassByType(et: TElementType ): TOdfElementClass; begin @@ -2562,6 +2758,15 @@ procedure TOdfDocument.SetMimeType(AValue: TOdfMimetype); OdfSetAttributeValue(oatOfficeMimetype, XmlDocument.DocumentElement, s); end; +class function TOdfDocument.CreateDocument(AMimeType: TOdfMimetype): TOdfDocument; +begin + case AMimeType of + omtText: Result:=TOdfTextDocument.Create; + else + Result:=TOdfDocument.Create; + end; +end; + class function TOdfDocument.LoadFromZipFile(AFilename, TempDir: string): TOdfDocument; var z: TUnZipper; @@ -2579,17 +2784,33 @@ class function TOdfDocument.LoadFromZipFile(AFilename, TempDir: string): TOdfDoc mt:=OdfGetMimeTypeFromFile(TempDir + 'mimetype'); - case mt of - omtText: result:=TOdfTextDocument.Create; - else - result:=TOdfDocument.Create; - end; + result:=TOdfDocument.CreateDocument(mt); ReadPackage(TempDir, result); result.MimeType:=mt; - RemoveDirUTF8(TempDir); + Result.FTempDir:=TempDir; +end; + +class function TOdfDocument.CreateFromXml(ADoc: TXMLDocument): TOdfDocument; +var + mt: TOdfMimetype; +begin + mt:=GetMimeType(ADoc); + + Result:=CreateDocument(mt); + + Result.XmlDocument:=ADoc; + Result.FindOdfRootElements; +end; + +class function TOdfDocument.LoadFromSingleXml(AStream: TStream): TOdfDocument; +var + vDoc: TXMLDocument; +begin + vDoc:=ParseXmlFile(AStream); + Result:=TOdfDocument.CreateFromXml(vDoc); end; class function TOdfDocument.LoadFromSingleXml(AFilename: string @@ -2600,9 +2821,7 @@ class function TOdfDocument.LoadFromSingleXml(AFilename: string result:=nil; fs:=TFileStream.Create(AFilename, fmOpenRead); try - result:=TOdfDocument.Create; - result.XmlDocument:=ParseXmlFile(fs); - result.FindOdfRootElements; + result:=TOdfDocument.LoadFromSingleXml(fs); finally fs.Free; end; @@ -2663,7 +2882,8 @@ function TOdfDocument.CreateStyle(AStyleName: string; result.OdfStyleParentStyleName:=AParentStyle.OdfStyleName; end; -function TOdfDocument.CreateSpan(AText: string; FontStyles: TFontStyles): TSpan; +function TOdfDocument.CreateSpan(AText: string; FontStyles: TOdfFontStyles + ): TSpan; begin result:=TSpan.CreateSpan(self.XmlDocument, AText); result.SetStyle(FontStyles); @@ -2887,11 +3107,91 @@ function TOdfDocument.GetStyleElementByName(AStyleName: string): TDOMElement; OdfLogExitProc; end; -function TOdfDocument.GetStyleByProperties(AFamily: TStyleFamilyValue; - AFontSize: integer; AFontSizeUnit: string; AFontStyle: TOdfFontStyle; - AFontWeigth: TOdfFontWeight; AStyleFamily: string): TStrings; +function TOdfDocument.GetStyleByProperties(AFontSize: integer; + AFontSizeUnit: string; AFontStyle: TOdfFontStyle; + AFontWeight: TOdfFontWeight; AFontUnderlineStyle: string): TStrings; +var + vConditions: string; + s: string; + + p: pointer; + PropertiesFound: TOdfNodeSet; + + AStyle: TDOMElement; + + procedure AddCondition(Att: TAttributeType; AValue: string); + begin + if vConditions<>'' + then + vConditions+=' and '; + + vConditions+='@' + OdfGetAttributeQName(Att) + '=' + QuotedStr(AValue); + end; + begin - { TODO : To be done.. } + Result:=TStringList.Create; + + vConditions:=''; + + { TODO : Include Complex and Asian font attributes? } + if AFontSize>0 + then + AddCondition(oatFoFontSize, IntToStr(AFontSize) + AFontSizeUnit); + + if AFontStyle<>ofsNone + then + AddCondition(oatFoFontStyle, OdfGetFontStyleValue(AFontStyle)); + + if AFontWeight<>fwNone + then + AddCondition(oatFoFontWeight, OdfGetFontWeightValue(AFontWeight)); + + if AFontUnderlineStyle<>'' + then + AddCondition(oatStyleTextUnderlineStyle, AFontUnderlineStyle); + + if vConditions='' + then + exit; + + try + { TODO : Automatic styles and Parent resolution } + + //s:=Format('//style:text-properties[%s]', [s]); + s:='//style:text-properties[' + vConditions + ']'; + + PropertiesFound:=nil; + if XPathSearch(s, FStyles, [], PropertiesFound) + then + for p in PropertiesFound do + if TObject(p) is TDOMElement + then + begin + AStyle:=TDOMElement(p).ParentNode as TDOMElement; + s:=OdfGetAttributeValue(oatStyleName, AStyle); + + if (s<>'') and (Result.IndexOf(s)<0) + then + Result.Add(s); + + end; + finally + if Assigned(PropertiesFound) + then + PropertiesFound.Free; + end; +end; + +function TOdfDocument.Clone: TOdfDocument; +var + vDoc: TXMLDocument; + e: TDOMElement; +begin + vDoc:=TXMLDocument.Create; + e:=FXmlDocument.DocumentElement.CloneNode(true, vDoc) as TDOMElement; + vDoc.AppendChild(e); + + Result:=TOdfDocument.CreateFromXml(vDoc); end; diff --git a/odf_xmlutils.pas b/odf_xmlutils.pas index 5cdb0da..a5e6808 100644 --- a/odf_xmlutils.pas +++ b/odf_xmlutils.pas @@ -3,7 +3,7 @@ fpOdf is a library used to help users to create and to modify OpenDocument Files(ODF) - Copyright (C) 2013-2015 Daniel F. Gaspary https://github.com/dgaspary + Copyright (C) 2013-2019 Daniel F. Gaspary https://github.com/dgaspary This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by @@ -52,20 +52,32 @@ interface ; +procedure OdfWriteXmlToStream(ADoc: TXMLDocument; AStream: TStream); procedure OdfWriteXmlToFile(ADoc: TXMLDocument; AFilename: string); function OdfAttributesAsStrings(e: TDOMElement; OnlyNames: boolean = true): TStrings; implementation -procedure OdfWriteXmlToFile(ADoc: TXMLDocument; AFilename: string); +procedure OdfWriteXmlToStream(ADoc: TXMLDocument; AStream: TStream); begin {$IfDef UseStaxWriter} - XmlStreamWrite(ADoc, AFilename, 'utf-8', '1.0'); + XmlStreamWrite(ADoc, AStream, 'utf-8', '1.0'); {$Else} - WriteXMLFile(ADoc, AFilename,[xwfPreserveWhiteSpace]); + WriteXMLFile(ADoc, AStream,[xwfPreserveWhiteSpace]); {$EndIf} +end; +procedure OdfWriteXmlToFile(ADoc: TXMLDocument; AFilename: string); +var + fs: TFileStream; +begin + try + fs:=TFileStream.Create(AFilename, fmCreate); + OdfWriteXmlToStream(ADoc, fs); + finally + fs.Free; + end; end; function OdfAttributesAsStrings(e: TDOMElement; OnlyNames: boolean): TStrings; diff --git a/package/fpodf.lpk b/package/fpodf.lpk index c47fcf5..cf98da7 100644 --- a/package/fpodf.lpk +++ b/package/fpodf.lpk @@ -1,59 +1,56 @@ -<?xml version="1.0" encoding="UTF-8"?> -<CONFIG> - <Package Version="4"> - <Name Value="fpOdf"/> - <Type Value="RunTimeOnly"/> - <Author Value="Daniel F. Gaspary"/> - <CompilerOptions> - <Version Value="11"/> - <SearchPaths> - <OtherUnitFiles Value=".."/> - <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> - </SearchPaths> - <Linking> - <Debugging> - <UseExternalDbgSyms Value="True"/> - </Debugging> - </Linking> - </CompilerOptions> +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <Package Version="4"> + <Name Value="fpOdf"/> + <Type Value="RunTimeOnly"/> + <Author Value="Daniel F. Gaspary"/> + <CompilerOptions> + <Version Value="11"/> + <SearchPaths> + <OtherUnitFiles Value=".."/> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Debugging> + <UseExternalDbgSyms Value="True"/> + </Debugging> + </Linking> + </CompilerOptions> <Description Value="Library to help to generate and to modify ODF files. -http://en.wikipedia.org/wiki/OpenDocument"/> +http://en.wikipedia.org/wiki/OpenDocument"/> <License Value="Modified LGPL. -Read the file COPYING.modifiedLGPL.txt for more informations."/> - <Version Release="1"/> - <Files Count="3"> - <Item1> - <Filename Value="../odf_types.pas"/> - <UnitName Value="odf_types"/> - </Item1> - <Item2> - <Filename Value="../odf_mimetypes.pas"/> - <UnitName Value="odf_mimetypes"/> - </Item2> - <Item3> - <Filename Value="../odf_xmlutils.pas"/> - <UnitName Value="odf_xmlutils"/> - </Item3> - </Files> - <LazDoc Paths="../FPdoc"/> - <RequiredPkgs Count="3"> - <Item1> - <PackageName Value="LCLBase"/> - </Item1> - <Item2> - <PackageName Value="LazUtils"/> - </Item2> - <Item3> - <PackageName Value="FCL"/> - </Item3> - </RequiredPkgs> - <UsageOptions> - <UnitPath Value="$(PkgOutDir)"/> - </UsageOptions> - <PublishOptions> - <Version Value="2"/> - </PublishOptions> - </Package> -</CONFIG> +Read the file COPYING.modifiedLGPL.txt for more informations."/> + <Version Release="1"/> + <Files Count="3"> + <Item1> + <Filename Value="../odf_types.pas"/> + <UnitName Value="odf_types"/> + </Item1> + <Item2> + <Filename Value="../odf_mimetypes.pas"/> + <UnitName Value="odf_mimetypes"/> + </Item2> + <Item3> + <Filename Value="../odf_xmlutils.pas"/> + <UnitName Value="odf_xmlutils"/> + </Item3> + </Files> + <LazDoc Paths="../FPdoc"/> + <RequiredPkgs Count="2"> + <Item1> + <PackageName Value="LazUtils"/> + </Item1> + <Item2> + <PackageName Value="FCL"/> + </Item2> + </RequiredPkgs> + <UsageOptions> + <UnitPath Value="$(PkgOutDir)"/> + </UsageOptions> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + </Package> +</CONFIG>