mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-11 08:08:18 +02:00
1388 lines
42 KiB
ObjectPascal
1388 lines
42 KiB
ObjectPascal
{
|
|
|
|
docxvectorialwriter.pas
|
|
|
|
License: The same modified LGPL as the Free Pascal RTL
|
|
See the file COPYING.modifiedLGPL for more details
|
|
|
|
THIS IS A UNIT CURRENTLY UNDER DEVELOPEMENT
|
|
|
|
Current Functionality
|
|
- Text support (inc Tabs and CRs)
|
|
- Paragraph/Character Style Support
|
|
- Supports Section Breaks (via PageSequences)
|
|
- Supports Header and Footer
|
|
- Supports Portrait/Landscape
|
|
- Support Tables
|
|
|
|
TODO
|
|
- Add following to both FPVectorial AND DOCXWriter
|
|
- Add Image Support (From file and from Stream, in Paragraph and in Table)
|
|
- Add simple Work Fields (NumPage, PageCount, Filename (Full, part), DatePrinted)
|
|
- Add TOC support
|
|
- Consider Unicode Support (eek)
|
|
|
|
Writes an OOXML (Office Open XML) document
|
|
|
|
An OOXML document is a compressed ZIP file with the following files inside:
|
|
|
|
[Content_Types].xml - An index of all files, defines their content format
|
|
_rels\.rels - Relationships between high level documents
|
|
word\_rels\document.xml.rels - defines relationships to files required by document.xml (ie styles.xml)
|
|
word\document.xml - this is the main document. Conforms to WordprocessingML
|
|
word\styles.xml - The Style Library
|
|
word\header%d.xml - One file per header
|
|
word\footer%d.xml - One file per footer
|
|
word\numbering.xml - Header and List numbering details
|
|
media\*.[png, jpg, etc] - Images
|
|
|
|
Specifications and examples obtained from:
|
|
|
|
http://openxmldeveloper.org/default.aspx
|
|
http://officeopenxml.com/
|
|
http://www.ecma-international.org/publications/standards/Ecma-376.htm
|
|
- Office Open XML Part 4 - Markup Language Reference.docx (First edition)
|
|
|
|
AUTHORS: Mike Thompson, Felipe Monteiro de Carvalho
|
|
|
|
Change History
|
|
0.1 - minimal document.xml produced
|
|
- experimental support for styles (not working, hidden behind INCLUDE_STYLES define)
|
|
0.2 - Refactored for new VectorialElement TvParagraph
|
|
- Removed INCLUDE_STYLES define
|
|
- Added support for Styles - resulting .docx contains ONLY styles defined in VectorialDocument
|
|
Only Paragraph Style supported for now
|
|
- Added IndentedStringList simply to prettify the resulting XML
|
|
0.3 - Added support for Character Styles (named TextSpan Styles within FPVectorial
|
|
- If Style.Name not defined, then create one based on index
|
|
- Refactored PrepareTextRunStyle out of Prepare Styles. Resulting XML is perfectly valid
|
|
inside Document.XML and Style.xml, though FPVectorial currently does not support this
|
|
- Closed out minor TODO items
|
|
0.4 - realised <>'"& would break the resulting XML. Added EscapeHTML when
|
|
User defined text is outputted into the HTML
|
|
- Multiple PageSequences are now handled.
|
|
- Added support for Portrait/Landscape (reverse Width/Height if you want Landscape)
|
|
- Added generic support for multiple files. Link IDs and Relationships between
|
|
Files are automatically handled. Currently works for XML files only
|
|
but should be easily extended for Image Support (code tagged with TODO)
|
|
- Added support for Header and Footer. Can be defined per PageSequence resulting
|
|
in multiple headers and footers or just on the first PageSequence
|
|
(in which case the rest of the document will inherit the same Header / Footer)
|
|
Couldn't get the inline Header/Footers to work, so implemented as separate files.
|
|
- Added handling for #11 (tab), #10 (line feed), #13 (CR) and #13#10 in Text Support
|
|
- Significant refactoring of code, and formatted code with Jedi Code Format.
|
|
I intend to Jedi Code Format before each Patch from here on
|
|
- Added support for Alignment to Styles
|
|
0.5 - Support TvParagraph margins
|
|
- Added numbering.xml
|
|
0.6 - Changed #11 (vert tab) to #09 (horiz tab, what it always should have been)
|
|
- Added Table Support
|
|
- Bug fix - Margin support in Styles fixed...
|
|
0.7 - Added experimental LocalAlignment support to TvParagraph
|
|
|
|
|
|
}
|
|
|
|
Unit docxvectorialwriter;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
Interface
|
|
|
|
Uses
|
|
Classes, SysUtils,
|
|
zipper, {NOTE: might require zipper from FPC 2.6.2+ }
|
|
fpimage, fpcanvas,
|
|
fpvectorial, fpvutils, lazutf8, Math;
|
|
|
|
Type
|
|
TIndentOption = (indInc, indDec, indNone);
|
|
|
|
{ TIndentedStringList }
|
|
|
|
// Here to just to ensure the resulting xml files are pretty :-)
|
|
{ TODO : Replace this with a genuine XML handler }
|
|
TIndentedStringList = Class(TStringList)
|
|
Private
|
|
FIndent: String;
|
|
FIndentSteps: String;
|
|
Public
|
|
Constructor Create;
|
|
Function Add(indBefore: TIndentOption = indNone; S: String = '';
|
|
indAfter: TIndentOption = indNone): Integer;
|
|
Function Add(Const S: String): Integer; Override;
|
|
Function Add(Const S: String; indAfter: TIndentOption): Integer;
|
|
|
|
Procedure IncIndent;
|
|
Procedure DecIndent;
|
|
End;
|
|
|
|
TFileType = (ftContentTypes, ftRelationships, ftDocRelationships);
|
|
TFileTypes = Set Of TFileType;
|
|
|
|
{ TFileInformation }
|
|
|
|
TFileInformation = Class(TObject)
|
|
Private
|
|
FStream: TStream;
|
|
Function GetStream: TStream;
|
|
Public
|
|
Index: Integer;
|
|
ContentType: String;
|
|
Path: String;
|
|
FileType: String;
|
|
MentionedIn: TFileTypes;
|
|
|
|
XML: TIndentedStringList; // Free'd internally;
|
|
//Image : TFPImage; { TODO: How are we going to handle images? }
|
|
|
|
Constructor Create;
|
|
Destructor Destroy; Override;
|
|
|
|
Function ID: String;
|
|
Function Filename: String;
|
|
|
|
Procedure FreeStream;
|
|
|
|
Property Stream: TStream read GetStream;
|
|
// This creates a TSream, Call .FreeStream to free
|
|
End;
|
|
|
|
{ TFileList }
|
|
|
|
TFileList = Class(TObject)
|
|
Private
|
|
FList: TList;
|
|
Function GetFile(AIndex: Integer): TFileInformation;
|
|
Public
|
|
Constructor Create;
|
|
Destructor Destroy; Override;
|
|
|
|
Function AddXMLFile(AContentType: String; APath: String;
|
|
AFileType: String; AMentionedIn: TFileTypes): TFileInformation;
|
|
|
|
Function Count: Integer;
|
|
Property FileInformation[AIndex: Integer]: TFileInformation read GetFile; Default;
|
|
End;
|
|
|
|
{ TvDOCXVectorialWriter }
|
|
|
|
|
|
TvDOCXVectorialWriter = Class(TvCustomVectorialWriter)
|
|
Private
|
|
FData: TvVectorialDocument;
|
|
|
|
FFiles: TFileList;
|
|
|
|
Function PrepareContentTypes: String;
|
|
Function PrepareRelationships: String;
|
|
Function PrepareDocRelationships: String;
|
|
|
|
Procedure PrepareDocument;
|
|
Procedure PrepareStyles; // Only created if FData has Styles defined
|
|
Procedure PrepareNumbering; // Only created if Numbered Styles exist
|
|
|
|
Procedure PrepareTextRunStyle(ADoc: TIndentedStringList; AStyle: TvStyle);
|
|
Function StyleNameToStyleID(AStyle: TvStyle): String;
|
|
Public
|
|
{ General reading methods }
|
|
Constructor Create; Override;
|
|
Destructor Destroy; Override;
|
|
Procedure WriteToFile(AFileName: String; AData: TvVectorialDocument); Override;
|
|
Procedure WriteToStream(AStream: TStream; AData: TvVectorialDocument); Override;
|
|
End;
|
|
|
|
Implementation
|
|
|
|
Uses
|
|
strutils, htmlelements;
|
|
|
|
Const
|
|
XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
|
|
|
|
{ OOXML general XML constants }
|
|
// Note: No leading '/'. Add where required
|
|
OOXML_PATH_TYPES = '[Content_Types].xml';
|
|
OOXML_PATH_RELS_RELS = '_rels/.rels';
|
|
OOXML_PATH_DOCUMENT_RELS = 'word/_rels/document.xml.rels';
|
|
OOXML_PATH_DOCUMENT = 'word/document.xml';
|
|
OOXML_PATH_STYLES = 'word/styles.xml';
|
|
OOXML_PATH_HEADER = 'word/header%d.xml'; // Use Format(OOXML_PATH_HEADER, [Index]);
|
|
OOXML_PATH_FOOTER = 'word/footer%d.xml'; // Use Format(OOXML_PATH_HEADER, [Index]);
|
|
OOXML_PATH_NUMBERING = 'word/numbering.xml';
|
|
|
|
OOXML_RELS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
|
|
OOXML_TYPE_DOCUMENT = OOXML_RELS + '/officeDocument';
|
|
OOXML_TYPE_STYLES = OOXML_RELS + '/styles';
|
|
OOXML_TYPE_HEADER = OOXML_RELS + '/header';
|
|
OOXML_TYPE_FOOTER = OOXML_RELS + '/footer';
|
|
OOXML_TYPE_NUMBERING = OOXML_RELS + '/numbering';
|
|
|
|
OOXML_CONTENTTYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml';
|
|
OOXML_CONTENTTYPE_DOCUMENT = OOXML_CONTENTTYPE + '.document.main+xml';
|
|
OOXML_CONTENTTYPE_STYLES = OOXML_CONTENTTYPE + '.styles+xml';
|
|
OOXML_CONTENTTYPE_HEADER = OOXML_CONTENTTYPE + '.header+xml';
|
|
OOXML_CONTENTTYPE_FOOTER = OOXML_CONTENTTYPE + '.footer+xml';
|
|
OOXML_CONTENTTYPE_NUMBERING = OOXML_CONTENTTYPE + '.numbering+xml';
|
|
|
|
// Shared between document.xml and each header.xml/footer.xml
|
|
OOXML_DOCUMENT_NAMESPACE =
|
|
'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" ' +
|
|
'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" ';
|
|
|
|
TAG_HEADER = 'hdr';
|
|
TAG_FOOTER = 'ftr';
|
|
|
|
// Lookups...
|
|
LU_ALIGN: Array [TvStyleAlignment] Of String =
|
|
('left', 'right', 'both', 'center');
|
|
|
|
LU_NUMBERFORMAT: Array [TvNumberFormat] Of String =
|
|
('decimal', 'lowerLetter', 'lowerRoman', 'upperLetter', 'upperRoman');
|
|
|
|
LU_NUMBERFORMATFORUMLA: Array [TvNumberFormat] Of String =
|
|
('Arabic', 'alphabetic', 'roman', 'ALPHABETIC', 'Roman');
|
|
|
|
LU_ON_OFF: Array[Boolean] Of String = ('off', 'on');
|
|
|
|
LU_BORDERTYPE: Array[TvTableBorderType] Of String =
|
|
('single', 'dashed', 'double', 'none', 'default');
|
|
|
|
LU_V_ALIGN: Array[TvVerticalAlignment] Of String = ('top', 'bottom', 'center', 'both');
|
|
|
|
//ONE_POINT_IN_MM = 0.35278;
|
|
|
|
// Generic Helper Units
|
|
|
|
Function PointsToTwipsS(APoints: Double): String;
|
|
Begin
|
|
// 1 Twip = 1/20 of a point
|
|
Result := IntToStr(Round(20 * APoints));
|
|
End;
|
|
|
|
Function mmToPointS(AMillimetres: Double): String;
|
|
Begin
|
|
Result := IntToStr(Round((0.0393701 * 1440 * AMillimetres) / 20));
|
|
End;
|
|
|
|
Function mmToTwipsS(AMillimetres: Double): String;
|
|
Begin
|
|
// 1 Twip = 1 / 1440 of an inch - sigh...
|
|
Result := IntToStr(Round(0.0393701 * 1440 * AMillimetres));
|
|
End;
|
|
|
|
Function DimAttribs(ADimension: TvDimension; AValueTag: String = 'w:w';
|
|
ATypeTag: String = 'w:type'): String;
|
|
Var
|
|
iValue: Integer;
|
|
sType: String;
|
|
Begin
|
|
Case ADimension.Units Of
|
|
dimMillimeter:
|
|
Begin
|
|
iValue := Round(0.0393701 * 1440 * ADimension.Value);
|
|
sType := 'dxa'; // most values in docx must be in twips
|
|
End;
|
|
dimPercent:
|
|
Begin
|
|
iValue := Round(50 * ADimension.Value);
|
|
sType := 'pct'; // 50ths of a percent
|
|
End;
|
|
dimPoint:
|
|
Begin
|
|
iValue := Round(20 * ADimension.Value);
|
|
sType := 'dxa'; // most values in docx must be in twips
|
|
End;
|
|
End;
|
|
|
|
Result := Format(' %s="%d" %s="%s" ', [AValueTag, iValue, ATypeTag, sType]);
|
|
End;
|
|
|
|
{ TFileInformation }
|
|
|
|
Constructor TFileInformation.Create;
|
|
Begin
|
|
FStream := nil;
|
|
|
|
XML := nil;
|
|
Path := '';
|
|
FileType := '';
|
|
ContentType := '';
|
|
Index := -1;
|
|
MentionedIn := [];
|
|
End;
|
|
|
|
Destructor TFileInformation.Destroy;
|
|
Begin
|
|
If Assigned(XML) Then
|
|
XML.Free;
|
|
|
|
Inherited Destroy;
|
|
End;
|
|
|
|
Function TFileInformation.ID: String;
|
|
Begin
|
|
Result := 'rId' + IntToStr(Index);
|
|
End;
|
|
|
|
Function TFileInformation.Filename: String;
|
|
Var
|
|
i: Integer;
|
|
Begin
|
|
i := RPos('/', Path);
|
|
|
|
If i > 0 Then
|
|
Result := Copy(Path, i + 1, Length(Path) - i)
|
|
Else
|
|
Result := Path;
|
|
End;
|
|
|
|
Function TFileInformation.GetStream: TStream;
|
|
Begin
|
|
If Not Assigned(FStream) Then
|
|
Begin
|
|
If Assigned(XML) Then
|
|
Begin
|
|
FStream := TMemoryStream.Create;
|
|
XML.SaveToStream(FStream);
|
|
FStream.Position := 0;
|
|
End;
|
|
(* { TODO : How are we going to handle images? }
|
|
Else If Assigned(Image) Then
|
|
Begin
|
|
|
|
end;
|
|
*)
|
|
End;
|
|
|
|
Result := FStream;
|
|
End;
|
|
|
|
Procedure TFileInformation.FreeStream;
|
|
Begin
|
|
If Assigned(FStream) Then
|
|
Begin
|
|
FStream.Free;
|
|
FStream := nil;
|
|
End;
|
|
End;
|
|
|
|
{ TFileList }
|
|
|
|
Function TFileList.GetFile(AIndex: Integer): TFileInformation;
|
|
Begin
|
|
Result := TFileInformation(FList[AIndex]);
|
|
End;
|
|
|
|
Constructor TFileList.Create;
|
|
Begin
|
|
FList := TList.Create;
|
|
End;
|
|
|
|
Destructor TFileList.Destroy;
|
|
Begin
|
|
While (FList.Count > 0) Do
|
|
Begin
|
|
TFileInformation(FList.Last).Free;
|
|
FList.Delete(FList.Count - 1);
|
|
End;
|
|
|
|
Inherited Destroy;
|
|
End;
|
|
|
|
Function TFileList.AddXMLFile(AContentType: String; APath: String;
|
|
AFileType: String; AMentionedIn: TFileTypes): TFileInformation;
|
|
Begin
|
|
Result := TFileInformation.Create;
|
|
Result.ContentType := AContentType;
|
|
Result.FileType := AFileType;
|
|
Result.Path := APath;
|
|
Result.MentionedIn := AMentionedIn;
|
|
Result.XML := TIndentedStringList.Create;
|
|
|
|
Result.Index := FList.Count;
|
|
FList.Add(Result);
|
|
End;
|
|
|
|
Function TFileList.Count: Integer;
|
|
Begin
|
|
Result := FList.Count;
|
|
End;
|
|
|
|
{ TIndentedStringList }
|
|
|
|
Constructor TIndentedStringList.Create;
|
|
Begin
|
|
FIndent := '';
|
|
FIndentSteps := ' ';
|
|
End;
|
|
|
|
Function TIndentedStringList.Add(indBefore: TIndentOption; S: String;
|
|
indAfter: TIndentOption): Integer;
|
|
Begin
|
|
If indBefore = indInc Then
|
|
IncIndent
|
|
Else If indAfter = indDec Then
|
|
DecIndent;
|
|
|
|
Result := Inherited Add(FIndent + S);
|
|
|
|
If indAfter = indInc Then
|
|
IncIndent
|
|
Else If indBefore = indDec Then
|
|
DecIndent;
|
|
End;
|
|
|
|
Function TIndentedStringList.Add(Const S: String): Integer;
|
|
Begin
|
|
Result := Inherited Add(FIndent + S);
|
|
End;
|
|
|
|
Function TIndentedStringList.Add(Const S: String; indAfter: TIndentOption): Integer;
|
|
Begin
|
|
Result := Add(indNone, S, indAfter);
|
|
End;
|
|
|
|
Procedure TIndentedStringList.DecIndent;
|
|
Begin
|
|
FIndent := Copy(FIndent, 1, Length(FIndent) - Length(FIndentSteps));
|
|
End;
|
|
|
|
Procedure TIndentedStringList.IncIndent;
|
|
Begin
|
|
FIndent := FIndent + FIndentSteps;
|
|
End;
|
|
|
|
{ TvDOCXVectorialWriter }
|
|
|
|
Constructor TvDOCXVectorialWriter.Create;
|
|
Begin
|
|
Inherited Create;
|
|
|
|
FFiles := TFileList.Create;
|
|
End;
|
|
|
|
Destructor TvDOCXVectorialWriter.Destroy;
|
|
Begin
|
|
// Free's all the XML IndentedStringLists automagically
|
|
FFiles.Free;
|
|
FFiles := nil;
|
|
|
|
Inherited Destroy;
|
|
End;
|
|
|
|
Function TvDOCXVectorialWriter.PrepareContentTypes: String;
|
|
Var
|
|
i: Integer;
|
|
Begin
|
|
Result := XML_HEADER + LineEnding +
|
|
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
|
|
LineEnding +
|
|
' <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
|
|
+ LineEnding + ' <Default Extension="xml" ContentType="application/xml"/>' +
|
|
LineEnding;
|
|
|
|
For i := 0 To FFiles.Count - 1 Do
|
|
If ftContentTypes In FFiles[i].MentionedIn Then
|
|
Result := Result + ' <Override PartName="/' + FFiles[i].Path +
|
|
'" ContentType="' + FFiles[i].ContentType + '"/>' + LineEnding;
|
|
|
|
Result := Result + '</Types>';
|
|
End;
|
|
|
|
Function TvDOCXVectorialWriter.PrepareRelationships: String;
|
|
Var
|
|
i: Integer;
|
|
Begin
|
|
Result := XML_HEADER + LineEnding +
|
|
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
|
+ LineEnding;
|
|
|
|
For i := 0 To FFiles.Count - 1 Do
|
|
If ftRelationships In FFiles[i].MentionedIn Then
|
|
Result := Result + ' <Relationship Id="' + FFiles[i].ID +
|
|
'" Type="' + FFiles[i].FileType + '" Target="/' + FFiles[i].Path +
|
|
'"/>' + LineEnding;
|
|
|
|
Result := Result + '</Relationships>';
|
|
End;
|
|
|
|
Function TvDOCXVectorialWriter.PrepareDocRelationships: String;
|
|
Var
|
|
i: Integer;
|
|
Begin
|
|
Result := XML_HEADER + LineEnding +
|
|
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
|
+ LineEnding;
|
|
|
|
For i := 0 To FFiles.Count - 1 Do
|
|
If ftDocRelationships In FFiles[i].MentionedIn Then
|
|
Result := Result + ' <Relationship Id="' + FFiles[i].ID +
|
|
'" Type="' + FFiles[i].FileType + '" Target="' + FFiles[i].Filename +
|
|
'"/>' + LineEnding;
|
|
|
|
Result := Result + '</Relationships>';
|
|
End;
|
|
|
|
Procedure TvDOCXVectorialWriter.PrepareDocument;
|
|
Var
|
|
// Generally this is document.xml, may also be header.xml or footer.xml though..
|
|
oDocXML: TIndentedStringList;
|
|
iPage: Integer;
|
|
|
|
Procedure ProcessRichText(ARichText: TvRichText); Forward;
|
|
|
|
Procedure AddTextRun(sText: String; AStyle: TvStyle);
|
|
Var
|
|
iLen, iStart, i: Integer;
|
|
sTemp: String;
|
|
Begin
|
|
// Don't both writing null Text Runs..
|
|
If sText <> '' Then
|
|
Begin
|
|
i := 1;
|
|
iStart := 1;
|
|
iLen := Length(sText);
|
|
|
|
// Place all text between Tabs and CRs into individual Text Runs,
|
|
// and render the Tabs and CRs appropriately
|
|
While i <= iLen Do
|
|
Begin
|
|
If (sText[i] In [#10, #09, #13]) Or (i = iLen) Then
|
|
Begin
|
|
// Add the text before this point into a single Text Run
|
|
If i > iStart Then
|
|
Begin
|
|
// If end of line AND end of line isn't a special char, then
|
|
// inc(i) to ensure the math in the Copy works :-)
|
|
If (i = iLen) And Not (sText[i] In [#10, #09, #13]) Then
|
|
Inc(i);
|
|
|
|
sTemp := Copy(sText, iStart, i - iStart);
|
|
|
|
oDocXML.Add(indInc, '<w:r>');
|
|
|
|
If Assigned(AStyle) Then
|
|
Begin
|
|
oDocXML.Add(indInc, '<w:rPr>');
|
|
oDocXML.Add(' <w:rStyle w:val="' + StyleNameToStyleID(AStyle) + '"/>');
|
|
oDocXML.Add(indDec, '</w:rPr>');
|
|
End;
|
|
|
|
oDocXML.Add(' <w:t>' + EscapeHTML(sTemp) + '</w:t>');
|
|
oDocXML.Add(indDec, '</w:r>');
|
|
End;
|
|
|
|
// Deal with the Tabs, LF and CRs appropriately
|
|
If sText[i] = #09 Then
|
|
oDocXML.Add(' <w:r><w:tab/></w:r>')
|
|
Else If sText[i] In [#10, #11, #13] Then
|
|
Begin
|
|
oDocXML.Add(' <w:r><w:br/></w:r>');
|
|
|
|
// Now deal with CRLF by skipping over any trailing LF
|
|
If (i < iLen) And (sText[i] = #13) Then
|
|
If sText[i + 1] = #10 Then
|
|
Inc(i);
|
|
End;
|
|
|
|
iStart := i + 1;
|
|
End;
|
|
|
|
Inc(i);
|
|
End;
|
|
End;
|
|
End;
|
|
|
|
Procedure AddField(AField : TvField);
|
|
var
|
|
sInstruction: String;
|
|
sDefault: String;
|
|
Begin
|
|
sInstruction := '';
|
|
sDefault := '';
|
|
|
|
Case AField.Kind of
|
|
vfkNumPages:
|
|
Begin
|
|
sInstruction := ' NUMPAGES \* '+LU_NUMBERFORMATFORUMLA[AField.NumberFormat]+' \* MERGEFORMAT ';
|
|
sDefault := IntToStr(FData.GetPageCount);
|
|
End;
|
|
vfkPage:
|
|
Begin
|
|
sInstruction := ' PAGE \* '+LU_NUMBERFORMATFORUMLA[AField.NumberFormat]+' \* MERGEFORMAT ';
|
|
sDefault := IntToStr(iPage+1);
|
|
End;
|
|
vfkAuthor:
|
|
Begin
|
|
sInstruction := ' AUTHOR \* Caps \* MERGEFORMAT ';
|
|
sDefault := 'FPVECTORIAL';
|
|
End;
|
|
vfkDateCreated:
|
|
Begin
|
|
sInstruction := ' CREATEDATE \@ "'+AField.DateFormat+'" \* MERGEFORMAT ';
|
|
sDefault := DateToStr(Now);
|
|
End;
|
|
vfkDate:
|
|
Begin
|
|
sInstruction := ' DATE \@ "'+AField.DateFormat+'" \* MERGEFORMAT ';
|
|
sDefault := DateToStr(Now);
|
|
End;
|
|
end;
|
|
|
|
If sInstruction<>'' Then
|
|
Begin
|
|
If Assigned(AField.Style) Then
|
|
Begin
|
|
oDocXML.Add(indInc, '<w:rPr>');
|
|
oDocXML.Add(' <w:rStyle w:val="' + StyleNameToStyleID(AField.Style) + '"/>');
|
|
oDocXML.Add(indDec, '</w:rPr>');
|
|
End;
|
|
|
|
// Start the Formula
|
|
oDocXML.Add('<w:r><w:fldChar w:fldCharType="begin"/></w:r>');
|
|
|
|
// Add the Instruction
|
|
oDocXML.Add('<w:r><w:instrText xml:space="preserve">'+
|
|
sInstruction+
|
|
'</w:instrText></w:r>');
|
|
|
|
// SEPARATE the Field (above) from the result (below)
|
|
oDocXML.Add('<w:r><w:fldChar w:fldCharType="separate"/></w:r>');
|
|
|
|
// Add the default text
|
|
oDocXML.Add('<w:r><w:t>'+sDefault+'</w:t></w:r>');
|
|
|
|
// End the Forumla
|
|
oDocXML.Add('<w:r><w:fldChar w:fldCharType="end"/></w:r>');
|
|
end;
|
|
end;
|
|
|
|
Procedure ProcessParagraph(AParagraph: TvParagraph; AListLevel : integer = -1; ANumID : Integer = -1);
|
|
Var
|
|
i: Integer;
|
|
oEntity: TvEntity;
|
|
sTemp: String;
|
|
Begin
|
|
oDocXML.Add(indInc, '<w:p>');
|
|
|
|
// Add the Paragraph Properties
|
|
oDocXML.Add(indInc, '<w:pPr>', indInc);
|
|
|
|
If Assigned(AParagraph.Style) Then
|
|
oDocXML.Add(Format('<w:pStyle w:val="%s"/>',
|
|
[StyleNameToStyleID(AParagraph.Style)]));
|
|
|
|
If (AListLevel<>-1) Then
|
|
Begin
|
|
oDocXML.Add('<w:numPr>');
|
|
oDocXML.Add(indInc, Format('<w:ilvl w:val="%d"/>', [AListLevel]));
|
|
oDocXML.Add(indDec, Format('<w:numId w:val="%d"/>', [ANumID]));
|
|
oDocXML.Add('</w:numPr>');
|
|
End;
|
|
|
|
oDocXML.Add(indDec, '</w:pPr>', indDec);
|
|
|
|
For i := 0 To AParagraph.GetEntitiesCount - 1 Do
|
|
Begin
|
|
oEntity := AParagraph.GetEntity(i);
|
|
|
|
// Adding the TvText like this means each line in the StringList
|
|
// will result in a <BR/> adding to the XML
|
|
If oEntity Is TvText Then
|
|
Begin
|
|
sTemp := TvText(oEntity).Value.Text;
|
|
|
|
// Strip out the trailing line break
|
|
// added by TStringList.Text
|
|
If DefaultTextLineBreakStyle = tlbsCRLF Then
|
|
sTemp := Copy(sTemp, 1, Length(sTemp) - 2)
|
|
Else
|
|
sTemp := Copy(sTemp, 1, Length(sTemp) - 1);
|
|
|
|
AddTextRun(sTemp, TvText(oEntity).Style);
|
|
End
|
|
Else If oEntity is TvField Then
|
|
AddField(TvField(oEntity))
|
|
Else
|
|
{ TODO : What other entities in TvParagraph do I need to process }
|
|
Raise Exception.Create('Unsupported Entity: ' + oEntity.ClassName);
|
|
End;
|
|
|
|
oDocXML.Add(indDec, '</w:p>');
|
|
End;
|
|
|
|
Procedure ProcessList(AList: TvList);
|
|
Var
|
|
i: Integer;
|
|
oEntity: TvEntity;
|
|
Begin
|
|
For i := 0 To AList.GetEntitiesCount - 1 Do
|
|
Begin
|
|
oEntity := AList.GetEntity(i);
|
|
|
|
If oEntity Is TvParagraph Then
|
|
Begin
|
|
If Not Assigned(TvParagraph(oEntity).Style) Then
|
|
TvParagraph(oEntity).Style := AList.Style;
|
|
|
|
ProcessParagraph(TvParagraph(oEntity), AList.GetLevel(), FData.FindListStyleIndex(AList.ListStyle) + 1);
|
|
End
|
|
Else If oEntity Is TvList Then
|
|
ProcessList(TvList(oEntity))
|
|
Else
|
|
Raise Exception.Create('Unsupported entity ' + oEntity.ClassName);
|
|
End;
|
|
End;
|
|
|
|
Procedure ProcessHeaderFooter(AElement: TvRichText; ATag: String);
|
|
Var
|
|
oTemp: TIndentedStringList;
|
|
oFile: TFileInformation;
|
|
Begin
|
|
// If Header or Footer contains no elements, don't add them...
|
|
If AElement.GetEntitiesCount > 0 Then
|
|
Begin
|
|
// Create additional XML files for each Header and Footer
|
|
If ATag = TAG_HEADER Then
|
|
oFile := FFiles.AddXMLFile(OOXML_CONTENTTYPE_HEADER,
|
|
Format(OOXML_PATH_HEADER, [FFiles.Count]), OOXML_TYPE_HEADER,
|
|
[ftContentTypes, ftDocRelationships])
|
|
Else
|
|
oFile := FFiles.AddXMLFile(OOXML_CONTENTTYPE_FOOTER,
|
|
Format(OOXML_PATH_FOOTER, [FFiles.Count]), OOXML_TYPE_FOOTER,
|
|
[ftContentTypes, ftDocRelationships]);
|
|
|
|
// For the next few steps, all the ProcessXXX routines will be working on the header or footer...
|
|
oTemp := oDocXML;
|
|
oDocXML := oFile.XML;
|
|
|
|
oDocXML.Add(XML_HEADER);
|
|
oDocXML.Add(Format('<w:%s %s>', [ATag, OOXML_DOCUMENT_NAMESPACE +
|
|
'xml:space="preserve"']));
|
|
|
|
ProcessRichText(AElement);
|
|
|
|
// Close the new xml file...
|
|
oDocXML.Add(Format('</w:%s>', [ATag]));
|
|
|
|
// ProcessXXX routines will now resume working on document.xml
|
|
oDocXML := oTemp;
|
|
|
|
{ TODO : FPVectorial currently doesn't support Odd or Even Page Header/Footers }
|
|
// Add the reference to the newly created Header or Footer into the main document
|
|
If ATag = TAG_HEADER Then
|
|
oDocXML.Add('<w:headerReference w:type="default" r:id="' + oFile.ID + '"/>')
|
|
Else
|
|
oDocXML.Add('<w:footerReference w:type="default" r:id="' + oFile.ID + '"/>');
|
|
End;
|
|
End;
|
|
|
|
Procedure FinalisePage(APageSequence: TvTextPageSequence; ALastPage: Boolean);
|
|
Var
|
|
dWidth, dHeight: Double;
|
|
sTemp: String;
|
|
Begin
|
|
// For the final pagesequence only w:sectPr shouldn't be wrapped inside w:p or w:pPr
|
|
If Not ALastPage Then
|
|
Begin
|
|
oDocXML.Add(indInc, '<w:p>');
|
|
oDocXML.Add(indInc, '<w:pPr>');
|
|
End;
|
|
|
|
oDocXML.Add(indInc, '<w:sectPr>', indInc);
|
|
|
|
ProcessHeaderFooter(APageSequence.Header, TAG_HEADER);
|
|
ProcessHeaderFooter(APageSequence.Footer, TAG_FOOTER);
|
|
|
|
// Define the Page Layout
|
|
dWidth := APageSequence.Width;
|
|
If dWidth = 0 Then
|
|
dWidth := FData.Width;
|
|
If dWidth=0 Then
|
|
dWidth := 210; // Default A4
|
|
|
|
dHeight := APageSequence.Height;
|
|
If dHeight = 0 Then
|
|
dHeight := FData.Height;
|
|
If dHeight=0 Then
|
|
dHeight := 297; // Default A4
|
|
|
|
If ((dWidth <> 0) And (dHeight <> 0)) Then
|
|
Begin
|
|
sTemp := Format('<w:pgSz w:w="%s" w:h="%s"',
|
|
[mmToTwipsS(dWidth), mmToTwipsS(dHeight)]);
|
|
|
|
If dWidth < dHeight Then
|
|
sTemp := sTemp + '/>'
|
|
Else
|
|
sTemp := sTemp + ' w:orient="landscape"/>';
|
|
|
|
oDocXML.Add(sTemp);
|
|
End;
|
|
|
|
{ TODO : Ensure these other properties can be set }
|
|
//<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708" w:footer="708" w:gutter="0"/>
|
|
//<w:cols w:space="708"/>
|
|
//<w:docGrid w:linePitch="360"/>
|
|
oDocXML.Add(indDec, '</w:sectPr>', indDec);
|
|
|
|
If Not ALastPage Then
|
|
Begin
|
|
oDocXML.Add(indDec, '</w:pPr>');
|
|
oDocXML.Add(indDec, '</w:p>');
|
|
End;
|
|
End;
|
|
|
|
Procedure AddTableBorderProperty(ATag: String; ABorder: TvTableBorder);
|
|
Var
|
|
sAttrib: String;
|
|
Begin
|
|
If ABorder.LineType <> tbtDefault Then
|
|
Begin
|
|
sAttrib := '';
|
|
|
|
If ABorder.LineType <> tbtNone Then
|
|
sAttrib := sAttrib + 'w:val="' + LU_BORDERTYPE[ABorder.LineType] + '" ';
|
|
|
|
sAttrib := sAttrib + 'w:space="' + mmToPointS(ABorder.Spacing) + '" ';
|
|
|
|
If ABorder.Width <> 0 Then
|
|
// Eights of a Point?? Really, they're just making this up...
|
|
sAttrib := sAttrib + 'w:sz="' + mmToPointS(Min(96, Max(2, 8 * ABorder.Width))) + '" '
|
|
Else
|
|
// 4 is the minimum
|
|
sAttrib := sAttrib + 'w:sz="4" ';
|
|
|
|
If ABorder.Color <> FPColor(0, 0, 0, 0) Then
|
|
sAttrib := sAttrib + 'w:color="' + FPColorToRGBHexString(ABorder.Color) + '" '
|
|
Else
|
|
sAttrib := sAttrib + 'w:color="auto" ';
|
|
|
|
oDocXML.Add(Format('<%s %s/>', [ATag, Trim(sAttrib)]));
|
|
End;
|
|
End;
|
|
|
|
Procedure ProcessTable(ATable: TvTable);
|
|
Var
|
|
i, j, k: Integer;
|
|
oRow: TvTableRow;
|
|
oCell: TvTableCell;
|
|
//sTemp : String;
|
|
Begin
|
|
oDocXML.Add(indInc, '<w:tbl>');
|
|
|
|
// Add the table properties
|
|
oDocXML.Add(indInc, '<w:tblPr>', indInc);
|
|
|
|
If ATable.PreferredWidth.Value <> 0 Then
|
|
oDocXML.Add(Format('<w:tblW %s />', [DimAttribs(ATable.PreferredWidth)]));
|
|
|
|
oDocXML.Add(indNone, '<w:tblBorders>', indInc);
|
|
|
|
AddTableBorderProperty('w:left', ATable.Borders.Left);
|
|
AddTableBorderProperty('w:right', ATable.Borders.Right);
|
|
AddTableBorderProperty('w:top', ATable.Borders.Top);
|
|
AddTableBorderProperty('w:bottom', ATable.Borders.Bottom);
|
|
AddTableBorderProperty('w:insideH', ATable.Borders.InsideHoriz);
|
|
AddTableBorderProperty('w:insideV', ATable.Borders.InsideVert);
|
|
|
|
oDocXML.Add(indNone, '</w:tblBorders>', indDec);
|
|
|
|
If Assigned(ATable.Style) Then
|
|
oDocXML.Add('<w:tblStyle w:val="' + StyleNameToStyleID(ATable.Style) + '" />');
|
|
|
|
If ATable.SpacingBetweenCells <> 0 Then
|
|
oDocXML.Add('<w:tblCellSpacing w:w="' + mmToTwipsS(ATable.SpacingBetweenCells) +
|
|
'" w:type="dxa" />');
|
|
|
|
If ATable.BackgroundColor <> FPColor(0, 0, 0, 0) Then
|
|
oDocXML.Add(Format('<w:shd w:val="clear" w:color="auto" w:fill="%s"/>',
|
|
[FPColorToRGBHexString(ATable.BackgroundColor)]));
|
|
|
|
oDocXML.Add(indDec, '</w:tblPr>', indDec);
|
|
|
|
// Define the grid. Grid is used to determine cell widths
|
|
// and boundaries
|
|
oDocXML.Add(indInc, '<w:tblGrid>', indInc);
|
|
|
|
// Percent cannot be set here, only absolutes
|
|
If ATable.ColWidthsUnits = dimMillimeter Then
|
|
For k := Low(ATable.ColWidths) To High(ATable.ColWidths) Do
|
|
oDocXML.Add(Format('<w:gridCol w:w="%s" />', [mmToTwipsS(ATable.ColWidths[k])]))
|
|
Else If ATable.ColWidthsUnits = dimPoint Then
|
|
For k := Low(ATable.ColWidths) To High(ATable.ColWidths) Do
|
|
oDocXML.Add(Format('<w:gridCol w:w="%s" />',
|
|
[IntToStr(Round(20 * ATable.ColWidths[k]))]));
|
|
|
|
oDocXML.Add(indDec, '</w:tblGrid>', indDec);
|
|
|
|
For i := 0 To ATable.GetRowCount - 1 Do
|
|
Begin
|
|
oRow := ATable.GetRow(i);
|
|
oDocXML.Add(indInc, '<w:tr>');
|
|
|
|
// Add the Row Properties
|
|
oDocXML.Add(indInc, '<w:trPr>', indInc);
|
|
|
|
If oRow.Header Then
|
|
oDocXML.Add('<w:tblHeader />');
|
|
|
|
If Not oRow.AllowSplitAcrossPage Then
|
|
oDocXML.Add('<w:cantSplit />');
|
|
|
|
If oRow.CellSpacing <> 0 Then
|
|
oDocXML.Add('<w:tblCellSpacing w:w="' + mmToTwipsS(oRow.CellSpacing) +
|
|
'" w:type="dxa" />');
|
|
|
|
{ TODO : w:hRule="exact", "auto" }
|
|
If oRow.Height <> 0 Then
|
|
oDocXML.Add('<w:trHeight w:val="' + mmToTwipsS(oRow.Height) +
|
|
'" w:hRule="atLeast"/>');
|
|
|
|
// Row Background Colour can't be applied here, have to apply to each cell in turn...
|
|
|
|
oDocXML.Add(indDec, '</w:trPr>', indDec);
|
|
|
|
For j := 0 To oRow.GetCellCount - 1 Do
|
|
Begin
|
|
oCell := oRow.GetCell(j);
|
|
|
|
oDocXML.Add(indInc, '<w:tc>');
|
|
|
|
// Add the Cell Properties
|
|
oDocXML.Add(indInc, '<w:tcPr>', indInc);
|
|
|
|
oDocXML.Add(indNone, '<w:tcBorders>', indInc);
|
|
|
|
AddTableBorderProperty('w:left', oCell.Borders.Left);
|
|
AddTableBorderProperty('w:right', oCell.Borders.Right);
|
|
AddTableBorderProperty('w:top', oCell.Borders.Top);
|
|
AddTableBorderProperty('w:bottom', oCell.Borders.Bottom);
|
|
|
|
oDocXML.Add(indNone, '</w:tcBorders>', indDec);
|
|
|
|
// Row background color can't be applied at the row level, so we'll
|
|
// apply it to each cell in turn (only if the cell doesn't have it's
|
|
// own value assigned)
|
|
If oCell.BackgroundColor <> FPColor(0, 0, 0, 0) Then
|
|
oDocXML.Add(Format('<w:shd w:val="clear" w:color="auto" w:fill="%s"/>',
|
|
[FPColorToRGBHexString(oCell.BackgroundColor)]))
|
|
Else If oRow.BackgroundColor <> FPColor(0, 0, 0, 0) Then
|
|
oDocXML.Add(Format('<w:shd w:val="clear" w:color="auto" w:fill="%s"/>',
|
|
[FPColorToRGBHexString(oRow.BackgroundColor)]));
|
|
|
|
// Either use Cell Preferred Width, or ColWidths if defined as %
|
|
If oCell.PreferredWidth.Value <> 0 Then
|
|
oDocXML.Add(Format('<w:tcW %s />', [DimAttribs(oCell.PreferredWidth)]))
|
|
Else If (j <= High(ATable.ColWidths)) Then
|
|
oDocXML.Add(Format('<w:tcW %s />',
|
|
[DimAttribs(Dimension(ATable.ColWidths[j],
|
|
ATable.ColWidthsUnits))]));
|
|
|
|
If ATable.ColWidthsUnits <> dimPercent Then
|
|
oDocXML.Add('<w:gridSpan w:val="' + IntToStr(oCell.SpannedCols) + '" />');
|
|
|
|
oDocXML.Add('<w:vAlign w:val="' + LU_V_ALIGN[oCell.VerticalAlignment] + '" />');
|
|
|
|
oDocXML.Add(indDec, '</w:tcPr>', indDec);
|
|
|
|
ProcessRichText(oCell);
|
|
|
|
oDocXML.Add(indDec, '</w:tc>');
|
|
End;
|
|
|
|
oDocXML.Add(indDec, '</w:tr>');
|
|
End;
|
|
|
|
oDocXML.Add(indDec, '</w:tbl>');
|
|
End;
|
|
|
|
(*
|
|
Procedure ProcessImage(AImage : TvImage);
|
|
begin
|
|
|
|
end;
|
|
*)
|
|
Procedure ProcessRichText(ARichText: TvRichText);
|
|
Var
|
|
i: Integer;
|
|
oEntity: TvEntity;
|
|
Begin
|
|
For i := 0 To ARichText.GetEntitiesCount - 1 Do
|
|
Begin
|
|
oEntity := ARichText.GetEntity(i);
|
|
|
|
If oEntity Is TvParagraph Then
|
|
ProcessParagraph(TvParagraph(oEntity))
|
|
Else If oEntity Is TvList Then
|
|
ProcessList(TvList(oEntity))
|
|
Else If oEntity Is TvTable Then
|
|
ProcessTable(TvTable(oEntity))
|
|
Else If oEntity Is TvRichText Then
|
|
ProcessRichText(TvRichText(oEntity))
|
|
(*
|
|
Else If oEntity Is TvImage Then
|
|
ProcessImage(TvImage(oEntity))
|
|
*)
|
|
Else
|
|
Raise Exception.Create('Unsupported entity ' + oEntity.ClassName);
|
|
End;
|
|
End;
|
|
|
|
Var
|
|
oPage: TvPage;
|
|
oPageSequence: TvTextPageSequence;
|
|
oFile: TFileInformation;
|
|
Begin
|
|
oFile := FFiles.AddXMLFile(OOXML_CONTENTTYPE_DOCUMENT, OOXML_PATH_DOCUMENT,
|
|
OOXML_TYPE_DOCUMENT, [ftContentTypes, ftRelationships]);
|
|
oDocXML := oFile.XML;
|
|
|
|
oDocXML.Clear;
|
|
oDocXML.Add(XML_HEADER);
|
|
oDocXML.Add(Format('<w:document %s>', [OOXML_DOCUMENT_NAMESPACE +
|
|
'xml:space="preserve"']));
|
|
|
|
oDocXML.Add(indInc, '<w:body>');
|
|
|
|
For iPage := 0 To FData.GetPageCount - 1 Do
|
|
Begin
|
|
oPage := FData.GetPageAsText(iPage);
|
|
|
|
If oPage Is TvTextPageSequence Then
|
|
Begin
|
|
oPageSequence := TvTextPageSequence(oPage);
|
|
|
|
ProcessRichText(oPageSequence.MainText);
|
|
|
|
// Add any dimensions, headers, footers etc
|
|
FinalisePage(oPageSequence, iPage = FData.GetPageCount - 1);
|
|
End;
|
|
End;
|
|
|
|
oDocXML.Add(indDec, '</w:body>');
|
|
oDocXML.Add('</w:document>');
|
|
End;
|
|
|
|
Function TvDOCXVectorialWriter.StyleNameToStyleID(AStyle: TvStyle): String;
|
|
Begin
|
|
If Trim(AStyle.Name) <> '' Then
|
|
Result := StringReplace(AStyle.Name, ' ', '', [rfReplaceAll, rfIgnoreCase])
|
|
Else
|
|
Begin
|
|
Result := Format('StyleID%d', [FData.FindStyleIndex(AStyle)]);
|
|
AStyle.Name := Result; // Saves having to do the FindIndex later...
|
|
End;
|
|
End;
|
|
|
|
Procedure TvDOCXVectorialWriter.PrepareTextRunStyle(ADoc: TIndentedStringList;
|
|
AStyle: TvStyle);
|
|
Begin
|
|
ADoc.Add(indInc, '<w:rPr>', indInc);
|
|
|
|
If (spbfFontName In AStyle.SetElements) And (AStyle.Font.Name <> '') Then
|
|
ADoc.Add('<w:rFonts w:ascii="' + AStyle.Font.Name + '" w:hAnsi="' +
|
|
AStyle.Font.Name + '"/>');
|
|
|
|
If spbfFontSize In AStyle.SetElements Then
|
|
{ TODO : Where does the magic Font.Size*2 come from? Confirm... }
|
|
ADoc.Add('<w:sz w:val="' + IntToStr(2 * AStyle.Font.Size) + '"/>');
|
|
|
|
If spbfFontBold In AStyle.SetElements Then
|
|
ADoc.Add('<w:b w:val="' + LU_ON_OFF[AStyle.Font.Bold] + '"/>');
|
|
|
|
If spbfFontItalic In AStyle.SetElements Then
|
|
ADoc.Add('<w:i w:val="' + LU_ON_OFF[AStyle.Font.Italic] + '"/>');
|
|
|
|
If spbfFontUnderline In AStyle.SetElements Then
|
|
ADoc.Add('<w:u w:val="' + LU_ON_OFF[AStyle.Font.Underline] + '"/>');
|
|
|
|
If spbfFontStrikeThrough In AStyle.SetElements Then
|
|
ADoc.Add('<w:strike w:val="' + LU_ON_OFF[AStyle.Font.StrikeThrough] + '"/>');
|
|
|
|
If CompareColors(AStyle.Font.Color, FPColor(0, 0, 0, 0)) <> 0 Then
|
|
ADoc.Add('<w:color w:val="' + FPColorToRGBHexString(AStyle.Font.Color) + '"/>');
|
|
|
|
// Can Word handled re-oriented text? I hope not...
|
|
If AStyle.Font.Orientation <> 0 Then;
|
|
|
|
ADoc.DecIndent;
|
|
|
|
// Don't bother adding an empty tag..
|
|
If ADoc[ADoc.Count - 1] <> '<w:rPr>' Then
|
|
ADoc.Add(indDec, '</w:rPr>')
|
|
Else
|
|
Begin
|
|
ADoc.Delete(ADoc.Count - 1);
|
|
ADoc.DecIndent;
|
|
End;
|
|
End;
|
|
|
|
Procedure TvDOCXVectorialWriter.PrepareStyles;
|
|
Var
|
|
sType, sTemp: String;
|
|
i: Integer;
|
|
oXML: TIndentedStringList;
|
|
oStyle: TvStyle;
|
|
oFile: TFileInformation;
|
|
|
|
Begin
|
|
// Only add this file if there are any styles defined...
|
|
If FData.GetStyleCount > 0 Then
|
|
Begin
|
|
oFile := FFiles.AddXMLFile(OOXML_CONTENTTYPE_STYLES, OOXML_PATH_STYLES,
|
|
OOXML_TYPE_STYLES, [ftContentTypes, ftDocRelationships]);
|
|
oXML := oFile.XML;
|
|
|
|
oXML.Clear;
|
|
oXML.Add(XML_HEADER);
|
|
oXML.Add(Format('<w:styles %s>', [OOXML_DOCUMENT_NAMESPACE]));
|
|
|
|
For i := 0 To FData.GetStyleCount - 1 Do
|
|
Begin
|
|
oStyle := FData.GetStyle(i);
|
|
|
|
If oStyle.GetKind In [vskTextBody, vskHeading] Then
|
|
sType := 'paragraph'
|
|
Else If oStyle.GetKind In [vskTextSpan] Then
|
|
sType := 'character'
|
|
Else
|
|
{ TODO : handle the other StyleKinds }
|
|
Raise Exception.Create('Unsupported StyleKind in ' + oStyle.Name);
|
|
|
|
oXML.Add(indInc, '<w:style w:type="' + sType + '" w:styleId="' +
|
|
StyleNameToStyleID(oStyle) + '">');
|
|
|
|
// Add the name and inheritance values
|
|
oXML.Add(' <w:name w:val="' + oStyle.Name + '"/>');
|
|
|
|
If Assigned(oStyle.Parent) Then
|
|
oXML.Add(' <w:basedOn w:val="' + StyleNameToStyleID(
|
|
oStyle.Parent) + '"/> ');
|
|
|
|
{ TODO : <w:qFormat/> doesn't always need to be set, but I don't yet understand the rules... }
|
|
oXML.Add(' <w:qFormat/> '); // Latent Style Primary Style Setting.
|
|
|
|
{ TODO : Specification states you CANNOT redeclare a identical property
|
|
declared in a parent. At the moment code is relying on Styles
|
|
correctly defined up through hierarchy }
|
|
If sType = 'paragraph' Then
|
|
Begin
|
|
// Add the Paragraph Properties
|
|
oXML.Add(indInc, '<w:pPr>', indInc);
|
|
|
|
// Define Before and After spacing
|
|
If (sseMarginTop In oStyle.SetElements) Or
|
|
(sseMarginBottom In oStyle.SetElements) Then
|
|
Begin
|
|
sTemp := '';
|
|
|
|
If sseMarginTop In oStyle.SetElements Then
|
|
sTemp := sTemp + ' w:before="' + mmToTwipsS(oStyle.MarginTop) + '"';
|
|
|
|
If sseMarginBottom In oStyle.SetElements Then
|
|
sTemp := sTemp + ' w:after="' + mmToTwipsS(oStyle.MarginBottom) + '"';
|
|
|
|
oXML.Add(Format('<w:spacing %s/>', [sTemp]));
|
|
End;
|
|
|
|
If (sseMarginLeft In oStyle.SetElements) Or
|
|
(sseMarginRight In oStyle.SetElements) Then
|
|
Begin
|
|
sTemp := '';
|
|
|
|
If sseMarginLeft In oStyle.SetElements Then
|
|
sTemp := sTemp + ' w:left="' + mmToTwipsS(oStyle.MarginLeft) + '"';
|
|
|
|
If sseMarginRight In oStyle.SetElements Then
|
|
sTemp := sTemp + ' w:right="' + mmToTwipsS(oStyle.MarginRight) + '"';
|
|
|
|
oXML.Add(Format('<w:ind %s/>', [sTemp]));
|
|
End;
|
|
|
|
|
|
// Alignment
|
|
If spbfAlignment In oStyle.SetElements Then
|
|
oXML.Add('<w:jc w:val="' + LU_ALIGN[oStyle.Alignment] + '"/>');
|
|
|
|
// Suppress Spacing between identical paragraphs...
|
|
If oStyle.SuppressSpacingBetweenSameParagraphs Then
|
|
oXML.Add('<w:contextualSpacing/>');
|
|
|
|
oXML.DecIndent;
|
|
|
|
If oXML[oXML.Count - 1] <> '<w:pPr>' Then
|
|
oXML.Add(indDec, '</w:pPr>')
|
|
Else
|
|
Begin
|
|
oXML.Delete(oXML.Count - 1);
|
|
oXML.DecIndent;
|
|
End;
|
|
End;
|
|
|
|
// Now add the actual formatting (rPr = Run Properties).
|
|
PrepareTextRunStyle(oXML, oStyle);
|
|
|
|
oXML.Add(indDec, '</w:style> ');
|
|
End;
|
|
|
|
oXML.Add('</w:styles>');
|
|
End;
|
|
End;
|
|
|
|
Procedure TvDOCXVectorialWriter.PrepareNumbering;
|
|
Var
|
|
oXML: TIndentedStringList;
|
|
oStyle: TvListStyle;
|
|
oFile: TFileInformation;
|
|
i: Integer;
|
|
j: Integer;
|
|
oListLevelStyle: TvListLevelStyle;
|
|
sTotalLeader: String;
|
|
sCurrentLeader: String;
|
|
slvlText: String;
|
|
Begin
|
|
// Only add this file if there are any List styles defined...
|
|
If FData.GetListStyleCount > 0 Then
|
|
Begin
|
|
oFile := FFiles.AddXMLFile(OOXML_CONTENTTYPE_NUMBERING,
|
|
OOXML_PATH_NUMBERING, OOXML_TYPE_NUMBERING, [ftContentTypes, ftDocRelationships]);
|
|
oXML := oFile.XML;
|
|
|
|
oXML.Clear;
|
|
oXML.Add(XML_HEADER);
|
|
oXML.Add(Format('<w:numbering %s>', [OOXML_DOCUMENT_NAMESPACE]));
|
|
|
|
For i := 0 To FData.GetListStyleCount - 1 Do
|
|
Begin
|
|
oStyle := FData.GetListStyle(i);
|
|
|
|
// abstractNumID allows us to group different list styles together.
|
|
// The way fpvectorial uses it, there will be a one to one relationship
|
|
// between abstractNumID and numID.
|
|
// abstractNumId is 0 based
|
|
// numID is 1 based. Go figure...
|
|
oXML.Add(indInc, Format('<w:abstractNum w:abstractNumId="%d">', [i]), indInc);
|
|
|
|
sTotalLeader := '';
|
|
|
|
For j := 0 To oStyle.GetListLevelStyleCount-1 Do
|
|
Begin
|
|
oListLevelStyle := oStyle.GetListLevelStyle(j);
|
|
|
|
oXML.Add(Format('<w:lvl w:ilvl="%d">', [oListLevelStyle.Level]), indInc);
|
|
|
|
with oListLevelStyle do
|
|
sCurrentLeader := Format('%s%s%d%s', [Prefix, '%', Level + 1, Suffix]);
|
|
|
|
sTotalLeader := sTotalLeader + sCurrentLeader;
|
|
|
|
If oListLevelStyle.Kind=vlskBullet Then
|
|
slvlText := oListLevelStyle.Bullet
|
|
Else If oListLevelStyle.DisplayLevels Then
|
|
slvlText := sTotalLeader
|
|
Else
|
|
slvlText := sCurrentLeader;
|
|
|
|
If oListLevelStyle.Kind=vlskBullet Then
|
|
oXML.Add('<w:numFmt w:val="bullet"/>')
|
|
Else
|
|
Begin // Numbered Lists
|
|
oXML.Add(Format('<w:start w:val="%d"/>', [oListLevelStyle.Start]));
|
|
|
|
oXML.Add('<w:numFmt w:val="' + LU_NUMBERFORMAT[oListLevelStyle.NumberFormat] + '"/>');
|
|
End;
|
|
|
|
oXML.Add('<w:lvlText w:val="' + slvlText + '"/>');
|
|
oXML.Add('<w:lvlJc w:val="' + LU_ALIGN[oListLevelStyle.Alignment] + '"/>');
|
|
|
|
oXML.Add('<w:pPr>');
|
|
oXML.Add(Format(' <w:ind w:left="%s" w:hanging="%s"/>',
|
|
[mmToTwipsS(oListLevelStyle.MarginLeft), mmToTwipsS(oListLevelStyle.HangingIndent)]));
|
|
oXML.Add('</w:pPr>');
|
|
|
|
oXML.Add('<w:rPr>');
|
|
oXML.Add(Format(' <w:rFonts w:ascii="%s" w:hAnsi="%s"/>',
|
|
[oListLevelStyle.LeaderFontName, oListLevelStyle.LeaderFontName]));
|
|
oXML.Add('</w:rPr>');
|
|
|
|
oXML.Add('</w:lvl>', indDec);
|
|
end;
|
|
|
|
oXML.Add(indDec, '</w:abstractNum>', indDec);
|
|
End;
|
|
|
|
For i := 0 To FData.GetListStyleCount - 1 Do
|
|
begin
|
|
oXML.Add(indInc, Format('<w:num w:numId="%d">', [i + 1]));
|
|
oXML.Add(Format(' <w:abstractNumId w:val="%d"/>', [i]));
|
|
oXML.Add(indDec, '</w:num>');
|
|
end;
|
|
|
|
oXML.Add('</w:numbering>');
|
|
End;
|
|
End;
|
|
|
|
Procedure TvDOCXVectorialWriter.WriteToFile(AFileName: String;
|
|
AData: TvVectorialDocument);
|
|
Var
|
|
oStream: TFileStream;
|
|
Begin
|
|
If ExtractFileExt(AFilename) = '' Then
|
|
AFilename := AFilename + STR_DOCX_EXTENSION;
|
|
|
|
oStream := TFileStream.Create(AFileName, fmCreate);
|
|
Try
|
|
WriteToStream(oStream, AData);
|
|
Finally
|
|
FreeAndNil(oStream);
|
|
End;
|
|
End;
|
|
|
|
Procedure TvDOCXVectorialWriter.WriteToStream(AStream: TStream;
|
|
AData: TvVectorialDocument);
|
|
Var
|
|
oContentTypes, oRelsRels, oDocumentRels: TStringStream;
|
|
oZip: TZipper;
|
|
i: Integer;
|
|
Begin
|
|
FData := AData;
|
|
|
|
PrepareStyles;
|
|
PrepareDocument;
|
|
PrepareNumbering;
|
|
|
|
// These documents need building up with details of all included files,
|
|
// not worth the overhead of handling as StringLists
|
|
oContentTypes := TStringStream.Create(PrepareContentTypes);
|
|
oRelsRels := TStringStream.Create(PrepareRelationships);
|
|
oDocumentRels := TStringStream.Create(PrepareDocRelationships);
|
|
|
|
oZip := TZipper.Create;
|
|
Try
|
|
oZip.Entries.AddFileEntry(oContentTypes, OOXML_PATH_TYPES);
|
|
oZip.Entries.AddFileEntry(oRelsRels, OOXML_PATH_RELS_RELS);
|
|
oZip.Entries.AddFileEntry(oDocumentRels, OOXML_PATH_DOCUMENT_RELS);
|
|
|
|
For i := 0 To FFiles.Count - 1 Do
|
|
oZip.Entries.AddFileEntry(FFiles[i].Stream, FFiles[i].Path);
|
|
|
|
oZip.SaveToStream(AStream);
|
|
Finally
|
|
oZip.Free;
|
|
|
|
For i := 0 To FFiles.Count - 1 Do
|
|
FFiles[i].FreeStream;
|
|
|
|
oContentTypes.Free;
|
|
oRelsRels.Free;
|
|
oDocumentRels.Free;
|
|
End;
|
|
End;
|
|
|
|
Initialization
|
|
RegisterVectorialWriter(TvDOCXVectorialWriter, vfDOCX);
|
|
|
|
End.
|