sax_xml.pp 16 KB

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