Delphi-PRAXiS
Seite 2 von 2     12   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   RegEx Frage (https://www.delphipraxis.net/187946-regex-frage.html)

hansklok 4. Feb 2016 20:12

AW: RegEx Frage
 
Zitat:

Zitat von SProske (Beitrag 1329453)
Hast du Möglichkeit, das ganze nachzubearbeiten?

Also einfach die @s wegzudoktorn, wenn in der Gruppe davor eines der Signalwörter auftritt? Das sollte wesentlich einfacher gehen, als das Regex so anzupassen, dass die Signalwörter berücksichtigt werden und dann eine Fallunterscheidung zu machen.

Was sollte denn passieren bei:

Code:
1 NAME @Max /Mustermann/@
1 FAMC I123
1 FAMC @I123
1 FAMC I123@
1 FAMC @I@12@3@

Es gibt einen Standard und da ist eindeutig definiert, dass nach FAMS, FAMC und CHIL ein von @-Zeichen eingeschlossener Wert folgen muss.

SProske 4. Feb 2016 21:14

AW: RegEx Frage
 
So langsam wird es kompliziert :)

Probier mal:

Code:
(0|[1-9][\d]*)\h+(?:@?((?<=@)[^@]+(?=@)|(?!@))(?:@\h+)?)(?<Special>FAM[SC]|CHIL)?(?(Special)|(?<NoSpecial>\w+))(?(Special)\h+@([^\v]*|)@|\h*([^\v]*|))
Im Prinzip prüfe ich, ob es einen deiner Specialfälle gibt, dann wird der gematcht - sonst das normale Verfahren. Und das nochmal für den Inhalt. Leider erzeugt Delphi im Match-Objekt einige eigentlich nicht vorhandene Gruppen - ich wüsste auch nicht wie ich die wegkriege. Deswegen mal zur Auswertung ein kleines Konsolenprogramm.

Zum selber durchgucken: https://regex101.com/r/gL9pX8/3

Delphi-Quellcode:
program Project1;

{$APPTYPE CONSOLE}

uses
  System.SysUtils, RegularExpressions;

const
  Sample = '0 @Reference@ Objekt' + sLineBreak +
           '1 NAME Max /Mustermann/' + sLineBreak +
           '2 DATE 22 APR 2016' + sLineBreak +
           '1 FAMC @I123@' + sLineBreak +
           '1 FAMS @I124@' + sLineBreak +
           '1 CHIL @I125@';
var
  Match: TMatch;
begin
  try
    Match := TRegEx.Match(Sample, '(0|[1-9][\d]*)\h+(?:@?((?<=@)[^@]+(?=@)|(?!@))(?:@\h+)?)(?<Special>FAM[SC]|CHIL)?(?(Special)|(?<NoSpecial>\w+))(?(Special)\h+@([^\v]*|)@|\h*([^\v]*|))');
    while Match.Success do
    begin
      if Match.Groups.Count = 7 then
      begin
        WriteLn('Number:' + Match.Groups[1].Value);
        WriteLn('Reference:' + Match.Groups[2].Value);
        WriteLn('Object:' + Match.Groups[4].Value);
        WriteLn('Content:' + Match.Groups[6].Value);
      end else if Match.Groups.Count = 6 then
      begin
        WriteLn('Number:' + Match.Groups[1].Value);
        WriteLn('Reference:' + Match.Groups[2].Value);
        WriteLn('Object:' + Match.Groups[3].Value);
        WriteLn('Content:' + Match.Groups[5].Value);
      end else
        WriteLn('Das ist anders :(:' + Match.Groups[0].Value);
      WriteLn('');
      Match := Match.NextMatch;
    end;
    ReadLn;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

hansklok 5. Feb 2016 00:22

AW: RegEx Frage
 
Wow, danke! Wenn es nicht kompliziert wäre, hätte ich die Frage eh nicht ins Forum geworfen ;)

Im Prinzip erfüllt das Pattern ja seine Sache :D

Also nochmal zum Aufbau:
Code:
Level (Reference) Tag (Value)
Level = Zahl 0-99
Reference = von @-Zeichen umschlossener Wert
Tag = einzelnes Wort, wobei die Wörter FAMC FAMS und CHIL eine Auswirkung auf Value, die dann ebenfalls von @-Zeichen umgeben ist haben
Value = Rest der Zeile, abhängig vom Tag

Die eingeklammerten Werte sind optional, ABER ich hätte gern immer genau 4 Match-SubStrings, also auch NIL Matches sind erlaubt. Das macht es einfacher für mich die Daten weiterzuverarbeiten.

Das Pattern
Code:
(?x)
(0|[1-9][\d]*)\h+
(?:@?((?<=@)[^@]+(?=@)|(?!@))(?:@\h+)?)
(?<Special>FAM[SC]|CHIL)?
(?(Special)|(?<NoSpecial>\w+))
(?(Special)\h+@([^\v]*|)@|\h*([^\v]*|))
hat aber bis zu 6 Match-SubStrings

Also sollte folgendes rauskommen:
Code:
1 = Level
2 = (Reference)
3 = Tag (Special/NoSpecial)
4 = Value

Sir Rufo 5. Feb 2016 00:58

AW: RegEx Frage
 
Wenn du einen Parser für eine Zeile schreiben würdest, hättest du zuverlässigere Ergebnisse und wärst wohl schon fertig. :stupid:

Hier mal so ein Parser mit der stateless Bibliothek
Delphi-Quellcode:
unit Unit2;

interface

uses
  System.SysUtils,
  stateless;

type
  TRow = record
  public
    Level   : Integer;
    Reference: string;
    Tag     : string;
    Value   : string;
    RefValue : string;
  public
    class function Parse( const AStr: string ): TRow; static;
    function ToString( ): string;
  end;

implementation

uses
  System.StrUtils;

class function TRow.Parse( const AStr: string ): TRow;
type
{$SCOPEDENUMS ON}
  TParserState   = ( Level, ReferenceOrTag, Reference, TagStart, Tag, ValueOrRefValue, Value, RefValue, Finished, Error );
  TParseerTrigger = ( ParseChar, ParseFinish );
{$SCOPEDENUMS OFF}
  TRowParserSM = TStateMachine<TParserState, TParseerTrigger>;
var
  sm            : TRowParserSM;
  pc            : TRowParserSM.TTriggerWithParameters<Char>;
  buffer        : string;
  c             : Char;
  res           : TRow;
  errorTransition: TRowParserSM.TTransition;
begin
  buffer := '';
  sm    := TRowParserSM.Create( TParserState.Level );
  try
    pc := sm.SetTriggerParameters<Char>( TParseerTrigger.ParseChar );

