View difference between Paste ID: 5sQmiDkn and NTx7zDUA
SHOW: | | - or go back to the newest paste.
1
Unit BODTUnit;
2
3
{$mode objfpc}{$H+}
4
5
Interface
6
7
Uses
8
  {$ifdef unix}
9
  unix,
10
  {$else}
11
  windows,
12
  {$endif}
13
  Classes, SysUtils, XMLRead, XMLWrite, DOM, zipper, strutils;
14
15
Type
16
  TFontStyle = (ftBold, ftItalic);
17
  TFontStyles = set of TFontStyle;
18
  TTextPosition = (
19
    tpCenter = 1,
20
    tpLeft = 2,
21
    tpRight = 3,
22
    tpJustify = 4);
23
  TVertAlign = (vaTop, vaMiddle, vaBottom, vaAutomatic);
24
25
Type BODTClass = Class;
26
27
Type
28
29
{ BODTTableClass }
30
31
 BODTTableClass = Class
32
  Private
33
    bODT: BODTClass;
34
    bRootNode: TDOMNode;
35
    bTableName: String;
36
    Function GetCellNode(Const aCol, aRow: Integer): TDOMNode;
37
    Function GetRowCount: Integer;
38
    Function GetRowNode(Const aRow: Integer): TDomNode;
39
  Public
40
    //** Ссылка к документу, содержащему эту таблицу
41
    Property ODT: BODTClass Read bODT;
42
    //** Имя таблицы
43
    Property TableName: String Read bTableName;
44
    //** Количество строк в таблице
45
    Property RowCount: Integer Read GetRowCount;
46
47
    //** Копирует строку
48
    Procedure MultiplyRow(Const aRow, aCount: Integer);
49
    //** Удаляет последнюю строку
50
    Procedure RemoveRow;
51
    //** Объединяет ячейки в строке
52
    Procedure SetCellColSpan(Const aCol, aRow: Integer;
53
      Const aColSpan: Integer);
54-
    //** Устанавливает значение в ячейке. Опционально позволяет указать стиль
54+
    //** Устанавливает значение в ячейке. Также позволяет указать стиль текста
55-
    //** текста
55+
56
      Const aStyleName: String = '');
57
    //** Сокращение для присвоения ячейке значения по маске (вызывает format)
58
    Procedure SetCellValue(Const aCol, aRow: Integer; Const aValueMask: String;
59
      Const aParams: Array Of Const; Const aStyleName: String = '');
60
    //** Сокращение для присвоения ячейке числового значения
61
    Procedure SetCellValue(Const aCol, aRow: Integer; Const aValue: Integer;
62
      Const aStyleName: String = '');
63
    //** Сокращение для присвоения ячейке логического значения
64-
    //** Сокращение для присвоения ячейке булева значения
64+
65
      Const aTrueString, aFalseString: String; Const aStyleName: String = '');
66
67
    //** Удаляет таблицу из документа
68
    Procedure DeleteTable;
69
70
    Constructor Build(Const aODT: BODTClass; Const aTableName: String);
71
    Destructor Burn;
72
End;
73
74
Type
75
76
{ BODTClass }
77
78
 BODTClass = Class
79
  Private
80
    bContent: TXMLDocument;
81
    bManifest: TXMLDocument;
82
    bMeta: TXMLDocument;
83
    bSettings: TXMLDocument;
84
    bStyles: TXMLDocument;
85
    bStyleSheetFile: String;
86
    bTempDir: String;
87
    bXSLTProcPath: String;
88
  Public
89
    //** content.xml - содержимое документа
90
    Property Content: TXMLDocument Read bContent;
91
    //** META-INF/manifest.xml - содержимое всего архива
92
    Property Manifest: TXMLDocument Read bManifest;
93
    //** meta.xml - мета-данные (автор, дата генерации документа и т.д.)
94
    Property Meta: TXMLDocument read bMeta;
95
    //** Еще один файл в котором лежат общие настройки документа OpenOffice
