xmlwrite.pp 26 KB

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