2
0

xmlwrite.pp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  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}{$MODE objfpc}{$endif}
  14. {$H+}
  15. interface
  16. uses Classes, DOM;
  17. procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String); overload;
  18. procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text); overload;
  19. procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream); overload;
  20. procedure WriteXML(Element: TDOMNode; const AFileName: String); overload;
  21. procedure WriteXML(Element: TDOMNode; var AFile: Text); overload;
  22. procedure WriteXML(Element: TDOMNode; AStream: TStream); overload;
  23. // ===================================================================
  24. implementation
  25. uses SysUtils, xmlutils;
  26. type
  27. TXMLWriter = class;
  28. TSpecialCharCallback = procedure(Sender: TXMLWriter; const s: DOMString;
  29. var idx: Integer);
  30. PAttrFixup = ^TAttrFixup;
  31. TAttrFixup = record
  32. Attr: TDOMNode;
  33. Prefix: PHashItem;
  34. end;
  35. TXMLWriter = class(TObject)
  36. private
  37. FStream: TStream;
  38. FInsideTextNode: Boolean;
  39. FCanonical: Boolean;
  40. FIndent: WideString;
  41. FIndentCount: Integer;
  42. FBuffer: PChar;
  43. FBufPos: PChar;
  44. FCapacity: Integer;
  45. FLineBreak: WideString;
  46. FNSHelper: TNSSupport;
  47. FAttrFixups: TFPList;
  48. FScratch: TFPList;
  49. FNSDefs: TFPList;
  50. procedure wrtChars(Src: PWideChar; Length: Integer);
  51. procedure IncIndent;
  52. procedure DecIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
  53. procedure wrtStr(const ws: WideString); {$IFDEF HAS_INLINE} inline; {$ENDIF}
  54. procedure wrtChr(c: WideChar); {$IFDEF HAS_INLINE} inline; {$ENDIF}
  55. procedure wrtIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
  56. procedure wrtQuotedLiteral(const ws: WideString);
  57. procedure ConvWrite(const s: WideString; const SpecialChars: TSetOfChar;
  58. const SpecialCharCallback: TSpecialCharCallback);
  59. procedure WriteNSDef(B: TBinding);
  60. procedure NamespaceFixup(Element: TDOMElement);
  61. protected
  62. procedure WriteNode(Node: TDOMNode);
  63. procedure VisitDocument(Node: TDOMNode);
  64. procedure VisitDocument_Canonical(Node: TDOMNode);
  65. procedure VisitElement(Node: TDOMNode);
  66. procedure VisitText(Node: TDOMNode);
  67. procedure VisitCDATA(Node: TDOMNode);
  68. procedure VisitComment(Node: TDOMNode);
  69. procedure VisitFragment(Node: TDOMNode);
  70. procedure VisitAttribute(Node: TDOMNode);
  71. procedure VisitEntityRef(Node: TDOMNode);
  72. procedure VisitDocumentType(Node: TDOMNode);
  73. procedure VisitPI(Node: TDOMNode);
  74. public
  75. constructor Create(AStream: TStream);
  76. destructor Destroy; override;
  77. end;
  78. TTextStream = class(TStream)
  79. Private
  80. F : ^Text;
  81. Public
  82. constructor Create(var AFile: Text);
  83. function Write(Const Buffer; Count: Longint): Longint; override;
  84. end;
  85. { ---------------------------------------------------------------------
  86. TTextStream
  87. ---------------------------------------------------------------------}
  88. constructor TTextStream.Create(var AFile: Text);
  89. begin
  90. inherited Create;
  91. f := @AFile;
  92. end;
  93. function TTextStream.Write(const Buffer; Count: Longint): Longint;
  94. var
  95. s: string;
  96. begin
  97. if Count>0 then
  98. begin
  99. SetString(s, PChar(@Buffer), Count);
  100. system.Write(f^, s);
  101. end;
  102. Result := Count;
  103. end;
  104. { ---------------------------------------------------------------------
  105. TXMLWriter
  106. ---------------------------------------------------------------------}
  107. const
  108. AttrSpecialChars = ['<', '"', '&', #9, #10, #13];
  109. TextSpecialChars = ['<', '>', '&', #10, #13];
  110. CDSectSpecialChars = [']'];
  111. LineEndingChars = [#13, #10];
  112. QuotStr = '&quot;';
  113. AmpStr = '&amp;';
  114. ltStr = '&lt;';
  115. gtStr = '&gt;';
  116. constructor TXMLWriter.Create(AStream: TStream);
  117. var
  118. I: Integer;
  119. begin
  120. inherited Create;
  121. FStream := AStream;
  122. // some overhead - always be able to write at least one extra UCS4
  123. FBuffer := AllocMem(512+32);
  124. FBufPos := FBuffer;
  125. FCapacity := 512;
  126. // Later on, this may be put under user control
  127. // for now, take OS setting
  128. if FCanonical then
  129. FLineBreak := #10
  130. else
  131. FLineBreak := sLineBreak;
  132. // Initialize Indent string
  133. // TODO: this must be done in setter of FLineBreak
  134. SetLength(FIndent, 100);
  135. FIndent[1] := FLineBreak[1];
  136. if Length(FLineBreak) > 1 then
  137. FIndent[2] := FLineBreak[2]
  138. else
  139. FIndent[2] := ' ';
  140. for I := 3 to 100 do FIndent[I] := ' ';
  141. FIndentCount := 0;
  142. FNSHelper := TNSSupport.Create;
  143. FScratch := TFPList.Create;
  144. FNSDefs := TFPList.Create;
  145. FAttrFixups := TFPList.Create;
  146. end;
  147. destructor TXMLWriter.Destroy;
  148. var
  149. I: Integer;
  150. begin
  151. for I := FAttrFixups.Count-1 downto 0 do
  152. Dispose(PAttrFixup(FAttrFixups.List^[I]));
  153. FAttrFixups.Free;
  154. FNSDefs.Free;
  155. FScratch.Free;
  156. FNSHelper.Free;
  157. if FBufPos > FBuffer then
  158. FStream.write(FBuffer^, FBufPos-FBuffer);
  159. FreeMem(FBuffer);
  160. inherited Destroy;
  161. end;
  162. procedure TXMLWriter.wrtChars(Src: PWideChar; Length: Integer);
  163. var
  164. pb: PChar;
  165. wc: Cardinal;
  166. SrcEnd: PWideChar;
  167. begin
  168. pb := FBufPos;
  169. SrcEnd := Src + Length;
  170. while Src < SrcEnd do
  171. begin
  172. if pb >= @FBuffer[FCapacity] then
  173. begin
  174. FStream.write(FBuffer^, FCapacity);
  175. Dec(pb, FCapacity);
  176. if pb > FBuffer then
  177. Move(FBuffer[FCapacity], FBuffer^, pb - FBuffer);
  178. end;
  179. wc := Cardinal(Src^); Inc(Src);
  180. case wc of
  181. 0..$7F: begin
  182. pb^ := char(wc); Inc(pb);
  183. end;
  184. $80..$7FF: begin
  185. pb^ := Char($C0 or (wc shr 6));
  186. pb[1] := Char($80 or (wc and $3F));
  187. Inc(pb,2);
  188. end;
  189. $D800..$DBFF: begin
  190. if (Src < SrcEnd) and (Src^ >= #$DC00) and (Src^ <= #$DFFF) then
  191. begin
  192. wc := ((LongInt(wc) - $D7C0) shl 10) + LongInt(word(Src^) xor $DC00);
  193. Inc(Src);
  194. pb^ := Char($F0 or (wc shr 18));
  195. pb[1] := Char($80 or ((wc shr 12) and $3F));
  196. pb[2] := Char($80 or ((wc shr 6) and $3F));
  197. pb[3] := Char($80 or (wc and $3F));
  198. Inc(pb,4);
  199. end
  200. else
  201. raise EConvertError.Create('High surrogate without low one');
  202. end;
  203. $DC00..$DFFF:
  204. raise EConvertError.Create('Low surrogate without high one');
  205. else // $800 >= wc > $FFFF, excluding surrogates
  206. begin
  207. pb^ := Char($E0 or (wc shr 12));
  208. pb[1] := Char($80 or ((wc shr 6) and $3F));
  209. pb[2] := Char($80 or (wc and $3F));
  210. Inc(pb,3);
  211. end;
  212. end;
  213. end;
  214. FBufPos := pb;
  215. end;
  216. procedure TXMLWriter.wrtStr(const ws: WideString); { inline }
  217. begin
  218. wrtChars(PWideChar(ws), Length(ws));
  219. end;
  220. { No checks here - buffer always has 32 extra bytes }
  221. procedure TXMLWriter.wrtChr(c: WideChar); { inline }
  222. begin
  223. FBufPos^ := char(ord(c));
  224. Inc(FBufPos);
  225. end;
  226. procedure TXMLWriter.wrtIndent; { inline }
  227. begin
  228. wrtChars(PWideChar(FIndent), FIndentCount*2+Length(FLineBreak));
  229. end;
  230. procedure TXMLWriter.IncIndent;
  231. var
  232. I, NewLen, OldLen: Integer;
  233. begin
  234. Inc(FIndentCount);
  235. if Length(FIndent) < 2 * FIndentCount then
  236. begin
  237. OldLen := Length(FIndent);
  238. NewLen := 4 * FIndentCount;
  239. SetLength(FIndent, NewLen);
  240. for I := OldLen to NewLen do
  241. FIndent[I] := ' ';
  242. end;
  243. end;
  244. procedure TXMLWriter.DecIndent; { inline }
  245. begin
  246. if FIndentCount>0 then dec(FIndentCount);
  247. end;
  248. procedure TXMLWriter.ConvWrite(const s: WideString; const SpecialChars: TSetOfChar;
  249. const SpecialCharCallback: TSpecialCharCallback);
  250. var
  251. StartPos, EndPos: Integer;
  252. begin
  253. StartPos := 1;
  254. EndPos := 1;
  255. while EndPos <= Length(s) do
  256. begin
  257. if (s[EndPos] < #128) and (Char(ord(s[EndPos])) in SpecialChars) then
  258. begin
  259. wrtChars(@s[StartPos], EndPos - StartPos);
  260. SpecialCharCallback(Self, s, EndPos);
  261. StartPos := EndPos + 1;
  262. end;
  263. Inc(EndPos);
  264. end;
  265. if StartPos <= length(s) then
  266. wrtChars(@s[StartPos], EndPos - StartPos);
  267. end;
  268. procedure AttrSpecialCharCallback(Sender: TXMLWriter; const s: DOMString;
  269. var idx: Integer);
  270. begin
  271. case s[idx] of
  272. '"': Sender.wrtStr(QuotStr);
  273. '&': Sender.wrtStr(AmpStr);
  274. '<': Sender.wrtStr(ltStr);
  275. // Escape whitespace using CharRefs to be consistent with W3 spec § 3.3.3
  276. #9: Sender.wrtStr('&#x9;');
  277. #10: Sender.wrtStr('&#xA;');
  278. #13: Sender.wrtStr('&#xD;');
  279. else
  280. Sender.wrtChr(s[idx]);
  281. end;
  282. end;
  283. procedure TextnodeNormalCallback(Sender: TXMLWriter; const s: DOMString;
  284. var idx: Integer);
  285. begin
  286. case s[idx] of
  287. '<': Sender.wrtStr(ltStr);
  288. '>': Sender.wrtStr(gtStr); // Required only in ']]>' literal, otherwise optional
  289. '&': Sender.wrtStr(AmpStr);
  290. #13:
  291. begin
  292. // We normalize #13#10 and #13 to FLineBreak, going somewhat
  293. // beyond the specs here, see issue #13879.
  294. Sender.wrtStr(Sender.FLineBreak);
  295. if (idx < Length(s)) and (s[idx+1] = #10) then
  296. Inc(idx);
  297. end;
  298. #10: Sender.wrtStr(Sender.FLineBreak);
  299. else
  300. Sender.wrtChr(s[idx]);
  301. end;
  302. end;
  303. procedure TextnodeCanonicalCallback(Sender: TXMLWriter; const s: DOMString;
  304. var idx: Integer);
  305. begin
  306. case s[idx] of
  307. '<': Sender.wrtStr(ltStr);
  308. '>': Sender.wrtStr(gtStr);
  309. '&': Sender.wrtStr(AmpStr);
  310. #13: Sender.wrtStr('&#xD;')
  311. else
  312. Sender.wrtChr(s[idx]);
  313. end;
  314. end;
  315. procedure CDSectSpecialCharCallback(Sender: TXMLWriter; const s: DOMString;
  316. var idx: Integer);
  317. begin
  318. if (idx <= Length(s)-2) and (s[idx+1] = ']') and (s[idx+2] = '>') then
  319. begin
  320. Sender.wrtStr(']]]]><![CDATA[>');
  321. Inc(idx, 2);
  322. // TODO: emit warning 'cdata-section-splitted'
  323. end
  324. else
  325. Sender.wrtChr(s[idx]);
  326. end;
  327. const
  328. TextnodeCallbacks: array[boolean] of TSpecialCharCallback = (
  329. @TextnodeNormalCallback,
  330. @TextnodeCanonicalCallback
  331. );
  332. procedure TXMLWriter.wrtQuotedLiteral(const ws: WideString);
  333. var
  334. Quote: WideChar;
  335. begin
  336. // TODO: need to check if the string also contains single quote
  337. // both quotes present is a error
  338. if Pos('"', ws) > 0 then
  339. Quote := ''''
  340. else
  341. Quote := '"';
  342. wrtChr(Quote);
  343. ConvWrite(ws, LineEndingChars, @TextnodeNormalCallback);
  344. wrtChr(Quote);
  345. end;
  346. procedure TXMLWriter.WriteNode(node: TDOMNode);
  347. begin
  348. case node.NodeType of
  349. ELEMENT_NODE: VisitElement(node);
  350. ATTRIBUTE_NODE: VisitAttribute(node);
  351. TEXT_NODE: VisitText(node);
  352. CDATA_SECTION_NODE: VisitCDATA(node);
  353. ENTITY_REFERENCE_NODE: VisitEntityRef(node);
  354. PROCESSING_INSTRUCTION_NODE: VisitPI(node);
  355. COMMENT_NODE: VisitComment(node);
  356. DOCUMENT_NODE:
  357. if FCanonical then
  358. VisitDocument_Canonical(node)
  359. else
  360. VisitDocument(node);
  361. DOCUMENT_TYPE_NODE: VisitDocumentType(node);
  362. ENTITY_NODE,
  363. DOCUMENT_FRAGMENT_NODE: VisitFragment(node);
  364. end;
  365. end;
  366. procedure TXMLWriter.WriteNSDef(B: TBinding);
  367. begin
  368. wrtChars(' xmlns', 6);
  369. if B.Prefix^.Key <> '' then
  370. begin
  371. wrtChr(':');
  372. wrtStr(B.Prefix^.Key);
  373. end;
  374. wrtChars('="', 2);
  375. ConvWrite(B.uri, AttrSpecialChars, @AttrSpecialCharCallback);
  376. wrtChr('"');
  377. end;
  378. // clone of system.FPC_WIDESTR_COMPARE which cannot be called directly
  379. function Compare(const s1, s2: DOMString): integer;
  380. var
  381. maxi, temp: integer;
  382. begin
  383. Result := 0;
  384. if pointer(S1) = pointer(S2) then
  385. exit;
  386. maxi := Length(S1);
  387. temp := Length(S2);
  388. if maxi > temp then
  389. maxi := temp;
  390. Result := CompareWord(S1[1], S2[1], maxi);
  391. if Result = 0 then
  392. Result := Length(S1)-Length(S2);
  393. end;
  394. function SortNSDefs(Item1, Item2: Pointer): Integer;
  395. begin
  396. Result := Compare(TBinding(Item1).Prefix^.Key, TBinding(Item2).Prefix^.Key);
  397. end;
  398. function SortAtts(Item1, Item2: Pointer): Integer;
  399. var
  400. p1: PAttrFixup absolute Item1;
  401. p2: PAttrFixup absolute Item2;
  402. s1, s2: DOMString;
  403. begin
  404. Result := Compare(p1^.Attr.namespaceURI, p2^.Attr.namespaceURI);
  405. if Result = 0 then
  406. begin
  407. // TODO: Must fix the parser so it doesn't produce Level 1 attributes
  408. if nfLevel2 in p1^.Attr.Flags then
  409. s1 := p1^.Attr.localName
  410. else
  411. s1 := p1^.Attr.nodeName;
  412. if nfLevel2 in p2^.Attr.Flags then
  413. s2 := p2^.Attr.localName
  414. else
  415. s2 := p2^.Attr.nodeName;
  416. Result := Compare(s1, s2);
  417. end;
  418. end;
  419. procedure TXMLWriter.NamespaceFixup(Element: TDOMElement);
  420. var
  421. B: TBinding;
  422. i, j: Integer;
  423. node: TDOMNode;
  424. s: DOMString;
  425. action: TAttributeAction;
  426. p: PAttrFixup;
  427. begin
  428. FScratch.Count := 0;
  429. FNSDefs.Count := 0;
  430. if Element.hasAttributes then
  431. begin
  432. j := 0;
  433. for i := 0 to Element.Attributes.Length-1 do
  434. begin
  435. node := Element.Attributes[i];
  436. if TDOMNode_NS(node).NSI.NSIndex = 2 then
  437. begin
  438. if TDOMNode_NS(node).NSI.PrefixLen = 0 then
  439. s := ''
  440. else
  441. s := node.localName;
  442. FNSHelper.DefineBinding(s, node.nodeValue, B);
  443. if Assigned(B) then // drop redundant namespace declarations
  444. FNSDefs.Add(B);
  445. end
  446. else if FCanonical or TDOMAttr(node).Specified then
  447. begin
  448. // obtain a TAttrFixup record (allocate if needed)
  449. if j >= FAttrFixups.Count then
  450. begin
  451. New(p);
  452. FAttrFixups.Add(p);
  453. end
  454. else
  455. p := PAttrFixup(FAttrFixups.List^[j]);
  456. // add it to the working list
  457. p^.Attr := node;
  458. p^.Prefix := nil;
  459. FScratch.Add(p);
  460. Inc(j);
  461. end;
  462. end;
  463. end;
  464. FNSHelper.DefineBinding(Element.Prefix, Element.namespaceURI, B);
  465. if Assigned(B) then
  466. FNSDefs.Add(B);
  467. for i := 0 to FScratch.Count-1 do
  468. begin
  469. node := PAttrFixup(FScratch.List^[i])^.Attr;
  470. action := FNSHelper.CheckAttribute(node.Prefix, node.namespaceURI, B);
  471. if action = aaBoth then
  472. FNSDefs.Add(B);
  473. if action in [aaPrefix, aaBoth] then
  474. PAttrFixup(FScratch.List^[i])^.Prefix := B.Prefix;
  475. end;
  476. if FCanonical then
  477. begin
  478. FNSDefs.Sort(@SortNSDefs);
  479. FScratch.Sort(@SortAtts);
  480. end;
  481. // now, at last, dump all this stuff.
  482. for i := 0 to FNSDefs.Count-1 do
  483. WriteNSDef(TBinding(FNSDefs.List^[I]));
  484. for i := 0 to FScratch.Count-1 do
  485. begin
  486. wrtChr(' ');
  487. with PAttrFixup(FScratch.List^[I])^ do
  488. begin
  489. if Assigned(Prefix) then
  490. begin
  491. wrtStr(Prefix^.Key);
  492. wrtChr(':');
  493. wrtStr(Attr.localName);
  494. end
  495. else
  496. wrtStr(Attr.nodeName);
  497. wrtChars('="', 2);
  498. // TODO: not correct w.r.t. entities
  499. ConvWrite(attr.nodeValue, AttrSpecialChars, @AttrSpecialCharCallback);
  500. wrtChr('"');
  501. end;
  502. end;
  503. end;
  504. procedure TXMLWriter.VisitElement(node: TDOMNode);
  505. var
  506. i: Integer;
  507. child: TDOMNode;
  508. SavedInsideTextNode: Boolean;
  509. begin
  510. if not FInsideTextNode then
  511. wrtIndent;
  512. FNSHelper.StartElement;
  513. wrtChr('<');
  514. wrtStr(TDOMElement(node).TagName);
  515. if nfLevel2 in node.Flags then
  516. NamespaceFixup(TDOMElement(node))
  517. else if node.HasAttributes then
  518. for i := 0 to node.Attributes.Length - 1 do
  519. begin
  520. child := node.Attributes.Item[i];
  521. if FCanonical or TDOMAttr(child).Specified then
  522. VisitAttribute(child);
  523. end;
  524. Child := node.FirstChild;
  525. if Child = nil then
  526. wrtChars('/>', 2)
  527. else
  528. begin
  529. // TODO: presence of zero-length textnodes triggers the indenting logic,
  530. // while they should be ignored altogeter.
  531. SavedInsideTextNode := FInsideTextNode;
  532. wrtChr('>');
  533. FInsideTextNode := FCanonical or (Child.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]);
  534. IncIndent;
  535. repeat
  536. WriteNode(Child);
  537. Child := Child.NextSibling;
  538. until Child = nil;
  539. DecIndent;
  540. if not (node.LastChild.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]) then
  541. wrtIndent;
  542. FInsideTextNode := SavedInsideTextNode;
  543. wrtChars('</', 2);
  544. wrtStr(TDOMElement(Node).TagName);
  545. wrtChr('>');
  546. end;
  547. FNSHelper.EndElement;
  548. end;
  549. procedure TXMLWriter.VisitText(node: TDOMNode);
  550. begin
  551. ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, TextnodeCallbacks[FCanonical]);
  552. end;
  553. procedure TXMLWriter.VisitCDATA(node: TDOMNode);
  554. begin
  555. if not FInsideTextNode then
  556. wrtIndent;
  557. if FCanonical then
  558. ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, @TextnodeCanonicalCallback)
  559. else
  560. begin
  561. wrtChars('<![CDATA[', 9);
  562. ConvWrite(TDOMCharacterData(node).Data, CDSectSpecialChars, @CDSectSpecialCharCallback);
  563. wrtChars(']]>', 3);
  564. end;
  565. end;
  566. procedure TXMLWriter.VisitEntityRef(node: TDOMNode);
  567. begin
  568. wrtChr('&');
  569. wrtStr(node.NodeName);
  570. wrtChr(';');
  571. end;
  572. procedure TXMLWriter.VisitPI(node: TDOMNode);
  573. begin
  574. if not FInsideTextNode then wrtIndent;
  575. wrtStr('<?');
  576. wrtStr(TDOMProcessingInstruction(node).Target);
  577. if TDOMProcessingInstruction(node).Data <> '' then
  578. begin
  579. wrtChr(' ');
  580. // TODO: How does this comply with c14n??
  581. ConvWrite(TDOMProcessingInstruction(node).Data, LineEndingChars, @TextnodeNormalCallback);
  582. end;
  583. wrtStr('?>');
  584. end;
  585. procedure TXMLWriter.VisitComment(node: TDOMNode);
  586. begin
  587. if not FInsideTextNode then wrtIndent;
  588. wrtChars('<!--', 4);
  589. // TODO: How does this comply with c14n??
  590. ConvWrite(TDOMCharacterData(node).Data, LineEndingChars, @TextnodeNormalCallback);
  591. wrtChars('-->', 3);
  592. end;
  593. procedure TXMLWriter.VisitDocument(node: TDOMNode);
  594. var
  595. child: TDOMNode;
  596. begin
  597. wrtStr('<?xml version="');
  598. // Definitely should not escape anything here
  599. if Length(TXMLDocument(node).XMLVersion) > 0 then
  600. wrtStr(TXMLDocument(node).XMLVersion)
  601. else
  602. wrtStr('1.0');
  603. wrtChr('"');
  604. // DISABLED - we are only able write in UTF-8 which does not require labeling
  605. // writing incorrect encoding will render xml unreadable...
  606. (*
  607. if Length(TXMLDocument(node).Encoding) > 0 then
  608. begin
  609. wrtStr(' encoding="');
  610. wrtStr(TXMLDocument(node).Encoding);
  611. wrtChr('"');
  612. end;
  613. *)
  614. wrtStr('?>');
  615. // TODO: now handled as a regular PI, remove this?
  616. if node is TXMLDocument then
  617. begin
  618. if Length(TXMLDocument(node).StylesheetType) > 0 then
  619. begin
  620. wrtStr(FLineBreak);
  621. wrtStr('<?xml-stylesheet type="');
  622. wrtStr(TXMLDocument(node).StylesheetType);
  623. wrtStr('" href="');
  624. wrtStr(TXMLDocument(node).StylesheetHRef);
  625. wrtStr('"?>');
  626. end;
  627. end;
  628. child := node.FirstChild;
  629. while Assigned(Child) do
  630. begin
  631. WriteNode(Child);
  632. Child := Child.NextSibling;
  633. end;
  634. wrtStr(FLineBreak);
  635. end;
  636. procedure TXMLWriter.VisitDocument_Canonical(Node: TDOMNode);
  637. var
  638. child, root: TDOMNode;
  639. begin
  640. root := TDOMDocument(Node).DocumentElement;
  641. child := node.FirstChild;
  642. while Assigned(child) and (child <> root) do
  643. begin
  644. if child.nodeType in [COMMENT_NODE, PROCESSING_INSTRUCTION_NODE] then
  645. begin
  646. WriteNode(child);
  647. wrtChr(#10);
  648. end;
  649. child := child.nextSibling;
  650. end;
  651. if root = nil then
  652. Exit;
  653. VisitElement(TDOMElement(root));
  654. child := root.nextSibling;
  655. while Assigned(child) do
  656. begin
  657. if child.nodeType in [COMMENT_NODE, PROCESSING_INSTRUCTION_NODE] then
  658. begin
  659. wrtChr(#10);
  660. WriteNode(child);
  661. end;
  662. child := child.nextSibling;
  663. end;
  664. end;
  665. procedure TXMLWriter.VisitAttribute(Node: TDOMNode);
  666. var
  667. Child: TDOMNode;
  668. begin
  669. wrtChr(' ');
  670. wrtStr(TDOMAttr(Node).Name);
  671. wrtChars('="', 2);
  672. Child := Node.FirstChild;
  673. while Assigned(Child) do
  674. begin
  675. case Child.NodeType of
  676. ENTITY_REFERENCE_NODE:
  677. VisitEntityRef(Child);
  678. TEXT_NODE:
  679. ConvWrite(TDOMCharacterData(Child).Data, AttrSpecialChars, @AttrSpecialCharCallback);
  680. end;
  681. Child := Child.NextSibling;
  682. end;
  683. wrtChr('"');
  684. end;
  685. procedure TXMLWriter.VisitDocumentType(Node: TDOMNode);
  686. begin
  687. wrtStr(FLineBreak);
  688. wrtStr('<!DOCTYPE ');
  689. wrtStr(Node.NodeName);
  690. wrtChr(' ');
  691. with TDOMDocumentType(Node) do
  692. begin
  693. if PublicID <> '' then
  694. begin
  695. wrtStr('PUBLIC ');
  696. wrtQuotedLiteral(PublicID);
  697. wrtChr(' ');
  698. wrtQuotedLiteral(SystemID);
  699. end
  700. else if SystemID <> '' then
  701. begin
  702. wrtStr('SYSTEM ');
  703. wrtQuotedLiteral(SystemID);
  704. end;
  705. if InternalSubset <> '' then
  706. begin
  707. wrtChr('[');
  708. ConvWrite(InternalSubset, LineEndingChars, @TextnodeNormalCallback);
  709. wrtChr(']');
  710. end;
  711. end;
  712. wrtChr('>');
  713. end;
  714. procedure TXMLWriter.VisitFragment(Node: TDOMNode);
  715. var
  716. Child: TDOMNode;
  717. begin
  718. // TODO: TextDecl is probably needed
  719. // Fragment itself should not be written, only its children should...
  720. Child := Node.FirstChild;
  721. while Assigned(Child) do
  722. begin
  723. WriteNode(Child);
  724. Child := Child.NextSibling;
  725. end;
  726. end;
  727. // -------------------------------------------------------------------
  728. // Interface implementation
  729. // -------------------------------------------------------------------
  730. procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String);
  731. var
  732. fs: TFileStream;
  733. begin
  734. fs := TFileStream.Create(AFileName, fmCreate);
  735. try
  736. WriteXMLFile(doc, fs);
  737. finally
  738. fs.Free;
  739. end;
  740. end;
  741. procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text);
  742. var
  743. s: TStream;
  744. begin
  745. s := TTextStream.Create(AFile);
  746. try
  747. with TXMLWriter.Create(s) do
  748. try
  749. WriteNode(doc);
  750. finally
  751. Free;
  752. end;
  753. finally
  754. s.Free;
  755. end;
  756. end;
  757. procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream);
  758. begin
  759. with TXMLWriter.Create(AStream) do
  760. try
  761. WriteNode(doc);
  762. finally
  763. Free;
  764. end;
  765. end;
  766. procedure WriteXML(Element: TDOMNode; const AFileName: String);
  767. begin
  768. WriteXMLFile(TXMLDocument(Element), AFileName);
  769. end;
  770. procedure WriteXML(Element: TDOMNode; var AFile: Text);
  771. begin
  772. WriteXMLFile(TXMLDocument(Element), AFile);
  773. end;
  774. procedure WriteXML(Element: TDOMNode; AStream: TStream);
  775. begin
  776. WriteXMLFile(TXMLDocument(Element), AStream);
  777. end;
  778. end.