xmlwrite.pp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  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('&quot;')
  134. else if c = '&' then
  135. wrt('&amp;')
  136. else
  137. wrt(c);
  138. end;
  139. procedure TextnodeSpecialCharCallback(c: Char);
  140. begin
  141. if c = '<' then
  142. wrt('&lt;')
  143. else if c = '>' then
  144. wrt('&gt;')
  145. else if c = '&' then
  146. wrt('&amp;')
  147. else
  148. wrt(c);
  149. end;
  150. // -------------------------------------------------------------------
  151. // Node writers implementations
  152. // -------------------------------------------------------------------
  153. procedure WriteElement(node: TDOMNode);
  154. var
  155. i: Integer;
  156. attr, child: TDOMNode;
  157. SavedInsideTextNode: Boolean;
  158. s: String;
  159. begin
  160. if not InsideTextNode then
  161. wrt(Indent);
  162. wrt('<' + node.NodeName);
  163. for i := 0 to node.Attributes.Length - 1 do
  164. begin
  165. attr := node.Attributes.Item[i];
  166. wrt(' ' + attr.NodeName + '=');
  167. s := attr.NodeValue;
  168. wrt('"');
  169. ConvWrite(s, AttrSpecialChars, @AttrSpecialCharCallback);
  170. wrt('"');
  171. end;
  172. Child := node.FirstChild;
  173. if Child = nil then
  174. if InsideTextNode then
  175. wrt('/>')
  176. else
  177. wrtln('/>')
  178. else
  179. begin
  180. SavedInsideTextNode := InsideTextNode;
  181. if InsideTextNode or Child.InheritsFrom(TDOMText) then
  182. wrt('>')
  183. else
  184. wrtln('>');
  185. IncIndent;
  186. repeat
  187. if Child.InheritsFrom(TDOMText) then
  188. InsideTextNode := True;
  189. WriteNode(Child);
  190. Child := Child.NextSibling;
  191. until child = nil;
  192. DecIndent;
  193. if not InsideTextNode then
  194. wrt(Indent);
  195. InsideTextNode := SavedInsideTextNode;
  196. s := '</' + node.NodeName + '>';
  197. if InsideTextNode then
  198. wrt(s)
  199. else
  200. wrtln(s);
  201. end;
  202. end;
  203. procedure WriteAttribute(node: TDOMNode);
  204. begin
  205. WriteLn('WriteAttribute');
  206. end;
  207. procedure WriteText(node: TDOMNode);
  208. begin
  209. ConvWrite(node.NodeValue, TextSpecialChars, @TextnodeSpecialCharCallback);
  210. end;
  211. procedure WriteCDATA(node: TDOMNode);
  212. begin
  213. if InsideTextNode then
  214. wrt('<![CDATA[' + node.NodeValue + ']]>')
  215. else
  216. wrtln(Indent + '<![CDATA[' + node.NodeValue + ']]>')
  217. end;
  218. procedure WriteEntityRef(node: TDOMNode);
  219. begin
  220. wrt('&' + node.NodeName + ';');
  221. end;
  222. procedure WriteEntity(node: TDOMNode);
  223. begin
  224. WriteLn('WriteEntity');
  225. end;
  226. procedure WritePI(node: TDOMNode);
  227. var
  228. s: String;
  229. begin
  230. s := '<!' + TDOMProcessingInstruction(node).Target + ' ' +
  231. TDOMProcessingInstruction(node).Data + '>';
  232. if InsideTextNode then
  233. wrt(s)
  234. else
  235. wrtln(Indent + s);
  236. end;
  237. procedure WriteComment(node: TDOMNode);
  238. begin
  239. if InsideTextNode then
  240. wrt('<!--' + node.NodeValue + '-->')
  241. else
  242. wrtln(Indent + '<!--' + node.NodeValue + '-->')
  243. end;
  244. procedure WriteDocument(node: TDOMNode);
  245. begin
  246. WriteLn('WriteDocument');
  247. end;
  248. procedure WriteDocumentType(node: TDOMNode);
  249. begin
  250. WriteLn('WriteDocumentType');
  251. end;
  252. procedure WriteDocumentFragment(node: TDOMNode);
  253. begin
  254. WriteLn('WriteDocumentFragment');
  255. end;
  256. procedure WriteNotation(node: TDOMNode);
  257. begin
  258. WriteLn('WriteNotation');
  259. end;
  260. procedure InitWriter;
  261. begin
  262. InsideTextNode := False;
  263. SetLength(Indent, 0);
  264. end;
  265. procedure RootWriter(doc: TXMLDocument);
  266. var
  267. Child: TDOMNode;
  268. begin
  269. InitWriter;
  270. wrt('<?xml version="');
  271. if Length(doc.XMLVersion) > 0 then
  272. ConvWrite(doc.XMLVersion, AttrSpecialChars, @AttrSpecialCharCallback)
  273. else
  274. wrt('1.0');
  275. wrt('"');
  276. if Length(doc.Encoding) > 0 then
  277. begin
  278. wrt(' encoding="');
  279. ConvWrite(doc.Encoding, AttrSpecialChars, @AttrSpecialCharCallback);
  280. wrt('"');
  281. end;
  282. wrtln('?>');
  283. if Length(doc.StylesheetType) > 0 then
  284. begin
  285. wrt('<?xml-stylesheet type="');
  286. ConvWrite(doc.StylesheetType, AttrSpecialChars, @AttrSpecialCharCallback);
  287. wrt('" href="');
  288. ConvWrite(doc.StylesheetHRef, AttrSpecialChars, @AttrSpecialCharCallback);
  289. wrtln('"?>');
  290. end;
  291. SetLength(Indent, 0);
  292. child := doc.FirstChild;
  293. while Assigned(Child) do
  294. begin
  295. WriteNode(Child);
  296. Child := Child.NextSibling;
  297. end;
  298. end;
  299. // -------------------------------------------------------------------
  300. // Interface implementation
  301. // -------------------------------------------------------------------
  302. {$IFDEF FPC}
  303. {$IFNDEF VER1_0}
  304. {$DEFINE UsesFPCWidestrings}
  305. {$ENDIF}
  306. {$ENDIF}
  307. {$IFDEF UsesFPCWidestrings}
  308. procedure SimpleWide2AnsiMove(source:pwidechar;dest:pchar;len:longint);
  309. var
  310. i : longint;
  311. begin
  312. for i:=1 to len do
  313. begin
  314. if word(source^)<256 then
  315. dest^:=char(word(source^))
  316. else
  317. dest^:='?';
  318. inc(dest);
  319. inc(source);
  320. end;
  321. end;
  322. procedure SimpleAnsi2WideMove(source:pchar;dest:pwidechar;len:longint);
  323. var
  324. i : longint;
  325. begin
  326. for i:=1 to len do
  327. begin
  328. dest^:=widechar(byte(source^));
  329. inc(dest);
  330. inc(source);
  331. end;
  332. end;
  333. const
  334. WideStringManager: TWideStringManager = (
  335. Wide2AnsiMove: @SimpleWide2AnsiMove;
  336. Ansi2WideMove: @SimpleAnsi2WideMove
  337. );
  338. {$ENDIF}
  339. procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String);
  340. {$IFDEF UsesFPCWidestrings}
  341. var
  342. OldWideStringManager: TWideStringManager;
  343. {$ENDIF}
  344. begin
  345. {$IFDEF UsesFPCWidestrings}
  346. SetWideStringManager(WideStringManager, OldWideStringManager);
  347. try
  348. {$ENDIF}
  349. Stream := TFileStream.Create(AFileName, fmCreate);
  350. wrt := @Stream_Write;
  351. wrtln := @Stream_WriteLn;
  352. RootWriter(doc);
  353. Stream.Free;
  354. {$IFDEF UsesFPCWidestrings}
  355. finally
  356. SetWideStringManager(OldWideStringManager);
  357. end;
  358. {$ENDIF}
  359. end;
  360. procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text);
  361. {$IFDEF UsesFPCWidestrings}
  362. var
  363. OldWideStringManager: TWideStringManager;
  364. {$ENDIF}
  365. begin
  366. {$IFDEF UsesFPCWidestrings}
  367. SetWideStringManager(WideStringManager, OldWideStringManager);
  368. try
  369. {$ENDIF}
  370. f := @AFile;
  371. wrt := @Text_Write;
  372. wrtln := @Text_WriteLn;
  373. RootWriter(doc);
  374. {$IFDEF UsesFPCWidestrings}
  375. finally
  376. SetWideStringManager(OldWideStringManager);
  377. end;
  378. {$ENDIF}
  379. end;
  380. procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream);
  381. {$IFDEF UsesFPCWidestrings}
  382. var
  383. OldWideStringManager: TWideStringManager;
  384. {$ENDIF}
  385. begin
  386. {$IFDEF UsesFPCWidestrings}
  387. SetWideStringManager(WideStringManager, OldWideStringManager);
  388. try
  389. {$ENDIF}
  390. Stream := AStream;
  391. wrt := @Stream_Write;
  392. wrtln := @Stream_WriteLn;
  393. RootWriter(doc);
  394. {$IFDEF UsesFPCWidestrings}
  395. finally
  396. SetWideStringManager(OldWideStringManager);
  397. end;
  398. {$ENDIF}
  399. end;
  400. procedure WriteXML(Node: TDOMNode; const AFileName: String);
  401. {$IFDEF UsesFPCWidestrings}
  402. var
  403. OldWideStringManager: TWideStringManager;
  404. {$ENDIF}
  405. begin
  406. {$IFDEF UsesFPCWidestrings}
  407. SetWideStringManager(WideStringManager, OldWideStringManager);
  408. try
  409. {$ENDIF}
  410. Stream := TFileStream.Create(AFileName, fmCreate);
  411. wrt := @Stream_Write;
  412. wrtln := @Stream_WriteLn;
  413. InitWriter;
  414. WriteNode(Node);
  415. Stream.Free;
  416. {$IFDEF UsesFPCWidestrings}
  417. finally
  418. SetWideStringManager(OldWideStringManager);
  419. end;
  420. {$ENDIF}
  421. end;
  422. procedure WriteXML(Node: TDOMNode; var AFile: Text);
  423. {$IFDEF UsesFPCWidestrings}
  424. var
  425. OldWideStringManager: TWideStringManager;
  426. {$ENDIF}
  427. begin
  428. {$IFDEF UsesFPCWidestrings}
  429. SetWideStringManager(WideStringManager, OldWideStringManager);
  430. try
  431. {$ENDIF}
  432. f := @AFile;
  433. wrt := @Text_Write;
  434. wrtln := @Text_WriteLn;
  435. InitWriter;
  436. WriteNode(Node);
  437. {$IFDEF UsesFPCWidestrings}
  438. finally
  439. SetWideStringManager(OldWideStringManager);
  440. end;
  441. {$ENDIF}
  442. end;
  443. procedure WriteXML(Node: TDOMNode; AStream: TStream);
  444. {$IFDEF UsesFPCWidestrings}
  445. var
  446. OldWideStringManager: TWideStringManager;
  447. {$ENDIF}
  448. begin
  449. {$IFDEF UsesFPCWidestrings}
  450. SetWideStringManager(WideStringManager, OldWideStringManager);
  451. try
  452. {$ENDIF}
  453. stream := AStream;
  454. wrt := @Stream_Write;
  455. wrtln := @Stream_WriteLn;
  456. InitWriter;
  457. WriteNode(Node);
  458. {$IFDEF UsesFPCWidestrings}
  459. finally
  460. SetWideStringManager(OldWideStringManager);
  461. end;
  462. {$ENDIF}
  463. end;
  464. end.
  465. {
  466. $Log$
  467. Revision 1.12 2003-12-01 23:59:12 sg
  468. * Added support for main branch to be able to read and write at least
  469. ISO8859-1 encoded files correctly. A much improved solution will be
  470. provided when the mainbranch RTL fully supports Unicode/WideStrings.
  471. Revision 1.11 2003/01/15 21:59:55 sg
  472. * the units DOM, XMLRead and XMLWrite now compile with Delphi without
  473. modifications as well
  474. Revision 1.10 2002/11/30 16:04:34 sg
  475. * Stream parameters are not "var" anymore (stupid copy&paste bug)
  476. Revision 1.9 2002/09/20 11:36:51 sg
  477. * Argument escaping improvements
  478. * Indent fixed for consecutive WriteXML calls
  479. Revision 1.8 2002/09/20 11:04:21 michael
  480. + Changed writexml type to TDomNode instead of TDomeElement
  481. Revision 1.7 2002/09/07 15:15:29 peter
  482. * old logs removed and tabs fixed
  483. }