sax_xml.pp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. {
  2. This file is part of the Free Component Library
  3. Copyright (c) 2006 by Michael Van Canneyt.
  4. Based on SAX_HTML implementation from Sebastian Guenther.
  5. XML parser with SAX interface
  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. {$mode objfpc}
  13. {$h+}
  14. unit SAX_XML;
  15. interface
  16. uses SysUtils, Classes, SAX, DOM;
  17. type
  18. { TXMLReader: The XML reader class }
  19. TXMLScannerContext = (
  20. scUnknown,
  21. scWhitespace, // within whitespace
  22. scText, // within text
  23. scEntityReference, // within entity reference ("&...;")
  24. scTag); // within a start tag or end tag
  25. TSAXXMLReader = class(TSAXReader)
  26. private
  27. FStarted: Boolean;
  28. FEndOfStream: Boolean;
  29. FScannerContext: TXMLScannerContext;
  30. FTokenText: SAXString;
  31. FCurStringValueDelimiter: Char;
  32. FAttrNameRead: Boolean;
  33. protected
  34. procedure EnterNewScannerContext(NewContext: TXMLScannerContext);
  35. public
  36. constructor Create;
  37. destructor Destroy; override;
  38. procedure Parse(AInput: TSAXInputSource); override; overload;
  39. property EndOfStream: Boolean read FEndOfStream;
  40. property ScannerContext: TXMLScannerContext read FScannerContext;
  41. property TokenText: SAXString read FTokenText;
  42. end;
  43. { TXMLToDOMConverter }
  44. TXMLNodeType = (ntWhitespace, ntText, ntEntityReference, ntTag);
  45. TXMLNodeInfo = class
  46. NodeType: TXMLNodeType;
  47. DOMNode: TDOMNode;
  48. end;
  49. TXMLToDOMConverter = class
  50. private
  51. FReader: TSAXXMLReader;
  52. FDocument: TDOMDocument;
  53. FElementStack: TList;
  54. FNodeBuffer: TList;
  55. IsFragmentMode, FragmentRootSet: Boolean;
  56. FragmentRoot: TDOMNode;
  57. procedure ReaderCharacters(Sender: TObject; const ch: PSAXChar;
  58. Start, Count: Integer);
  59. procedure ReaderIgnorableWhitespace(Sender: TObject; const ch: PSAXChar;
  60. Start, Count: Integer);
  61. procedure ReaderSkippedEntity(Sender: TObject; const Name: SAXString);
  62. procedure ReaderStartElement(Sender: TObject;
  63. const NamespaceURI, LocalName, RawName: SAXString; Attr: TSAXAttributes);
  64. procedure ReaderEndElement(Sender: TObject;
  65. const NamespaceURI, LocalName, RawName: SAXString);
  66. public
  67. constructor Create(AReader: TSAXXMLReader; ADocument: TDOMDocument);
  68. constructor CreateFragment(AReader: TSAXXMLReader; AFragmentRoot: TDOMNode);
  69. destructor Destroy; override;
  70. end;
  71. // Helper functions; these ones are XML equivalents of ReadXML[File|Fragment]
  72. procedure ReadXMLFile(var ADoc: TXMLDocument; const AFilename: String);
  73. procedure ReadXMLFile(var ADoc: TXMLDocument; var f: TStream);
  74. procedure ReadXMLFragment(AParentNode: TDOMNode; const AFilename: String);
  75. procedure ReadXMLFragment(AParentNode: TDOMNode; var f: TStream);
  76. implementation
  77. uses htmldefs; // for entities...
  78. const
  79. WhitespaceChars = [#9, #10, #13, ' '];
  80. char_lt: SAXChar = '<';
  81. char_gt: SAXChar = '>';
  82. char_quot: SAXChar = '"';
  83. char_apos: SAXChar = '''';
  84. char_amp: SAXChar = '&';
  85. constructor TSAXXMLReader.Create;
  86. begin
  87. inherited Create;
  88. FScannerContext := scUnknown;
  89. end;
  90. destructor TSAXXMLReader.Destroy;
  91. begin
  92. if FStarted then
  93. DoEndDocument;
  94. inherited Destroy;
  95. end;
  96. procedure TSAXXMLReader.Parse(AInput: TSAXInputSource);
  97. const
  98. MaxBufferSize = 1024;
  99. var
  100. Buffer: array[0..MaxBufferSize - 1] of Char;
  101. BufferSize, BufferPos: Integer;
  102. begin
  103. if not FStarted then
  104. begin
  105. FStarted := True;
  106. DoStartDocument;
  107. end;
  108. FEndOfStream := False;
  109. while True do
  110. begin
  111. // Read data into the input buffer
  112. BufferSize := AInput.Stream.Read(Buffer, MaxBufferSize);
  113. if BufferSize = 0 then
  114. begin
  115. FEndOfStream := True;
  116. break;
  117. end;
  118. BufferPos := 0;
  119. while BufferPos < BufferSize do
  120. case ScannerContext of
  121. scUnknown:
  122. case Buffer[BufferPos] of
  123. #9, #10, #13, ' ':
  124. EnterNewScannerContext(scWhitespace);
  125. '&':
  126. begin
  127. Inc(BufferPos);
  128. EnterNewScannerContext(scEntityReference);
  129. end;
  130. '<':
  131. begin
  132. Inc(BufferPos);
  133. EnterNewScannerContext(scTag);
  134. end;
  135. else
  136. EnterNewScannerContext(scText);
  137. end;
  138. scWhitespace:
  139. case Buffer[BufferPos] of
  140. #9, #10, #13, ' ':
  141. begin
  142. FTokenText := FTokenText + Buffer[BufferPos];
  143. Inc(BufferPos);
  144. end;
  145. '&':
  146. begin
  147. Inc(BufferPos);
  148. EnterNewScannerContext(scEntityReference);
  149. end;
  150. '<':
  151. begin
  152. Inc(BufferPos);
  153. EnterNewScannerContext(scTag);
  154. end;
  155. else
  156. FScannerContext := scText
  157. end;
  158. scText:
  159. case Buffer[BufferPos] of
  160. '&':
  161. begin
  162. Inc(BufferPos);
  163. EnterNewScannerContext(scEntityReference);
  164. end;
  165. '<':
  166. begin
  167. Inc(BufferPos);
  168. EnterNewScannerContext(scTag);
  169. end;
  170. else
  171. begin
  172. FTokenText := FTokenText + Buffer[BufferPos];
  173. Inc(BufferPos);
  174. end;
  175. end;
  176. scEntityReference:
  177. if Buffer[BufferPos] = ';' then
  178. begin
  179. Inc(BufferPos);
  180. EnterNewScannerContext(scUnknown);
  181. end else if not (Buffer[BufferPos] in
  182. ['a'..'z', 'A'..'Z', '0'..'9', '#']) then
  183. EnterNewScannerContext(scUnknown)
  184. else
  185. begin
  186. FTokenText := FTokenText + Buffer[BufferPos];
  187. Inc(BufferPos);
  188. end;
  189. scTag:
  190. case Buffer[BufferPos] of
  191. '''', '"':
  192. begin
  193. if FAttrNameRead then
  194. begin
  195. if FCurStringValueDelimiter = #0 then
  196. FCurStringValueDelimiter := Buffer[BufferPos]
  197. else if FCurStringValueDelimiter = Buffer[BufferPos] then
  198. begin
  199. FCurStringValueDelimiter := #0;
  200. FAttrNameRead := False;
  201. end;
  202. end;
  203. FTokenText := FTokenText + Buffer[BufferPos];
  204. Inc(BufferPos);
  205. end;
  206. '=':
  207. begin
  208. FAttrNameRead := True;
  209. FTokenText := FTokenText + Buffer[BufferPos];
  210. Inc(BufferPos);
  211. end;
  212. '>':
  213. begin
  214. Inc(BufferPos);
  215. if FCurStringValueDelimiter = #0 then
  216. EnterNewScannerContext(scUnknown);
  217. end;
  218. else
  219. begin
  220. FTokenText := FTokenText + Buffer[BufferPos];
  221. Inc(BufferPos);
  222. end;
  223. end;
  224. end;
  225. end;
  226. end;
  227. procedure TSAXXMLReader.EnterNewScannerContext(NewContext: TXMLScannerContext);
  228. function SplitTagString(const s: String; var Attr: TSAXAttributes): String;
  229. var
  230. i, j: Integer;
  231. AttrName: String;
  232. ValueDelimiter: Char;
  233. DoIncJ: Boolean;
  234. begin
  235. Attr := nil;
  236. i := Pos(' ', s);
  237. if i <= 0 then
  238. Result := LowerCase(s)
  239. else
  240. begin
  241. Result := LowerCase(Copy(s, 1, i - 1));
  242. Attr := TSAXAttributes.Create;
  243. Inc(i);
  244. while (i <= Length(s)) and (s[i] in WhitespaceChars) do
  245. Inc(i);
  246. SetLength(AttrName, 0);
  247. j := i;
  248. while j <= Length(s) do
  249. if s[j] = '=' then
  250. begin
  251. AttrName := LowerCase(Copy(s, i, j - i));
  252. Inc(j);
  253. if (j < Length(s)) and ((s[j] = '''') or (s[j] = '"')) then
  254. begin
  255. ValueDelimiter := s[j];
  256. Inc(j);
  257. end else
  258. ValueDelimiter := #0;
  259. i := j;
  260. DoIncJ := False;
  261. while j <= Length(s) do
  262. if ValueDelimiter = #0 then
  263. if s[j] in WhitespaceChars then
  264. break
  265. else
  266. Inc(j)
  267. else if s[j] = ValueDelimiter then
  268. begin
  269. DoIncJ := True;
  270. break
  271. end else
  272. Inc(j);
  273. Attr.AddAttribute('', AttrName, '', '', Copy(s, i, j - i));
  274. if DoIncJ then
  275. Inc(j);
  276. while (j <= Length(s)) and (s[j] in WhitespaceChars) do
  277. Inc(j);
  278. i := j;
  279. end
  280. else if s[j] in WhitespaceChars then
  281. begin
  282. Attr.AddAttribute('', Copy(s, i, j - i), '', '', '');
  283. Inc(j);
  284. while (j <= Length(s)) and (s[j] in WhitespaceChars) do
  285. Inc(j);
  286. i := j;
  287. end else
  288. Inc(j);
  289. end;
  290. end;
  291. var
  292. Attr: TSAXAttributes;
  293. TagName: String;
  294. Found: Boolean;
  295. Ent: SAXChar;
  296. i: Integer;
  297. begin
  298. case ScannerContext of
  299. scWhitespace:
  300. DoIgnorableWhitespace(PSAXChar(TokenText), 1, Length(TokenText));
  301. scText:
  302. DoCharacters(PSAXChar(TokenText), 0, Length(TokenText));
  303. scEntityReference:
  304. begin
  305. if (Length(TokenText) >= 2) and (TokenText[1] = '#') and
  306. (((TokenText[2] >= '0') and (TokenText[2] <= '9')) or (TokenText[2]='x')) and
  307. // here actually using it to resolve character references
  308. ResolveHTMLEntityReference(TokenText, Ent) then
  309. DoCharacters(@Ent, 0, 1)
  310. else if TokenText = 'lt' then
  311. DoCharacters(@char_lt, 0, 1)
  312. else if TokenText = 'gt' then
  313. DoCharacters(@char_gt, 0, 1)
  314. else if TokenText = 'amp' then
  315. DoCharacters(@char_amp, 0, 1)
  316. else if TokenText = 'quot' then
  317. DoCharacters(@char_quot, 0, 1)
  318. else if TokenText = 'apos' then
  319. DoCharacters(@char_apos, 0, 1)
  320. else
  321. DoSkippedEntity(TokenText);
  322. end;
  323. scTag:
  324. if Length(TokenText) > 0 then
  325. begin
  326. Attr := nil;
  327. if TokenText[Length(fTokenText)]='/' then // handle empty tag
  328. begin
  329. setlength(fTokenText,length(fTokenText)-1);
  330. // Do NOT combine to a single line, as Attr is an output value!
  331. TagName := SplitTagString(TokenText, Attr);
  332. DoStartElement('', TagName, '', Attr);
  333. DoEndElement('', TagName, '');
  334. end
  335. else if TokenText[1] = '/' then
  336. begin
  337. DoEndElement('',
  338. SplitTagString(Copy(TokenText, 2, Length(TokenText)), Attr), '');
  339. end
  340. else if TokenText[1] <> '!' then
  341. begin
  342. // Do NOT combine to a single line, as Attr is an output value!
  343. TagName := SplitTagString(TokenText, Attr);
  344. DoStartElement('', TagName, '', Attr);
  345. end;
  346. if Assigned(Attr) then
  347. Attr.Free;
  348. end;
  349. end;
  350. FScannerContext := NewContext;
  351. SetLength(FTokenText, 0);
  352. FCurStringValueDelimiter := #0;
  353. FAttrNameRead := False;
  354. end;
  355. { TXMLToDOMConverter }
  356. constructor TXMLToDOMConverter.Create(AReader: TSAXXMLReader;
  357. ADocument: TDOMDocument);
  358. begin
  359. inherited Create;
  360. FReader := AReader;
  361. FReader.OnCharacters := @ReaderCharacters;
  362. FReader.OnIgnorableWhitespace := @ReaderIgnorableWhitespace;
  363. FReader.OnSkippedEntity := @ReaderSkippedEntity;
  364. FReader.OnStartElement := @ReaderStartElement;
  365. FReader.OnEndElement := @ReaderEndElement;
  366. FDocument := ADocument;
  367. FElementStack := TList.Create;
  368. FNodeBuffer := TList.Create;
  369. end;
  370. constructor TXMLToDOMConverter.CreateFragment(AReader: TSAXXMLReader;
  371. AFragmentRoot: TDOMNode);
  372. begin
  373. inherited Create;
  374. FReader := AReader;
  375. FReader.OnCharacters := @ReaderCharacters;
  376. FReader.OnIgnorableWhitespace := @ReaderIgnorableWhitespace;
  377. FReader.OnSkippedEntity := @ReaderSkippedEntity;
  378. FReader.OnStartElement := @ReaderStartElement;
  379. FReader.OnEndElement := @ReaderEndElement;
  380. FDocument := AFragmentRoot.OwnerDocument;
  381. FElementStack := TList.Create;
  382. FNodeBuffer := TList.Create;
  383. FragmentRoot := AFragmentRoot;
  384. IsFragmentMode := True;
  385. end;
  386. destructor TXMLToDOMConverter.Destroy;
  387. var
  388. i: Integer;
  389. begin
  390. // Theoretically, always exactly one item will remain - the root element:
  391. for i := 0 to FNodeBuffer.Count - 1 do
  392. TXMLNodeInfo(FNodeBuffer[i]).Free;
  393. FNodeBuffer.Free;
  394. FElementStack.Free;
  395. inherited Destroy;
  396. end;
  397. procedure TXMLToDOMConverter.ReaderCharacters(Sender: TObject;
  398. const ch: PSAXChar; Start, Count: Integer);
  399. var
  400. s: SAXString;
  401. NodeInfo: TXMLNodeInfo;
  402. begin
  403. SetLength(s, Count);
  404. Move(ch^, s[1], Count * SizeOf(SAXChar));
  405. NodeInfo := TXMLNodeInfo.Create;
  406. NodeInfo.NodeType := ntText;
  407. NodeInfo.DOMNode := FDocument.CreateTextNode(s);
  408. FNodeBuffer.Add(NodeInfo);
  409. end;
  410. procedure TXMLToDOMConverter.ReaderIgnorableWhitespace(Sender: TObject;
  411. const ch: PSAXChar; Start, Count: Integer);
  412. var
  413. s: SAXString;
  414. NodeInfo: TXMLNodeInfo;
  415. begin
  416. SetLength(s, Count);
  417. Move(ch^, s[1], Count * SizeOf(SAXChar));
  418. NodeInfo := TXMLNodeInfo.Create;
  419. NodeInfo.NodeType := ntWhitespace;
  420. NodeInfo.DOMNode := FDocument.CreateTextNode(s);
  421. FNodeBuffer.Add(NodeInfo);
  422. end;
  423. procedure TXMLToDOMConverter.ReaderSkippedEntity(Sender: TObject;
  424. const Name: SAXString);
  425. var
  426. NodeInfo: TXMLNodeInfo;
  427. begin
  428. NodeInfo := TXMLNodeInfo.Create;
  429. NodeInfo.NodeType := ntEntityReference;
  430. NodeInfo.DOMNode := FDocument.CreateEntityReference(Name);
  431. FNodeBuffer.Add(NodeInfo);
  432. end;
  433. procedure TXMLToDOMConverter.ReaderStartElement(Sender: TObject;
  434. const NamespaceURI, LocalName, RawName: SAXString; Attr: TSAXAttributes);
  435. var
  436. NodeInfo: TXMLNodeInfo;
  437. Element: TDOMElement;
  438. i: Integer;
  439. begin
  440. // WriteLn('Start: ', LocalName, '. Node buffer before: ', FNodeBuffer.Count, ' elements');
  441. Element := FDocument.CreateElement(LocalName);
  442. if Assigned(Attr) then
  443. begin
  444. // WriteLn('Attribute: ', Attr.GetLength);
  445. for i := 0 to Attr.GetLength - 1 do
  446. begin
  447. // WriteLn('#', i, ': LocalName = ', Attr.GetLocalName(i), ', Value = ', Attr.GetValue(i));
  448. Element[Attr.GetLocalName(i)] := Attr.GetValue(i);
  449. end;
  450. end;
  451. NodeInfo := TXMLNodeInfo.Create;
  452. NodeInfo.NodeType := ntTag;
  453. NodeInfo.DOMNode := Element;
  454. if IsFragmentMode then
  455. begin
  456. if not FragmentRootSet then
  457. begin
  458. FragmentRoot.AppendChild(Element);
  459. FragmentRootSet := True;
  460. end;
  461. end else
  462. if not Assigned(FDocument.DocumentElement) then
  463. FDocument.AppendChild(Element);
  464. FNodeBuffer.Add(NodeInfo);
  465. // WriteLn('Start: ', LocalName, '. Node buffer after: ', FNodeBuffer.Count, ' elements');
  466. end;
  467. procedure TXMLToDOMConverter.ReaderEndElement(Sender: TObject;
  468. const NamespaceURI, LocalName, RawName: SAXString);
  469. var
  470. NodeInfo, NodeInfo2: TXMLNodeInfo;
  471. i : Integer;
  472. begin
  473. // WriteLn('End: ', LocalName, '. Node buffer: ', FNodeBuffer.Count, ' elements');
  474. // Find the matching start tag
  475. i := FNodeBuffer.Count - 1;
  476. while i >= 0 do
  477. begin
  478. NodeInfo := TXMLNodeInfo(FNodeBuffer.Items[i]);
  479. if (NodeInfo.NodeType = ntTag) and
  480. (CompareText(NodeInfo.DOMNode.NodeName, LocalName) = 0) then
  481. begin
  482. // We found the matching start tag
  483. Inc(i);
  484. while i < FNodeBuffer.Count do
  485. begin
  486. NodeInfo2 := TXMLNodeInfo(FNodeBuffer.Items[i]);
  487. NodeInfo.DOMNode.AppendChild(NodeInfo2.DOMNode);
  488. NodeInfo2.Free;
  489. FNodeBuffer.Delete(i);
  490. end;
  491. break;
  492. end;
  493. Dec(i);
  494. end;
  495. end;
  496. procedure ReadXMLFile(var ADoc: TXMLDocument; const AFilename: String);
  497. var
  498. f: TStream;
  499. begin
  500. ADoc := nil;
  501. f := TFileStream.Create(AFilename, fmOpenRead);
  502. try
  503. ReadXMLFile(ADoc, f);
  504. finally
  505. f.Free;
  506. end;
  507. end;
  508. procedure ReadXMLFile(var ADoc: TXMLDocument; var f: TStream);
  509. var
  510. Reader: TSAXXMLReader;
  511. Converter: TXMLToDOMConverter;
  512. begin
  513. ADoc := TXMLDocument.Create;
  514. Reader := TSAXXMLReader.Create;
  515. try
  516. Converter := TXMLToDOMConverter.Create(Reader, ADoc);
  517. try
  518. Reader.ParseStream(f);
  519. finally
  520. Converter.Free;
  521. end;
  522. finally
  523. Reader.Free;
  524. end;
  525. end;
  526. procedure ReadXMLFragment(AParentNode: TDOMNode; const AFilename: String);
  527. var
  528. f: TStream;
  529. begin
  530. f := TFileStream.Create(AFilename, fmOpenRead);
  531. try
  532. ReadXMLFragment(AParentNode, f);
  533. finally
  534. f.Free;
  535. end;
  536. end;
  537. procedure ReadXMLFragment(AParentNode: TDOMNode; var f: TStream);
  538. var
  539. Reader: TSAXXMLReader;
  540. Converter: TXMLToDOMConverter;
  541. begin
  542. Reader := TSAXXMLReader.Create;
  543. try
  544. Converter := TXMLToDOMConverter.CreateFragment(Reader, AParentNode);
  545. try
  546. Reader.ParseStream(f);
  547. finally
  548. Converter.Free;
  549. end;
  550. finally
  551. Reader.Free;
  552. end;
  553. end;
  554. end.