cldrxml.pas 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207
  1. { Parser of the CLDR collation xml files.
  2. Copyright (c) 2013, 2014, 2015 by Inoussa OUEDRAOGO
  3. The source code is distributed under the Library GNU
  4. General Public License with the following modification:
  5. - object files and libraries linked into an application may be
  6. distributed without source code.
  7. If you didn't receive a copy of the file COPYING, contact:
  8. Free Software Foundation
  9. 675 Mass Ave
  10. Cambridge, MA 02139
  11. USA
  12. This program is distributed in the hope that it will be useful,
  13. but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15. }
  16. { The procedure whoses names lasted by 'XML' (ParseInitialDocumentXML,
  17. ParseCollationDocumentXML, ...) are for older CLDR versions (CDLR <= 23); The
  18. old version was unsing a XML syntax for collation's rules specifications.
  19. The new versions (and going forward) will be using the text syntax.
  20. }
  21. unit cldrxml;
  22. {$mode delphi}{$H+}
  23. {$TypedAddress on}
  24. interface
  25. uses
  26. Classes, SysUtils, DOM,
  27. cldrhelper;
  28. type
  29. { TCldrCollationFileLoader }
  30. TCldrCollationFileLoader = class(TInterfacedObject,ICldrCollationLoader)
  31. private
  32. FPath : string;
  33. private
  34. procedure SetPath(APath : string);
  35. function BuildFileName(ALanguage : string) : string;
  36. procedure CheckFile(AFileName : string);
  37. protected
  38. procedure LoadCollation(
  39. const ALanguage : string;
  40. ACollation : TCldrCollation;
  41. AMode : TCldrParserMode
  42. );
  43. procedure LoadCollationType(
  44. const ALanguage,
  45. ATypeName : string;
  46. AType : TCldrCollationItem
  47. );
  48. public
  49. constructor Create(APath : string);
  50. end;
  51. { TCldrCollationStreamLoader }
  52. TCldrCollationStreamLoader = class(TInterfacedObject,ICldrCollationLoader)
  53. private
  54. FLanguages : array of string;
  55. FStreams : array of TStream;
  56. private
  57. procedure CheckContent(ALanguage : string);
  58. function IndexOf(ALanguage : string) : Integer;
  59. protected
  60. procedure LoadCollation(
  61. const ALanguage : string;
  62. ACollation : TCldrCollation;
  63. AMode : TCldrParserMode
  64. );
  65. procedure LoadCollationType(
  66. const ALanguage,
  67. ATypeName : string;
  68. AType : TCldrCollationItem
  69. );
  70. public
  71. constructor Create(
  72. const ALanguages : array of string;
  73. const AStreams : array of TStream
  74. );
  75. destructor Destroy();override;
  76. end;
  77. procedure ParseInitialDocumentXML(ASequence : POrderedCharacters; ADoc : TDOMDocument);overload;
  78. procedure ParseInitialDocumentXML(ASequence : POrderedCharacters; AFileName : string);overload;
  79. //-----------------------------------------------------
  80. procedure ParseCollationDocument2(
  81. ADoc : TDOMDocument;
  82. ACollation : TCldrCollation;
  83. AMode : TCldrParserMode
  84. );overload;
  85. procedure ParseCollationDocument2(
  86. const AFileName : string;
  87. ACollation : TCldrCollation;
  88. AMode : TCldrParserMode
  89. );overload;
  90. procedure ParseCollationDocument2(
  91. AStream : TStream;
  92. ACollation : TCldrCollation;
  93. AMode : TCldrParserMode
  94. );overload;
  95. procedure ParseCollationDocument2(
  96. const AFileName : string;
  97. ACollation : TCldrCollationItem;
  98. AType : string
  99. );overload;
  100. procedure ParseCollationDocument2(
  101. ADoc : TDOMDocument;
  102. ACollation : TCldrCollationItem;
  103. AType : string
  104. );overload;
  105. procedure ParseCollationDocument2(
  106. AStream : TStream;
  107. ACollation : TCldrCollationItem;
  108. AType : string
  109. );overload;
  110. implementation
  111. uses
  112. typinfo, RtlConsts, XMLRead, XPath, Helper, unicodeset, cldrtxt;
  113. const
  114. s_ALT = 'alt';
  115. s_AT = 'at';
  116. //s_BEFORE = 'before';
  117. s_CODEPOINT = 'codepoint';
  118. s_COLLATION = 'collation';
  119. s_COLLATIONS = 'collations';
  120. s_CONTEXT = 'context';
  121. //s_DEFAULT = 'default';
  122. s_EXTEND = 'extend';
  123. s_HEX = 'hex';
  124. s_POSITION = 'position';
  125. s_RESET = 'reset';
  126. s_RULES = 'rules';
  127. //s_STANDART = 'standard';
  128. s_TYPE = 'type';
  129. s_CR = 'cr';
  130. procedure CheckNodeName(ANode : TDOMNode; const AExpectedName : DOMString);
  131. begin
  132. if (ANode.NodeName <> AExpectedName) then
  133. raise Exception.CreateFmt(sNodeNameAssertMessage,[AExpectedName,ANode.NodeName]);
  134. end;
  135. function CharToReorderWeigthKind(const AChar : Char) : TReorderWeigthKind;inline;
  136. begin
  137. case AChar of
  138. 'p' : Result := TReorderWeigthKind.PriMary;
  139. 's' : Result := TReorderWeigthKind.Secondary;
  140. 't' : Result := TReorderWeigthKind.Tertiary;
  141. 'i' : Result := TReorderWeigthKind.Identity;
  142. else
  143. Result := TReorderWeigthKind.Identity;
  144. end;
  145. end;
  146. function DomString2UnicodeCodePointArray(const AValue : DOMString): TUnicodeCodePointArray;
  147. var
  148. u4str : UCS4String;
  149. k : Integer;
  150. begin
  151. if (Length(AValue) = 0) then
  152. exit(nil);
  153. if (Length(AValue) = 1) then begin
  154. SetLength(Result,1);
  155. Result[0] := Ord(AValue[1])
  156. end else begin
  157. u4str := WideStringToUCS4String(AValue);
  158. k := Length(u4str) - 1; // remove the last #0
  159. SetLength(Result,k);
  160. for k := 0 to k - 1 do
  161. Result[k] := u4str[k];
  162. end;
  163. end;
  164. function ParseStatementXML(
  165. ARules : TDOMElement;
  166. AStartPosition : Integer;
  167. AStatement : PReorderSequence;
  168. var ANextPos : Integer
  169. ) : Boolean;
  170. var
  171. startPosition : Integer;
  172. statement : PReorderSequence;
  173. elementActualCount : Integer;
  174. list : TDOMNodeList;
  175. inBlock : Boolean;
  176. procedure SkipComments();
  177. begin
  178. while (startPosition < list.Count) do begin
  179. if (list[startPosition].NodeType <> COMMENT_NODE) then
  180. Break;
  181. Inc(startPosition);
  182. end;
  183. end;
  184. function parse_reset() : Integer;
  185. var
  186. n, t : TDOMNode;
  187. s : string;
  188. logicalPos : TReorderLogicalReset;
  189. begin
  190. SkipComments();
  191. n := list[startPosition];
  192. CheckNodeName(n,s_RESET);
  193. if n.HasChildNodes() then begin
  194. n := n.FirstChild;
  195. if (n.NodeType = TEXT_NODE) then begin
  196. statement^.Reset := DomString2UnicodeCodePointArray(Trim(TDOMText(n).Data));
  197. Result := startPosition+1;
  198. end else begin
  199. if not TryStrToLogicalReorder(n.NodeName,logicalPos) then
  200. raise Exception.CreateFmt(sUnknownResetLogicalPosition,[n.NodeName]);
  201. statement^.LogicalPosition := logicalPos;
  202. Result := startPosition+1;
  203. end;
  204. end else if not n.HasChildNodes() then begin
  205. if (list[startPosition+1].NodeName = s_POSITION) then begin
  206. s := list[startPosition+1].Attributes.GetNamedItem(s_AT).NodeValue;
  207. if not TryStrToLogicalReorder(s,logicalPos) then
  208. raise Exception.CreateFmt(sUnknownResetLogicalPosition,[s]);
  209. statement^.LogicalPosition := logicalPos;
  210. Result := startPosition+2;
  211. end else begin
  212. t := list[startPosition+1];
  213. {if (t.NodeType <> TEXT_NODE) then
  214. raise Exception.CreateFmt(sTextNodeChildExpected,[(startPosition+1),(t.NodeName+'('+t.ClassName+')')]);}
  215. if (t.NodeType = TEXT_NODE) then
  216. statement^.Reset := DomString2UnicodeCodePointArray(Trim(TDOMText(t).Data))
  217. else
  218. statement^.Reset := DomString2UnicodeCodePointArray(' ');
  219. Result := startPosition+2;
  220. end;
  221. end;
  222. if (statement^.LogicalPosition = TReorderLogicalReset.None) and
  223. (Length(statement^.Reset) = 0)
  224. then
  225. raise Exception.Create(sInvalidResetClause);
  226. end;
  227. procedure EnsureElementLength(const ALength : Integer);
  228. var
  229. k, d : Integer;
  230. begin
  231. k := Length(statement^.Elements);
  232. if (k < ALength) then begin
  233. k := ALength;
  234. if (k = 0) then begin
  235. k := 50;
  236. end else begin
  237. if (k < 10) then
  238. d := 10
  239. else
  240. d := 2;
  241. k := k * d;
  242. end;
  243. SetLength(statement^.Elements,k);
  244. end;
  245. end;
  246. procedure AddElement(
  247. const AChars : array of UCS4Char;
  248. const AWeigthKind : TReorderWeigthKind;
  249. const AContext : DOMString
  250. );overload;
  251. var
  252. kp : PReorderUnit;
  253. k : Integer;
  254. begin
  255. EnsureElementLength(elementActualCount+1);
  256. kp := @statement^.Elements[elementActualCount];
  257. SetLength(kp^.Characters,Length(AChars));
  258. for k := 0 to Length(AChars) - 1 do
  259. kp^.Characters[k] := AChars[k];
  260. kp^.WeigthKind := AWeigthKind;
  261. elementActualCount := elementActualCount + 1;
  262. if (AContext <> '') then
  263. kp^.Context := DomString2UnicodeCodePointArray(AContext);
  264. end;
  265. procedure ReadChars(
  266. ANode : TDOMNode;
  267. APos : Integer;
  268. var AChars : UCS4String
  269. );
  270. var
  271. t : TDOMNode;
  272. u4str : UCS4String;
  273. s : DOMString;
  274. begin
  275. if not ANode.HasChildNodes() then begin
  276. SetLength(AChars,1);
  277. AChars[0] := Ord(UnicodeChar(' '));
  278. exit;
  279. //raise Exception.CreateFmt(sCodePointExpected + ANode.ClassName,[APos]);
  280. end;
  281. t := ANode.FindNode(s_CODEPOINT);
  282. if (t = nil) then begin
  283. if (ANode.ChildNodes.Count <> 1) then
  284. raise Exception.CreateFmt(sUniqueChildNodeExpected,[APos]);
  285. t := ANode.ChildNodes[0];
  286. if not t.InheritsFrom(TDOMText) then
  287. raise Exception.CreateFmt(sTextNodeChildExpected,[APos,(t.NodeName+'('+t.ClassName+')')]);
  288. s := TDOMText(t).Data;
  289. if (Length(s) = 1) then begin
  290. SetLength(AChars,1);
  291. AChars[0] := Ord(s[1]);
  292. end else begin
  293. u4str := WideStringToUCS4String(s);
  294. AChars := u4str;
  295. SetLength(AChars,Length(AChars)-1);
  296. end;
  297. end else begin
  298. t := t.Attributes.GetNamedItem(s_HEX);
  299. if (t = nil) then
  300. raise Exception.CreateFmt(sHexAttributeExpected,[APos]);
  301. SetLength(AChars,1);
  302. AChars[0] := StrToInt('$'+t.NodeValue);
  303. end
  304. end;
  305. procedure AddPrefixChars(const APrefix : array of UCS4Char; var ADest : TUnicodeCodePointArray);
  306. var
  307. k : Integer;
  308. begin
  309. k := Length(ADest);
  310. SetLength(ADest,(k+Length(APrefix)));
  311. Move(ADest[0],ADest[k+1],(SizeOf(k*ADest[0])));
  312. for k := 0 to k - 1 do
  313. ADest[k] := APrefix[k];
  314. end;
  315. function ReadNextItem(const APos : Integer) : Integer;
  316. var
  317. n, t : TDOMNode;
  318. contextStr : DOMString;
  319. w : TReorderWeigthKind;
  320. isSimpleCharTag : Boolean;
  321. simpleCharTag : AnsiChar;
  322. last : PReorderUnit;
  323. u4str : UCS4String;
  324. k : Integer;
  325. begin
  326. contextStr := '';
  327. Result := APos;
  328. n := list[APos];
  329. isSimpleCharTag := (Length(n.NodeName) = 1) and (Ord(n.NodeName[1])<=127);
  330. if isSimpleCharTag then begin
  331. simpleCharTag := AnsiChar(n.NodeName[1]);
  332. if (simpleCharTag = 'x') then begin
  333. inBlock := True;
  334. n := n.FirstChild;
  335. if (n.NodeName = s_CONTEXT) then begin
  336. if n.HasChildNodes() then begin
  337. t := n.FirstChild;
  338. if (t.NodeType = TEXT_NODE) then
  339. contextStr := TDOMText(t).Data;
  340. end;
  341. n := n.NextSibling;
  342. end;
  343. isSimpleCharTag := (Length(n.NodeName) = 1) and (Ord(n.NodeName[1])<=127);
  344. if isSimpleCharTag then
  345. simpleCharTag := AnsiChar(n.NodeName[1]);
  346. end;
  347. end;
  348. if isSimpleCharTag and (simpleCharTag in ['p','s','t','i']) then begin
  349. w := CharToReorderWeigthKind(AnsiChar(n.NodeName[1]));
  350. ReadChars(n,APos,u4str);
  351. AddElement(u4str,w,contextStr);
  352. Result := Result + 1;
  353. if not inBlock then
  354. exit;
  355. last := @statement^.Elements[elementActualCount-1];
  356. n := n.NextSibling;
  357. if (n <> nil) and (n.NodeName = s_EXTEND) then begin
  358. ReadChars(n,APos,u4str);
  359. SetLength(last^.ExpansionChars,Length(u4str));
  360. for k := 0 to Length(u4str) - 1 do
  361. last^.ExpansionChars[k] := u4str[k];
  362. end;
  363. exit;
  364. end;
  365. if (Length(n.NodeName) = 2) and (n.NodeName[2] = 'c') and
  366. (Ord(n.NodeName[1])<=127) and (AnsiChar(n.NodeName[1]) in ['p','s','t','i'])
  367. then begin
  368. w := CharToReorderWeigthKind(AnsiChar(n.NodeName[1]));
  369. ReadChars(n,APos,u4str);
  370. for k := Low(u4str) to High(u4str) do
  371. AddElement(u4str[k],w,contextStr);
  372. Result := Result + 1;
  373. exit;
  374. end;
  375. raise Exception.CreateFmt(sCaseNothandled,[n.NodeName,APos]);
  376. end;
  377. var
  378. i, c : Integer;
  379. n : TDOMNode;
  380. begin
  381. Result := False;
  382. inBlock := False;
  383. elementActualCount := 0;
  384. if (AStartPosition <= 0) then
  385. startPosition := 0
  386. else
  387. startPosition := AStartPosition;
  388. i := startPosition;
  389. list := ARules.ChildNodes;
  390. c := list.Count;
  391. if (c <= i) then
  392. exit;
  393. statement := AStatement;
  394. statement^.Clear();
  395. n := list[i];
  396. i := parse_reset();
  397. while (i < c) do begin
  398. n := list[i];
  399. if (n.NodeName = s_RESET) then
  400. Break;
  401. i := ReadNextItem(i);
  402. end;
  403. SetLength(statement^.Elements,elementActualCount);
  404. Result := (i > startPosition);
  405. if Result then
  406. ANextPos := i;
  407. end;
  408. procedure ParseInitialDocumentXML(ASequence : POrderedCharacters; ADoc : TDOMDocument);
  409. var
  410. n : TDOMNode;
  411. rulesElement : TDOMElement;
  412. i, c, nextPost : Integer;
  413. statement : TReorderSequence;
  414. p : PReorderUnit;
  415. begin
  416. n := ADoc.DocumentElement.FindNode(s_RULES);
  417. if (n = nil) then
  418. raise Exception.Create(sRulesNodeNotFound);
  419. rulesElement := n as TDOMElement;
  420. c := rulesElement.ChildNodes.Count;
  421. ASequence^.Clear();
  422. SetLength(ASequence^.Data,c+100);
  423. nextPost := 0;
  424. i := 0;
  425. while (i < c) do begin
  426. statement.Clear();
  427. if not ParseStatementXML(rulesElement,i,@statement,nextPost) then
  428. Break;
  429. i := nextPost;
  430. try
  431. ASequence^.ApplyStatement(@statement);
  432. except
  433. on e : Exception do begin
  434. e.Message := Format('%s Position = %d',[e.Message,i]);
  435. raise;
  436. end;
  437. end;
  438. end;
  439. if (ASequence^.ActualLength > 0) then begin
  440. p := @ASequence^.Data[0];
  441. for i := 0 to ASequence^.ActualLength - 1 do begin
  442. p^.Changed := False;
  443. Inc(p);
  444. end;
  445. end;
  446. end;
  447. procedure ParseInitialDocumentXML(ASequence : POrderedCharacters; AFileName : string);
  448. var
  449. doc : TXMLDocument;
  450. begin
  451. ReadXMLFile(doc,AFileName);
  452. try
  453. ParseInitialDocumentXML(ASequence,doc);
  454. finally
  455. doc.Free();
  456. end;
  457. end;
  458. function EvaluateXPathStr(const AExpression : string; AContextNode : TDOMNode): DOMString;
  459. var
  460. xv : TXPathVariable;
  461. begin
  462. xv := EvaluateXPathExpression(AExpression,AContextNode);
  463. try
  464. if (xv <> nil) then
  465. Result := xv.AsText
  466. else
  467. Result := '';
  468. finally
  469. xv.Free();
  470. end;
  471. end;
  472. function ParseDeletion(
  473. const APattern : DOMString;
  474. ASequence : PReorderSequence
  475. ) : Integer;
  476. var
  477. r : array of TReorderUnit;
  478. c : Integer;
  479. uset : TUnicodeSet;
  480. it : TUnicodeSet.TIterator;
  481. p : PReorderUnit;
  482. begin
  483. if (APattern = '') then
  484. exit(0);
  485. it := nil;
  486. uset := TUnicodeSet.Create();
  487. try
  488. uset.AddPattern(APattern);
  489. it := uset.CreateIterator();
  490. c := 0;
  491. it.Reset();
  492. while it.MoveNext() do begin
  493. Inc(c);
  494. end;
  495. SetLength(r,c);
  496. p := @r[0];
  497. it.Reset();
  498. while it.MoveNext() do begin
  499. p^.Clear();
  500. p^.WeigthKind := TReorderWeigthKind.Deletion;
  501. p^.Characters := Copy(it.GetCurrent());
  502. Inc(p);
  503. end;
  504. ASequence^.Clear();
  505. ASequence^.Elements := r;
  506. finally
  507. it.Free();
  508. uset.Free();
  509. end;
  510. r := nil;
  511. Result := c;
  512. end;
  513. function NextPart(
  514. const ABuffer : string;
  515. const AStartPos : Integer;
  516. const ASeparator : Char;
  517. out ANextStart : Integer
  518. ) : string;
  519. var
  520. c, sp, i : Integer;
  521. begin
  522. c := Length(ABuffer);
  523. if (c < 1) or (AStartPos > c) then begin
  524. ANextStart := c+1;
  525. Result := '';
  526. exit;
  527. end;
  528. if (AStartPos > 0) then
  529. sp := AStartPos
  530. else
  531. sp := 1;
  532. i := sp;
  533. while (i <= c) do begin
  534. if (ABuffer[i] = ASeparator) then
  535. break;
  536. i := i+1;
  537. end;
  538. Result := Copy(ABuffer,sp,(i-sp));
  539. if (i <= c) then
  540. i := i+1;
  541. ANextStart := i;
  542. end;
  543. procedure HandleSetting_Import(
  544. AItem : TCldrCollationItem;
  545. ASetting : PSettingRec
  546. );
  547. var
  548. buffer, lang, col, s : UTF8String;
  549. i, ns : Integer;
  550. begin
  551. if (Length(ASetting^.Values) <> 1) then begin
  552. buffer := '';
  553. if (Length(ASetting^.Values) > 0) then begin
  554. for i := 0 to Length(ASetting^.Values)-1 do
  555. buffer := Format('%s + "%s"',[ASetting^.Values[i]]);
  556. end;
  557. raise Exception.CreateFmt(sInvalidImportStatement,[buffer]);
  558. end;
  559. buffer := ASetting^.Values[0];
  560. lang := NextPart(buffer,1,'-',ns);
  561. i := ns;
  562. col := '';
  563. s := NextPart(buffer,i,'-',ns);
  564. if (s <> '') then begin
  565. if (s <> 'u') then
  566. raise Exception.CreateFmt(sInvalidImportStatement,[buffer]);
  567. i := ns;
  568. s := NextPart(buffer,i,'-',ns);
  569. if (s <> 'co') then
  570. raise Exception.CreateFmt(sInvalidImportStatement,[buffer]);
  571. s := Trim(Copy(buffer,ns,(Length(buffer)-ns+1)));
  572. if (s = '') then
  573. raise Exception.CreateFmt(sInvalidImportStatement,[buffer]);
  574. col := s;
  575. end;
  576. if (col = '') then
  577. col := COLLATION_ITEM_DEFAULT;
  578. if (LowerCase(lang) = 'und') then
  579. lang := 'root';
  580. AItem.Imports.Add(lang,col);
  581. ASetting^.Understood := True;
  582. end;
  583. procedure HandleSetting_Backwards(
  584. AItem : TCldrCollationItem;
  585. ASetting : PSettingRec
  586. );
  587. var
  588. buffer : UTF8String;
  589. i : Integer;
  590. begin
  591. if (Length(ASetting^.Values) <> 1) then begin
  592. buffer := '';
  593. if (Length(ASetting^.Values) > 0) then begin
  594. for i := 0 to Length(ASetting^.Values)-1 do
  595. buffer := Format('%s + "%s"',[ASetting^.Values[i]]);
  596. end;
  597. raise Exception.CreateFmt(sInvalidBackwardsStatement,[buffer]);
  598. end;
  599. if (ASetting^.Values[0] = '2') then
  600. AItem.Backwards := True
  601. else
  602. raise Exception.CreateFmt(
  603. sInvalidSettingValue,
  604. [SETTING_OPTION_STRINGS[ASetting^.OptionValue],ASetting^.Values[0]]
  605. );
  606. AItem.ChangedFields := AItem.ChangedFields+[TCollationField.BackWards];
  607. ASetting^.Understood := True;
  608. end;
  609. procedure HandleSetting_Alternate(
  610. AItem : TCldrCollationItem;
  611. ASetting : PSettingRec
  612. );
  613. var
  614. buffer : UTF8String;
  615. i : Integer;
  616. begin
  617. if (Length(ASetting^.Values) <> 1) then begin
  618. buffer := '';
  619. if (Length(ASetting^.Values) > 0) then begin
  620. for i := 0 to Length(ASetting^.Values)-1 do
  621. buffer := Format('%s + "%s"',[ASetting^.Values[i]]);
  622. end;
  623. raise Exception.CreateFmt(sInvalidAlternateStatement,[buffer]);
  624. end;
  625. buffer := UTF8String(LowerCase(UnicodeString(ASetting^.Values[0])));
  626. if (buffer = 'non-ignorable') then
  627. AItem.VariableWeight := ucaNonIgnorable
  628. else if (buffer = 'shifted') then
  629. AItem.VariableWeight := ucaShifted
  630. else
  631. raise Exception.CreateFmt(
  632. sInvalidSettingValue,
  633. [SETTING_OPTION_STRINGS[ASetting^.OptionValue],ASetting^.Values[0]]
  634. );
  635. AItem.ChangedFields := AItem.ChangedFields+[TCollationField.Alternate];
  636. ASetting^.Understood := True;
  637. end;
  638. procedure HandleSetting_Normalization(
  639. AItem : TCldrCollationItem;
  640. ASetting : PSettingRec
  641. );
  642. var
  643. buffer : UTF8String;
  644. i : Integer;
  645. begin
  646. if (Length(ASetting^.Values) <> 1) then begin
  647. buffer := '';
  648. if (Length(ASetting^.Values) > 0) then begin
  649. for i := 0 to Length(ASetting^.Values)-1 do
  650. buffer := Format('%s + "%s"',[ASetting^.Values[i]]);
  651. end;
  652. raise Exception.CreateFmt(sInvalidNormalizationStatement,[buffer]);
  653. end;
  654. buffer := UTF8String(LowerCase(UnicodeString(ASetting^.Values[0])));
  655. if (buffer = 'off') then
  656. AItem.Normalization := False
  657. else if (buffer = 'on') then
  658. AItem.Normalization := True
  659. else
  660. raise Exception.CreateFmt(
  661. sInvalidSettingValue,
  662. [SETTING_OPTION_STRINGS[ASetting^.OptionValue],ASetting^.Values[0]]
  663. );
  664. AItem.ChangedFields := AItem.ChangedFields+[TCollationField.Normalization];
  665. ASetting^.Understood := True;
  666. end;
  667. procedure HandleSetting_Strength(
  668. AItem : TCldrCollationItem;
  669. ASetting : PSettingRec
  670. );
  671. var
  672. buffer : UTF8String;
  673. i : Integer;
  674. begin
  675. if (Length(ASetting^.Values) <> 1) then begin
  676. buffer := '';
  677. if (Length(ASetting^.Values) > 0) then begin
  678. for i := 0 to Length(ASetting^.Values)-1 do
  679. buffer := Format('%s + "%s"',[ASetting^.Values[i]]);
  680. end;
  681. raise Exception.CreateFmt(sInvalidStrengthStatement,[buffer]);
  682. end;
  683. buffer := UTF8String(LowerCase(UnicodeString(ASetting^.Values[0])));
  684. if (buffer = '1') then
  685. AItem.Strength := TComparisonStrength.Primary
  686. else if (buffer = '2') then
  687. AItem.Strength := TComparisonStrength.Secondary
  688. else if (buffer = '3') then
  689. AItem.Strength := TComparisonStrength.Tertiary
  690. else if (buffer = '4') then
  691. AItem.Strength := TComparisonStrength.Quaternary
  692. else if (buffer = 'i') then
  693. AItem.Strength := TComparisonStrength.Identity
  694. else
  695. raise Exception.CreateFmt(
  696. sInvalidSettingValue,
  697. [SETTING_OPTION_STRINGS[ASetting^.OptionValue],ASetting^.Values[0]]
  698. );
  699. AItem.ChangedFields := AItem.ChangedFields+[TCollationField.Strength];
  700. ASetting^.Understood := True;
  701. end;
  702. procedure HandleSetting_EMPTY_PROC(
  703. AItem : TCldrCollationItem;
  704. ASetting : PSettingRec
  705. );
  706. begin
  707. //
  708. end;
  709. type
  710. TSettingHandlerProc = procedure (
  711. AItem : TCldrCollationItem;
  712. ASetting : PSettingRec
  713. );
  714. const
  715. SETTING_HANDLERS : array[TSettingOption] of TSettingHandlerProc =(
  716. HandleSetting_EMPTY_PROC, HandleSetting_Strength, HandleSetting_Alternate,
  717. //Unknown, Strength, Alternate,
  718. HandleSetting_Backwards, HandleSetting_Normalization, HandleSetting_EMPTY_PROC,
  719. //Backwards, Normalization, CaseLevel,
  720. HandleSetting_EMPTY_PROC, HandleSetting_EMPTY_PROC, HandleSetting_EMPTY_PROC,
  721. //CaseFirst, HiraganaQ, NumericOrdering,
  722. HandleSetting_EMPTY_PROC, HandleSetting_EMPTY_PROC, HandleSetting_Import,
  723. //Reorder, MaxVariable Import
  724. HandleSetting_EMPTY_PROC,
  725. //SuppressContractions has a special handling see Process_SuppressContractions
  726. HandleSetting_EMPTY_PROC
  727. //Optimize
  728. );
  729. procedure HandleSetting(AItem : TCldrCollationItem; ASetting : PSettingRec);
  730. begin
  731. if not ASetting^.Understood then
  732. SETTING_HANDLERS[ASetting^.OptionValue](AItem,ASetting);
  733. end;
  734. procedure HandleSettings(AItem : TCldrCollationItem);
  735. var
  736. i, c : Integer;
  737. p : PSettingRec;
  738. begin
  739. c := Length(AItem.Settings);
  740. if (c < 1) then
  741. exit;
  742. p := @AItem.Settings[0];
  743. for i := 0 to c-1 do begin
  744. HandleSetting(AItem,p);
  745. Inc(p);
  746. end;
  747. end;
  748. function Process_SuppressContractions(
  749. ASetting : PSettingRec;
  750. AStatement : PReorderSequence
  751. ) : Boolean;
  752. var
  753. buffer : UTF8String;
  754. i : Integer;
  755. begin
  756. if (Length(ASetting^.Values) <> 1) then begin
  757. buffer := '';
  758. if (Length(ASetting^.Values) > 0) then begin
  759. for i := 0 to Length(ASetting^.Values)-1 do
  760. buffer := Format('%s + "%s"',[ASetting^.Values[i]]);
  761. end;
  762. raise Exception.CreateFmt(sInvalidSuppressContractionsStatement,[buffer]);
  763. end;
  764. Result := (ParseDeletion(DOMString(ASetting^.Values[0]),AStatement) > 0);
  765. ASetting.Understood := Result;
  766. end;
  767. procedure ParseCollationItem2(
  768. ACollationNode : TDOMElement;
  769. AItem : TCldrCollationItem;
  770. AMode : TCldrParserMode
  771. );
  772. var
  773. statementList : TCldrCollationRuleArray;
  774. sal : Integer;//statement actual length
  775. procedure AddStatementToArray(AStatement : PReorderSequence);
  776. begin
  777. statementList[sal].Kind := TCldrCollationRuleKind.ReorderSequence;
  778. statementList[sal].Reorder.Assign(AStatement);
  779. Inc(sal);
  780. if (sal >= Length(statementList)) then
  781. SetLength(statementList,(sal*2));
  782. end;
  783. procedure AddImportToArray(AImport : TCldrImport);
  784. begin
  785. statementList[sal].Kind := TCldrCollationRuleKind.Import;
  786. statementList[sal].Import := AImport;
  787. Inc(sal);
  788. if (sal >= Length(statementList)) then
  789. SetLength(statementList,(sal*2));
  790. end;
  791. var
  792. n : TDOMNode;
  793. rulesElement : TDOMCDATASection;
  794. i, c, nextPos : Integer;
  795. parsedStatement : TParsedStatement;
  796. s : DOMString;
  797. u8 : UTF8String;
  798. buffer : PAnsiChar;
  799. lineCount : Integer;
  800. settingArray : TSettingRecArray;
  801. begin
  802. AItem.TypeName := ACollationNode.GetAttribute(s_TYPE);
  803. AItem.Alt := ACollationNode.GetAttribute(s_ALT);
  804. AItem.Settings := nil;
  805. AItem.Rules := nil;
  806. AItem.Mode := AMode;
  807. if (AMode = TCldrParserMode.FullParsing) then begin
  808. SetLength(statementList,15);
  809. sal := 0;
  810. n := ACollationNode.FindNode(s_CR);
  811. if (n <> nil) then begin
  812. n := (n as TDOMElement).FirstChild;
  813. rulesElement := n as TDOMCDATASection;
  814. s := rulesElement.Data;
  815. u8 := UTF8Encode(s);
  816. c := Length(u8);
  817. buffer := @u8[1];
  818. nextPos := 0;
  819. i := 0;
  820. lineCount := 0;
  821. Clear(parsedStatement);
  822. settingArray := AItem.Settings;
  823. while (i < c) do begin
  824. if not ParseStatement(buffer,i,c,@parsedStatement,nextPos,lineCount) then
  825. Break;
  826. if (parsedStatement.Kind = TStatementKind.Sequence) then begin
  827. AddStatementToArray(@parsedStatement.ReorderSequence);
  828. end else if (parsedStatement.Kind = TStatementKind.Setting) then begin
  829. if (parsedStatement.Setting.OptionValue = TSettingOption.SuppressContractions) then begin
  830. if Process_SuppressContractions(@parsedStatement.Setting,@parsedStatement.ReorderSequence) then
  831. AddStatementToArray(@parsedStatement.ReorderSequence);
  832. end;
  833. AddItem(settingArray,@parsedStatement.Setting);
  834. if (parsedStatement.Setting.OptionValue = TSettingOption.Import) then begin
  835. HandleSetting(AItem,@settingArray[Length(settingArray)-1]);
  836. AddImportToArray(AItem.Imports[AItem.Imports.Count-1]);
  837. end;
  838. end;
  839. i := nextPos;
  840. end;
  841. AItem.Settings := settingArray;
  842. if (Length(AItem.Settings) > 0) then
  843. HandleSettings(AItem);
  844. end;
  845. SetLength(statementList,sal);
  846. AItem.Rules := statementList;
  847. end;
  848. end;
  849. procedure ParseCollationDocument2(
  850. ADoc : TDOMDocument;
  851. ACollation : TCldrCollation;
  852. AMode : TCldrParserMode
  853. );
  854. var
  855. n : TDOMNode;
  856. collationsElement : TDOMElement;
  857. i, c : Integer;
  858. item, tempItem : TCldrCollationItem;
  859. nl : TDOMNodeList;
  860. isnew : boolean;
  861. begin
  862. n := ADoc.DocumentElement.FindNode(s_COLLATIONS);
  863. if (n = nil) then
  864. raise Exception.Create(sCollationsNodeNotFound);
  865. collationsElement := n as TDOMElement;
  866. //ACollation.Clear();
  867. ACollation.Mode := AMode;
  868. ACollation.Language := EvaluateXPathStr('identity/language/@type',ADoc.DocumentElement);
  869. ACollation.Version := EvaluateXPathStr('identity/version/@number',ADoc.DocumentElement);
  870. ACollation.DefaultType := EvaluateXPathStr('collations/defaultCollation',ADoc.DocumentElement);
  871. if collationsElement.HasChildNodes() then begin
  872. nl := collationsElement.ChildNodes;
  873. c := nl.Count;
  874. tempItem := TCldrCollationItem.Create();
  875. try
  876. item := nil;
  877. try
  878. for i := 0 to c - 1 do begin
  879. n := nl[i];
  880. if (n.NodeName = s_COLLATION) then begin
  881. tempItem.Clear();
  882. ParseCollationItem2((n as TDOMElement),tempItem,TCldrParserMode.HeaderParsing);
  883. item := ACollation.Find(tempItem.TypeName);
  884. isnew := (item = nil);
  885. if isnew then
  886. item := TCldrCollationItem.Create();
  887. if isnew or (item.Mode < AMode) then
  888. ParseCollationItem2((n as TDOMElement),item,AMode);
  889. if isnew then
  890. ACollation.Add(item);
  891. item := nil;
  892. end
  893. end;
  894. except
  895. FreeAndNil(item);
  896. raise;
  897. end;
  898. finally
  899. tempItem.Free();
  900. end;
  901. end;
  902. end;
  903. procedure ParseCollationDocument2(
  904. ADoc : TDOMDocument;
  905. ACollation : TCldrCollationItem;
  906. AType : string
  907. );
  908. var
  909. xv : TXPathVariable;
  910. begin
  911. xv := EvaluateXPathExpression(Format('collations/collation[@type=%s]',[QuotedStr(AType)]),ADoc.DocumentElement);
  912. try
  913. if (xv.AsNodeSet.Count = 0) then
  914. raise Exception.CreateFmt(sCollationTypeNotFound,[AType]);
  915. ACollation.Clear();
  916. ParseCollationItem2((TDOMNode(xv.AsNodeSet[0]) as TDOMElement),ACollation,TCldrParserMode.FullParsing);
  917. finally
  918. xv.Free();
  919. end
  920. end;
  921. function ReadXMLFile(f: TStream) : TXMLDocument;overload;
  922. var
  923. src : TXMLInputSource;
  924. parser: TDOMParser;
  925. begin
  926. src := TXMLInputSource.Create(f);
  927. parser := TDOMParser.Create();
  928. try
  929. parser.Options.IgnoreComments := True;
  930. parser.Parse(src, Result);
  931. finally
  932. src.Free();
  933. parser.Free;
  934. end;
  935. end;
  936. function ReadXMLFile(const AFilename: String) : TXMLDocument;overload;
  937. var
  938. FileStream: TStream;
  939. begin
  940. Result := nil;
  941. FileStream := TFileStream.Create(AFilename, fmOpenRead+fmShareDenyWrite);
  942. try
  943. Result := ReadXMLFile(FileStream);
  944. finally
  945. FileStream.Free;
  946. end;
  947. end;
  948. procedure ParseCollationDocument2(
  949. const AFileName : string;
  950. ACollation : TCldrCollation;
  951. AMode : TCldrParserMode
  952. );
  953. var
  954. doc : TXMLDocument;
  955. begin
  956. doc := ReadXMLFile(AFileName);
  957. try
  958. ParseCollationDocument2(doc,ACollation,AMode);
  959. ACollation.LocalID := ExtractFileName(ChangeFileExt(AFileName,''));
  960. finally
  961. doc.Free();
  962. end;
  963. end;
  964. procedure ParseCollationDocument2(
  965. AStream : TStream;
  966. ACollation : TCldrCollation;
  967. AMode : TCldrParserMode
  968. );
  969. var
  970. doc : TXMLDocument;
  971. begin
  972. doc := ReadXMLFile(AStream);
  973. try
  974. ParseCollationDocument2(doc,ACollation,AMode);
  975. finally
  976. doc.Free();
  977. end;
  978. end;
  979. procedure ParseCollationDocument2(
  980. const AFileName : string;
  981. ACollation : TCldrCollationItem;
  982. AType : string
  983. );
  984. var
  985. doc : TXMLDocument;
  986. begin
  987. doc := ReadXMLFile(AFileName);
  988. try
  989. ParseCollationDocument2(doc,ACollation,AType);
  990. finally
  991. doc.Free();
  992. end;
  993. end;
  994. procedure ParseCollationDocument2(
  995. AStream : TStream;
  996. ACollation : TCldrCollationItem;
  997. AType : string
  998. );
  999. var
  1000. doc : TXMLDocument;
  1001. begin
  1002. doc := ReadXMLFile(AStream);
  1003. try
  1004. ParseCollationDocument2(doc,ACollation,AType);
  1005. finally
  1006. doc.Free();
  1007. end;
  1008. end;
  1009. { TCldrCollationStreamLoader }
  1010. procedure TCldrCollationStreamLoader.CheckContent(ALanguage: string);
  1011. begin
  1012. if not FileExists(ALanguage) then
  1013. raise EFOpenError.CreateFmt(SFOpenError,[ALanguage]);
  1014. end;
  1015. function TCldrCollationStreamLoader.IndexOf(ALanguage: string): Integer;
  1016. var
  1017. i : Integer;
  1018. begin
  1019. for i := Low(FLanguages) to High(FLanguages) do begin
  1020. if (FLanguages[i] = ALanguage) then begin
  1021. Result := i;
  1022. exit;
  1023. end;
  1024. end;
  1025. Result := -1;
  1026. end;
  1027. procedure TCldrCollationStreamLoader.LoadCollation(
  1028. const ALanguage : string;
  1029. ACollation : TCldrCollation;
  1030. AMode : TCldrParserMode
  1031. );
  1032. var
  1033. i : Integer;
  1034. locStream : TStream;
  1035. begin
  1036. i := IndexOf(ALanguage);
  1037. if (i < 0) then
  1038. CheckContent(ALanguage);
  1039. locStream := FStreams[i];
  1040. locStream.Position := 0;
  1041. ParseCollationDocument2(locStream,ACollation,AMode);
  1042. end;
  1043. procedure TCldrCollationStreamLoader.LoadCollationType(
  1044. const ALanguage,
  1045. ATypeName : string;
  1046. AType : TCldrCollationItem
  1047. );
  1048. var
  1049. i : Integer;
  1050. locStream : TStream;
  1051. begin
  1052. i := IndexOf(ALanguage);
  1053. if (i < 0) then
  1054. CheckContent(ALanguage);
  1055. locStream := FStreams[i];
  1056. locStream.Position := 0;
  1057. ParseCollationDocument2(locStream,AType,ATypeName);
  1058. end;
  1059. constructor TCldrCollationStreamLoader.Create(
  1060. const ALanguages : array of string;
  1061. const AStreams : array of TStream
  1062. );
  1063. var
  1064. c, i : Integer;
  1065. begin
  1066. c := Length(ALanguages);
  1067. if (Length(AStreams) < c) then
  1068. c := Length(AStreams);
  1069. SetLength(FLanguages,c);
  1070. SetLength(FStreams,c);
  1071. for i := Low(ALanguages) to High(ALanguages) do begin
  1072. FLanguages[i] := ALanguages[i];
  1073. FStreams[i] := AStreams[i];
  1074. end;
  1075. end;
  1076. destructor TCldrCollationStreamLoader.Destroy();
  1077. var
  1078. i : Integer;
  1079. begin
  1080. for i := Low(FStreams) to High(FStreams) do
  1081. FreeAndNil(FStreams[i]);
  1082. end;
  1083. { TCldrCollationFileLoader }
  1084. procedure TCldrCollationFileLoader.SetPath(APath: string);
  1085. var
  1086. s : string;
  1087. begin
  1088. if (APath = '') then
  1089. s := ''
  1090. else
  1091. s := IncludeTrailingPathDelimiter(APath);
  1092. if (s <> FPath) then
  1093. FPath := s;
  1094. end;
  1095. function TCldrCollationFileLoader.BuildFileName(ALanguage: string): string;
  1096. begin
  1097. Result := Format('%s%s.xml',[FPath,ALanguage]);
  1098. end;
  1099. procedure TCldrCollationFileLoader.CheckFile(AFileName: string);
  1100. begin
  1101. if not FileExists(AFileName) then
  1102. raise EFOpenError.CreateFmt(SFOpenError,[AFileName]);
  1103. end;
  1104. procedure TCldrCollationFileLoader.LoadCollation(
  1105. const ALanguage : string;
  1106. ACollation : TCldrCollation;
  1107. AMode : TCldrParserMode
  1108. );
  1109. var
  1110. locFileName : string;
  1111. begin
  1112. locFileName := BuildFileName(ALanguage);
  1113. CheckFile(locFileName);
  1114. //ACollation.Clear();
  1115. ParseCollationDocument2(locFileName,ACollation,AMode);
  1116. end;
  1117. procedure TCldrCollationFileLoader.LoadCollationType(
  1118. const ALanguage,
  1119. ATypeName : string;
  1120. AType : TCldrCollationItem
  1121. );
  1122. var
  1123. locFileName : string;
  1124. begin
  1125. locFileName := BuildFileName(ALanguage);
  1126. CheckFile(locFileName);
  1127. //AType.Clear();
  1128. ParseCollationDocument2(locFileName,AType,ATypeName);
  1129. end;
  1130. constructor TCldrCollationFileLoader.Create(APath: string);
  1131. begin
  1132. SetPath(APath);
  1133. end;
  1134. end.