xmlwrite.pp 26 KB

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