{$REGION 'Configuration'}
    { Level }

    sm.Configure( TParserState.Level )
    {} .OnEntryFrom<Char>( pc,
      procedure( const c: Char; const t: TRowParserSM.TTransition )
      begin
        if t.IsReentry
        then
          buffer := buffer + c;
      end )
    {} .OnExit(
      procedure( const t: TRowParserSM.TTransition )
      begin
        if not t.IsReentry
        then
          begin
            res.Level := StrToInt( buffer );
            buffer := '';
          end;
      end )
    {} .Permit( TParseerTrigger.ParseFinish, TParserState.Error )
    {} .PermitDynamic<Char>( pc,
      function( const c: Char ): TParserState
      begin
        case c of
          '0' .. '9':
            Result := TParserState.Level;
          ' ':
            begin
              if buffer.IsEmpty
              then
                Result := TParserState.Error
              else
                begin
                  Result := TParserState.ReferenceOrTag;
                end;
            end;
        else
          Result := TParserState.Error;
        end;
      end );

    { ReferenceOrTag }

    sm.Configure( TParserState.ReferenceOrTag )
    {} .Permit( TParseerTrigger.ParseFinish, TParserState.Error )
    {} .PermitDynamic<Char>( pc,
      function( const c: Char ): TParserState
      begin
        case c of
          '@':
            Result := TParserState.Reference;
          ' ':
            Result := TParserState.ReferenceOrTag;
          'A' .. 'Z':
            Result := TParserState.Tag;
        else
          Result := TParserState.Error;
        end;
      end );

    { Reference }

    sm.Configure( TParserState.Reference )
    {} .OnEntryFrom<Char>( pc,
      procedure( const c: Char; const t: TRowParserSM.TTransition )
      begin
        if t.IsReentry
        then
          buffer := buffer + c;
      end )
    {} .OnExit(
      procedure( const t: TRowParserSM.TTransition )
      begin
        if not t.IsReentry
        then
          begin
            res.Reference := buffer;
            buffer := '';
          end;
      end )
    {} .Permit( TParseerTrigger.ParseFinish, TParserState.Error )
    {} .PermitDynamic<Char>( pc,
      function( const c: Char ): TParserState
      begin
        case c of
          '@':
            if buffer.IsEmpty
            then
              Result := TParserState.Error
            else
              Result := TParserState.TagStart;
        else
          Result := TParserState.Reference;
        end;
      end );

    { TagStart }

    sm.Configure( TParserState.TagStart )
    {} .Permit( TParseerTrigger.ParseFinish, TParserState.Error )
    {} .PermitDynamic<Char>( pc,
      function( const c: Char ): TParserState
      begin
        case c of
          ' ':
            Result := TParserState.TagStart;
          'A' .. 'Z':
            Result := TParserState.Tag;
        else
          Result := TParserState.Error;
        end;
      end );

    { Tag }

    sm.Configure( TParserState.Tag )
    {} .OnEntryFrom<Char>( pc,
      procedure( const c: Char )
      begin
        buffer := buffer + c;
      end )
    {} .OnExit(
      procedure( const t: TRowParserSM.TTransition )
      begin
        if not t.IsReentry
        then
          begin
            res.Tag := buffer;
            buffer := '';
          end;
      end )
    {} .Permit( TParseerTrigger.ParseFinish, TParserState.Finished )
    {} .PermitDynamic<Char>( pc,
      function( const c: Char ): TParserState
      begin
        case c of
          'A' .. 'Z':
            if buffer.Length >= 4
            then
              Result := TParserState.Error
            else
              Result := TParserState.Tag;
          ' ':
            Result := TParserState.ValueOrRefValue;
        else
          Result := TParserState.Error;
        end;
      end );

    { ValueOrRefValue }

    sm.Configure( TParserState.ValueOrRefValue )
    {} .Permit( TParseerTrigger.ParseFinish, TParserState.Finished )
    {} .PermitDynamic<Char>( pc,
      function( const c: Char ): TParserState
      begin
        case c of
          '@':
            Result := TParserState.RefValue;
          ' ':
            Result := TParserState.ValueOrRefValue;
        else
          Result := TParserState.Value;
        end;
      end );

    { Value }

    sm.Configure( TParserState.Value )
    {} .OnEntryFrom<Char>( pc,
      procedure( const c: Char; const t: TRowParserSM.TTransition )
      begin
        buffer := buffer + c;
      end )
    {} .OnExit(
      procedure( const t: TRowParserSM.TTransition )
      begin
        if not t.IsReentry
        then
          begin
            res.Value := buffer;
            buffer := '';
          end;
      end )
    {} .Permit( TParseerTrigger.ParseFinish, TParserState.Finished )
    {} .PermitReentry( TParseerTrigger.ParseChar );

    { RefValue }

    sm.Configure( TParserState.RefValue )
    {} .OnEntryFrom<Char>( pc,
      procedure( const c: Char; const t: TRowParserSM.TTransition )
      begin
        if t.IsReentry
        then
          buffer := buffer + c;
      end )
    {} .OnExit(
      procedure( const t: TRowParserSM.TTransition )
      begin
        if not t.IsReentry
        then
          begin
            res.RefValue := buffer;
            buffer := '';
          end;
      end )
    {} .Permit( TParseerTrigger.ParseFinish, TParserState.Error )
    {} .PermitDynamic<Char>( pc,
      function( const c: Char ): TParserState
      begin
        case c of
          '@':
            if buffer.IsEmpty
            then
              Result := TParserState.Error
            else
              Result := TParserState.Finished;
        else
          Result := TParserState.RefValue;
        end;
      end );

    { Finished }

    sm.Configure( TParserState.Finished )
    {} .PermitReentry( TParseerTrigger.ParseFinish )
    {} .PermitDynamic<Char>( pc,
      function( const c: Char ): TParserState
      begin
        case c of
          ' ':
            Result := TParserState.Finished;
        else
          Result := TParserState.Error;
        end;
      end );

    { Error }

    sm.Configure( TParserState.Error )
    {} .OnEntry(
      procedure( const t: TRowParserSM.TTransition )
      begin
        errorTransition := t;
      end );
{$ENDREGION}
    { Parse the string }

    for c in AStr do
      begin
        if not sm.CanFire( TParseerTrigger.ParseChar )
        then
          break;
        sm.Fire<Char>( pc, c );
      end;

    { Fire Finish Trigger }

    if sm.CanFire( TParseerTrigger.ParseFinish )
    then
      sm.Fire( TParseerTrigger.ParseFinish );

    { Check the final state }

    if sm.State <> TParserState.Finished
    then
      raise Exception.Create( errorTransition.ToString );

    Result := res;

  finally
    sm.Free;
  end;
