xmlutils.pp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  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. {$ifdef fpc}{$mode objfpc}{$endif}
  13. {$H+}
  14. {$ifopt Q+}{$define overflow_check}{$endif}
  15. interface
  16. uses
  17. SysUtils, Classes;
  18. function IsXmlName(const Value: WideString; Xml11: Boolean = False): Boolean; overload;
  19. function IsXmlName(Value: PWideChar; Len: Integer; Xml11: Boolean = False): Boolean; overload;
  20. function IsXmlNames(const Value: WideString; Xml11: Boolean = False): Boolean;
  21. function IsXmlNmToken(const Value: WideString; Xml11: Boolean = False): Boolean;
  22. function IsXmlNmTokens(const Value: WideString; Xml11: Boolean = False): Boolean;
  23. function IsValidXmlEncoding(const Value: WideString): Boolean;
  24. function Xml11NamePages: PByteArray;
  25. procedure NormalizeSpaces(var Value: WideString);
  26. function IsXmlWhiteSpace(c: WideChar): Boolean;
  27. function Hash(InitValue: LongWord; Key: PWideChar; KeyLen: Integer): LongWord;
  28. { beware, works in ASCII range only }
  29. function WStrLIComp(S1, S2: PWideChar; Len: Integer): Integer;
  30. { a simple hash table with WideString keys }
  31. type
  32. {$ifndef fpc}
  33. PtrInt = LongInt;
  34. TFPList = TList;
  35. {$endif}
  36. PPHashItem = ^PHashItem;
  37. PHashItem = ^THashItem;
  38. THashItem = record
  39. Key: WideString;
  40. HashValue: LongWord;
  41. Next: PHashItem;
  42. Data: TObject;
  43. end;
  44. THashItemArray = array[0..MaxInt div sizeof(Pointer)-1] of PHashItem;
  45. PHashItemArray = ^THashItemArray;
  46. THashForEach = function(Entry: PHashItem; arg: Pointer): Boolean;
  47. THashTable = class(TObject)
  48. private
  49. FCount: LongWord;
  50. FBucketCount: LongWord;
  51. FBucket: PHashItemArray;
  52. FOwnsObjects: Boolean;
  53. function Lookup(Key: PWideChar; KeyLength: Integer; out Found: Boolean; CanCreate: Boolean): PHashItem;
  54. procedure Resize(NewCapacity: LongWord);
  55. public
  56. constructor Create(InitSize: Integer; OwnObjects: Boolean);
  57. destructor Destroy; override;
  58. procedure Clear;
  59. function Find(Key: PWideChar; KeyLen: Integer): PHashItem;
  60. function FindOrAdd(Key: PWideChar; KeyLen: Integer; var Found: Boolean): PHashItem; overload;
  61. function FindOrAdd(Key: PWideChar; KeyLen: Integer): PHashItem; overload;
  62. function Get(Key: PWideChar; KeyLen: Integer): TObject;
  63. function Remove(Entry: PHashItem): Boolean;
  64. function RemoveData(aData: TObject): Boolean;
  65. procedure ForEach(proc: THashForEach; arg: Pointer);
  66. property Count: LongWord read FCount;
  67. end;
  68. { another hash, for detecting duplicate namespaced attributes without memory allocations }
  69. TExpHashEntry = record
  70. rev: LongWord;
  71. hash: LongWord;
  72. uriPtr: PWideString;
  73. lname: PWideChar;
  74. lnameLen: Integer;
  75. end;
  76. TExpHashEntryArray = array[0..MaxInt div sizeof(TExpHashEntry)-1] of TExpHashEntry;
  77. PExpHashEntryArray = ^TExpHashEntryArray;
  78. TDblHashArray = class(TObject)
  79. private
  80. FSizeLog: Integer;
  81. FRevision: LongWord;
  82. FData: PExpHashEntryArray;
  83. public
  84. procedure Init(NumSlots: Integer);
  85. function Locate(uri: PWideString; localName: PWideChar; localLength: Integer): Boolean;
  86. destructor Destroy; override;
  87. end;
  88. TBinding = class
  89. public
  90. uri: WideString;
  91. next: TBinding;
  92. prevPrefixBinding: TObject;
  93. Prefix: PHashItem;
  94. end;
  95. TAttributeAction = (aaUnchanged, aaPrefix, aaBoth);
  96. TNSSupport = class(TObject)
  97. private
  98. FNesting: Integer;
  99. FPrefixSeqNo: Integer;
  100. FFreeBindings: TBinding;
  101. FBindings: TFPList;
  102. FBindingStack: array of TBinding;
  103. FPrefixes: THashTable;
  104. FDefaultPrefix: THashItem;
  105. public
  106. constructor Create;
  107. destructor Destroy; override;
  108. procedure DefineBinding(const Prefix, nsURI: WideString; out Binding: TBinding);
  109. function CheckAttribute(const Prefix, nsURI: WideString;
  110. out Binding: TBinding): TAttributeAction;
  111. function IsPrefixBound(P: PWideChar; Len: Integer; out Prefix: PHashItem): Boolean;
  112. function GetPrefix(P: PWideChar; Len: Integer): PHashItem;
  113. function BindPrefix(const nsURI: WideString; aPrefix: PHashItem): TBinding;
  114. function DefaultNSBinding: TBinding;
  115. procedure StartElement;
  116. procedure EndElement;
  117. end;
  118. {$i names.inc}
  119. implementation
  120. var
  121. Xml11Pg: PByteArray = nil;
  122. function Xml11NamePages: PByteArray;
  123. var
  124. I: Integer;
  125. p: PByteArray;
  126. begin
  127. if Xml11Pg = nil then
  128. begin
  129. GetMem(p, 512);
  130. for I := 0 to 255 do
  131. p^[I] := ord(Byte(I) in Xml11HighPages);
  132. p^[0] := 2;
  133. p^[3] := $2c;
  134. p^[$20] := $2a;
  135. p^[$21] := $2b;
  136. p^[$2f] := $29;
  137. p^[$30] := $2d;
  138. p^[$fd] := $28;
  139. p^[$ff] := $30;
  140. Move(p^, p^[256], 256);
  141. p^[$100] := $19;
  142. p^[$103] := $2E;
  143. p^[$120] := $2F;
  144. Xml11Pg := p;
  145. end;
  146. Result := Xml11Pg;
  147. end;
  148. function IsXml11Char(Value: PWideChar; var Index: Integer): Boolean; overload;
  149. begin
  150. if (Value[Index] >= #$D800) and (Value[Index] <= #$DB7F) then
  151. begin
  152. Inc(Index);
  153. Result := (Value[Index] >= #$DC00) and (Value[Index] <= #$DFFF);
  154. end
  155. else
  156. Result := False;
  157. end;
  158. function IsXml11Char(const Value: WideString; var Index: Integer): Boolean; overload;
  159. begin
  160. if (Value[Index] >= #$D800) and (Value[Index] <= #$DB7F) then
  161. begin
  162. Inc(Index);
  163. Result := (Value[Index] >= #$DC00) and (Value[Index] <= #$DFFF);
  164. end
  165. else
  166. Result := False;
  167. end;
  168. function IsXmlName(const Value: WideString; Xml11: Boolean): Boolean;
  169. begin
  170. Result := IsXmlName(PWideChar(Value), Length(Value), Xml11);
  171. end;
  172. function IsXmlName(Value: PWideChar; Len: Integer; Xml11: Boolean = False): Boolean;
  173. var
  174. Pages: PByteArray;
  175. I: Integer;
  176. begin
  177. Result := False;
  178. if Xml11 then
  179. Pages := Xml11NamePages
  180. else
  181. Pages := @NamePages;
  182. I := 0;
  183. if (Len = 0) or not ((Byte(Value[I]) in NamingBitmap[Pages^[hi(Word(Value[I]))]]) or
  184. (Value[I] = ':') or
  185. (Xml11 and IsXml11Char(Value, I))) then
  186. Exit;
  187. Inc(I);
  188. while I < Len do
  189. begin
  190. if not ((Byte(Value[I]) in NamingBitmap[Pages^[$100+hi(Word(Value[I]))]]) or
  191. (Value[I] = ':') or
  192. (Xml11 and IsXml11Char(Value, I))) then
  193. Exit;
  194. Inc(I);
  195. end;
  196. Result := True;
  197. end;
  198. function IsXmlNames(const Value: WideString; Xml11: Boolean): Boolean;
  199. var
  200. Pages: PByteArray;
  201. I: Integer;
  202. Offset: Integer;
  203. begin
  204. if Xml11 then
  205. Pages := Xml11NamePages
  206. else
  207. Pages := @NamePages;
  208. Result := False;
  209. if Value = '' then
  210. Exit;
  211. I := 1;
  212. Offset := 0;
  213. while I <= Length(Value) do
  214. begin
  215. if not ((Byte(Value[I]) in NamingBitmap[Pages^[Offset+hi(Word(Value[I]))]]) or
  216. (Value[I] = ':') or
  217. (Xml11 and IsXml11Char(Value, I))) then
  218. begin
  219. if (I = Length(Value)) or (Value[I] <> #32) then
  220. Exit;
  221. Offset := 0;
  222. Inc(I);
  223. Continue;
  224. end;
  225. Offset := $100;
  226. Inc(I);
  227. end;
  228. Result := True;
  229. end;
  230. function IsXmlNmToken(const Value: WideString; Xml11: Boolean): Boolean;
  231. var
  232. I: Integer;
  233. Pages: PByteArray;
  234. begin
  235. if Xml11 then
  236. Pages := Xml11NamePages
  237. else
  238. Pages := @NamePages;
  239. Result := False;
  240. if Value = '' then
  241. Exit;
  242. I := 1;
  243. while I <= Length(Value) do
  244. begin
  245. if not ((Byte(Value[I]) in NamingBitmap[Pages^[$100+hi(Word(Value[I]))]]) or
  246. (Value[I] = ':') or
  247. (Xml11 and IsXml11Char(Value, I))) then
  248. Exit;
  249. Inc(I);
  250. end;
  251. Result := True;
  252. end;
  253. function IsXmlNmTokens(const Value: WideString; Xml11: Boolean): Boolean;
  254. var
  255. I: Integer;
  256. Pages: PByteArray;
  257. begin
  258. if Xml11 then
  259. Pages := Xml11NamePages
  260. else
  261. Pages := @NamePages;
  262. I := 1;
  263. Result := False;
  264. if Value = '' then
  265. Exit;
  266. while I <= Length(Value) do
  267. begin
  268. if not ((Byte(Value[I]) in NamingBitmap[Pages^[$100+hi(Word(Value[I]))]]) or
  269. (Value[I] = ':') or
  270. (Xml11 and IsXml11Char(Value, I))) then
  271. begin
  272. if (I = Length(Value)) or (Value[I] <> #32) then
  273. Exit;
  274. end;
  275. Inc(I);
  276. end;
  277. Result := True;
  278. end;
  279. function IsValidXmlEncoding(const Value: WideString): Boolean;
  280. var
  281. I: Integer;
  282. begin
  283. Result := False;
  284. if (Value = '') or (Value[1] > #255) or not (char(ord(Value[1])) in ['A'..'Z', 'a'..'z']) then
  285. Exit;
  286. for I := 2 to Length(Value) do
  287. if (Value[I] > #255) or not (char(ord(Value[I])) in ['A'..'Z', 'a'..'z', '0'..'9', '.', '_', '-']) then
  288. Exit;
  289. Result := True;
  290. end;
  291. procedure NormalizeSpaces(var Value: WideString);
  292. var
  293. I, J: Integer;
  294. begin
  295. I := Length(Value);
  296. // speed: trim only whed needed
  297. if (I > 0) and ((Value[1] = #32) or (Value[I] = #32)) then
  298. Value := Trim(Value);
  299. I := 1;
  300. while I < Length(Value) do
  301. begin
  302. if Value[I] = #32 then
  303. begin
  304. J := I+1;
  305. while (J <= Length(Value)) and (Value[J] = #32) do Inc(J);
  306. if J-I > 1 then Delete(Value, I+1, J-I-1);
  307. end;
  308. Inc(I);
  309. end;
  310. end;
  311. function IsXmlWhiteSpace(c: WideChar): Boolean;
  312. begin
  313. Result := (c = #32) or (c = #9) or (c = #10) or (c = #13);
  314. end;
  315. function WStrLIComp(S1, S2: PWideChar; Len: Integer): Integer;
  316. var
  317. counter: Integer;
  318. c1, c2: Word;
  319. begin
  320. counter := 0;
  321. result := 0;
  322. if Len = 0 then
  323. exit;
  324. repeat
  325. c1 := ord(S1[counter]);
  326. c2 := ord(S2[counter]);
  327. if (c1 = 0) or (c2 = 0) then break;
  328. if c1 <> c2 then
  329. begin
  330. if c1 in [97..122] then
  331. Dec(c1, 32);
  332. if c2 in [97..122] then
  333. Dec(c2, 32);
  334. if c1 <> c2 then
  335. Break;
  336. end;
  337. Inc(counter);
  338. until counter >= Len;
  339. result := c1 - c2;
  340. end;
  341. function Hash(InitValue: LongWord; Key: PWideChar; KeyLen: Integer): LongWord;
  342. begin
  343. Result := InitValue;
  344. while KeyLen <> 0 do
  345. begin
  346. {$ifdef overflow_check}{$q-}{$endif}
  347. Result := Result * $F4243 xor ord(Key^);
  348. {$ifdef overflow_check}{$q+}{$endif}
  349. Inc(Key);
  350. Dec(KeyLen);
  351. end;
  352. end;
  353. function KeyCompare(const Key1: WideString; Key2: Pointer; Key2Len: Integer): Boolean;
  354. begin
  355. {$IFDEF FPC}
  356. Result := (Length(Key1)=Key2Len) and (CompareWord(Pointer(Key1)^, Key2^, Key2Len) = 0);
  357. {$ELSE}
  358. Result := (Length(Key1)=Key2Len) and CompareMem(Pointer(Key1), Key2, Key2Len*2);
  359. {$ENDIF}
  360. end;
  361. { THashTable }
  362. constructor THashTable.Create(InitSize: Integer; OwnObjects: Boolean);
  363. var
  364. I: Integer;
  365. begin
  366. inherited Create;
  367. FOwnsObjects := OwnObjects;
  368. I := 256;
  369. while I < InitSize do I := I shl 1;
  370. FBucketCount := I;
  371. FBucket := AllocMem(I * sizeof(PHashItem));
  372. end;
  373. destructor THashTable.Destroy;
  374. begin
  375. Clear;
  376. FreeMem(FBucket);
  377. inherited Destroy;
  378. end;
  379. procedure THashTable.Clear;
  380. var
  381. I: Integer;
  382. item, next: PHashItem;
  383. begin
  384. for I := 0 to FBucketCount-1 do
  385. begin
  386. item := FBucket^[I];
  387. while Assigned(item) do
  388. begin
  389. next := item^.Next;
  390. if FOwnsObjects then
  391. item^.Data.Free;
  392. Dispose(item);
  393. item := next;
  394. end;
  395. FBucket^[I] := nil;
  396. end;
  397. end;
  398. function THashTable.Find(Key: PWideChar; KeyLen: Integer): PHashItem;
  399. var
  400. Dummy: Boolean;
  401. begin
  402. Result := Lookup(Key, KeyLen, Dummy, False);
  403. end;
  404. function THashTable.FindOrAdd(Key: PWideChar; KeyLen: Integer;
  405. var Found: Boolean): PHashItem;
  406. begin
  407. Result := Lookup(Key, KeyLen, Found, True);
  408. end;
  409. function THashTable.FindOrAdd(Key: PWideChar; KeyLen: Integer): PHashItem;
  410. var
  411. Dummy: Boolean;
  412. begin
  413. Result := Lookup(Key, KeyLen, Dummy, True);
  414. end;
  415. function THashTable.Get(Key: PWideChar; KeyLen: Integer): TObject;
  416. var
  417. e: PHashItem;
  418. Dummy: Boolean;
  419. begin
  420. e := Lookup(Key, KeyLen, Dummy, False);
  421. if Assigned(e) then
  422. Result := e^.Data
  423. else
  424. Result := nil;
  425. end;
  426. function THashTable.Lookup(Key: PWideChar; KeyLength: Integer;
  427. out Found: Boolean; CanCreate: Boolean): PHashItem;
  428. var
  429. Entry: PPHashItem;
  430. h: LongWord;
  431. begin
  432. h := Hash(0, Key, KeyLength);
  433. Entry := @FBucket^[h mod FBucketCount];
  434. while Assigned(Entry^) and not ((Entry^^.HashValue = h) and KeyCompare(Entry^^.Key, Key, KeyLength) ) do
  435. Entry := @Entry^^.Next;
  436. Found := Assigned(Entry^);
  437. if Found or (not CanCreate) then
  438. begin
  439. Result := Entry^;
  440. Exit;
  441. end;
  442. if FCount > FBucketCount then { arbitrary limit, probably too high }
  443. begin
  444. Resize(FBucketCount * 2);
  445. Result := Lookup(Key, KeyLength, Found, CanCreate);
  446. end
  447. else
  448. begin
  449. New(Result);
  450. // SetString for WideStrings trims on zero chars
  451. // need to investigate and report
  452. SetLength(Result^.Key, KeyLength);
  453. Move(Key^, Pointer(Result^.Key)^, KeyLength*sizeof(WideChar));
  454. Result^.HashValue := h;
  455. Result^.Data := nil;
  456. Result^.Next := nil;
  457. Inc(FCount);
  458. Entry^ := Result;
  459. end;
  460. end;
  461. procedure THashTable.Resize(NewCapacity: LongWord);
  462. var
  463. p: PHashItemArray;
  464. chain: PPHashItem;
  465. i: Integer;
  466. e, n: PHashItem;
  467. begin
  468. p := AllocMem(NewCapacity * sizeof(PHashItem));
  469. for i := 0 to FBucketCount-1 do
  470. begin
  471. e := FBucket^[i];
  472. while Assigned(e) do
  473. begin
  474. chain := @p^[e^.HashValue mod NewCapacity];
  475. n := e^.Next;
  476. e^.Next := chain^;
  477. chain^ := e;
  478. e := n;
  479. end;
  480. end;
  481. FBucketCount := NewCapacity;
  482. FreeMem(FBucket);
  483. FBucket := p;
  484. end;
  485. function THashTable.Remove(Entry: PHashItem): Boolean;
  486. var
  487. chain: PPHashItem;
  488. begin
  489. chain := @FBucket^[Entry^.HashValue mod FBucketCount];
  490. while Assigned(chain^) do
  491. begin
  492. if chain^ = Entry then
  493. begin
  494. chain^ := Entry^.Next;
  495. if FOwnsObjects then
  496. Entry^.Data.Free;
  497. Dispose(Entry);
  498. Dec(FCount);
  499. Result := True;
  500. Exit;
  501. end;
  502. chain := @chain^^.Next;
  503. end;
  504. Result := False;
  505. end;
  506. // this does not free the aData object
  507. function THashTable.RemoveData(aData: TObject): Boolean;
  508. var
  509. i: Integer;
  510. chain: PPHashItem;
  511. e: PHashItem;
  512. begin
  513. for i := 0 to FBucketCount-1 do
  514. begin
  515. chain := @FBucket^[i];
  516. while Assigned(chain^) do
  517. begin
  518. if chain^^.Data = aData then
  519. begin
  520. e := chain^;
  521. chain^ := e^.Next;
  522. Dispose(e);
  523. Dec(FCount);
  524. Result := True;
  525. Exit;
  526. end;
  527. chain := @chain^^.Next;
  528. end;
  529. end;
  530. Result := False;
  531. end;
  532. procedure THashTable.ForEach(proc: THashForEach; arg: Pointer);
  533. var
  534. i: Integer;
  535. e: PHashItem;
  536. begin
  537. for i := 0 to FBucketCount-1 do
  538. begin
  539. e := FBucket^[i];
  540. while Assigned(e) do
  541. begin
  542. if not proc(e, arg) then
  543. Exit;
  544. e := e^.Next;
  545. end;
  546. end;
  547. end;
  548. { TDblHashArray }
  549. destructor TDblHashArray.Destroy;
  550. begin
  551. FreeMem(FData);
  552. inherited Destroy;
  553. end;
  554. procedure TDblHashArray.Init(NumSlots: Integer);
  555. var
  556. i: Integer;
  557. begin
  558. if ((NumSlots * 2) shr FSizeLog) <> 0 then // need at least twice more entries, and no less than 8
  559. begin
  560. FSizeLog := 3;
  561. while (NumSlots shr FSizeLog) <> 0 do
  562. Inc(FSizeLog);
  563. ReallocMem(FData, (1 shl FSizeLog) * sizeof(TExpHashEntry));
  564. FRevision := 0;
  565. end;
  566. if FRevision = 0 then
  567. begin
  568. FRevision := $FFFFFFFF;
  569. for i := (1 shl FSizeLog)-1 downto 0 do
  570. FData^[i].rev := FRevision;
  571. end;
  572. Dec(FRevision);
  573. end;
  574. function TDblHashArray.Locate(uri: PWideString; localName: PWideChar; localLength: Integer): Boolean;
  575. var
  576. step: Byte;
  577. mask: LongWord;
  578. idx: Integer;
  579. HashValue: LongWord;
  580. begin
  581. HashValue := Hash(0, PWideChar(uri^), Length(uri^));
  582. HashValue := Hash(HashValue, localName, localLength);
  583. mask := (1 shl FSizeLog) - 1;
  584. step := (HashValue and (not mask)) shr (FSizeLog-1) and (mask shr 2) or 1;
  585. idx := HashValue and mask;
  586. result := True;
  587. while FData^[idx].rev = FRevision do
  588. begin
  589. if (HashValue = FData^[idx].hash) and (FData^[idx].uriPtr^ = uri^) and
  590. (FData^[idx].lnameLen = localLength) and
  591. CompareMem(FData^[idx].lname, localName, localLength * sizeof(WideChar)) then
  592. Exit;
  593. if idx < step then
  594. Inc(idx, (1 shl FSizeLog) - step)
  595. else
  596. Dec(idx, step);
  597. end;
  598. with FData^[idx] do
  599. begin
  600. rev := FRevision;
  601. hash := HashValue;
  602. uriPtr := uri;
  603. lname := localName;
  604. lnameLen := localLength;
  605. end;
  606. result := False;
  607. end;
  608. { TNSSupport }
  609. constructor TNSSupport.Create;
  610. var
  611. b: TBinding;
  612. begin
  613. inherited Create;
  614. FPrefixes := THashTable.Create(16, False);
  615. FBindings := TFPList.Create;
  616. SetLength(FBindingStack, 16);
  617. { provide implicit binding for the 'xml' prefix }
  618. // TODO: move stduri_xml, etc. to this unit, so they are reused.
  619. DefineBinding('xml', 'http://www.w3.org/XML/1998/namespace', b);
  620. end;
  621. destructor TNSSupport.Destroy;
  622. var
  623. I: Integer;
  624. begin
  625. for I := FBindings.Count-1 downto 0 do
  626. TObject(FBindings.List^[I]).Free;
  627. FBindings.Free;
  628. FPrefixes.Free;
  629. inherited Destroy;
  630. end;
  631. function TNSSupport.BindPrefix(const nsURI: WideString; aPrefix: PHashItem): TBinding;
  632. begin
  633. { try to reuse an existing binding }
  634. result := FFreeBindings;
  635. if Assigned(result) then
  636. FFreeBindings := result.Next
  637. else { no free bindings, create a new one }
  638. begin
  639. result := TBinding.Create;
  640. FBindings.Add(result);
  641. end;
  642. { link it into chain of bindings at the current element level }
  643. result.Next := FBindingStack[FNesting];
  644. FBindingStack[FNesting] := result;
  645. { bind }
  646. result.uri := nsURI;
  647. result.Prefix := aPrefix;
  648. result.PrevPrefixBinding := aPrefix^.Data;
  649. aPrefix^.Data := result; // ** null binding not used here **
  650. end;
  651. function TNSSupport.DefaultNSBinding: TBinding;
  652. begin
  653. result := TBinding(FDefaultPrefix.Data);
  654. end;
  655. procedure TNSSupport.DefineBinding(const Prefix, nsURI: WideString;
  656. out Binding: TBinding);
  657. var
  658. Pfx: PHashItem;
  659. begin
  660. Pfx := @FDefaultPrefix;
  661. if (nsURI <> '') and (Prefix <> '') then
  662. Pfx := FPrefixes.FindOrAdd(PWideChar(Prefix), Length(Prefix));
  663. if (Pfx^.Data = nil) or (TBinding(Pfx^.Data).uri <> nsURI) then
  664. Binding := BindPrefix(nsURI, Pfx)
  665. else
  666. Binding := nil;
  667. end;
  668. function TNSSupport.CheckAttribute(const Prefix, nsURI: WideString;
  669. out Binding: TBinding): TAttributeAction;
  670. var
  671. Pfx: PHashItem;
  672. I: Integer;
  673. b: TBinding;
  674. buf: array[0..31] of WideChar;
  675. p: PWideChar;
  676. begin
  677. Binding := nil;
  678. Pfx := nil;
  679. if Prefix <> '' then
  680. Pfx := FPrefixes.FindOrAdd(PWideChar(Prefix), Length(Prefix));
  681. Result := aaUnchanged;
  682. // no prefix, not bound, or bound to wrong URI
  683. if (Pfx = nil) or (Pfx^.Data = nil) or (TBinding(Pfx^.Data).uri <> nsURI) then
  684. begin
  685. // see if there's another prefix bound to the target URI
  686. // TODO: should use something faster than linear search
  687. for i := FNesting downto 0 do
  688. begin
  689. b := FBindingStack[i];
  690. while Assigned(b) do
  691. begin
  692. if (b.uri = nsURI) and (b.Prefix <> @FDefaultPrefix) then
  693. begin
  694. Binding := b; // found one -> override the attribute's prefix
  695. Result := aaPrefix;
  696. Exit;
  697. end;
  698. b := b.Next;
  699. end;
  700. end;
  701. // no prefix, or bound (to wrong URI) -> must use generated prefix instead
  702. if (Pfx = nil) or Assigned(Pfx^.Data) then
  703. repeat
  704. Inc(FPrefixSeqNo);
  705. i := FPrefixSeqNo; // This is just 'NS'+IntToStr(FPrefixSeqNo);
  706. p := @Buf[high(Buf)]; // done without using strings
  707. while i <> 0 do
  708. begin
  709. p^ := WideChar(i mod 10+ord('0'));
  710. dec(p);
  711. i := i div 10;
  712. end;
  713. p^ := 'S'; dec(p);
  714. p^ := 'N';
  715. Pfx := FPrefixes.FindOrAdd(p, @Buf[high(Buf)]-p+1);
  716. until Pfx^.Data = nil;
  717. Binding := BindPrefix(nsURI, Pfx);
  718. Result := aaBoth;
  719. end;
  720. end;
  721. function TNSSupport.IsPrefixBound(P: PWideChar; Len: Integer; out
  722. Prefix: PHashItem): Boolean;
  723. begin
  724. Prefix := FPrefixes.FindOrAdd(P, Len);
  725. Result := Assigned(Prefix^.Data) and (TBinding(Prefix^.Data).uri <> '');
  726. end;
  727. function TNSSupport.GetPrefix(P: PWideChar; Len: Integer): PHashItem;
  728. begin
  729. if Assigned(P) and (Len > 0) then
  730. Result := FPrefixes.FindOrAdd(P, Len)
  731. else
  732. Result := @FDefaultPrefix;
  733. end;
  734. procedure TNSSupport.StartElement;
  735. begin
  736. Inc(FNesting);
  737. if FNesting >= Length(FBindingStack) then
  738. SetLength(FBindingStack, FNesting * 2);
  739. end;
  740. procedure TNSSupport.EndElement;
  741. var
  742. b, temp: TBinding;
  743. begin
  744. temp := FBindingStack[FNesting];
  745. while Assigned(temp) do
  746. begin
  747. b := temp;
  748. temp := b.next;
  749. b.next := FFreeBindings;
  750. FFreeBindings := b;
  751. b.Prefix^.Data := b.prevPrefixBinding;
  752. end;
  753. FBindingStack[FNesting] := nil;
  754. if FNesting > 0 then
  755. Dec(FNesting);
  756. end;
  757. initialization
  758. finalization
  759. if Assigned(Xml11Pg) then
  760. FreeMem(Xml11Pg);
  761. end.