sax_xml.pp 16 KB

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