Advertisement
Guest User

Untitled

a guest
May 13th, 2013
125
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Pascal 9.52 KB | None | 0 0
  1. Uses SysUtils, Classes, FGL;
  2.  
  3. Type EParserError = Class(Exception);
  4.      EXMLDocumentError = Class(Exception);
  5.  
  6. // -- tokenizer --
  7. Const TokenNewline = #0;
  8.  
  9.       IdentifierValid = ['a'..'z', 'A'..'Z', '_', '0'..'9']; // znaki, które mogą występować *wewnątrz* identyfikatora
  10.       NumberValid = ['0'..'9']; // znaki, które mogą występować wewnątrz liczby
  11.  
  12. Type TTokenKind = (tkBracketOpen, tkBracketClose, tkSlash, tkSpace, tkIdentifier, tkString, tkNumber, tkChar); // typ tokenów
  13. Type PToken = ^TToken; // wskaźnik na token
  14.      TToken = Record // pojedynczy token
  15.                Kind      : TTokenKind;
  16.                Line, Char: Integer;
  17.                Value     : String;
  18.               End;
  19.  
  20. Type TTokenList = specialize TFPGList<PToken>; // lista tokenów
  21.  
  22. Const TokenDisplay: Array[TTokenKind] of String = ('<', '>', '/', ' ', '[identifier]', '[string]', '[number]', '[char]'); // reprezentacja string-owa tokenów
  23.  
  24. Type TScanner = Class // klasa skanera
  25.                  Private
  26.                  Public
  27.                   TokenList: TTokenList;
  28.  
  29.                   Constructor Create;
  30.                   Procedure Parse(const Lines: TStringList);
  31.                  End;
  32.  
  33. (* TScanner.Create *)
  34. Constructor TScanner.Create;
  35. Begin
  36.  TokenList := nil;
  37. End;
  38.  
  39. (* TScanner.Parse *)
  40. Procedure TScanner.Parse(const Lines: TStringList);
  41. Var I : Integer;
  42.     Ch: Char;
  43.  
  44.     Text    : String;
  45.     Position: Integer;
  46.  
  47.     Line, Char: Integer;
  48.  
  49.     { AddToken }
  50.     // dodaje token do listy
  51.     Procedure AddToken(const Kind: TTokenKind; const Value: String);
  52.     Var Pnt: PToken;
  53.     Begin
  54.      New(Pnt);
  55.      Pnt^.Kind  := Kind;
  56.      Pnt^.Char  := Char;
  57.      Pnt^.Line  := Line;
  58.      Pnt^.Value := Value;
  59.      TokenList.Add(Pnt);
  60.     End;
  61.  
  62.     { read_identifier }
  63.     // czyta identyfikator
  64.     Function read_identifier: String;
  65.     Begin
  66.      Result := Ch;
  67.  
  68.      While (Text[Position] in IdentifierValid) Do
  69.      Begin
  70.       Result += Text[Position];
  71.       Inc(Position);
  72.      End;
  73.     End;
  74.  
  75.     { read_number }
  76.     // czyta liczbę
  77.     Function read_number: String;
  78.     Begin
  79.      Result := Ch;
  80.  
  81.      While (Text[Position] in NumberValid) Do
  82.      Begin
  83.       Result += Text[Position];
  84.       Inc(Position);
  85.      End;
  86.     End;
  87.  
  88. Begin
  89.  Text     := '';
  90.  Position := 1;
  91.  
  92.  Line := 1;
  93.  Char := 0;
  94.  
  95.  if (TokenList <> nil) Then
  96.   TokenList.Free;
  97.  TokenList := TTokenList.Create;
  98.  
  99.  For I := 0 To Lines.Count-1 Do // przerabiamy TStringList na String, dodając co każdą linię znak `TokenNewline` (jest to ZNACZNIE wydajniejsze od operaowania na `TStringList.Lines` czy `TStringList.Text`)
  100.   Text += Lines[I]+TokenNewline;
  101.  
  102.  While (Position < Length(Text)) Do // parsujemy tekst
  103.  Begin
  104.   Inc(Char);
  105.   Ch := Text[Position]; // czytamy znak
  106.   Inc(Position);
  107.  
  108.   Case Ch of
  109.    '<': AddToken(tkBracketOpen, Ch);
  110.    '>': AddToken(tkBracketClose, Ch);
  111.    '/': AddToken(tkSlash, Ch);
  112.    ' ': AddToken(tkSpace, Ch);
  113.    'a'..'z', 'A'..'Z', '_': AddToken(tkIdentifier, read_identifier);
  114.    '0'..'9': AddToken(tkNumber, read_number);
  115.  
  116.    // '"': AddToken(tkString, read_string);
  117.    TokenNewline:
  118.    Begin
  119.     Inc(Line);
  120.     Char := 0;
  121.    End;
  122.  
  123.    else
  124.     AddToken(tkChar, Ch); // wszelkie znaki spoza "zakresu ubsługi" dodajemy jako tokeny `tkChar`
  125.   End;
  126.  End;
  127. End;
  128.  
  129. // -- XML --
  130. Type TXMLElement = Class;
  131. Type TXMLElementList = specialize TFPGList<TXMLElement>;
  132.  
  133. Type TXMLElement = Class
  134.                     Private
  135.                     Public
  136.                      Name, Value: String; // nazwa, stringowa wartość
  137.                      // Attributes: Array of Record Name, Value: String; Typ: (tString, tNumber); End;
  138.  
  139.                      Parent: TXMLElement;
  140.                      Children: TXMLElementList;
  141.  
  142.                      Function FindChild(const cName: String): TXMLElement;
  143.                     End;
  144.  
  145. Type TXMLDocument = Class
  146.                      Private
  147.                      Public
  148.                       Root: TXMLElement; // węzeł główny
  149.  
  150.                       Constructor Create;
  151.                       Procedure Parse(const Lines: TStringList);
  152.                       Function FindElement(const Name: String): TXMLElement;
  153.                      End;
  154.  
  155. (* TXMLElement.FindChild *)
  156. Function TXMLElement.FindChild(const cName: String): TXMLElement;
  157. Begin
  158.  For Result in Children Do
  159.   if (Result.Name = cName) Then
  160.    Exit(Result);
  161.  
  162.  Exit(nil);
  163. End;
  164.  
  165. (* TXMLDocument.Create *)
  166. Constructor TXMLDocument.Create;
  167. Begin
  168.  Root := nil;
  169. End;
  170.  
  171. (* TXMLDocument.Parse *)
  172. Procedure TXMLDocument.Parse(const Lines: TStringList);
  173. Var Scanner : TScanner;
  174.     TokenPos: Integer = 0;
  175.  
  176.   { getLine }
  177.   // zwraca aktualny numer linii
  178.   Function getLine: Integer;
  179.   Begin
  180.    Exit(Scanner.TokenList[TokenPos]^.Line);
  181.   End;
  182.  
  183.   { getChar }
  184.   // zwraca aktualny numer znaku
  185.   Function getChar: Integer;
  186.   Begin
  187.    Exit(Scanner.TokenList[TokenPos]^.Char);
  188.   End;
  189.  
  190.   { read }
  191.   // czyta jeden token i zwraca go
  192.   Function read: TToken;
  193.   Begin
  194.    With Scanner do
  195.    Begin
  196.     if (TokenPos >= TokenList.Count) Then
  197.      raise EParserError.CreateFmt('Unexpected end of file at %d: %d', [getLine, getChar]);
  198.  
  199.     Result := TokenList[TokenPos]^;
  200.     Inc(TokenPos);
  201.    End;
  202.   End;
  203.  
  204.   { next }
  205.   // zwraca token znajdujący się zaraz po aktualnym
  206.   // (czyli działa identycznie jak `read`, lecz nie przesuwa wskaźnika)
  207.   Function next(const Add: Integer=0): TToken;
  208.   Begin
  209.    With Scanner do
  210.    Begin
  211.     if (TokenPos+Add >= TokenList.Count) Then
  212.      raise EParserError.CreateFmt('Unexpected end of file at %d: %d', [getLine, getChar]);
  213.  
  214.     Result := TokenList[TokenPos+Add]^;
  215.    End;
  216.   End;
  217.  
  218.   { next_t }
  219.   // działa podobnie jak `next`, lecz zwraca jedynie sam typ tokenu
  220.   Function next_t(const Add: Integer=0): TTokenKind;
  221.   Begin
  222.    Exit(next(Add).Kind);
  223.   End;
  224.  
  225.   { eat }
  226.   // "zjada" token; tj.jeżeli następny token nie jest tym pożądanym, wyświetla błąd
  227.   Procedure eat(const Token: TTokenKind);
  228.   Begin
  229.    if (next_t <> Token) Then
  230.     raise EParserError.CreateFmt('Unexpected token `%s` expecting `%s` at %d: %d', [TokenDisplay[next_t], TokenDisplay[Token], getLine, getChar]);
  231.  
  232.    read;
  233.   End;
  234.  
  235.   { read_ident }
  236.   // czyta identyfikator
  237.   Function read_ident: String;
  238.   Begin
  239.    if (next_t <> tkIdentifier) Then
  240.     raise EParserError.CreateFmt('Unexpected token `%s` expecting identifier at %d: %d', [TokenDisplay[next_t], getLine, getChar]);
  241.  
  242.    Exit(read.Value);
  243.   End;
  244.  
  245.   { read_until_bracket_open }
  246.   // czyta, dopóki nie napotkany zostanie znak `<`
  247.   Function read_until_bracket_open: String;
  248.   Var Token: TToken;
  249.   Begin
  250.    Result := '';
  251.  
  252.    While (true) Do
  253.    Begin
  254.     Token := next;
  255.     if (Token.Kind = tkBracketOpen) Then
  256.      Exit;
  257.  
  258.     Result += Token.Value;
  259.     read;
  260.    End;
  261.   End;
  262.  
  263.   { SkipSpaces }
  264.   // pomija spacje
  265.   Procedure SkipSpaces;
  266.   Begin
  267.    While (next_t = tkSpace) Do
  268.     read;
  269.   End;
  270.  
  271.   { ReadNode }
  272.   // czyta cały węzeł
  273.   Function ReadNode(const Parent: TXMLElement): TXMLElement;
  274.   Var ClosingNodeName: String;
  275.   Begin
  276.    Result          := TXMLElement.Create;
  277.    Result.Parent   := Parent;
  278.    Result.Children := TXMLElementList.Create;
  279.  
  280.    With Scanner do
  281.    Begin
  282.     eat(tkBracketOpen); // `<`
  283.     Result.Name := read_ident; // `[identifier]`
  284.     eat(tkBracketClose); // `>`
  285.  
  286.     SkipSpaces; // pomijamy spacje
  287.  
  288.     if (next_t = tkBracketOpen) Then // jeżeli następny token to `<`...
  289.     Begin
  290.      Result.Value := '';
  291.  
  292.      While (next_t = tkBracketOpen) Do // ... dopóki następny token to `<`, czytamy "dzieci"-węzły
  293.      Begin
  294.       if (next_t(1) = tkSlash) Then // jeżeli napotkamy na `</`, przestajemy czytać węzły dzieci
  295.        Break;
  296.  
  297.       Result.Children.Add(ReadNode(Result)); // parsujemy węzeł-dziecko oraz dodajemy go do listy
  298.       SkipSpaces; // pomijamy spacje
  299.      End;
  300.     End Else
  301.      Result.Value := read_until_bracket_open; // ... w innym wypadku czytamy zawartość węzła
  302.  
  303.     eat(tkBracketOpen); // `<`
  304.     eat(tkSlash); // `/`
  305.     ClosingNodeName := read_ident; // `[identifier]`
  306.     if (AnsiCompareStr(Result.Name, ClosingNodeName) <> 0) Then // porównujemy (węzeł zamykający musi się zgadzać z otwierającym)
  307.      raise EXMLDocumentError.CreateFmt('Unexpected node-closing; expecting `</%s>`, got `</%s>` at %d: %d', [Result.Name, ClosingNodeName, getLine, getChar]);
  308.  
  309.     eat(tkBracketClose); // `>`
  310.    End;
  311.   End;
  312.  
  313. Begin
  314.  Scanner := TScanner.Create;
  315.  
  316.  Try
  317.   Scanner.Parse(Lines);
  318.   Root := ReadNode(nil);
  319.  Finally
  320.   Scanner.Free;
  321.  End;
  322. End;
  323.  
  324. (* TXMLDocument.FindElement *)
  325. Function TXMLDocument.FindElement(const Name: String): TXMLElement;
  326. Begin
  327.  if (Root = nil) Then
  328.   Exit(nil);
  329.  
  330.  if (Root.Name = Name) Then
  331.   Exit(Root) Else
  332.   Exit(Root.FindChild(Name));
  333. End;
  334.  
  335. // -------------------------------------------------------------------------- //
  336. Var Src: TStringList;
  337.     Doc: TXMLDocument;
  338. Begin
  339.  Src := TStringList.Create;
  340.  Doc := TXMLDocument.Create;
  341.  
  342.  Try
  343.   Try
  344.    Src.Add('<root>');
  345.    Src.Add(' <child_1>child 1 value</child_1>');
  346.    Src.Add(' <child_2>child 2 value</child_2>');
  347.    Src.Add(' <child_3>');
  348.    Src.Add('  <subchild_1>subchild 1 value</subchild_1>');
  349.    Src.Add(' </child_3>');
  350.    Src.Add('</root>');
  351.  
  352.    Doc.Parse(Src);
  353.    Writeln(Doc.FindElement('child_2').Value);
  354.    Writeln(Doc.FindElement('child_3').FindChild('subchild_1').Value);
  355.   Except
  356.    On E: Exception Do
  357.     Writeln(E.Message);
  358.   End;
  359.  Finally
  360.   Src.Free;
  361.   Doc.Free;
  362.   Writeln('-- done --');
  363.   Readln;
  364.  End;
  365. End.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement