1 {********************************************************}
2 { TSsParser Component - Parser for Sakura Script }
4 { Copyright (c) 2001-2003 naruto/CANO-Lab }
5 {********************************************************}
12 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
16 // mtTag: Tag. (begins with \)
17 // mtMeta: Meta expression. (begins with %)
18 // mtTagErr: Seems to be a markup error
19 // mtStr: Other normal talk string
20 TSsMarkUpType = (mtTag, mtMeta, mtTagErr, mtStr);
22 // Intercepting the parsing
23 TSsParseEvent = procedure (Sender: TObject; const Script: String;
24 var Len: integer; var MarkType: TSsMarkUpType; var Extra: String) of object;
27 ESsParserError = class(Exception);
29 TSsMarkUp = class(TObject)
34 FMarkUpType: TSsMarkUpType;
35 procedure SetExtra(const Value: String);
37 constructor Create(Pos: integer; MarkUpType: TSsMarkUpType; Str: String; Extra: String = '');
38 property Pos: integer read FPos; //This markup is Pos'th character in InputString
39 property MarkUpType: TSsMarkUpType read FMarkUpType;
40 property Str: String read FStr;
41 property Extra: String read FExtra write SetExtra;
44 TSsParser = class(TComponent)
46 FTagPattern: TStrings; //SakuraScript tag pattern string
47 FMetaPattern: TStrings; //SakuraScript meta expression pattern string
50 FLeaveEscape: boolean;
51 FEscapeInvalidMeta: boolean;
52 FOnParse: TSsParseEvent;
53 procedure SetInputString(const Value: String);
54 function GetCount: integer;
55 function GetExtra(Index: integer): String;
56 function GetMarkUpType(Index: integer): TSsMarkUpType;
57 function GetStr(Index: integer): String;
58 procedure SetExtra(Index: integer; const Value: String);
60 procedure SetMetaPattern(const Value: TStrings);
61 procedure SetTagPattern(const Value: TStrings);
62 procedure SetOnParse(const Value: TSsParseEvent);
63 function GetFirstChar(const Str: String): String;
64 function ChopFirstChar(var Str: String): String;
65 function GetPosition(Index: integer): integer;
69 constructor Create(AOwner: TComponent); override;
70 destructor Destroy; override;
71 function MatchP(PStr, PPattern: PChar): integer;
72 function Match(Str, Pattern: String): integer;
73 function GetParam(Tag: String; const Index: integer): String;
74 function EscapeParam(const Param: String): String;
75 function MarkUpAt(const Pos: integer): integer;
77 property Count: integer read GetCount;
78 property MarkUpType[Index: integer]: TSsMarkUpType read GetMarkUpType;
79 property Str[Index: integer]: String read GetStr; default;
80 property Extra[Index: integer]: String read GetExtra write SetExtra;
81 property Position[Index: integer]: integer read GetPosition;
82 property InputString: String read FInputString write SetInputString;
84 // Script parsing patterns.
85 property TagPattern: TStrings read FTagPattern write SetTagPattern;
86 property MetaPattern: TStrings read FMetaPattern write SetMetaPattern;
87 // Whether to leave escape sequence "\\" and "\%" in mtStr elements
88 property LeaveEscape: boolean read FLeaveEscape write FLeaveEscape
90 // Replace '%' to '\%' if sequence follwing the '%' could not
91 // be parsed as a meta expression
92 property EscapeInvalidMeta: boolean read FEscapeInvalidMeta
93 write FEscapeInvalidMeta default false;
94 // Component users can intercept and handle part of the parsing using this event.
95 property OnParse: TSsParseEvent read FOnParse write SetOnParse;
103 CElementTypeError = '
\90³
\82µ
\82¢
\83G
\83\8c\83\81\83\93\83g
\83^
\83C
\83v
\82ð
\95Ô
\82µ
\82Ä
\82
\82¾
\82³
\82¢';
104 CTagPatternSyntaxError = 'TagPattern
\95\
\8bL
\83~
\83X %d
\8ds
\96Ú';
105 CMarkupIndexOutofRange = '
\83C
\83\93\83f
\83b
\83N
\83X
\82ª
\94Í
\88Í
\82ð
\82±
\82¦
\82Ä
\82¢
\82Ü
\82·';
108 // English error message
109 CElementTypeError = 'Returned element type is invalid in OnParse.';
110 CTagPatternSyntaxError = 'TagPattern syntax error at line %d.';
111 CMarkupIndexOutofRange = 'Markup index if out of range.';
117 RegisterComponents('Samples', [TSsParser]);
122 procedure TSsParser.BeginParse;
123 var Str, Talk, T, Ex: String;
130 Str := FInputString; // The string to be parsed from now
131 // This is to avoid access violation if `Str` is terminated
132 // with DBCS leadbyte. (Such string is invalid from the beginning of course)
134 if Length(Str) = 0 then Exit;
137 while PStr^ <> #0 do begin
138 if PStr^ = '\' then begin
142 // Escaped sequence "\\"
149 end else if PStr^ = '%' then
151 // Escaped sequence "\%"
162 // Generate OnParser event
166 if Assigned(FOnParse) then begin
167 FOnParse(Self, String(PStr), Le, Mt, Ex);
169 if (Mt <> mtTag) and (Mt <> mtTagErr) then begin
170 raise ESsParserError.Create(CElementTypeError);
173 if Mt = mtTagErr then IsErr := true;
176 if Le <= 0 then begin
177 for i := 0 to FTagPattern.Count-1 do begin
179 if Length(T) = 0 then Continue;
181 if T[1] = '!' then begin
184 end else if T[1] <> '\' then
185 raise ESsParserError.CreateFmt(CTagPatternSyntaxError, [i+1]);
186 Le := MatchP(PStr, PChar(T));
187 if Le > 0 then Break;
190 if Length(Talk) > 0 then begin
191 FMarkUpList.Add(TSsMarkUp.Create(PStr-PChar(Str)-Length(Talk), mtStr, Talk));
195 // Parsed as a correct tag
196 T := Copy(String(PStr), 1, Le);
198 FMarkUpList.Add(TSsMarkUp.Create(PStr-PChar(Str), mtTagErr, T, Ex))
200 FMarkUpList.Add(TSsMarkUp.Create(PStr-PChar(Str), mtTag, T, Ex));
203 // Parsing failed; The character '\' and the next one character is
204 // marked as a tag error.
205 Inc(PStr); // Skip '\'
206 if PStr^ in LeadBytes then
208 T := '\' + Copy(String(PStr), 1, 2);
209 FMarkUpList.Add(TSsMarkUp.Create(PStr-PChar(Str)-1, mtTagErr, T));
214 FMarkUpList.Add(TSsMarkUp.Create(PStr-PChar(Str)-1, mtTagErr, T));
219 end else if (PStr^ = '%') then begin
221 if Assigned(FOnParse) then begin
222 FOnParse(Self, String(PStr), Le, Mt, Ex);
224 if Mt <> mtMeta then begin
225 raise ESsParserError.Create(CElementTypeError);
231 Inc(PStr); // Skip '%'
234 for i := 0 to FMetaPattern.Count-1 do
236 if Length(FMetaPattern[i]) = 0 then
238 Le := MatchP(PStr, PChar(FMetaPattern[i]));
245 if Length(Talk) > 0 then
247 FMarkUpList.Add(TSsMarkUp.Create(PStr-PChar(Str)-Length(Talk), mtStr, Talk));
250 T := Copy(String(PStr), 1, Le);
251 FMarkUpList.Add(TSsMarkUp.Create(PStr-PChar(Str)-1, mtMeta, '%' + T, Ex));
255 // in case this %??? sequence seems NOT be a meta expression
256 if FEscapeInvalidMeta then
263 if PStr^ in LeadBytes then
265 Talk := Talk + Copy(String(PStr), 1, 2);
269 Talk := Talk + PStr^;
274 if Length(Talk) > 0 then FMarkUpList.Add(TSsMarkUp.Create(PStr-PChar(Str)-Length(Talk), mtStr, Talk));
277 function TSsParser.ChopFirstChar(var Str: String): String;
279 Result := GetFirstChar(Str);
280 Delete(Str, 1, Length(Result));
283 procedure TSsParser.ClearList;
286 for i := FMarkUpList.Count-1 downto 0 do begin
287 TSsMarkUp(FMarkUpList[i]).Free;
290 FMarkUpList := TList.Create;
293 constructor TSsParser.Create(AOwner: TComponent);
296 FTagPattern := TStringList.Create;
297 FMetaPattern := TStringList.Create;
298 FMarkUpList := TList.Create;
299 FLeaveEscape := true;
302 destructor TSsParser.Destroy;
311 function TSsParser.EscapeParam(const Param: String): String;
313 //StringReplace supports DBCS
314 Result := StringReplace(Param, '\', '\\', [rfReplaceAll]);
315 Result := StringReplace(Param, ']', '\]', [rfReplaceAll]);
318 function TSsParser.GetCount: integer;
320 Result := FMarkUpList.Count;
323 function TSsParser.GetExtra(Index: integer): String;
325 if (Index >= 0) and (Index <= FMarkUpList.Count-1) then
326 Result := TSsMarkUp(FMarkUpList[Index]).Extra
331 function TSsParser.GetFirstChar(const Str: String): String;
333 // Get the first character of the given string. Supports DBCS
334 if Length(Str) <= 1 then begin
337 if Str[1] in LeadBytes then begin
338 Result := Str[1] + Str[2];
345 function TSsParser.GetMarkUpType(Index: integer): TSsMarkUpType;
347 if (Index >= 0) and (Index <= FMarkUpList.Count-1) then
348 Result := TSsMarkUp(FMarkUpList[Index]).MarkUpType
350 raise ESsParserError.Create(CMarkupIndexOutOfRange);
353 function TSsParser.GetParam(Tag: String;
354 const Index: integer): String;
355 var ParamCount: integer;
356 First, Param: String;
357 Escape, Inside: boolean;
359 if Index <= 0 then Exit;
364 First := ChopFirstChar(Tag);
367 if First = '\' then Param := Param + '\'
368 else if First = ']' then Param := Param + ']'
369 else Param := Param + '\' + First;
371 end else if First = '\' then
373 else if First = ']' then
377 Param := Param + First;
379 end else if First = '[' then begin
385 until (First = '') or ((ParamCount = Index) and not Inside);
386 if ((ParamCount = Index) and not Inside) then
392 function TSsParser.GetPosition(Index: integer): integer;
394 if (Index >= 0) and (Index <= FMarkUpList.Count-1) then
395 Result := TSsMarkUp(FMarkUpList[Index]).Pos
400 function TSsParser.GetStr(Index: integer): String;
402 if (Index >= 0) and (Index <= FMarkUpList.Count-1) then
403 Result := TSsMarkUp(FMarkUpList[Index]).Str
408 function TSsParser.MarkUpAt(const Pos: integer): integer;
409 var i, lo, hi: integer;
411 if FMarkUpList.Count = 0 then
415 hi := FMarkUpList.Count-2;
416 i := (hi-lo) div 2 + lo;
419 i := (hi-lo) div 2 + lo;
420 if (TSsMarkUp(FMarkUpList[i]).Pos >= Pos) and
421 (TSsMarkUp(FMarkUpList[i+1]).Pos < Pos) then
425 end else if TSsMarkUp(FMarkUpList[i]).Pos > Pos then
434 function TSsParser.Match(Str, Pattern: String): integer;
436 if (Length(Str) = 0) or (Length(Pattern) = 0) then
439 Result := MatchP(@Str[1], @Pattern[1]);
442 function TSsParser.MatchP(PStr, PPattern: PChar): integer;
443 var Matched, F, Escape: boolean;
447 while Matched and (PPattern^ <> #0) do begin
448 if PPattern^ = '%' then
472 if PStr^ in ['0' .. '9'] then
482 if PStr^ in ['0' .. '9'] then
484 while PStr^ in ['0' .. '9'] do
493 'b': //String enclosed by '[' and ']'. The content may be an empty string.
501 Escape := false; //After escape character
510 if PStr^ = '\' then Escape := true;
511 if PStr^ = ']' then F := true;
513 if PStr^ in LeadBytes then
522 until (PStr^ = #0) or F;
528 'c': // String which can be the argument content enclosed by '[' and ']'
531 if not (PStr^ = ']') then
537 else if PStr^ = ']' then
540 if PStr^ = '\' then Escape := true;
541 if PStr^ in LeadBytes then
556 if not (PStr^ in LeadBytes) then
561 end else Matched := false;
565 if (PStr^ in LeadBytes) then
570 end else Matched := false;
573 if (PStr^ in LeadBytes) then
596 if PStr^ <> PPattern^ then
606 if not Matched then Result := 0;
609 procedure TSsParser.SetExtra(Index: integer; const Value: String);
611 if (Index >= 0) and (Index <= FMarkUpList.Count-1) then
612 TSsMarkUp(FMarkUpList[Index]).Extra := Value
614 raise ESsParserError.Create(CMarkupIndexOutOfRange);
617 procedure TSsParser.SetInputString(const Value: String);
619 FInputString := Value;
623 procedure TSsParser.SetMetaPattern(const Value: TStrings);
625 FMetaPattern.Assign(Value);
628 procedure TSsParser.SetOnParse(const Value: TSsParseEvent);
633 procedure TSsParser.SetTagPattern(const Value: TStrings);
635 FTagPattern.Assign(Value);
640 constructor TSsMarkUp.Create(Pos: integer; MarkUpType: TSsMarkUpType; Str: String; Extra: String = '');
643 FMarkUpType := MarkUpType;
648 procedure TSsMarkUp.SetExtra(const Value: String);