xmlwrite.pp 22 KB

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