end;

function TRow.ToString: string;
begin
  Result := Level.ToString( )
  {} + IfThen( Reference.IsEmpty, '', ' @' + Reference + '@' )
  {} + ' ' + Tag.ToUpperInvariant( )
  {} + IfThen( Value.IsEmpty, '', ' ' + Value )
  {} + IfThen( RefValue.IsEmpty, '', ' @' + RefValue + '@' );
end;

end.

hansklok 8. Mai 2016 15:40

AW: RegEx Frage
 
So, ich greife das Thema erneut auf. Ich habe nun ein wunderbar funktionierendes RegEx-Pattern:
Code:
(0|[1-9][\d]*) (?:@?((?<=@)[^@]+(?=@)|(?!@))(?:@ )?)([A-Za-z0-9_]+)( [^\n\r]*|)
Nun gibt es drei Ausnahmen, die ich gern integrieren würde. Sie betreffen die Teil-Abschnitte
Code:
([A-Za-z0-9_]+)
und
Code:
( [^\n\r]*|)
. Wenn
Code:
([A-Za-z0-9_]+)
<> NOTE, CONT oder CONC ist, soll
Code:
( [^\n\r]*|)
getrimmt werden, sonst nicht. Wie würde das aussehen? Ich kenne mich mit Konditionalen Ausdrücken leider gar nicht aus.

Vielen Dank im Voraus

idefix2 17. Mai 2016 10:50

AW: RegEx Frage
 
Was meinst du mit "soll getrimmt werden"?

Prinzipiell kannst du die drei Sonderfälle NOTE, CONT oder CONC behandeln, in dem du etwas auf die Art:

Code:
(((NOTE|CONT|CONC)Sonderfall)|(([A-Za-z0-9_]+)Normalfall))
machst - das würde zwar auch den String "NOTENormalfall" parsen, ich glaube allerdings auf Grund der Angaben, das das Problem in deinem Fall nicht relevant ist.

hansklok 17. Mai 2016 13:33

AW: RegEx Frage
 
Also, es geht darum, dass bei den drei genannten Fällen, das gematchte Ergebnis nicht getrimmt werden soll, in allen anderen Fällen schon. Ich meine also, dass alle Leerzeichen vor und nach dem Match automatisch entfernt werden. Ist das mittels RegEx möglich? Das würde mir erheblich Rechenzeit Sören, dennbisher prüfe ich dann jedes Match und führe ggf. die interne String-Trimfunktion aus. Das kostet Zeit, vor allem bei sehr langen Strings.

idefix2 18. Mai 2016 08:34

AW: RegEx Frage
 
Naja, prinzipiell kannst du es so machen, wie ich eben beschrieben habe. Also eine Alternative zwischen "Sonderfall" und "Normalfall", wobei du im Normalfall trimmst und in den Sonderfällen nicht. Klar ist, dass du dann das Ergebnis in zwei verschiedenen Variablen erhältst, und je nachdem die eine oder die andere Variable verwenden musst. Ob aber diese Vorgangsweise insgesamt schneller ist als ein nachträgliches Trim, halte ich für sehr fraglich, die Rechenzeit für die Regex-Auswertung steigt möglicherweise stärker an als um die Zeit, die ein Trim kostet.


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:20 Uhr.
Seite 2 von 2     12   

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz