xmlwrite.pp 13 KB

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