PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/jvcl/run/JvLinkLabelParser.pas

http://my-chuanqi.googlecode.com/
Pascal | 475 lines | 328 code | 60 blank | 87 comment | 20 complexity | c29cebf142756cddd7bba3ec5c7cbda6 MD5 | raw file
  1. {-----------------------------------------------------------------------------
  2. The contents of this file are subject to the Mozilla Public License
  3. Version 1.1 (the "License"); you may not use this file except in compliance
  4. with the License. You may obtain a copy of the License at
  5. http://www.mozilla.org/MPL/
  6. Software distributed under the License is distributed on an "AS IS" basis,
  7. WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
  8. the specific language governing rights and limitations under the License.
  9. The Original Code is: Parser.pas, released 2002-01-06.
  10. The Initial Developer of the Original Code is David Polberger <dpol att swipnet dott se>
  11. Portions created by David Polberger are Copyright (C) 2002 David Polberger.
  12. All Rights Reserved.
  13. Contributor(s): Cetkovsky
  14. Current Version: 2.00
  15. You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
  16. located at http://jvcl.sourceforge.net
  17. Known Issues:
  18. Please see the accompanying documentation.
  19. Description:
  20. Parser.pas provides both the IParser interface, as well as a class providing
  21. a default implementation. A class implementing IParser is supposed to parse
  22. a string, and return a tree representation represented by a TNodeTree.
  23. Note: Documentation for this unit can be found in Doc\Source.txt and
  24. Doc\Readme.txt!
  25. -----------------------------------------------------------------------------}
  26. // $Id: JvLinkLabelParser.pas 10612 2006-05-19 19:04:09Z jfudickar $
  27. unit JvLinkLabelParser;
  28. {$I jvcl.inc}
  29. interface
  30. uses
  31. {$IFDEF UNITVERSIONING}
  32. JclUnitVersioning,
  33. {$ENDIF UNITVERSIONING}
  34. Classes, SysUtils, Graphics,
  35. JvLinkLabelTree, JvLinkLabelTools;
  36. type
  37. IDynamicNodeHandler = interface
  38. procedure HandleDynamicNode(out Source: string; const Node: TDynamicNode);
  39. end;
  40. IParser = interface
  41. function Parse(const Text: string): TNodeTree; overload;
  42. function Parse(const List: TStringList): TNodeTree; overload;
  43. procedure SetDynamicNodeHandler(Handler: IDynamicNodeHandler);
  44. procedure AddSourceTreeToDynamicNode(const Node: TDynamicNode;
  45. const Source: string);
  46. end;
  47. IElementEnumerator = interface;
  48. TDefaultParser = class(TInterfacedObject, IParser)
  49. private
  50. FEnum: IElementEnumerator;
  51. FDynamicNodeHandler: IDynamicNodeHandler;
  52. procedure ParseNode(const Node: TParentNode);
  53. protected
  54. function GetNodeFromTag(const Tag: string): TNode; virtual;
  55. procedure HandleDynamicTag(const Node: TDynamicNode);
  56. public
  57. procedure SetElementEnumerator(NewEnum: IElementEnumerator);
  58. function Parse(const Text: string): TNodeTree; overload;
  59. function Parse(const List: TStringList): TNodeTree; overload;
  60. procedure SetDynamicNodeHandler(Handler: IDynamicNodeHandler);
  61. procedure AddSourceTreeToDynamicNode(const Node: TDynamicNode;
  62. const Source: string);
  63. end;
  64. TElementKind = (ekBeginTag, ekEndTag, ekString);
  65. TElement = record
  66. Kind: TElementKind;
  67. Text: string;
  68. end;
  69. IElementEnumerator = interface
  70. function PopNextElement: TElement;
  71. function PeekNextElement: TElement;
  72. function IsEndReached: Boolean;
  73. end;
  74. {$IFDEF UNITVERSIONING}
  75. const
  76. UnitVersioning: TUnitVersionInfo = (
  77. RCSfile: '$URL: https://jvcl.svn.sourceforge.net:443/svnroot/jvcl/trunk/jvcl/run/JvLinkLabelParser.pas $';
  78. Revision: '$Revision: 10612 $';
  79. Date: '$Date: 2006-05-19 12:04:09 -0700 (Fri, 19 May 2006) $';
  80. LogPath: 'JVCL\run'
  81. );
  82. {$ENDIF UNITVERSIONING}
  83. implementation
  84. uses
  85. JvResources;
  86. //=== { TElementEnumerator } =================================================
  87. type
  88. TElementEnumerator = class(TInterfacedObject, IElementEnumerator)
  89. private
  90. FText: string;
  91. FPosInText: Integer;
  92. FOldPosInText: Integer; // Used to see whether we should use our cached copy
  93. FCachedElement: TElement;
  94. FNewPosInText: Integer;
  95. function GetNextElement(const IncrementPos: Boolean): TElement;
  96. public
  97. constructor Create(const Text: string);
  98. function PopNextElement: TElement;
  99. function PeekNextElement: TElement;
  100. function IsEndReached: Boolean;
  101. end;
  102. const
  103. OpenTag = '<';
  104. CloseTag = '>';
  105. EndMarker = '/';
  106. constructor TElementEnumerator.Create(const Text: string);
  107. begin
  108. inherited Create;
  109. FPosInText := 1;
  110. FOldPosInText := -1;
  111. FText := Text;
  112. end;
  113. function TElementEnumerator.GetNextElement(const IncrementPos: Boolean): TElement;
  114. function GetElementKind: TElementKind;
  115. var
  116. TempString: string;
  117. begin
  118. TempString := Copy(FText, FPosInText, 2);
  119. if Copy(TempString, 1, 2) = OpenTag + EndMarker then // "</..."
  120. Result := ekEndTag
  121. else
  122. if Copy(TempString, 1, 1) = OpenTag then // "<..."
  123. Result := ekBeginTag
  124. else
  125. Result := ekString;
  126. end;
  127. function GetElementText(const Kind: TElementKind): string;
  128. var
  129. StartPos: Integer;
  130. EndPos: Integer;
  131. Padding: Integer;
  132. procedure FindNewTagPos(const I: Integer);
  133. begin
  134. Inc(StartPos, I); // To get in front of the "<" or "</" character(s)
  135. EndPos := StartPos;
  136. while (EndPos < Length(FText)) and (FText[EndPos] <> CloseTag) do
  137. Inc(EndPos);
  138. Inc(EndPos);
  139. Padding := 1;
  140. end;
  141. begin
  142. StartPos := FPosInText;
  143. EndPos := FPosInText;
  144. Padding := 0;
  145. case Kind of
  146. ekBeginTag:
  147. FindNewTagPos(1);
  148. ekEndTag:
  149. FindNewTagPos(2);
  150. ekString:
  151. while (EndPos <= Length(FText)) and (FText[EndPos] <> OpenTag) do
  152. Inc(EndPos);
  153. end;
  154. Result := Copy(FText, StartPos, (EndPos - StartPos - Padding));
  155. FNewPosInText := EndPos;
  156. end;
  157. begin
  158. if IsEndReached then
  159. raise EParserError.CreateRes(@RsENoMoreElementsToReturn);
  160. if FOldPosInText = FPosInText then // Use cached element
  161. Result := FCachedElement
  162. else
  163. begin
  164. FOldPosInText := FPosInText;
  165. Result.Kind := GetElementKind;
  166. Result.Text := GetElementText(Result.Kind);
  167. FCachedElement := Result;
  168. end;
  169. if IncrementPos then
  170. FPosInText := FNewPosInText;
  171. end;
  172. function TElementEnumerator.IsEndReached: Boolean;
  173. begin
  174. Result := FPosInText > Length(FText);
  175. end;
  176. function TElementEnumerator.PeekNextElement: TElement;
  177. begin
  178. Result := GetNextElement(False);
  179. end;
  180. function TElementEnumerator.PopNextElement: TElement;
  181. begin
  182. Result := GetNextElement(True);
  183. end;
  184. //=== { TDefaultParser } =====================================================
  185. procedure TDefaultParser.AddSourceTreeToDynamicNode(const Node: TDynamicNode;
  186. const Source: string);
  187. var
  188. Parser: TDefaultParser;
  189. Tree: TNodeTree;
  190. I: Integer;
  191. begin
  192. Tree := nil;
  193. try
  194. Parser := TDefaultParser.Create;
  195. try
  196. Tree := Parser.Parse(Source);
  197. finally
  198. Parser.Free;
  199. end;
  200. Tree.Root.OwnsChildren := False;
  201. for I := 0 to Tree.Root.Children.Count - 1 do
  202. Node.AddChild(Tree.Root.Children[I], Tree.Root);
  203. finally
  204. Tree.Free;
  205. end;
  206. end;
  207. function TDefaultParser.GetNodeFromTag(const Tag: string): TNode;
  208. type
  209. TTag = (ttBold, ttItalic, ttUnderline, ttColor,
  210. ttLink, ttLineBreak, ttParagraphBreak, ttDynamic);
  211. var
  212. CurrentTag: TTag;
  213. UnknownTag: Boolean;
  214. //Cetkovsky -->
  215. function GetStringFromTag: string;
  216. begin
  217. if (Pos('=', Tag) > 0) then
  218. Result := Copy(Tag, Pos('=', Tag) + 1, Length(Tag))
  219. else
  220. Result := '';
  221. end;
  222. //<-- Cetkovsky
  223. // Bianconi
  224. function GetColorFromTag: TColor;
  225. var
  226. sVar: string;
  227. begin
  228. Result := clNone;
  229. //Cetkovsky -->
  230. sVar := GetStringFromTag;
  231. //<-- Cetkovsky
  232. try
  233. Result := StringToColor(sVar);
  234. except // Only to avoid raise an exception on invalid color
  235. end;
  236. end;
  237. // End of Bianconi
  238. function GetTagFromString: TTag;
  239. const
  240. TagStrings: array [TTag] of PChar =
  241. ('B',
  242. 'I',
  243. 'U',
  244. 'COLOR=', // Bianconi
  245. // 'LINK',
  246. //Cetkovsky -->
  247. 'LINK=',
  248. //<-- Cetkovsky
  249. 'BR',
  250. 'P',
  251. 'DYNAMIC');
  252. DontCare = 0;
  253. var
  254. S: string;
  255. begin
  256. UnknownTag := False;
  257. // Bianconi
  258. for Result := Low(TTag) to High(TTag) do
  259. begin
  260. S := TagStrings[Result];
  261. if (AnsiUpperCase(Tag) = S) or
  262. // (Copy(AnsiUpperCase(Tag), 1, Length(TagStrings[Result])) = 'COLOR=')
  263. //Cetkovsky -->
  264. //We allow <url> style tag without "="
  265. ((Pos('=', S) > 0) and
  266. ((Copy(AnsiUpperCase(Tag), 1, Length(S) - 1) = Copy(S, 1, Length(S) - 1)))) then
  267. //<-- Cetkovsky
  268. Exit;
  269. end;
  270. //End of Bianconi
  271. Result := TTag(DontCare);
  272. UnknownTag := True;
  273. end;
  274. begin
  275. { Descendant parsers should override this routine, call inherited and add
  276. support for proprietary tags (using custom node objects, which descend from
  277. TNode). Note that appropriate modifications need to be made to the renderer
  278. as well, either by creating a new class which implements the IRenderer
  279. interface, or by extending the TDefaultRenderer class. See this class for
  280. more information. }
  281. CurrentTag := GetTagFromString;
  282. if not UnknownTag then
  283. case CurrentTag of
  284. ttBold:
  285. Result := TStyleNode.Create(fsBold);
  286. ttItalic:
  287. Result := TStyleNode.Create(fsItalic);
  288. ttUnderline:
  289. Result := TStyleNode.Create(fsUnderline);
  290. // Bianconi
  291. ttColor:
  292. Result := TColorNode.Create(GetColorFromTag);
  293. // End of Bianconi
  294. //Cetkovsky -->
  295. ttLink:
  296. Result := TLinkNode.Create(GetStringFromTag);
  297. //<-- Cetkovsky
  298. ttLineBreak:
  299. Result := TActionNode.Create(atLineBreak);
  300. ttParagraphBreak:
  301. Result := TActionNode.Create(atParagraphBreak);
  302. ttDynamic:
  303. Result := TDynamicNode.Create;
  304. else
  305. Result := TUnknownNode.Create(Tag);
  306. end
  307. else
  308. Result := TUnknownNode.Create(Tag);
  309. if CurrentTag = ttDynamic then
  310. HandleDynamicTag(Result as TDynamicNode);
  311. end;
  312. procedure TDefaultParser.HandleDynamicTag(const Node: TDynamicNode);
  313. var
  314. Source: string;
  315. begin
  316. if Assigned(FDynamicNodeHandler) then
  317. begin
  318. FDynamicNodeHandler.HandleDynamicNode(Source, Node);
  319. if Source <> '' then
  320. AddSourceTreeToDynamicNode(Node, Source);
  321. end;
  322. end;
  323. function TDefaultParser.Parse(const List: TStringList): TNodeTree;
  324. begin
  325. Result := Parse(List.Text);
  326. end;
  327. function TDefaultParser.Parse(const Text: string): TNodeTree;
  328. begin
  329. Result := TNodeTree.Create;
  330. FEnum := TElementEnumerator.Create(TStringTools.RemoveCRLF(Text));
  331. try
  332. ParseNode(Result.Root);
  333. finally
  334. FEnum := nil;
  335. end;
  336. end;
  337. procedure TDefaultParser.ParseNode(const Node: TParentNode);
  338. var
  339. Element: TElement;
  340. NewNode: TNode;
  341. function EndReached: Boolean;
  342. begin
  343. Result := FEnum.IsEndReached or (FEnum.PeekNextElement.Kind = ekEndTag);
  344. end;
  345. function IsNodeContainer(const Node: TNode; const Element: TElement): Boolean;
  346. begin
  347. { Returns whether the given node is can contain other elements and thus
  348. descends from TParentNode. Descendants from this class begin with <?> and
  349. end with </?> (for example, <B> and </B>. Nodes that descend from
  350. TActionNode shouldn't be terminated with </?> (for example, <P>). Note
  351. that TDynamicNode is special; while it descends from TParentNode, it never
  352. contains children at parse-time, thus we shouldn't wait for a redundant
  353. </DYNAMIC>. Instead, its contents are supplied before it's rendered by
  354. compiled program code. }
  355. Result := (Element.Kind = ekBeginTag) and
  356. (Node is TParentNode) and not (Node is TDynamicNode);
  357. end;
  358. begin
  359. while not EndReached do
  360. begin
  361. Element := FEnum.PopNextElement;
  362. case Element.Kind of
  363. ekString:
  364. NewNode := TStringNode.Create(Element.Text);
  365. ekBeginTag:
  366. NewNode := GetNodeFromTag(Element.Text);
  367. else
  368. raise EParserError.CreateRes(@RsEUnsupportedState);
  369. end;
  370. if (Node.GetNodeType = ntRootNode) then
  371. Node.AddChild(NewNode, TRootNode(Node))
  372. else
  373. Node.AddChild(NewNode, Node.Root);
  374. if IsNodeContainer(NewNode, Element) then
  375. ParseNode(NewNode as TParentNode);
  376. end;
  377. { When we have reached the end of a tag (</LINK> for example) we don't enter
  378. the main body. We have called FEnum.PeekElement and have determined (in
  379. EndReached in this routine) that the next element to be returned by FEnum.
  380. PopElement will be an end-tag. Thus, we exit this routine and return either
  381. to another copy of ParseNode (if we've been called recursively) or to Parse.
  382. However, if we only check the next element to be returned using PeekElement,
  383. it won't be popped off our "stack", which is what we do here. If we hadn't
  384. popped it here, EndReached would've returned True in all other incarnations
  385. of this routine in the call stack; thus, one single end-tag would've caused
  386. the whole parse process to stop. This is obviously not what we want. }
  387. if not FEnum.IsEndReached then
  388. FEnum.PopNextElement;
  389. end;
  390. procedure TDefaultParser.SetDynamicNodeHandler(
  391. Handler: IDynamicNodeHandler);
  392. begin
  393. FDynamicNodeHandler := Handler;
  394. end;
  395. procedure TDefaultParser.SetElementEnumerator(NewEnum: IElementEnumerator);
  396. begin
  397. if Assigned(NewEnum) then
  398. FEnum := NewEnum;
  399. end;
  400. {$IFDEF UNITVERSIONING}
  401. initialization
  402. RegisterUnitVersion(HInstance, UnitVersioning);
  403. finalization
  404. UnregisterUnitVersion(HInstance);
  405. {$ENDIF UNITVERSIONING}
  406. end.