XMLWrite.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. {
  2. This file is part of the Free Component Library
  3. XML writing routines
  4. Copyright (c) 1999-2003 by Sebastian Guenther, [email protected]
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit XMLWrite;
  12. interface
  13. uses Classes, DOM;
  14. procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String); overload;
  15. procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text); overload;
  16. procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream); overload;
  17. procedure WriteXML(Node: TDOMNode; const AFileName: String); overload;
  18. procedure WriteXML(Node: TDOMNode; var AFile: Text); overload;
  19. procedure WriteXML(Node: TDOMNode; AStream: TStream); overload;
  20. // ===================================================================
  21. implementation
  22. uses SysUtils;
  23. // -------------------------------------------------------------------
  24. // Writers for the different node types
  25. // -------------------------------------------------------------------
  26. procedure WriteElement(node: TDOMNode); forward;
  27. procedure WriteAttribute(node: TDOMNode); forward;
  28. procedure WriteText(node: TDOMNode); forward;
  29. procedure WriteCDATA(node: TDOMNode); forward;
  30. procedure WriteEntityRef(node: TDOMNode); forward;
  31. procedure WriteEntity(node: TDOMNode); forward;
  32. procedure WritePI(node: TDOMNode); forward;
  33. procedure WriteComment(node: TDOMNode); forward;
  34. procedure WriteDocument(node: TDOMNode); forward;
  35. procedure WriteDocumentType(node: TDOMNode); forward;
  36. procedure WriteDocumentFragment(node: TDOMNode); forward;
  37. procedure WriteNotation(node: TDOMNode); forward;
  38. type
  39. TWriteNodeProc = procedure(node: TDOMNode);
  40. const
  41. WriteProcs: array[ELEMENT_NODE..NOTATION_NODE] of TWriteNodeProc =
  42. {$IFDEF FPC}
  43. (@WriteElement, @WriteAttribute, @WriteText, @WriteCDATA, @WriteEntityRef,
  44. @WriteEntity, @WritePI, @WriteComment, @WriteDocument, @WriteDocumentType,
  45. @WriteDocumentFragment, @WriteNotation);
  46. {$ELSE}
  47. (WriteElement, WriteAttribute, WriteText, WriteCDATA, WriteEntityRef,
  48. WriteEntity, WritePI, WriteComment, WriteDocument, WriteDocumentType,
  49. WriteDocumentFragment, WriteNotation);
  50. {$ENDIF}
  51. procedure WriteNode(node: TDOMNode);
  52. begin
  53. WriteProcs[node.NodeType](node);
  54. end;
  55. // -------------------------------------------------------------------
  56. // Text file and TStream support
  57. // -------------------------------------------------------------------
  58. type
  59. TOutputProc = procedure(const s: String);
  60. var
  61. f: ^Text;
  62. stream: TStream;
  63. wrt, wrtln: TOutputProc;
  64. InsideTextNode: Boolean;
  65. procedure Text_Write(const s: String);
  66. begin
  67. Write(f^, s);
  68. end;
  69. procedure Text_WriteLn(const s: String);
  70. begin
  71. WriteLn(f^, s);
  72. end;
  73. procedure Stream_Write(const s: String);
  74. begin
  75. if Length(s) > 0 then
  76. Stream.Write(s[1], Length(s));
  77. end;
  78. procedure Stream_WriteLn(const s: String);
  79. const
  80. LF: Char = #10;
  81. begin
  82. if Length(s) > 0 then
  83. Stream.Write(s[1], Length(s));
  84. Stream.Write(LF, 1);
  85. end;
  86. // -------------------------------------------------------------------
  87. // Indent handling
  88. // -------------------------------------------------------------------
  89. var
  90. Indent: String;
  91. procedure IncIndent;
  92. begin
  93. Indent := Indent + ' ';
  94. end;
  95. procedure DecIndent;
  96. begin
  97. if Length(Indent) >= 2 then
  98. SetLength(Indent, Length(Indent) - 2);
  99. end;
  100. // -------------------------------------------------------------------
  101. // String conversion
  102. // -------------------------------------------------------------------
  103. type
  104. TCharacters = set of Char;
  105. TSpecialCharCallback = procedure(c: Char);
  106. const
  107. AttrSpecialChars = ['<', '>', '"', '&'];
  108. TextSpecialChars = ['<', '>', '&'];
  109. procedure ConvWrite(const s: String; const SpecialChars: TCharacters;
  110. const SpecialCharCallback: TSpecialCharCallback);
  111. var
  112. StartPos, EndPos: Integer;
  113. begin
  114. StartPos := 1;
  115. EndPos := 1;
  116. while EndPos <= Length(s) do
  117. begin
  118. if s[EndPos] in SpecialChars then
  119. begin
  120. wrt(Copy(s, StartPos, EndPos - StartPos));
  121. SpecialCharCallback(s[EndPos]);
  122. StartPos := EndPos + 1;
  123. end;
  124. Inc(EndPos);
  125. end;
  126. if EndPos > StartPos then
  127. wrt(Copy(s, StartPos, EndPos - StartPos));
  128. end;
  129. procedure AttrSpecialCharCallback(c: Char);
  130. begin
  131. if c = '<' then
  132. wrt('&lt;')
  133. else if c = '>' then
  134. wrt('&gt;')
  135. else if c = '"' then
  136. wrt('&quot;')
  137. else if c = '&' then
  138. wrt('&amp;')
  139. else
  140. wrt(c);
  141. end;
  142. procedure TextnodeSpecialCharCallback(c: Char);
  143. begin
  144. if c = '<' then
  145. wrt('&lt;')
  146. else if c = '>' then
  147. wrt('&gt;')
  148. else if c = '&' then
  149. wrt('&amp;')
  150. else
  151. wrt(c);
  152. end;
  153. // -------------------------------------------------------------------
  154. // Node writers implementations
  155. // -------------------------------------------------------------------
  156. procedure WriteElement(node: TDOMNode);
  157. var
  158. i: Integer;
  159. attr, child: TDOMNode;
  160. SavedInsideTextNode: Boolean;
  161. s: String;
  162. begin
  163. if not InsideTextNode then
  164. wrt(Indent);
  165. wrt('<' + node.NodeName);
  166. for i := 0 to node.Attributes.Length - 1 do
  167. begin
  168. attr := node.Attributes.Item[i];
  169. wrt(' ' + attr.NodeName + '=');
  170. s := attr.NodeValue;
  171. wrt('"');
  172. ConvWrite(s, AttrSpecialChars, @AttrSpecialCharCallback);
  173. wrt('"');
  174. end;
  175. Child := node.FirstChild;
  176. if Child = nil then
  177. if InsideTextNode then
  178. wrt('/>')
  179. else
  180. wrtln('/>')
  181. else
  182. begin
  183. SavedInsideTextNode := InsideTextNode;
  184. if InsideTextNode or Child.InheritsFrom(TDOMText) then
  185. wrt('>')
  186. else
  187. wrtln('>');
  188. IncIndent;
  189. repeat
  190. if Child.InheritsFrom(TDOMText) then
  191. InsideTextNode := True;
  192. WriteNode(Child);
  193. Child := Child.NextSibling;
  194. until child = nil;
  195. DecIndent;
  196. if not InsideTextNode then
  197. wrt(Indent);
  198. InsideTextNode := SavedInsideTextNode;
  199. s := '</' + node.NodeName + '>';
  200. if InsideTextNode then
  201. wrt(s)
  202. else
  203. wrtln(s);
  204. end;
  205. end;
  206. procedure WriteAttribute(node: TDOMNode);
  207. begin
  208. WriteLn('WriteAttribute');
  209. end;
  210. procedure WriteText(node: TDOMNode);
  211. begin
  212. ConvWrite(node.NodeValue, TextSpecialChars, @TextnodeSpecialCharCallback);
  213. end;
  214. procedure WriteCDATA(node: TDOMNode);
  215. begin
  216. if InsideTextNode then
  217. wrt('<![CDATA[' + node.NodeValue + ']]>')
  218. else
  219. wrtln(Indent + '<![CDATA[' + node.NodeValue + ']]>')
  220. end;
  221. procedure WriteEntityRef(node: TDOMNode);
  222. begin
  223. wrt('&' + node.NodeName + ';');
  224. end;
  225. procedure WriteEntity(node: TDOMNode);
  226. begin
  227. WriteLn('WriteEntity');
  228. end;
  229. procedure WritePI(node: TDOMNode);
  230. var
  231. s: String;
  232. begin
  233. s := '<!' + TDOMProcessingInstruction(node).Target + ' ' +
  234. TDOMProcessingInstruction(node).Data + '>';
  235. if InsideTextNode then
  236. wrt(s)
  237. else
  238. wrtln(Indent + s);
  239. end;
  240. procedure WriteComment(node: TDOMNode);
  241. begin
  242. if InsideTextNode then
  243. wrt('<!--' + node.NodeValue + '-->')
  244. else
  245. wrtln(Indent + '<!--' + node.NodeValue + '-->')
  246. end;
  247. procedure WriteDocument(node: TDOMNode);
  248. begin
  249. WriteLn('WriteDocument');
  250. end;
  251. procedure WriteDocumentType(node: TDOMNode);
  252. begin
  253. WriteLn('WriteDocumentType');
  254. end;
  255. procedure WriteDocumentFragment(node: TDOMNode);
  256. begin
  257. WriteLn('WriteDocumentFragment');
  258. end;
  259. procedure WriteNotation(node: TDOMNode);
  260. begin
  261. WriteLn('WriteNotation');
  262. end;
  263. procedure InitWriter;
  264. begin
  265. InsideTextNode := False;
  266. SetLength(Indent, 0);
  267. end;
  268. procedure RootWriter(doc: TXMLDocument);
  269. var
  270. Child: TDOMNode;
  271. begin
  272. InitWriter;
  273. wrt('<?xml version="');
  274. if Length(doc.XMLVersion) > 0 then
  275. ConvWrite(doc.XMLVersion, AttrSpecialChars, @AttrSpecialCharCallback)
  276. else
  277. wrt('1.0');
  278. wrt('"');
  279. if Length(doc.Encoding) > 0 then
  280. begin
  281. wrt(' encoding="');
  282. ConvWrite(doc.Encoding, AttrSpecialChars, @AttrSpecialCharCallback);
  283. wrt('"');
  284. end;
  285. wrtln('?>');
  286. if Length(doc.StylesheetType) > 0 then
  287. begin
  288. wrt('<?xml-stylesheet type="');
  289. ConvWrite(doc.StylesheetType, AttrSpecialChars, @AttrSpecialCharCallback);
  290. wrt('" href="');
  291. ConvWrite(doc.StylesheetHRef, AttrSpecialChars, @AttrSpecialCharCallback);
  292. wrtln('"?>');
  293. end;
  294. SetLength(Indent, 0);
  295. child := doc.FirstChild;
  296. while Assigned(Child) do
  297. begin
  298. WriteNode(Child);
  299. Child := Child.NextSibling;
  300. end;
  301. end;
  302. // -------------------------------------------------------------------
  303. // Interface implementation
  304. // -------------------------------------------------------------------
  305. {$IFDEF FPC}
  306. {$IFNDEF VER1_0}
  307. {$DEFINE UsesFPCWidestrings}
  308. {$ENDIF}
  309. {$ENDIF}
  310. {$IFDEF UsesFPCWidestrings}
  311. procedure SimpleWide2AnsiMove(source:pwidechar;dest:pchar;len:sizeint);
  312. var
  313. i : sizeint;
  314. begin
  315. for i:=1 to len do
  316. begin
  317. if word(source^)<256 then
  318. dest^:=char(word(source^))
  319. else
  320. dest^:='?';
  321. inc(dest);
  322. inc(source);
  323. end;
  324. end;
  325. procedure SimpleAnsi2WideMove(source:pchar;dest:pwidechar;len:sizeint);
  326. var
  327. i : sizeint;
  328. begin
  329. for i:=1 to len do
  330. begin
  331. dest^:=widechar(byte(source^));
  332. inc(dest);
  333. inc(source);
  334. end;
  335. end;
  336. const
  337. WideStringManager: TWideStringManager = (
  338. Wide2AnsiMove: @SimpleWide2AnsiMove;
  339. Ansi2WideMove: @SimpleAnsi2WideMove
  340. );
  341. {$ENDIF}
  342. procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String);
  343. {$IFDEF UsesFPCWidestrings}
  344. var
  345. OldWideStringManager: TWideStringManager;
  346. {$ENDIF}
  347. begin
  348. {$IFDEF UsesFPCWidestrings}
  349. SetWideStringManager(WideStringManager, OldWideStringManager);
  350. try
  351. {$ENDIF}
  352. Stream := TFileStream.Create(AFileName, fmCreate);
  353. wrt := @Stream_Write;
  354. wrtln := @Stream_WriteLn;
  355. RootWriter(doc);
  356. Stream.Free;
  357. {$IFDEF UsesFPCWidestrings}
  358. finally
  359. SetWideStringManager(OldWideStringManager);
  360. end;
  361. {$ENDIF}
  362. end;
  363. procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text);
  364. {$IFDEF UsesFPCWidestrings}
  365. var
  366. OldWideStringManager: TWideStringManager;
  367. {$ENDIF}
  368. begin
  369. {$IFDEF UsesFPCWidestrings}
  370. SetWideStringManager(WideStringManager, OldWideStringManager);
  371. try
  372. {$ENDIF}
  373. f := @AFile;
  374. wrt := @Text_Write;
  375. wrtln := @Text_WriteLn;
  376. RootWriter(doc);
  377. {$IFDEF UsesFPCWidestrings}
  378. finally
  379. SetWideStringManager(OldWideStringManager);
  380. end;
  381. {$ENDIF}
  382. end;
  383. procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream);
  384. {$IFDEF UsesFPCWidestrings}
  385. var
  386. OldWideStringManager: TWideStringManager;
  387. {$ENDIF}
  388. begin
  389. {$IFDEF UsesFPCWidestrings}
  390. SetWideStringManager(WideStringManager, OldWideStringManager);
  391. try
  392. {$ENDIF}
  393. Stream := AStream;
  394. wrt := @Stream_Write;
  395. wrtln := @Stream_WriteLn;
  396. RootWriter(doc);
  397. {$IFDEF UsesFPCWidestrings}
  398. finally
  399. SetWideStringManager(OldWideStringManager);
  400. end;
  401. {$ENDIF}
  402. end;
  403. procedure WriteXML(Node: TDOMNode; const AFileName: String);
  404. {$IFDEF UsesFPCWidestrings}
  405. var
  406. OldWideStringManager: TWideStringManager;
  407. {$ENDIF}
  408. begin
  409. {$IFDEF UsesFPCWidestrings}
  410. SetWideStringManager(WideStringManager, OldWideStringManager);
  411. try
  412. {$ENDIF}
  413. Stream := TFileStream.Create(AFileName, fmCreate);
  414. wrt := @Stream_Write;
  415. wrtln := @Stream_WriteLn;
  416. InitWriter;
  417. WriteNode(Node);
  418. Stream.Free;
  419. {$IFDEF UsesFPCWidestrings}
  420. finally
  421. SetWideStringManager(OldWideStringManager);
  422. end;
  423. {$ENDIF}
  424. end;
  425. procedure WriteXML(Node: TDOMNode; var AFile: Text);
  426. {$IFDEF UsesFPCWidestrings}
  427. var
  428. OldWideStringManager: TWideStringManager;
  429. {$ENDIF}
  430. begin
  431. {$IFDEF UsesFPCWidestrings}
  432. SetWideStringManager(WideStringManager, OldWideStringManager);
  433. try
  434. {$ENDIF}
  435. f := @AFile;
  436. wrt := @Text_Write;
  437. wrtln := @Text_WriteLn;
  438. InitWriter;
  439. WriteNode(Node);
  440. {$IFDEF UsesFPCWidestrings}
  441. finally
  442. SetWideStringManager(OldWideStringManager);
  443. end;
  444. {$ENDIF}
  445. end;
  446. procedure WriteXML(Node: TDOMNode; AStream: TStream);
  447. {$IFDEF UsesFPCWidestrings}
  448. var
  449. OldWideStringManager: TWideStringManager;
  450. {$ENDIF}
  451. begin
  452. {$IFDEF UsesFPCWidestrings}
  453. SetWideStringManager(WideStringManager, OldWideStringManager);
  454. try
  455. {$ENDIF}
  456. stream := AStream;
  457. wrt := @Stream_Write;
  458. wrtln := @Stream_WriteLn;
  459. InitWriter;
  460. WriteNode(Node);
  461. {$IFDEF UsesFPCWidestrings}
  462. finally
  463. SetWideStringManager(OldWideStringManager);
  464. end;
  465. {$ENDIF}
  466. end;
  467. end.
  468. {
  469. Revision 1.14 2004/05/02 20:17:53 peter
  470. * use sizeint
  471. Revision 1.13 2004/01/20 12:27:19 sg
  472. * "<" and ">" are now written as "&lt;" and "&gt;"
  473. Revision 1.12 2003/12/01 23:59:12 sg
  474. * Added support for main branch to be able to read and write at least
  475. ISO8859-1 encoded files correctly. A much improved solution will be
  476. provided when the mainbranch RTL fully supports Unicode/WideStrings.
  477. Revision 1.11 2003/01/15 21:59:55 sg
  478. * the units DOM, XMLRead and XMLWrite now compile with Delphi without
  479. modifications as well
  480. Revision 1.10 2002/11/30 16:04:34 sg
  481. * Stream parameters are not "var" anymore (stupid copy&paste bug)
  482. Revision 1.9 2002/09/20 11:36:51 sg
  483. * Argument escaping improvements
  484. * Indent fixed for consecutive WriteXML calls
  485. Revision 1.8 2002/09/20 11:04:21 michael
  486. + Changed writexml type to TDomNode instead of TDomeElement
  487. Revision 1.7 2002/09/07 15:15:29 peter
  488. * old logs removed and tabs fixed
  489. }