AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

QuoteText parsen?

Ein Thema von Bjoerk · begonnen am 5. Nov 2014 · letzter Beitrag vom 12. Nov 2014
Antwort Antwort
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#1

AW: QuoteText parsen?

  Alt 5. Nov 2014, 18:23
Nur so zum Starten ein kleiner Parser (als State-Machine) quick und dirty.
Der kann auf jeden Fall das hier produzieren
dp_182623.PNG
Delphi-Quellcode:
unit Parser.BBCode;

interface

uses
  Vcl.Graphics, System.SysUtils, System.Generics.Collections;

type
  TTextPart = class
  private
    FFont: TFont;
    FText: string;
  public
    constructor Create( AFont: TFont; const AText: string );
    property Font: TFont read FFont;
    property Text: string read FText;
  end;

  TBBCodeParser = class
  private type
    TState = procedure( AChar: Char ) of object;
  private
    FParts: TList<TTextPart>;
    FCommandStack: TList<string>;
    FDefaultFont: TFont;
    FState: TState;
    FFont: TFont;
    FTextBuffer: TStringBuilder;
    FCommandBuffer: TStringBuilder;
    procedure InitParser;
    procedure ParseText( AChar: Char );
    procedure ParseCommand( AChar: Char );
    procedure ParseCloseCommand( AChar: Char );
    procedure HandleTextBuffer;
    procedure HandleCommandBuffer( Closing: Boolean = False );
  public
    constructor Create( ADefaultFont: TFont );
    destructor Destroy; override;

    procedure Parse( AText: string );
    property Parts: TList<TTextPart> read FParts;
  end;

implementation

uses
  System.StrUtils;

{ TBBCodeParser }

constructor TBBCodeParser.Create( ADefaultFont: TFont );
begin
  inherited Create;
  FDefaultFont := ADefaultFont;
  FFont := TFont.Create;
  FTextBuffer := TStringBuilder.Create;
  FCommandBuffer := TStringBuilder.Create;
  FParts := TObjectList<TTextPart>.Create;
  FCommandStack := TList<string>.Create;
end;

destructor TBBCodeParser.Destroy;
begin
  FFont.Free;
  FTextBuffer.Free;
  FCommandBuffer.Free;
  FParts.Free;
  FCommandStack.Free;
  inherited;
end;

procedure TBBCodeParser.HandleCommandBuffer( Closing: Boolean );
const
  FontStyle: array [TFontStyle] of string = ( 'fsBold', 'fsItalic', 'fsUnderline', 'fsStrikeOut' );
var
  LCommand: string;
  LIdx: Integer;
begin
  LCommand := FCommandBuffer.ToString;
  FCommandBuffer.Clear;

  if Closing
  then
    begin
      // von hinten aus dem Stack nehmen
      for LIdx := FCommandStack.Count - 1 downto 0 do
        if FCommandStack[LIdx] = LCommand
        then
          begin
            FCommandStack.Delete( LIdx );
            Break;
          end;
    end
  else
    // Einfach an den Stack anhängen
    FCommandStack.Add( LCommand );

  // Font einstellen
  FFont.Assign( FDefaultFont );

  for LCommand in FCommandStack do
    begin
      LIdx := IndexText( LCommand, FontStyle );
      if LIdx >= 0
      then
        FFont.Style := FFont.Style + [TFontStyle( LIdx )];

      if LCommand.StartsWith( 'cl', True )
      then
        FFont.Color := StringToColor( LCommand );
    end;
end;

procedure TBBCodeParser.HandleTextBuffer;
begin
  if FTextBuffer.Length > 0
  then
    begin
      FParts.Add( TTextPart.Create( FFont, FTextBuffer.ToString ) );
      FTextBuffer.Clear;
    end;
end;

procedure TBBCodeParser.InitParser;
begin
  FFont.Assign( FDefaultFont );
  FTextBuffer.Clear;
  FCommandBuffer.Clear;
  FCommandStack.Clear;
  FParts.Clear;
  FState := ParseText;
end;

procedure TBBCodeParser.Parse( AText: string );
var
  LChar: Char;
begin
  InitParser;
  for LChar in AText do
    FState( LChar );
  HandleTextBuffer;
end;