96-
    //** meta.xml - мета-данные (автор, дата генерации документа, генератор и т.д.)
96+
97
    //** styles.xml - стили текста, форматирования таблиц и т.д.
98
    Property Styles: TXMLDocument Read bStyles;
99
    //** Путь к файлу стилей для XSLT преобразования
100
    Property StyleSheetFile: String Read bStyleSheetFile Write bStyleSheetFile;
101
    //** Путь к исполняемому файлу xsltproc для XSLT преобразования
102
    Property XSLTProcPath: String Read bXSLTProcPath Write bXSLTProcPath;
103
104
105
    //** Создаёт документ
106
    Procedure GenerateDocument(Const aFileName: String);
107
    //** Создаёт HTML через xslt преобразование
108
    Procedure GenerateHTML(Const aFileName: String);
109
110
    //** Загружает шаблон
111
    Function LoadTemplate(Const aFileName: String): Boolean;
112
113
    //** Регистрирует стиль текста
114
    Procedure RegisterStyle(Const aName, aFontName: String;
115
      Const aFontSize: Integer; Const aFontStyle: TFontStyles;
116
      Const aTextPosition: TTextPosition);
117
118
    Constructor Build;
119
    Destructor Burn;
120
End;
121
122
Implementation
123
124
ResourceString
125
  //Files names
126
  FILEcontentxml = 'content.xml';
127
  FILEstylesxml = 'styles.xml';
128
  {$IFDEF unix}
129
  FILEmanifestxml = 'META-INF/manifest.xml';
130
  {$ELSE}
131
  FILEmanifestxml = 'META-INF\manifest.xml';
132
  {$ENDIF}
133
  FILEmetaxml = 'meta.xml';
134
  FILEsettingsxml = 'settings.xml';
135
136
  //ODT Nodes names
137
  XMLTableTableRow = 'table:table-row';
138
  XMLTextStyleName = 'text:style-name';
139
  XMLTableNumberColumnsSpanned = 'table:number-columns-spanned';
140
  XMLTableTable = 'table:table';
141
  XMLTableName = 'table:name';
142
  XMLStyleStyle = 'style:style';
143
  XMLStyleName = 'style:name';
144
  XMLStyleFamily = 'style:family';
145
  XMLStyleParentStyleName = 'style:parent-style-name';
146
  XMLStyleTextProperties = 'style:text-properties';
147
  XMLFoFontSize = 'fo:font-size';
148
  XMLStyleFontName = 'style:font-name';
149
  XMLFoFontStyle = 'fo:font-style';
150
  XMLStyleFontStyleAsian = 'style:font-style-asian';
151
  XMLStyleFontStyleComplex = 'style:font-style-complex';
152
  XMLFoFontWeight = 'fo:font-weight';
153
  XMLStyleFontWeightAsian = 'style:font-weight-asian';
154
  XMLStyleFontWeightComplex = 'style:font-weight-complex';
155
  XMLStyleParagraphProperies = 'style:paragraph-properies';
156
  XMLFoTextAlign = 'fo:text-align';
157
  XMLStyleJustifySingleWord = 'style:justify-single-word';
158
  XMLOfficeAutomaticStyles = 'office:automatic-styles';
159
  XMLXmlVersion10EncodingUTF8 = '<?xml version=''1.0'' encoding=''UTF-8''?>';
160
  XMLOfficeDocumentXmlns = '<office:document '
161
    +'xmlns:office=''urn:oasis:names:tc:opendocument:xmlns:office:1.0''>';
162
163
  // Font names
164
  FontItalic = 'italic';
165
  FontBold = 'bold';
166
167
  //Errors messages
168
  ERRUnableFindCell = 'Unable find cell';
169
  ERRIllegalDOMStructure = 'Illegal DOM structure';
170
  ERRUnableFindAutomaticStylesNode = 'Unable find automatic-styles node';
171
  XMLOfficeDocument = '</office:document>';
172
  FILESinglefileXml = 'singlefile.xml';
173
174
Const
175
  TTextPositionName: Array [1..4] Of String =
176
    ('center', 'start', 'end', 'justify');
177
178
{ BODTTableClass }
179
180
Function BODTTableClass.GetRowNode(Const aRow: Integer): TDomNode;
181
var
182
  i:integer;
183
  aNode: TDOMNode;
184
  aList: TDOMNodeList;
185
  aRowIndex: Integer = 0;
186
begin
187
  Result := nil;
188
  aList := bRootNode.ChildNodes;
189
  For i := 0 To aList.Count - 1 Do
190
    If aList[i].NodeName = XMLTableTableRow Then
191
      Begin
192
        If aRowIndex = ARow Then
193
          Exit(aList[i]);
194
        aRowIndex += 1;
195
      End;
196
End;
197
198
Function BODTTableClass.GetRowCount: Integer;
199
Var
200
  i: Integer;
201
  aNodes: TDOMNodeList;
202
Begin
203
  Result := 0;
204
  aNodes := bRootNode.ChildNodes;
205
  For i := 0 To aNodes.Count - 1 Do
206
    If aNodes[i].NodeName = XMLTableTableRow Then
207
      Begin
208
        Inc(Result);
209
      End;
210
end;
211
212
Function BODTTableClass.GetCellNode(Const aCol, aRow: Integer): TDOMNode;
213
Var
214
  aRowNode: TDomNode;
215
Begin
216
  Result := nil;
217
  aRowNode := GetRowNode(aRow);
218
  If aRowNode = nil Then Exit;
219
  Result := aRowNode.ChildNodes[aCol].FirstChild;
220
End;
221
222
Procedure BODTTableClass.MultiplyRow(Const aRow, aCount: Integer);
223
Var
224
  i: Integer;
225
  aNode, aClone: TDomNode;
226
Begin
227
  For i := 0 to aCount - 1 Do
228
    Begin
229
      aNode := GetRowNode(aRow);
230
      aClone := aNode.CloneNode(TRUE);
231
      bRootNode.InsertBefore(aClone, aNode);
232
    End;
233
End;
234
235
Procedure BODTTableClass.RemoveRow;
236
Begin
237
  bRootNode.RemoveChild(bRootNode.LastChild);
238
End;
239
240
Procedure BODTTableClass.SetCellValue(Const aCol, aRow: Integer;
241
  Const aValue: String; Const aStyleName: String);
242
Var
243
  aCellNode: TDOMNode;
244
  aStyleNode: TDOMNode;
245
Begin
246
  aCellNode := GetCellNode(aCol, aRow);
247
  If aCellNode = nil Then
248
    Raise Exception.Create(ERRUnableFindCell);
249
  aCellNode.TextContent := UTF8Decode(aValue);
250
  If Not(aStyleName = '') Then
251
    Begin
252
      aStyleNode := aCellNode.Attributes.GetNamedItem(XMLTextStyleName);
253
      If Not(aStyleNode = nil) Then
254
        aStyleNode.NodeValue := aStyleName;
255
    End;
256
End;
257
258
Procedure BODTTableClass.SetCellColSpan(Const aCol, aRow: Integer;
259
  Const aColSpan: Integer);
260
Var
261
  i: Integer;
262
  aNode: TDOMNode;
263
  aSpanNode: TDOMNode;
264
Begin
265
  aNode := GetCellNode(aCol, aRow).ParentNode;
266
  If aNode = nil Then
267
    Raise Exception.Create(ERRIllegalDOMStructure);
268
269
  aSpanNode := aNode.Attributes.GetNamedItem(XMLTableNumberColumnsSpanned);
270
  If aSpanNode = nil Then
271
    Begin
272
      aSpanNode := bODT.Content.CreateAttribute(XMLTableNumberColumnsSpanned);
273
      aNode.Attributes.SetNamedItem(aSpanNode);
274
    End;
275
276
  aSpanNode.NodeValue := IntToStr(aColSpan);
277
  For i := 1 To aColSpan - 1 Do
278
    aNode.ParentNode.RemoveChild(aNode.NextSibling);
279
End;
280
281
Procedure BODTTableClass.SetCellValue(Const aCol, aRow: Integer;
282
  Const aValueMask: String; Const aParams: Array Of Const;
283
  Const aStyleName: String);
284
Begin
285
  SetCellValue(aCol, aRow, Format(aValueMask, aParams), aStyleName);
286
End;
287
288
Procedure BODTTableClass.SetCellValue(Const aCol, aRow: Integer;
289
  Const aValue: Integer; Const aStyleName: String);
290
Begin
291
  SetCellValue(aCol, aRow, IntToStr(aValue), aStyleName);
292
End;
293
294
Procedure BODTTableClass.SetCellValue(Const aCol, aRow: Integer;
295
  Const aValue: Boolean; Const aTrueString, aFalseString: String;
296
  Const aStyleName: String);
297
Begin
298
  SetCellValue(aCol, aRow, BoolToStr(aValue, aTrueString, aFalseString),
299
    aStyleName);
300
End;
301
302
Procedure BODTTableClass.DeleteTable;
303
Begin
304
  bRootNode.Free;
305
End;
306
307
Constructor BODTTableClass.Build(Const aODT: BODTClass;
308
  Const aTableName: String);
309
Var
310
  aNodes: TDOMNodeList;
311
  i: Integer;
312
Begin
313
  bODT := aODT;
314
  bTableName := aTableName;
315
  aNodes := ODT.Content.DocumentElement.GetElementsByTagName(XMLTableTable);
316
  For i := 0 To aNodes.Count - 1 Do
317
    If aNodes[i].Attributes.GetNamedItem(XMLTableName).NodeValue=aTableName Then
318
      Begin
319
        bRootNode := aNodes[i];
320
        Break;
321
      End;
322
  aNodes.Free;
323
End;
324
325
Destructor BODTTableClass.Burn;
326
Begin
327
  bTableName := '';
328
End;
329
330
{ BODTClass }
331
332
Procedure BODTClass.RegisterStyle(Const aName, aFontName: String;
333
  Const aFontSize: Integer; Const aFontStyle: TFontStyles;
334
  Const aTextPosition: TTextPosition);
335
Const
336
  MASK_POINTER = '%dpt';
337
  STR_PARAGRAPH = 'paragraph';
338
  STR_STANDART = 'Standart';
339
Var
340
  i: Integer;
341
  aNode, aFontNode, aParagraphNode: TDOMElement;
342
  aFontStyles: TStringList;
343
  aStyles: TDOMNodeList;
344
Begin
345
  aNode := bContent.CreateElement(XMLStyleStyle);
346
  aNode.SetAttribute(XMLStyleName, aName);
347
  aNode.SetAttribute(XMLStyleFamily, STR_PARAGRAPH);
348
  aNode.SetAttribute(XMLStyleParentStyleName, STR_STANDART);
349
350
  aFontNode := bContent.CreateElement(XMLStyleTextProperties);
351
  aFontNode.SetAttribute(XMLFoFontSize, Format(MASK_POINTER, [aFontSize]));
352
  aFontNode.SetAttribute(XMLStyleFontName, aFontName);
353
  If ftItalic In aFontStyle Then
354
    Begin
355
      aFontNode.SetAttribute(XMLFoFontStyle, FontItalic);
356
      aFontNode.SetAttribute(XMLStyleFontStyleAsian, FontItalic);
357
      aFontNode.SetAttribute(XMLStyleFontStyleComplex, FontItalic);
358
    End;
359
  If ftBold In aFontStyle Then
360
    Begin
361
      aFontNode.SetAttribute(XMLFoFontWeight, FontBold);
362
      aFontNode.SetAttribute(XMLStyleFontWeightAsian, FontBold);
363
      aFontNode.SetAttribute(XMLStyleFontWeightComplex, FontBold);
364
    End;
365
  aParagraphNode := bContent.CreateElement(XMLStyleParagraphProperies);
366
  aParagraphNode.SetAttribute(XMLFoTextAlign,
367
    TTextPositionName[Integer(aTextPosition)]);
368
  aParagraphNode.SetAttribute(XMLStyleJustifySingleWord, BoolToStr(FALSE));
369
370
  aFontNode.AppendChild(aParagraphNode);
371
  aNode.AppendChild(aFontNode);
372
373
  aStyles := bContent.GetElementsByTagName(XMLOfficeAutomaticStyles);
374
  Try
375
    If aStyles.Count = 1 Then
376
      aStyles[0].AppendChild(aNode)
377
    Else
378
      Raise Exception.Create(ERRUnableFindAutomaticStylesNode);
379
  Finally
380
    aStyles.Free;
381
  End;
382
End;
383
384
Function BODTClass.LoadTemplate(Const aFileName: String): Boolean;
385
Var
386
  aUnZipper: TUnZipper;
387
Begin
388
  Result := FALSE;
389
  aUnZipper := TUnZipper.Create;
390
  Try
391
    aUnZipper.OutputPath := bTempDir;
392
    aUnZipper.UnZipAllFiles(aFileName);
393
394
    ReadXMLFile(bStyles, bTempDir + FILEstylesxml);
395
    ReadXMLFile(bContent, bTempDir + FILEcontentxml);
396
    ReadXMLFile(bManifest, bTempDir + FILEmanifestxml);
397
    ReadXMLFile(bMeta, bTempDir + FILEmetaxml);
398
    ReadXMLFile(bSettings, bTempDir + FILEsettingsxml);
399
400
  Finally
401
    aUnZipper.Free;
402
  End;
403
  Result := TRUE;
404
End;
405
406
Procedure BODTClass.GenerateDocument(Const aFileName: String);
407
Var
408
  aZipper: TZipper;
409
  aStringList: TStringList;
410
  i: Integer;
411
  aBuffer: String;
412
  Procedure AddEntries(Const aDir:string; Const aFilesList: TStringList);
413
  Const
414
    STR_DOT = '.';
415
    STR_DOUBLE_DOT = '..';
416
  Var
417
    aSearchRec: TSearchRec;
418
    aResult: integer;
419
    aBuffer: String;
420
  Begin
421
    aResult := FindFirst(aDir+'*', faAnyFile, aSearchRec);
422
    While aResult = 0 Do
423
      Begin
424
        If Not((aSearchRec.Name = STR_DOT) Or
425
          (aSearchRec.Name = STR_DOUBLE_DOT)) Then
426
          Begin
427
            If (aSearchRec.Attr And faDirectory > 0) Then
428
              Begin
429
                aFilesList.Add(aDir +  aSearchRec.Name + '/');
430
                AddEntries(aDir + aSearchRec.Name + PathDelim, aFilesList)
431
              End
432
            Else
433
              aFilesList.Add(aDir +  aSearchRec.Name);
434
          End;
435
        aResult := FindNext(aSearchRec);
436
      End;
437
    FindClose(aSearchRec);
438
  End;
439
Begin
440
  aZipper := TZipper.Create;
441
  aStringList := TStringList.Create;
442
  Try
443
    WriteXMLFile(bContent, bTempDir + FILEcontentxml);
444
    WriteXMLFile(bStyles, bTempDir + FILEstylesxml);
445
    WriteXMLFile(bMeta, bTempDir + FILEmetaxml);
446
    WriteXMLFile(bSettings, bTempDir + FILEsettingsxml);
447
    WriteXMLFile(bManifest, bTempDir + FILEmanifestxml);
448
449
    aZipper.FileName := aFileName;
450
    AddEntries(bTempDir, aStringList);
451
    For i := 0 To aStringList.Count - 1 Do
452
      Begin
453
        aBuffer := aStringList[i];
454
        Delete(aBuffer, 1, Length(bTempDir));
455
        aZipper.Entries.AddFileEntry(aStringList[i], aBuffer);
456
      End;
457
    aZipper.ZipAllFiles;
458
  Finally
459
    aStringList.Free;
460
    aZipper.Free;
461
  End;
462
End;
463
464
Procedure BODTClass.GenerateHTML(Const aFileName: String);
465
Const
466
  XSLT_BIN_MASK = '%sxsltproc -o "%s" "%s" "%s"';
467
Var
468
  i: Integer;
469
  aBuffer: TStringList;
470
  aSingleFile: TFileStream;
471
  Procedure WriteString(Const aString: String; Const aForce: Boolean = FALSE);
472
  Const
473
    STR_XML = '<?xml';
474
    STR_005pt = '0.05pt';
475
    STR_1px = '1px';
476
    STR_BORDER = 'border';
477
  Var
478
    aBuffer: String;
479
  Begin
480
    If Not((Pos(STR_XML, aString) = 0) Or aForce) Then Exit;
481
    // dirty workarounds for clean HTML view
482
    If Pos(STR_BORDER, aString) > 0 Then
483
      Begin
484
        aBuffer := StringReplace(aString, STR_005pt, STR_1px, [rfReplaceAll]);
485
        aSingleFile.Write(PChar(aBuffer)[0],Length(aBuffer));
486
        Exit;
487
      End;
488
    aSingleFile.Write(PChar(aString)[0],Length(aString));
489
  End;
490
Begin
491
  WriteXMLFile(bMeta, bTempDir + FILEmetaxml);
492
  WriteXMLFile(bContent, bTempDir + FILEcontentxml);
493
  WriteXMLFile(bStyles, bTempDir + FILEstylesxml);
494
495
  aSingleFile := TFileStream.Create(bTempDir + FILESinglefileXml,
496
    fmCreate Or fmOpenReadWrite);
497
  WriteString(XMLXmlVersion10EncodingUTF8, TRUE);
498
  WriteString(XMLOfficeDocumentXmlns);
499
500
  aBuffer := TStringList.Create;
501
  aBuffer.LoadFromFile(bTempDir + FILEmetaxml);
502
  For i := 0 To aBuffer.Count - 1 Do
503
    WriteString(aBuffer[i]);
504
  aBuffer.LoadFromFile(bTempDir + FILEcontentxml);
505
  For i := 0 To aBuffer.Count - 1 Do
506
    WriteString(aBuffer[i]);
507
  aBuffer.LoadFromFile(bTempDir + FILEstylesxml);
508
  For i := 0 To aBuffer.Count - 1 Do
509
    WriteString(aBuffer[i]);
510
  aBuffer.Free;
511
  WriteString(XMLOfficeDocument);
512
  aSingleFile.Free;
513
514
  {$ifdef unix}
515
  fpSystem(Format(XSLT_BIN_MASK, [XSLTProcPath, aFileName, StyleSheetFile,
516
    bTempDir + FILESinglefileXml]));
517
  {$else}
518
  WinExec(Format(XSLT_BIN_MASK, [XSLTProcPath, aFileName, StyleSheetFile,
519
    bTempDir + File]), SW_HIDE);
520
  {$endif}
521
End;
522
523
Constructor BODTClass.Build;
524
Begin
525
  bTempDir := GetTempDir(FALSE) + IntToStr(Random(MaxInt)) + PathDelim;
526
End;
527
528
Destructor BODTClass.Burn;
529
Const
530
  {$ifdef unix}
531
    PROG_REMOVER_MASK = 'rm -r "%s"';
532
  {$else}
533
    PROG_REMOVER_MASK = 'rmdir /S /Q "%s"';
534
  {$endif}
535
Begin
536
  //dirty workaround
537
  {$ifdef unix}
538
  fpSystem(Format(PROG_REMOVER_MASK, [bTempDir]));
539
  {$else}
540
  WinExec(Format(PROG_REMOVER_MASK, [bTempDir]), SW_HIDE);
541
  {$endif}
542
543
  bContent.Free;
544
  bStyles.Free;
545
  bMeta.Free;
546
  bSettings.Free;
547
  bManifest.Free;
548
End;
549
550
End.