xmlwrite.pp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. {
  2. This file is part of the Free Component Library
  3. XML writing routines
  4. Copyright (c) 1999-2000 by Sebastian Guenther, [email protected]
  5. Modified in 2006 by Sergei Gorelkin, [email protected]
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. unit XMLWrite;
  13. {$ifdef fpc}
  14. {$MODE objfpc}
  15. {$H+}
  16. {/$DEFINE HAS_INLINE}
  17. {$INLINE OFF}
  18. {$endif}
  19. interface
  20. uses Classes, DOM;
  21. procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String); overload;
  22. procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text); overload;
  23. procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream); overload;
  24. procedure WriteXML(Element: TDOMNode; const AFileName: String); overload;
  25. procedure WriteXML(Element: TDOMNode; var AFile: Text); overload;
  26. procedure WriteXML(Element: TDOMNode; AStream: TStream); overload;
  27. // ===================================================================
  28. implementation
  29. uses SysUtils;
  30. type
  31. TCharacters = set of Char;
  32. TSpecialCharCallback = procedure(c: WideChar) of object;
  33. TXMLWriter = class(TObject) // (TAbstractDOMVisitor)?
  34. private
  35. FInsideTextNode: Boolean;
  36. FIndent: WideString;
  37. FIndentCount: Integer;
  38. FBuffer: PChar;
  39. FBufPos: PChar;
  40. FCapacity: Integer;
  41. procedure wrtChars(Buf: PWideChar; Length: Integer);
  42. procedure IncIndent;
  43. procedure DecIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
  44. procedure wrtStr(const ws: WideString); {$IFDEF HAS_INLINE} inline; {$ENDIF}
  45. procedure wrtChr(c: WideChar); {$IFDEF HAS_INLINE} inline; {$ENDIF}
  46. procedure wrtLineEnd; {$IFDEF HAS_INLINE} inline; {$ENDIF}
  47. procedure wrtIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
  48. procedure ConvWrite(const s: WideString; const SpecialChars: TCharacters;
  49. const SpecialCharCallback: TSpecialCharCallback);
  50. procedure AttrSpecialCharCallback(c: WideChar);
  51. procedure TextNodeSpecialCharCallback(c: WideChar);
  52. protected
  53. procedure Write(const Buffer; Count: Longint); virtual; abstract;
  54. procedure WriteNode(Node: TDOMNode);
  55. procedure VisitDocument(Node: TDOMNode); // override;
  56. procedure VisitElement(Node: TDOMNode);
  57. procedure VisitText(Node: TDOMNode);
  58. procedure VisitCDATA(Node: TDOMNode);
  59. procedure VisitComment(Node: TDOMNode);
  60. procedure VisitFragment(Node: TDOMNode);
  61. procedure VisitAttribute(Node: TDOMNode);
  62. procedure VisitEntity(Node: TDOMNode);
  63. procedure VisitEntityRef(Node: TDOMNode);
  64. procedure VisitDocumentType(Node: TDOMNode);
  65. procedure VisitPI(Node: TDOMNode);
  66. procedure VisitNotation(Node: TDOMNode);
  67. public
  68. constructor Create;
  69. destructor Destroy; override;
  70. end;
  71. TTextXMLWriter = Class(TXMLWriter)
  72. Private
  73. F : ^Text;
  74. Protected
  75. Procedure Write(Const Buffer; Count : Longint);override;
  76. Public
  77. constructor Create(var AFile: Text);
  78. end;
  79. TStreamXMLWriter = Class(TXMLWriter)
  80. Private
  81. F : TStream;
  82. Protected
  83. Procedure Write(Const Buffer; Count : Longint);override;
  84. Public
  85. constructor Create(AStream: TStream);
  86. end;
  87. { ---------------------------------------------------------------------
  88. TTextXMLWriter
  89. ---------------------------------------------------------------------}
  90. constructor TTextXMLWriter.Create(var AFile: Text);
  91. begin
  92. inherited Create;
  93. f := @AFile;
  94. end;
  95. procedure TTextXMLWriter.Write(const Buffer; Count: Longint);
  96. var
  97. s: string;
  98. begin
  99. if Count>0 then
  100. begin
  101. SetString(s, PChar(@Buffer), Count);
  102. system.Write(f^, s);
  103. end;
  104. end;
  105. { ---------------------------------------------------------------------
  106. TStreamXMLWriter
  107. ---------------------------------------------------------------------}
  108. constructor TStreamXMLWriter.Create(AStream: TStream);
  109. begin
  110. inherited Create;
  111. F := AStream;
  112. end;
  113. procedure TStreamXMLWriter.Write(const Buffer; Count: Longint);
  114. begin
  115. if Count > 0 then
  116. F.Write(Buffer, Count);
  117. end;
  118. { ---------------------------------------------------------------------
  119. TXMLWriter
  120. ---------------------------------------------------------------------}
  121. constructor TXMLWriter.Create;
  122. var
  123. I: Integer;
  124. begin
  125. inherited Create;
  126. // some overhead - always be able to write at least one extra UCS4
  127. FBuffer := AllocMem(512+32);
  128. FBufPos := FBuffer;
  129. FCapacity := 512;
  130. // Initialize Indent string
  131. SetLength(FIndent, 100);
  132. for I := 1 to 100 do FIndent[I] := ' ';
  133. FIndentCount := 0;
  134. end;
  135. destructor TXMLWriter.Destroy;
  136. begin
  137. if FBufPos > FBuffer then
  138. write(FBuffer^, FBufPos-FBuffer);
  139. FreeMem(FBuffer);
  140. inherited Destroy;
  141. end;
  142. procedure TXMLWriter.wrtChars(Buf: PWideChar; Length: Integer);
  143. var
  144. pb: PChar;
  145. wc: Cardinal;
  146. I: Integer;
  147. begin
  148. pb := FBufPos;
  149. for I := 0 to Length-1 do
  150. begin
  151. if pb >= @FBuffer[FCapacity] then
  152. begin
  153. write(FBuffer^, FCapacity);
  154. Dec(pb, FCapacity);
  155. if pb > FBuffer then
  156. Move(FBuffer[FCapacity], FBuffer^, pb - FBuffer);
  157. end;
  158. wc := Cardinal(Buf^); Inc(Buf);
  159. if wc <= $7F then
  160. begin
  161. pb^ := char(wc); Inc(pb);
  162. end
  163. else if wc > $7FF then
  164. begin
  165. pb^ := Char($E0 or (wc shr 12)); Inc(pb);
  166. pb^ := Char($80 or ((wc shr 6) and $3F)); Inc(pb);
  167. pb^ := Char($80 or (wc and $3F)); Inc(pb);
  168. end
  169. else // $7f < wc <= $7FF
  170. begin
  171. pb^ := Char($C0 or (wc shr 6)); Inc(pb);
  172. pb^ := Char($80 or (wc and $3F)); Inc(pb);
  173. end;
  174. end;
  175. FBufPos := pb;
  176. end;
  177. procedure TXMLWriter.wrtStr(const ws: WideString); { inline }
  178. begin
  179. wrtChars(PWideChar(ws), Length(ws));
  180. end;
  181. procedure TXMLWriter.wrtChr(c: WideChar); { inline }
  182. begin
  183. wrtChars(@c,1);
  184. end;
  185. procedure TXMLWriter.wrtLineEnd; { inline }
  186. begin
  187. wrtStr(slinebreak);
  188. end;
  189. procedure TXMLWriter.wrtIndent; { inline }
  190. begin
  191. wrtChars(PWideChar(FIndent), FIndentCount*2);
  192. end;
  193. procedure TXMLWriter.IncIndent;
  194. var
  195. I, NewLen, OldLen: Integer;
  196. begin
  197. Inc(FIndentCount);
  198. if Length(FIndent) < 2 * FIndentCount then
  199. begin
  200. OldLen := Length(FIndent);
  201. NewLen := 4 * FIndentCount;
  202. SetLength(FIndent, NewLen);
  203. for I := OldLen to NewLen do
  204. FIndent[I] := ' ';
  205. end;
  206. end;
  207. procedure TXMLWriter.DecIndent; { inline }
  208. begin
  209. if FIndentCount>0 then dec(FIndentCount);
  210. end;
  211. const
  212. AttrSpecialChars = ['<', '>', '"', '&'];
  213. TextSpecialChars = ['<', '>', '&'];
  214. procedure TXMLWriter.ConvWrite(const s: WideString; const SpecialChars: TCharacters;
  215. const SpecialCharCallback: TSpecialCharCallback);
  216. var
  217. StartPos, EndPos: Integer;
  218. begin
  219. StartPos := 1;
  220. EndPos := 1;
  221. while EndPos <= Length(s) do
  222. begin
  223. if (s[EndPos] < #255) and (Char(s[EndPos]) in SpecialChars) then
  224. begin
  225. wrtChars(@s[StartPos], EndPos - StartPos);
  226. SpecialCharCallback(s[EndPos]);
  227. StartPos := EndPos + 1;
  228. end;
  229. Inc(EndPos);
  230. end;
  231. if StartPos <= length(s) then
  232. wrtChars(@s[StartPos], EndPos - StartPos);
  233. end;
  234. procedure TXMLWriter.AttrSpecialCharCallback(c: WideChar);
  235. const
  236. QuotStr = '&quot;';
  237. AmpStr = '&amp;';
  238. ltStr = '&lt;';
  239. begin
  240. if c = '"' then
  241. wrtStr(QuotStr)
  242. else if c = '&' then
  243. wrtStr(AmpStr)
  244. else if c = '<' then
  245. wrtStr(ltStr)
  246. else
  247. wrtChr(c);
  248. end;
  249. procedure TXMLWriter.TextnodeSpecialCharCallback(c: WideChar);
  250. const
  251. ltStr = '&lt;';
  252. gtStr = '&gt;';
  253. AmpStr = '&amp;';
  254. begin
  255. if c = '<' then
  256. wrtStr(ltStr)
  257. else if c = '>' then
  258. wrtStr(gtStr)
  259. else if c = '&' then
  260. wrtStr(AmpStr)
  261. else
  262. wrtChr(c);
  263. end;
  264. procedure TXMLWriter.WriteNode(node: TDOMNode);
  265. begin
  266. // Must be: node.Accept(Self);
  267. case node.NodeType of
  268. ELEMENT_NODE: VisitElement(node);
  269. ATTRIBUTE_NODE: VisitAttribute(node);
  270. TEXT_NODE: VisitText(node);
  271. CDATA_SECTION_NODE: VisitCDATA(node);
  272. ENTITY_REFERENCE_NODE: VisitEntityRef(node);
  273. ENTITY_NODE: VisitEntity(node);
  274. PROCESSING_INSTRUCTION_NODE: VisitPI(node);
  275. COMMENT_NODE: VisitComment(node);
  276. DOCUMENT_NODE: VisitDocument(node);
  277. DOCUMENT_TYPE_NODE: VisitDocumentType(node);
  278. DOCUMENT_FRAGMENT_NODE: VisitFragment(node);
  279. NOTATION_NODE: VisitNotation(node);
  280. end;
  281. end;
  282. procedure TXMLWriter.VisitElement(node: TDOMNode);
  283. var
  284. i: Integer;
  285. attr, child: TDOMNode;
  286. SavedInsideTextNode: Boolean;
  287. IsLeaf: Boolean;
  288. MixedContent: Boolean;
  289. begin
  290. if not FInsideTextNode then
  291. wrtIndent;
  292. wrtChr('<');
  293. wrtStr(node.NodeName);
  294. // FIX: Accessing Attributes was causing them to be created for every element :(
  295. if node.HasAttributes then
  296. for i := 0 to node.Attributes.Length - 1 do
  297. begin
  298. attr := node.Attributes.Item[i];
  299. VisitAttribute(attr);
  300. end;
  301. Child := node.FirstChild;
  302. if Child = nil then
  303. wrtStr('/>')
  304. else
  305. begin
  306. SavedInsideTextNode := FInsideTextNode;
  307. wrtChr('>');
  308. MixedContent := False;
  309. repeat
  310. if Assigned(Child.PreviousSibling) and
  311. (Child.PreviousSibling.InheritsFrom(TDOMText) <> Child.InheritsFrom(TDOMText)) then
  312. MixedContent := True;
  313. Child := Child.NextSibling;
  314. until Child = nil;
  315. Child := node.FirstChild; // restore
  316. IsLeaf := (Child = node.LastChild) and (Child.FirstChild = nil);
  317. if not (FInsideTextNode or MixedContent or IsLeaf) then
  318. wrtLineEnd;
  319. FInsideTextNode := {FInsideTextNode or} MixedContent or IsLeaf;
  320. IncIndent;
  321. repeat
  322. WriteNode(Child);
  323. Child := Child.NextSibling;
  324. until Child = nil;
  325. DecIndent;
  326. if not FInsideTextNode then
  327. wrtIndent;
  328. FInsideTextNode := SavedInsideTextNode;
  329. wrtStr('</');
  330. wrtStr(Node.NodeName);
  331. wrtChr('>');
  332. end;
  333. if not FInsideTextNode then
  334. wrtLineEnd;
  335. end;
  336. procedure TXMLWriter.VisitText(node: TDOMNode);
  337. begin
  338. ConvWrite(node.NodeValue, TextSpecialChars, {$IFDEF FPC}@{$ENDIF}TextnodeSpecialCharCallback);
  339. end;
  340. procedure TXMLWriter.VisitCDATA(node: TDOMNode);
  341. begin
  342. if not FInsideTextNode then
  343. wrtIndent;
  344. wrtStr('<![CDATA[');
  345. wrtStr(node.NodeValue);
  346. wrtStr(']]>');
  347. if not FInsideTextNode then
  348. wrtLineEnd;
  349. end;
  350. procedure TXMLWriter.VisitEntityRef(node: TDOMNode);
  351. begin
  352. wrtChr('&');
  353. wrtStr(node.NodeName);
  354. wrtChr(';');
  355. end;
  356. procedure TXMLWriter.VisitEntity(node: TDOMNode);
  357. begin
  358. end;
  359. procedure TXMLWriter.VisitPI(node: TDOMNode);
  360. begin
  361. if not FInsideTextNode then wrtIndent;
  362. wrtStr('<?');
  363. wrtStr(TDOMProcessingInstruction(node).Target);
  364. wrtChr(' ');
  365. wrtStr(TDOMProcessingInstruction(node).Data);
  366. wrtStr('?>');
  367. if not FInsideTextNode then wrtLineEnd;
  368. end;
  369. procedure TXMLWriter.VisitComment(node: TDOMNode);
  370. begin
  371. if not FInsideTextNode then wrtIndent;
  372. wrtStr('<!--');
  373. wrtStr(node.NodeValue);
  374. wrtStr('-->');
  375. if not FInsideTextNode then wrtLineEnd;
  376. end;
  377. procedure TXMLWriter.VisitDocument(node: TDOMNode);
  378. var
  379. child: TDOMNode;
  380. begin
  381. wrtStr('<?xml version="');
  382. if Length(TXMLDocument(node).XMLVersion) > 0 then
  383. ConvWrite(TXMLDocument(node).XMLVersion, AttrSpecialChars, {$IFDEF FPC}@{$ENDIF}AttrSpecialCharCallback)
  384. else
  385. wrtStr('1.0');
  386. wrtChr('"');
  387. if Length(TXMLDocument(node).Encoding) > 0 then
  388. begin
  389. wrtStr(' encoding="');
  390. ConvWrite(TXMLDocument(node).Encoding, AttrSpecialChars, {$IFDEF FPC}@{$ENDIF}AttrSpecialCharCallback);
  391. wrtChr('"');
  392. end;
  393. wrtStr('?>');
  394. wrtLineEnd;
  395. if Length(TXMLDocument(node).StylesheetType) > 0 then
  396. begin
  397. wrtStr('<?xml-stylesheet type="');
  398. ConvWrite(TXMLDocument(node).StylesheetType, AttrSpecialChars, {$IFDEF FPC}@{$ENDIF}AttrSpecialCharCallback);
  399. wrtStr('" href="');
  400. ConvWrite(TXMLDocument(node).StylesheetHRef, AttrSpecialChars, {$IFDEF FPC}@{$ENDIF}AttrSpecialCharCallback);
  401. wrtStr('"?>');
  402. wrtLineEnd;
  403. end;
  404. child := node.FirstChild;
  405. while Assigned(Child) do
  406. begin
  407. WriteNode(Child);
  408. Child := Child.NextSibling;
  409. end;
  410. end;
  411. procedure TXMLWriter.VisitAttribute(Node: TDOMNode);
  412. var
  413. Child: TDOMNode;
  414. begin
  415. wrtChr(' ');
  416. wrtStr(Node.NodeName);
  417. wrtStr('="');
  418. Child := Node.FirstChild;
  419. while Assigned(Child) do
  420. begin
  421. if Child.NodeType = ENTITY_REFERENCE_NODE then
  422. VisitEntityRef(Child)
  423. else
  424. ConvWrite(Child.NodeValue, AttrSpecialChars, {$IFDEF FPC}@{$ENDIF}AttrSpecialCharCallback);
  425. Child := Child.NextSibling;
  426. end;
  427. wrtChr('"');
  428. end;
  429. procedure TXMLWriter.VisitDocumentType(Node: TDOMNode);
  430. begin
  431. end;
  432. procedure TXMLWriter.VisitFragment(Node: TDOMNode);
  433. var
  434. Child: TDOMNode;
  435. begin
  436. // Fragment itself should not be written, only its children should...
  437. Child := Node.FirstChild;
  438. while Assigned(Child) do
  439. begin
  440. WriteNode(Child);
  441. Child := Child.NextSibling;
  442. end;
  443. end;
  444. procedure TXMLWriter.VisitNotation(Node: TDOMNode);
  445. begin
  446. end;
  447. // -------------------------------------------------------------------
  448. // Interface implementation
  449. // -------------------------------------------------------------------
  450. procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String);
  451. var
  452. fs: TFileStream;
  453. begin
  454. fs := TFileStream.Create(AFileName, fmCreate);
  455. try
  456. WriteXMLFile(doc, fs);
  457. finally
  458. fs.Free;
  459. end;
  460. end;
  461. procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text);
  462. begin
  463. with TTextXMLWriter.Create(AFile) do
  464. try
  465. WriteNode(doc);
  466. finally
  467. Free;
  468. end;
  469. end;
  470. procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream);
  471. begin
  472. with TStreamXMLWriter.Create(AStream) do
  473. try
  474. WriteNode(doc);
  475. finally
  476. Free;
  477. end;
  478. end;
  479. procedure WriteXML(Element: TDOMNode; const AFileName: String);
  480. begin
  481. WriteXMLFile(TXMLDocument(Element), AFileName);
  482. end;
  483. procedure WriteXML(Element: TDOMNode; var AFile: Text);
  484. begin
  485. WriteXMLFile(TXMLDocument(Element), AFile);
  486. end;
  487. procedure WriteXML(Element: TDOMNode; AStream: TStream);
  488. begin
  489. WriteXMLFile(TXMLDocument(Element), AStream);
  490. end;
  491. end.