procedure TBBCodeParser.ParseCloseCommand( AChar: Char );
begin
  case AChar of
    ']':
      begin
        HandleTextBuffer;
        HandleCommandBuffer( True );
        FState := ParseText;
      end;
  else
    FCommandBuffer.Append( AChar );
  end;
end;

procedure TBBCodeParser.ParseCommand( AChar: Char );
begin
  case AChar of
    ']':
      begin
        HandleTextBuffer;
        HandleCommandBuffer( False );
        FState := ParseText;
      end;
    '/':
      FState := ParseCloseCommand;
  else
    FCommandBuffer.Append( AChar );
  end;
end;

procedure TBBCodeParser.ParseText( AChar: Char );
begin
  case AChar of
    '[':
      FState := ParseCommand;
  else
    FTextBuffer.Append( AChar );
  end;
end;

{ TTextPart }

constructor TTextPart.Create( AFont: TFont; const AText: string );
begin
  inherited Create;
  FFont := TFont.Create;
  FFont.Assign( AFont );
  FText := AText;
end;

end.
UPDATE
So mit der kleinen Änderung (CommandStack) können jetzt auch verschachtelte Commands verarbeitet werden.
So z.B.
Code:
normal [clRed]rot [clGreen]grün [/clGreen]wieder rot [/clRed]normal
normal [fsBold]fett [fsItalic]fett-kursiv [fsStrikeOut]fett-kursiv-durchgestrichen [fsUnderline]fett-kursiv-durchgestrichen-unterstrichen [/fsBold]kursiv-durchgestrichen-unterstrichen [/fsItalic]durchgestrichen-unterstrichen [/fsStrikeOut]unterstrichen [/fsUnderline]normal
Ist dann das gleiche Ergebnis:
Zitat:
normal rot grün wieder rot normal
normal fett fett-kursiv fett-kursiv-durchgestrichen fett-kursiv-durchgestrichen-unterstrichen kursiv-durchgestrichen-unterstrichen durchgestrichen-unterstrichen unterstrichen normal
wie hier mit den BBCodes
Code:
normal [COLOR="Red"]rot [COLOR="Green"]grün [/COLOR]wieder rot [/COLOR]normal
normal [B]fett [I]fett-kursiv [S]fett-kursiv-durchgestrichen [U]fett-kursiv-durchgestrichen-unterstrichen [/U][/S][/I][/B][I][S][U]kursiv-durchgestrichen-unterstrichen [/U][/S][/I][S][U]durchgestrichen-unterstrichen [/U][/S][U]unterstrichen [/U]normal
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)

Geändert von Sir Rufo ( 5. Nov 2014 um 18:51 Uhr)
  Mit Zitat antworten Zitat
Bjoerk

Registriert seit: 28. Feb 2011
Ort: Mannheim
1.384 Beiträge
 
Delphi 10.4 Sydney
 
#2

AW: QuoteText parsen?

  Alt 5. Nov 2014, 20:52
Thanx. Wie immer genial deine Posts. Aber, den Code hab ich fast Null verstanden. Und Generics und TStringBuilder hab ich nich..
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

AW: QuoteText parsen?

  Alt 5. Nov 2014, 21:33
Das du das nicht hast weiß ich, aber du willst ja auch etwas selber machen

Eigentlich sind diese State-Machines als Parser super simpel zu erstellen:
  • Ein Parser geht Zeichen für Zeichen durch den String -> Wir brauchen eine Methode die einen Char entgegennimmt. Da aber je nach Status eine andere Methode benötigt wird brauchen wir erst mal so einen Methodentyp TState = procedure(AChar:Char) of object;
  • Die erste Methode ist ganz simpel, es wird einfach jedes Zeichen an den TextBuffer gehängt (das kann ein einfacher String sein, ein TStringBuilder ist dafür nur schneller, mehr nicht). Nennen wir die einfach mal ParseText . Trifft man hier nun auf das Zeichen '[' , dann kommt jetzt ein Kommando. Also müssen wir ab nun das aus einer anderen Sicht betrachten, denn der Status hat sich geändert. Wir parsen jetzt ein Kommando, also eine weitere Methode ParseCommand
  • In parseCommand werden alle eingehenden Zeichen an den CommandBuffer gehängt, bis wir auf das Zeichen ']' treffen.
  • Für weitere Stati verfährt man genauso und baut diese Schritt für Schritt auf.
