PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/Components/FastReport3/FastScript/fs_xml.pas

http://github.com/mitshel/tech-inv
Pascal | 698 lines | 578 code | 101 blank | 19 comment | 63 complexity | 055207d3ab0c60715a20b908e1dbfb98 MD5 | raw file
Possible License(s): AGPL-3.0
  1. {******************************************}
  2. { }
  3. { FastScript v1.7 }
  4. { XML document }
  5. { }
  6. { (c) 2003, 2004 by Alexander Tzyganenko, }
  7. { Fast Reports, Inc }
  8. { }
  9. {******************************************}
  10. unit fs_xml;
  11. interface
  12. {$i fs.inc}
  13. uses
  14. Windows, SysUtils, Classes;
  15. type
  16. TfsXMLItem = class(TObject)
  17. private
  18. FData: Pointer; { optional item data }
  19. FItems: TList; { subitems }
  20. FName: String; { item name }
  21. FParent: TfsXMLItem; { item parent }
  22. FText: String; { item attributes }
  23. function GetCount: Integer;
  24. function GetItems(Index: Integer): TfsXMLItem;
  25. function GetProp(Index: String): String;
  26. procedure SetProp(Index: String; const Value: String);
  27. procedure SetParent(const Value: TfsXMLItem);
  28. public
  29. destructor Destroy; override;
  30. procedure AddItem(Item: TfsXMLItem);
  31. procedure Assign(Item: TfsXMLItem);
  32. procedure Clear;
  33. procedure InsertItem(Index: Integer; Item: TfsXMLItem);
  34. function Add: TfsXMLItem;
  35. function Find(const Name: String): Integer;
  36. function FindItem(const Name: String): TfsXMLItem;
  37. function IndexOf(Item: TfsXMLItem): Integer;
  38. function PropExists(const Index: String): Boolean;
  39. function Root: TfsXMLItem;
  40. property Count: Integer read GetCount;
  41. property Data: Pointer read FData write FData;
  42. property Items[Index: Integer]: TfsXMLItem read GetItems; default;
  43. property Name: String read FName write FName;
  44. property Parent: TfsXMLItem read FParent write SetParent;
  45. property Prop[Index: String]: String read GetProp write SetProp;
  46. property Text: String read FText write FText;
  47. end;
  48. TfsXMLDocument = class(TObject)
  49. private
  50. FAutoIndent: Boolean; { use indents when writing document to a file }
  51. FRoot: TfsXMLItem; { root item }
  52. public
  53. constructor Create;
  54. destructor Destroy; override;
  55. procedure Clear;
  56. procedure SaveToStream(Stream: TStream);
  57. procedure LoadFromStream(Stream: TStream);
  58. procedure SaveToFile(const FileName: String);
  59. procedure LoadFromFile(const FileName: String);
  60. property AutoIndent: Boolean read FAutoIndent write FAutoIndent;
  61. property Root: TfsXMLItem read FRoot;
  62. end;
  63. { TfsXMLReader and TfsXMLWriter are doing actual read/write to the XML file.
  64. Read/write process is buffered. }
  65. TfsXMLReader = class(TObject)
  66. private
  67. FBuffer: PChar;
  68. FBufPos: Integer;
  69. FBufEnd: Integer;
  70. FPosition: Int64;
  71. FSize: Int64;
  72. FStream: TStream;
  73. procedure SetPosition(const Value: Int64);
  74. procedure ReadBuffer;
  75. procedure ReadItem(var Name, Text: String);
  76. public
  77. constructor Create(Stream: TStream);
  78. destructor Destroy; override;
  79. procedure RaiseException;
  80. procedure ReadHeader;
  81. procedure ReadRootItem(Item: TfsXMLItem);
  82. property Position: Int64 read FPosition write SetPosition;
  83. property Size: Int64 read FSize;
  84. end;
  85. TfsXMLWriter = class(TObject)
  86. private
  87. FAutoIndent: Boolean;
  88. FBuffer: String;
  89. FStream: TStream;
  90. FTempStream: TStream;
  91. procedure FlushBuffer;
  92. procedure WriteLn(const s: String);
  93. procedure WriteItem(Item: TfsXMLItem; Level: Integer = 0);
  94. public
  95. constructor Create(Stream: TStream);
  96. procedure WriteHeader;
  97. procedure WriteRootItem(RootItem: TfsXMLItem);
  98. property TempStream: TStream read FTempStream write FTempStream;
  99. end;
  100. { StrToXML changes '<', '>', '"', cr, lf symbols to its ascii codes }
  101. function StrToXML(const s: String): String;
  102. { ValueToXML convert a value to the valid XML string }
  103. function ValueToXML(const Value: Variant): String;
  104. { XMLToStr is opposite to StrToXML function }
  105. function XMLToStr(const s: String): String;
  106. implementation
  107. function StrToXML(const s: String): String;
  108. const
  109. SpecChars = ['<', '>', '"', #10, #13];
  110. var
  111. i: Integer;
  112. procedure ReplaceChars(var s: String; i: Integer);
  113. begin
  114. Insert('#' + IntToStr(Ord(s[i])) + ';', s, i + 1);
  115. s[i] := '&';
  116. end;
  117. begin
  118. Result := s;
  119. for i := Length(s) downto 1 do
  120. if s[i] in SpecChars then
  121. ReplaceChars(Result, i);
  122. end;
  123. function XMLToStr(const s: String): String;
  124. var
  125. i, j, h, n: Integer;
  126. begin
  127. Result := s;
  128. i := 1;
  129. n := Length(s);
  130. while i < n do
  131. begin
  132. if i + 3 <= n then
  133. if (Result[i] = '&') and (Result[i + 1] = '#') then
  134. begin
  135. j := i + 3;
  136. while Result[j] <> ';' do
  137. Inc(j);
  138. h := StrToInt(Copy(Result, i + 2, j - i - 2));
  139. Delete(Result, i, j - i);
  140. Result[i] := Chr(h);
  141. Dec(n, j - i);
  142. end;
  143. Inc(i);
  144. end;
  145. end;
  146. function ValueToXML(const Value: Variant): String;
  147. begin
  148. case TVarData(Value).VType of
  149. varSmallint, varInteger, varByte:
  150. Result := IntToStr(Value);
  151. varSingle, varDouble, varCurrency:
  152. Result := FloatToStr(Value);
  153. varDate:
  154. Result := DateToStr(Value);
  155. varOleStr, varString, varVariant:
  156. Result := StrToXML(Value);
  157. varBoolean:
  158. if Value = True then Result := '1' else Result := '0';
  159. else
  160. Result := '';
  161. end;
  162. end;
  163. { TfsXMLItem }
  164. destructor TfsXMLItem.Destroy;
  165. begin
  166. Clear;
  167. if FParent <> nil then
  168. FParent.FItems.Remove(Self);
  169. inherited;
  170. end;
  171. procedure TfsXMLItem.Clear;
  172. begin
  173. if FItems <> nil then
  174. begin
  175. while FItems.Count > 0 do
  176. TfsXMLItem(FItems[0]).Free;
  177. FItems.Free;
  178. FItems := nil;
  179. end;
  180. end;
  181. function TfsXMLItem.GetItems(Index: Integer): TfsXMLItem;
  182. begin
  183. Result := TfsXMLItem(FItems[Index]);
  184. end;
  185. function TfsXMLItem.GetCount: Integer;
  186. begin
  187. if FItems = nil then
  188. Result := 0 else
  189. Result := FItems.Count;
  190. end;
  191. function TfsXMLItem.Add: TfsXMLItem;
  192. begin
  193. Result := TfsXMLItem.Create;
  194. AddItem(Result);
  195. end;
  196. procedure TfsXMLItem.AddItem(Item: TfsXMLItem);
  197. begin
  198. if FItems = nil then
  199. FItems := TList.Create;
  200. FItems.Add(Item);
  201. if Item.FParent <> nil then
  202. Item.FParent.FItems.Remove(Item);
  203. Item.FParent := Self;
  204. end;
  205. procedure TfsXMLItem.InsertItem(Index: Integer; Item: TfsXMLItem);
  206. begin
  207. AddItem(Item);
  208. FItems.Delete(FItems.Count - 1);
  209. FItems.Insert(Index, Item);
  210. end;
  211. procedure TfsXMLItem.SetParent(const Value: TfsXMLItem);
  212. begin
  213. if FParent <> nil then
  214. FParent.FItems.Remove(Self);
  215. FParent := Value;
  216. end;
  217. function TfsXMLItem.Find(const Name: String): Integer;
  218. var
  219. i: Integer;
  220. begin
  221. Result := -1;
  222. for i := 0 to Count - 1 do
  223. if AnsiCompareText(Items[i].Name, Name) = 0 then
  224. begin
  225. Result := i;
  226. break;
  227. end;
  228. end;
  229. function TfsXMLItem.FindItem(const Name: String): TfsXMLItem;
  230. var
  231. i: Integer;
  232. begin
  233. i := Find(Name);
  234. if i = -1 then
  235. begin
  236. Result := Add;
  237. Result.Name := Name;
  238. end
  239. else
  240. Result := Items[i];
  241. end;
  242. function TfsXMLItem.Root: TfsXMLItem;
  243. begin
  244. Result := Self;
  245. while Result.Parent <> nil do
  246. Result := Result.Parent;
  247. end;
  248. function TfsXMLItem.GetProp(Index: String): String;
  249. var
  250. i: Integer;
  251. begin
  252. i := Pos(' ' + AnsiUppercase(Index) + '="', AnsiUppercase(' ' + FText));
  253. if i <> 0 then
  254. begin
  255. Result := Copy(FText, i + Length(Index + '="'), MaxInt);
  256. Result := XMLToStr(Copy(Result, 1, Pos('"', Result) - 1));
  257. end
  258. else
  259. Result := '';
  260. end;
  261. procedure TfsXMLItem.SetProp(Index: String; const Value: String);
  262. var
  263. i, j: Integer;
  264. s: String;
  265. begin
  266. i := Pos(' ' + AnsiUppercase(Index) + '="', AnsiUppercase(' ' + FText));
  267. if i <> 0 then
  268. begin
  269. j := i + Length(Index + '="');
  270. while (j <= Length(FText)) and (FText[j] <> '"') do
  271. Inc(j);
  272. Delete(FText, i, j - i + 1);
  273. end
  274. else
  275. i := Length(FText) + 1;
  276. s := Index + '="' + StrToXML(Value) + '"';
  277. if (i > 1) and (FText[i - 1] <> ' ') then
  278. s := ' ' + s;
  279. Insert(s, FText, i);
  280. end;
  281. function TfsXMLItem.PropExists(const Index: String): Boolean;
  282. begin
  283. Result := Pos(' ' + AnsiUppercase(Index) + '="', ' ' + AnsiUppercase(FText)) > 0;
  284. end;
  285. function TfsXMLItem.IndexOf(Item: TfsXMLItem): Integer;
  286. begin
  287. Result := FItems.IndexOf(Item);
  288. end;
  289. procedure TfsXMLItem.Assign(Item: TfsXMLItem);
  290. procedure DoAssign(ItemFrom, ItemTo: TfsXMLItem);
  291. var
  292. i: Integer;
  293. begin
  294. ItemTo.Name := ItemFrom.Name;
  295. ItemTo.Text := ItemFrom.Text;
  296. ItemTo.Data := ItemFrom.Data;
  297. for i := 0 to ItemFrom.Count - 1 do
  298. DoAssign(ItemFrom[i], ItemTo.Add);
  299. end;
  300. begin
  301. Clear;
  302. if Item <> nil then
  303. DoAssign(Item, Self);
  304. end;
  305. { TfsXMLDocument }
  306. constructor TfsXMLDocument.Create;
  307. begin
  308. FRoot := TfsXMLItem.Create;
  309. end;
  310. destructor TfsXMLDocument.Destroy;
  311. begin
  312. FRoot.Free;
  313. inherited;
  314. end;
  315. procedure TfsXMLDocument.Clear;
  316. begin
  317. FRoot.Clear;
  318. end;
  319. procedure TfsXMLDocument.LoadFromStream(Stream: TStream);
  320. var
  321. rd: TfsXMLReader;
  322. begin
  323. rd := TfsXMLReader.Create(Stream);
  324. try
  325. FRoot.Clear;
  326. rd.ReadHeader;
  327. rd.ReadRootItem(FRoot);
  328. finally
  329. rd.Free;
  330. end;
  331. end;
  332. procedure TfsXMLDocument.SaveToStream(Stream: TStream);
  333. var
  334. wr: TfsXMLWriter;
  335. begin
  336. wr := TfsXMLWriter.Create(Stream);
  337. wr.FAutoIndent := FAutoIndent;
  338. try
  339. wr.WriteHeader;
  340. wr.WriteRootItem(FRoot);
  341. finally
  342. wr.Free;
  343. end;
  344. end;
  345. procedure TfsXMLDocument.LoadFromFile(const FileName: String);
  346. var
  347. s: TFileStream;
  348. begin
  349. s := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  350. try
  351. LoadFromStream(s);
  352. finally
  353. s.Free;
  354. end;
  355. end;
  356. procedure TfsXMLDocument.SaveToFile(const FileName: String);
  357. var
  358. s: TFileStream;
  359. begin
  360. s := TFileStream.Create(FileName, fmCreate);
  361. try
  362. SaveToStream(s);
  363. finally
  364. s.Free;
  365. end;
  366. end;
  367. { TfsXMLReader }
  368. constructor TfsXMLReader.Create(Stream: TStream);
  369. begin
  370. FStream := Stream;
  371. FSize := Stream.Size;
  372. FPosition := Stream.Position;
  373. GetMem(FBuffer, 4096);
  374. end;
  375. destructor TfsXMLReader.Destroy;
  376. begin
  377. FreeMem(FBuffer, 4096);
  378. FStream.Position := FPosition;
  379. inherited;
  380. end;
  381. procedure TfsXMLReader.ReadBuffer;
  382. begin
  383. FBufEnd := FStream.Read(FBuffer^, 4096);
  384. FBufPos := 0;
  385. end;
  386. procedure TfsXMLReader.SetPosition(const Value: Int64);
  387. begin
  388. FPosition := Value;
  389. FStream.Position := Value;
  390. FBufPos := 0;
  391. FBufEnd := 0;
  392. end;
  393. procedure TfsXMLReader.RaiseException;
  394. begin
  395. raise Exception.Create('Invalid file format');
  396. end;
  397. procedure TfsXMLReader.ReadHeader;
  398. var
  399. s1, s2: String;
  400. begin
  401. ReadItem(s1, s2);
  402. if Pos('?xml', s1) <> 1 then
  403. RaiseException;
  404. end;
  405. procedure TfsXMLReader.ReadItem(var Name, Text: String);
  406. var
  407. c: Integer;
  408. curpos, len: Integer;
  409. state: (FindLeft, FindRight, FindComment, Done);
  410. i, comment: Integer;
  411. ps: PChar;
  412. begin
  413. Text := '';
  414. comment := 0;
  415. state := FindLeft;
  416. curpos := 0;
  417. len := 4096;
  418. SetLength(Name, len);
  419. ps := @Name[1];
  420. while FPosition < FSize do
  421. begin
  422. if FBufPos = FBufEnd then
  423. ReadBuffer;
  424. c := Ord(FBuffer[FBufPos]);
  425. Inc(FBufPos);
  426. Inc(FPosition);
  427. if state = FindLeft then
  428. begin
  429. if c = Ord('<') then
  430. state := FindRight
  431. end
  432. else if state = FindRight then
  433. begin
  434. if c = Ord('>') then
  435. begin
  436. state := Done;
  437. break;
  438. end
  439. else if c = Ord('<') then
  440. RaiseException
  441. else
  442. begin
  443. ps[curpos] := Chr(c);
  444. Inc(curpos);
  445. if (curpos = 3) and (Pos('!--', Name) = 1) then
  446. begin
  447. state := FindComment;
  448. comment := 0;
  449. curpos := 0;
  450. end;
  451. if curpos >= len - 1 then
  452. begin
  453. Inc(len, 4096);
  454. SetLength(Name, len);
  455. ps := @Name[1];
  456. end;
  457. end;
  458. end
  459. else if State = FindComment then
  460. begin
  461. if comment = 2 then
  462. begin
  463. if c = Ord('>') then
  464. state := FindLeft
  465. end
  466. else if c = Ord('-') then
  467. Inc(comment) else
  468. comment := 0;
  469. end;
  470. end;
  471. len := curpos;
  472. SetLength(Name, len);
  473. if state = FindRight then
  474. RaiseException;
  475. if (Name <> '') and (Name[len] = ' ') then
  476. SetLength(Name, len - 1);
  477. i := Pos(' ', Name);
  478. if i <> 0 then
  479. begin
  480. Text := Copy(Name, i + 1, len - i);
  481. Delete(Name, i, len - i + 1);
  482. end;
  483. end;
  484. procedure TfsXMLReader.ReadRootItem(Item: TfsXMLItem);
  485. var
  486. LastName: String;
  487. function DoRead(RootItem: TfsXMLItem): Boolean;
  488. var
  489. n: Integer;
  490. ChildItem: TfsXMLItem;
  491. Done: Boolean;
  492. begin
  493. Result := False;
  494. ReadItem(RootItem.FName, RootItem.FText);
  495. LastName := RootItem.FName;
  496. if (RootItem.Name = '') or (RootItem.Name[1] = '/') then
  497. begin
  498. Result := True;
  499. Exit;
  500. end;
  501. n := Length(RootItem.Name);
  502. if RootItem.Name[n] = '/' then
  503. begin
  504. SetLength(RootItem.FName, n - 1);
  505. Exit;
  506. end;
  507. n := Length(RootItem.Text);
  508. if (n > 0) and (RootItem.Text[n] = '/') then
  509. begin
  510. SetLength(RootItem.FText, n - 1);
  511. Exit;
  512. end;
  513. repeat
  514. ChildItem := TfsXMLItem.Create;
  515. Done := DoRead(ChildItem);
  516. if not Done then
  517. RootItem.AddItem(ChildItem) else
  518. ChildItem.Free;
  519. until Done;
  520. if (LastName <> '') and (AnsiCompareText(LastName, '/' + RootItem.Name) <> 0) then
  521. RaiseException;
  522. end;
  523. begin
  524. DoRead(Item);
  525. end;
  526. { TfsXMLWriter }
  527. constructor TfsXMLWriter.Create(Stream: TStream);
  528. begin
  529. FStream := Stream;
  530. end;
  531. procedure TfsXMLWriter.FlushBuffer;
  532. begin
  533. if FBuffer <> '' then
  534. FStream.Write(FBuffer[1], Length(FBuffer));
  535. FBuffer := '';
  536. end;
  537. procedure TfsXMLWriter.WriteLn(const s: String);
  538. begin
  539. if not FAutoIndent then
  540. Insert(s, FBuffer, MaxInt) else
  541. Insert(s + #13#10, FBuffer, MaxInt);
  542. if Length(FBuffer) > 4096 then
  543. FlushBuffer;
  544. end;
  545. procedure TfsXMLWriter.WriteHeader;
  546. begin
  547. WriteLn('<?xml version="1.0"?>');
  548. end;
  549. function Dup(n: Integer): String;
  550. begin
  551. SetLength(Result, n);
  552. FillChar(Result[1], n, ' ');
  553. end;
  554. procedure TfsXMLWriter.WriteItem(Item: TfsXMLItem; Level: Integer = 0);
  555. var
  556. s: String;
  557. begin
  558. if Item.FText <> '' then
  559. begin
  560. s := Item.FText;
  561. if (s = '') or (s[1] <> ' ') then
  562. s := ' ' + s;
  563. end
  564. else
  565. s := '';
  566. if Item.Count = 0 then
  567. s := s + '/>' else
  568. s := s + '>';
  569. if not FAutoIndent then
  570. s := '<' + Item.Name + s else
  571. s := Dup(Level) + '<' + Item.Name + s;
  572. WriteLn(s);
  573. end;
  574. procedure TfsXMLWriter.WriteRootItem(RootItem: TfsXMLItem);
  575. procedure DoWrite(RootItem: TfsXMLItem; Level: Integer = 0);
  576. var
  577. i: Integer;
  578. NeedClear: Boolean;
  579. begin
  580. NeedClear := False;
  581. if not FAutoIndent then
  582. Level := 0;
  583. WriteItem(RootItem, Level);
  584. for i := 0 to RootItem.Count - 1 do
  585. DoWrite(RootItem[i], Level + 2);
  586. if RootItem.Count > 0 then
  587. if not FAutoIndent then
  588. WriteLn('</' + RootItem.Name + '>') else
  589. WriteLn(Dup(Level) + '</' + RootItem.Name + '>');
  590. if NeedClear then
  591. RootItem.Clear;
  592. end;
  593. begin
  594. DoWrite(RootItem);
  595. FlushBuffer;
  596. end;
  597. end.