2
0

xmlutils.pp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. {
  2. This file is part of the Free Component Library
  3. XML utility routines.
  4. Copyright (c) 2006 by Sergei Gorelkin, [email protected]
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit xmlutils;
  12. {$mode objfpc}
  13. {$H+}
  14. interface
  15. uses
  16. SysUtils;
  17. function IsXmlName(const Value: WideString; Xml11: Boolean = False): Boolean; overload;
  18. function IsXmlName(Value: PWideChar; Len: Integer; Xml11: Boolean = False): Boolean; overload;
  19. function IsXmlNames(const Value: WideString; Xml11: Boolean = False): Boolean;
  20. function IsXmlNmToken(const Value: WideString; Xml11: Boolean = False): Boolean;
  21. function IsXmlNmTokens(const Value: WideString; Xml11: Boolean = False): Boolean;
  22. function IsValidXmlEncoding(const Value: WideString): Boolean;
  23. function Xml11NamePages: PByteArray;
  24. procedure NormalizeSpaces(var Value: WideString);
  25. function Hash(InitValue: LongWord; Key: PWideChar; KeyLen: Integer): LongWord;
  26. { a simple hash table with WideString keys }
  27. type
  28. PPHashItem = ^PHashItem;
  29. PHashItem = ^THashItem;
  30. THashItem = record
  31. Key: WideString;
  32. HashValue: LongWord;
  33. Next: PHashItem;
  34. Data: TObject;
  35. end;
  36. THashForEach = function(Entry: PHashItem; arg: Pointer): Boolean;
  37. THashTable = class(TObject)
  38. private
  39. FCount: LongWord;
  40. FBucketCount: LongWord;
  41. FBucket: PPHashItem;
  42. FOwnsObjects: Boolean;
  43. function Lookup(Key: PWideChar; KeyLength: Integer; var Found: Boolean; CanCreate: Boolean): PHashItem;
  44. procedure Resize(NewCapacity: LongWord);
  45. public
  46. constructor Create(InitSize: Integer; OwnObjects: Boolean);
  47. destructor Destroy; override;
  48. procedure Clear;
  49. function Find(Key: PWideChar; KeyLen: Integer): PHashItem;
  50. function FindOrAdd(Key: PWideChar; KeyLen: Integer; var Found: Boolean): PHashItem; overload;
  51. function FindOrAdd(Key: PWideChar; KeyLen: Integer): PHashItem; overload;
  52. function Get(Key: PWideChar; KeyLen: Integer): TObject;
  53. function Remove(Entry: PHashItem): Boolean;
  54. procedure ForEach(proc: THashForEach; arg: Pointer);
  55. property Count: LongWord read FCount;
  56. end;
  57. {$i names.inc}
  58. implementation
  59. var
  60. Xml11Pg: PByteArray = nil;
  61. function Xml11NamePages: PByteArray;
  62. var
  63. I: Integer;
  64. p: PByteArray;
  65. begin
  66. if Xml11Pg = nil then
  67. begin
  68. GetMem(p, 512);
  69. for I := 0 to 255 do
  70. p^[I] := ord(Byte(I) in Xml11HighPages);
  71. p^[0] := 2;
  72. p^[3] := $2c;
  73. p^[$20] := $2a;
  74. p^[$21] := $2b;
  75. p^[$2f] := $29;
  76. p^[$30] := $2d;
  77. p^[$fd] := $28;
  78. p^[$ff] := $30;
  79. Move(p^, p^[256], 256);
  80. p^[$100] := $19;
  81. p^[$103] := $2E;
  82. p^[$120] := $2F;
  83. Xml11Pg := p;
  84. end;
  85. Result := Xml11Pg;
  86. end;
  87. function IsXml11Char(Value: PWideChar; var Index: Integer): Boolean; overload;
  88. begin
  89. if (Value[Index] >= #$D800) and (Value[Index] <= #$DB7F) then
  90. begin
  91. Inc(Index);
  92. Result := (Value[Index] >= #$DC00) and (Value[Index] <= #$DFFF);
  93. end
  94. else
  95. Result := False;
  96. end;
  97. function IsXml11Char(const Value: WideString; var Index: Integer): Boolean; overload;
  98. begin
  99. if (Value[Index] >= #$D800) and (Value[Index] <= #$DB7F) then
  100. begin
  101. Inc(Index);
  102. Result := (Value[Index] >= #$DC00) and (Value[Index] <= #$DFFF);
  103. end
  104. else
  105. Result := False;
  106. end;
  107. function IsXmlName(const Value: WideString; Xml11: Boolean): Boolean;
  108. begin
  109. Result := IsXmlName(PWideChar(Value), Length(Value), Xml11);
  110. end;
  111. function IsXmlName(Value: PWideChar; Len: Integer; Xml11: Boolean = False): Boolean; overload;
  112. var
  113. Pages: PByteArray;
  114. I: Integer;
  115. begin
  116. Result := False;
  117. if Xml11 then
  118. Pages := Xml11NamePages
  119. else
  120. Pages := @NamePages;
  121. I := 0;
  122. if (Len = 0) or not ((Byte(Value[I]) in NamingBitmap[Pages^[hi(Word(Value[I]))]]) or
  123. (Value[I] = ':') or
  124. (Xml11 and IsXml11Char(Value, I))) then
  125. Exit;
  126. Inc(I);
  127. while I < Len do
  128. begin
  129. if not ((Byte(Value[I]) in NamingBitmap[Pages^[$100+hi(Word(Value[I]))]]) or
  130. (Value[I] = ':') or
  131. (Xml11 and IsXml11Char(Value, I))) then
  132. Exit;
  133. Inc(I);
  134. end;
  135. Result := True;
  136. end;
  137. function IsXmlNames(const Value: WideString; Xml11: Boolean): Boolean;
  138. var
  139. Pages: PByteArray;
  140. I: Integer;
  141. Offset: Integer;
  142. begin
  143. if Xml11 then
  144. Pages := Xml11NamePages
  145. else
  146. Pages := @NamePages;
  147. Result := False;
  148. if Value = '' then
  149. Exit;
  150. I := 1;
  151. Offset := 0;
  152. while I <= Length(Value) do
  153. begin
  154. if not ((Byte(Value[I]) in NamingBitmap[Pages^[Offset+hi(Word(Value[I]))]]) or
  155. (Value[I] = ':') or
  156. (Xml11 and IsXml11Char(Value, I))) then
  157. begin
  158. if (I = Length(Value)) or (Value[I] <> #32) then
  159. Exit;
  160. Offset := 0;
  161. Inc(I);
  162. Continue;
  163. end;
  164. Offset := $100;
  165. Inc(I);
  166. end;
  167. Result := True;
  168. end;
  169. function IsXmlNmToken(const Value: WideString; Xml11: Boolean): Boolean;
  170. var
  171. I: Integer;
  172. Pages: PByteArray;
  173. begin
  174. if Xml11 then
  175. Pages := Xml11NamePages
  176. else
  177. Pages := @NamePages;
  178. Result := False;
  179. if Value = '' then
  180. Exit;
  181. I := 1;
  182. while I <= Length(Value) do
  183. begin
  184. if not ((Byte(Value[I]) in NamingBitmap[Pages^[$100+hi(Word(Value[I]))]]) or
  185. (Value[I] = ':') or
  186. (Xml11 and IsXml11Char(Value, I))) then
  187. Exit;
  188. Inc(I);
  189. end;
  190. Result := True;
  191. end;
  192. function IsXmlNmTokens(const Value: WideString; Xml11: Boolean): Boolean;
  193. var
  194. I: Integer;
  195. Pages: PByteArray;
  196. begin
  197. if Xml11 then
  198. Pages := Xml11NamePages
  199. else
  200. Pages := @NamePages;
  201. I := 1;
  202. Result := False;
  203. if Value = '' then
  204. Exit;
  205. while I <= Length(Value) do
  206. begin
  207. if not ((Byte(Value[I]) in NamingBitmap[Pages^[$100+hi(Word(Value[I]))]]) or
  208. (Value[I] = ':') or
  209. (Xml11 and IsXml11Char(Value, I))) then
  210. begin
  211. if (I = Length(Value)) or (Value[I] <> #32) then
  212. Exit;
  213. end;
  214. Inc(I);
  215. end;
  216. Result := True;
  217. end;
  218. function IsValidXmlEncoding(const Value: WideString): Boolean;
  219. var
  220. I: Integer;
  221. begin
  222. Result := False;
  223. if (Value = '') or (Value[1] > #255) or not (char(ord(Value[1])) in ['A'..'Z', 'a'..'z']) then
  224. Exit;
  225. for I := 2 to Length(Value) do
  226. if (Value[I] > #255) or not (char(ord(Value[I])) in ['A'..'Z', 'a'..'z', '0'..'9', '.', '_', '-']) then
  227. Exit;
  228. Result := True;
  229. end;
  230. procedure NormalizeSpaces(var Value: WideString);
  231. var
  232. I, J: Integer;
  233. begin
  234. I := Length(Value);
  235. // speed: trim only whed needed
  236. if (I > 0) and ((Value[1] = #32) or (Value[I] = #32)) then
  237. Value := Trim(Value);
  238. I := 1;
  239. while I < Length(Value) do
  240. begin
  241. if Value[I] = #32 then
  242. begin
  243. J := I+1;
  244. while (J <= Length(Value)) and (Value[J] = #32) do Inc(J);
  245. if J-I > 1 then Delete(Value, I+1, J-I-1);
  246. end;
  247. Inc(I);
  248. end;
  249. end;
  250. function Hash(InitValue: LongWord; Key: PWideChar; KeyLen: Integer): LongWord;
  251. begin
  252. Result := InitValue;
  253. while KeyLen <> 0 do
  254. begin
  255. Result := Result * $F4243 xor ord(Key^);
  256. Inc(Key);
  257. Dec(KeyLen);
  258. end;
  259. end;
  260. function KeyCompare(const Key1: WideString; Key2: Pointer; Key2Len: Integer): Boolean;
  261. begin
  262. Result := (Length(Key1)=Key2Len) and (CompareWord(Pointer(Key1)^, Key2^, Key2Len) = 0);
  263. end;
  264. { THashTable }
  265. constructor THashTable.Create(InitSize: Integer; OwnObjects: Boolean);
  266. var
  267. I: Integer;
  268. begin
  269. inherited Create;
  270. FOwnsObjects := OwnObjects;
  271. I := 256;
  272. while I < InitSize do I := I shl 1;
  273. FBucketCount := I;
  274. FBucket := AllocMem(I * sizeof(PHashItem));
  275. end;
  276. destructor THashTable.Destroy;
  277. begin
  278. Clear;
  279. FreeMem(FBucket);
  280. inherited Destroy;
  281. end;
  282. procedure THashTable.Clear;
  283. var
  284. I: Integer;
  285. item, next: PHashItem;
  286. begin
  287. for I := 0 to FBucketCount-1 do
  288. begin
  289. item := FBucket[I];
  290. while Assigned(item) do
  291. begin
  292. next := item^.Next;
  293. if FOwnsObjects then
  294. item^.Data.Free;
  295. Dispose(item);
  296. item := next;
  297. end;
  298. end;
  299. FillChar(FBucket^, FBucketCount * sizeof(PHashItem), 0);
  300. end;
  301. function THashTable.Find(Key: PWideChar; KeyLen: Integer): PHashItem;
  302. var
  303. Dummy: Boolean;
  304. begin
  305. Result := Lookup(Key, KeyLen, Dummy, False);
  306. end;
  307. function THashTable.FindOrAdd(Key: PWideChar; KeyLen: Integer;
  308. var Found: Boolean): PHashItem;
  309. begin
  310. Result := Lookup(Key, KeyLen, Found, True);
  311. end;
  312. function THashTable.FindOrAdd(Key: PWideChar; KeyLen: Integer): PHashItem;
  313. var
  314. Dummy: Boolean;
  315. begin
  316. Result := Lookup(Key, KeyLen, Dummy, True);
  317. end;
  318. function THashTable.Get(Key: PWideChar; KeyLen: Integer): TObject;
  319. var
  320. e: PHashItem;
  321. Dummy: Boolean;
  322. begin
  323. e := Lookup(Key, KeyLen, Dummy, False);
  324. if Assigned(e) then
  325. Result := e^.Data
  326. else
  327. Result := nil;
  328. end;
  329. function THashTable.Lookup(Key: PWideChar; KeyLength: Integer;
  330. var Found: Boolean; CanCreate: Boolean): PHashItem;
  331. var
  332. Entry: PPHashItem;
  333. h: LongWord;
  334. begin
  335. h := Hash(0, Key, KeyLength);
  336. Entry := @FBucket[h mod FBucketCount];
  337. while Assigned(Entry^) and not ((Entry^^.HashValue = h) and KeyCompare(Entry^^.Key, Key, KeyLength) ) do
  338. Entry := @Entry^^.Next;
  339. Found := Assigned(Entry^);
  340. if Found or (not CanCreate) then
  341. begin
  342. Result := Entry^;
  343. Exit;
  344. end;
  345. if FCount > FBucketCount then { arbitrary limit, probably too high }
  346. begin
  347. Resize(FBucketCount * 2);
  348. Result := Lookup(Key, KeyLength, Found, CanCreate);
  349. end
  350. else
  351. begin
  352. New(Result);
  353. SetString(Result^.Key, Key, KeyLength);
  354. Result^.HashValue := h;
  355. Result^.Data := nil;
  356. Result^.Next := nil;
  357. Inc(FCount);
  358. Entry^ := Result;
  359. end;
  360. end;
  361. procedure THashTable.Resize(NewCapacity: LongWord);
  362. var
  363. p, chain: PPHashItem;
  364. i: Integer;
  365. e, n: PHashItem;
  366. begin
  367. p := AllocMem(NewCapacity * sizeof(PHashItem));
  368. for i := 0 to FBucketCount-1 do
  369. begin
  370. e := FBucket[i];
  371. while Assigned(e) do
  372. begin
  373. chain := @p[e^.HashValue mod NewCapacity];
  374. n := e^.Next;
  375. e^.Next := chain^;
  376. chain^ := e;
  377. e := n;
  378. end;
  379. end;
  380. FBucketCount := NewCapacity;
  381. FreeMem(FBucket);
  382. FBucket := p;
  383. end;
  384. function THashTable.Remove(Entry: PHashItem): Boolean;
  385. var
  386. chain: PPHashItem;
  387. begin
  388. chain := @FBucket[Entry^.HashValue mod FBucketCount];
  389. while Assigned(chain^) do
  390. begin
  391. if chain^ = Entry then
  392. begin
  393. chain^ := Entry^.Next;
  394. if FOwnsObjects then
  395. Entry^.Data.Free;
  396. Dispose(Entry);
  397. Dec(FCount);
  398. Result := True;
  399. Exit;
  400. end;
  401. chain := @chain^^.Next;
  402. end;
  403. Result := False;
  404. end;
  405. procedure THashTable.ForEach(proc: THashForEach; arg: Pointer);
  406. var
  407. i: Integer;
  408. e: PHashItem;
  409. begin
  410. for i := 0 to FBucketCount-1 do
  411. begin
  412. e := FBucket[i];
  413. while Assigned(e) do
  414. begin
  415. if not proc(e, arg) then
  416. Exit;
  417. e := e^.Next;
  418. end;
  419. end;
  420. end;
  421. initialization
  422. finalization
  423. if Assigned(Xml11Pg) then
  424. FreeMem(Xml11Pg);
  425. end.