Eigentlich ist es ein ganz stupides Abarbeiten der Vorgaben. Interessant wird dann der Teil, wo die Commands in den CommandStack ein- und ausgetragen werden und darüber der aktuelle Font "errechnet" wird.

Wichtig: Vor jeder Änderung durch ein Command muss der aktuelle TextBuffer mit dem aktuellen Font gespeichert werden (wenn der TextBuffer leer ist, dann brauchen wir auch nichts speichern).

Nimm dir am Besten einen kurzen Text
Code:
a[b]c[d]e[/b]f[/d]g
und gehe den Code auf dem Papier durch, dann sollte auch die Erkenntnis kommen
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
Jens01

Registriert seit: 14. Apr 2009
673 Beiträge
 
#4

AW: QuoteText parsen?

  Alt 5. Nov 2014, 22:18
Meinst Du das?
Das Ding hatte ich auch noch ein Stück bei mir erweitert, da ich es aber nicht mehr benötigte, habe ich es weggetan und finde es gerade nicht.
Achtung: Bin kein Informatiker sondern komme vom Bau.
  Mit Zitat antworten Zitat
Dejan Vu
(Gast)

n/a Beiträge
 
#5

AW: QuoteText parsen?

  Alt 6. Nov 2014, 07:34
Ein Parser geht Zeichen für Zeichen durch den String
Das ist -streng genommen- nicht richtig. Ein Parser geht Token für Token durch eine Eingabe und prüft die Syntax einer Sprache bzw. erstellt einen Syntaxbaum. Ein Scanner/Tokenizer geht Zeichen für Zeichen durch einen String und erstellt Token anhand des Alphabets der zu scannenden Eingabe. Das Alphabet besteht hier aus normalen Zeichen, sowie den Sonderzeichen '[','/' und ']'. Dies sind gleichzeitig die Terminalsymbole. Man könnte die Terminalsymbole auch als '[','[/' und ']' definieren (ein Symbol ist 2 Zeichen lang). Dann würde der Scanner mit einem Lookahead arbeiten, aber es macht die Sache einfacher. Ähnliches haben wir bei Delphi mit den Symbolen ':=', '..' '(*' , '*)' usw.

Der Scanner erkennt meist mit Hilfe eines Automaten (->State machine) die einzelnen Token. Die Token sind hier: CHARS, BEGIN-GROUP, GROUPNAME, END-GROUP.

Der Parser prüft nun, ob die vom Scanner gelieferten Token der zu prüfenden Grammatik entsprechen. Hier z.B.:
Code:
BBText  ::= <Literals> [<BBText>]
Literals ::= CHARS | <Group>
Group   ::= <BeginGroup> <BBText> <EndGroup>
BeginGroup ::= BEGIN-GROUP GROUPNAME
EndGroup ::= END-GROUP GROUPNAME
Während des Erkennens kann ein Syntaxbaum erstellt werden. Dieser Syntaxbaum kann Grundlage für einen Compiler sein, der die einzelnen Knoten des Baumes in die Zielsprache übersetzt. Hier wäre die Zielsprache eine Liste von 'TTextPart' Einträgen. Eine andere Zielsprache könnte 'RTF' sein. Oder 'DOCX', oder z.B. formatierter Quelltext etc.

Viele Parser spannen den Baum nicht mehr explizit auf (obwohl damit Codepfade analysiert werden können, Stichwort: Die Variable 'X' ist wahrscheinlich nicht initialisiert, Rückgabewert undefiniert etc.), sondern spucken direkt das Compilat aus.

Das alles hat Sir Rufo in die Klasse gepackt, alles andere wäre ein wenig oversized. Und da die Sprache so einfach ist, spannt er einen eindimensionalen Baum auf: Einen Stack.

Übrigens: Es gibt Programme (Compilergeneratoren), denen gibt man die Grammatik der Zielsprache sowie reguläre Ausdrücke zum Erkennen der Token und die spucken dann einen fertigen und garantiert fehlerfreien Parser aus. Füttert man den Generator noch mit Übersetzungsregeln, hat man einen Compiler.

Geändert von Dejan Vu ( 6. Nov 2014 um 07:49 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 06:48 Uhr.
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz