Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Parsen von Seitenzahlen (https://www.delphipraxis.net/137353-parsen-von-seitenzahlen.html)

Schwedenbitter 19. Jul 2009 10:47


Parsen von Seitenzahlen
 
Hallo,

ich habe schon die Suchfunktion sowohl vom Forum als auch von bekannten Suchmaschinen genutzt - leider ohne Ergebnis. Eventuell habe ich auch die falschen Suchbegriffe benutzt. Falls das Thema woanders besser hinpasst, dann bitte ich die Moderatoren, das mal zu verschieben:

Ich drucke mit einem Programm viele Bilder. Dabei möchte ich jetzt dem Benutzer die Möglichkeit geben, über ein Edit-Feld die zu druckenden Seiten auszuwählen. Das bieten im Grunde alle Textprogramme, Adobe Reader etc. an. Wenn also z.B: "1-3; 10; 17; 21-23" angegeben wird, dann sollen nur die 8 Seiten 1, 2, 3, 10, 17, 21, 22 und 23 ausgedruckt werden. Wie man Strings zerlegt etc. ist bekannt. Ich habe nur leider Probleme, auf einen Lösungsansatz zu kommen. Ich erwarte keinen fertigen Code.

Ich bin für Tipps (auch Suchbegriffe s.o.) dankbar. Schönen Sonntag noch

Alex

alzaimar 19. Jul 2009 10:54

Re: Parsen von Seitenzahlen
 
Deklariere eine 'Liste von Seitenzahlen'
Zerteile die Seitenzahlangabe in Teilstrings, die durch ';' getrennt sind.
Jeder Teilstring ist entweder eine Seitenzahl oder ein Bereich.
Eine Seitenzahl besteht nur aus Ziffern. Hänge die einzelne Seitenzahl in die Liste.
Ein Bereich ist eine Seitenzahl gefolgt von einem '-' gefolgt von einer Seitenzahl. Hänge alle Seitenzahlen zwischen der ersten und der Zweiten in die Liste.

Drucke alle in der Liste befindlichen Seiten.

TurboMartin 19. Jul 2009 10:57

Re: Parsen von Seitenzahlen
 
Z.B. das hier oder ganz allgemein Hier im Forum suchenexplode/explodeexplode

Schwedenbitter 19. Jul 2009 23:03

Re: Parsen von Seitenzahlen
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo alzaimar und TurboMartin! Danke für Eure Hilfe!

Die Sache mit explode etc. ist mir zu kompliziert, auch wenn sie vermutlich dasselbe macht wie ich. Auch bei der Suche mit dem Suchwort explode habe ich immer nur etwas zum Zerteilen von Strings gefunden. Aber
Zitat:

Zitat von Schwedenbitter
Wie man Strings zerlegt etc. ist bekannt.

Ich habe es jetzt nach dem Schema von alzaimar gelöst. Den Quellcode habe ich beigefügt, falls nochmal jemand das Bedürfnis hat, bestimmte Seiten zu drucken oder sonstwas damit zu machen. Eine Fehlerbehandlung ist nicht dabei, wird in meinem kompletten Programm über die Abfrage der Eingabe beim Edit-Feld gemacht.
Für Ratschläge und insbesondere Verbesserungen bin ich trotzdem immer noch zu haben.

Kann mir bitte noch jemand sagen, ob es (1) eine Möglichkeit gibt, mein Array Of Boolean als offenes Array zu deklarieren und (2) ob mir das speichermäßig Ersparnisse/Vorteile bringt?

Gruß, Alex

DP-Maintenance 20. Jul 2009 05:33

DP-Maintenance
 
Dieses Thema wurde von "mkinzler" von "Programmieren allgemein" nach "VCL / WinForms / Controls" verschoben.
Scheint ein Delphi-Problem zu sein

himitsu 20. Jul 2009 06:57

Re: Parsen von Seitenzahlen
 
Zitat:

Zitat von Schwedenbitter
Kann mir bitte noch jemand sagen, ob es (1) eine Möglichkeit gibt, mein Array Of Boolean als offenes Array zu deklarieren und (2) ob mir das speichermäßig Ersparnisse/Vorteile bringt?

Seiten: Array of Boolean;

und dann mit SetLength die Größe ändern

Vorteile bringt es dann, wenn es z.B. nur 10 statt 1000 Seiten, da dann ja auch nur 10 Byte im Array nötig sind, + ~12 Byte für die Speicherverwaltung

so ist das Array ja 1000 Byte groß (Boolean = 1 Byte)
obwohl 1 KB jetzt nicht soooooo groß ist.

mehr Speicher könnte man nur noch mit einem SET bzw. einer Bitmaske sparen (1 Bit pro Seite)

alzaimar 20. Jul 2009 07:23

Re: Parsen von Seitenzahlen
 
Hallo Alex,

Ein Alternativ-Vorschlag:
Folgende Routine liefert mir den ersten Teilstring bis zu einem Trenner, oder den ganzen String, wenn es kein Trenner mehr gibt. Gleichzeitig wird der String um den ersten Teilstring verkürzt.
Delphi-Quellcode:
Funtion ExtractString (Var aString : String; Const aDelimiter : String) : String;
Var
  P : Integer;

Begin
  P := Pos(aDelimiter, aString);
  If P=0 Then Begin
    Result := Trim (aString);
    aString := '';
  End
  Else Begin
    Result := Trim (Copy (1, p-1));
    aString := Trim (Copy (aString, p+Length(aDelimiter), MaxInt));
  End
End;
(*
 s := '12;345';
 t := ExtractString(s,';'); // Liefert t<-'12' und s<-'345'
 t := ExtractString(s,';'); // Liefert t<-'345' und s<-''
*)
Nun kann ich die Eingabe also Stück für Stück abarbeiten. O.g. Funktion verwende ich in fast jedem Programm irgendwo, sie ist ungeheuer praktisch.
Delphi-Quellcode:
Type
  TBooleanDynArray = Array Of Boolean;
...
Procedure CreatePageNumbers (aPageNumberDesc : String; Var aPagesToPrint : TBooleanDynArray);
Var
  pageDesc,
  rangeStart : String;
  pagenumber, p, q : Integer;

Begin
  If aPageNumberDesc = '' Then                             // Initialisieren: Wenn Beschreibung leer ist,...
    For p := Low(aPagesToPrint) To High (aPagesToPrint) Do     // ...dann sollen alle Seiten gedruckt werden
      aPagesToPrint[p] := True
  else                                                   // Sonst sollen erstmal keine Seite gedruckt werden
    For p := Low(aPagesToPrint) To High (aPagesToPrint) Do
      aPagesToPrint[p] := False;

  While aPageNumberDesc<>'' do Begin                     // Solange noch etwas in der Seitenangabe drin steht
    pageDesc := ExtractString (aPageNumberDesc,';');                             // Nächsten Teilstring holen
    If TryStrToInt (pageDesc, pagenumber) Then          // Teilstring = einzelne Zahl => Zur Liste hinzufügen
      aPagesToPrint[pageNumber] := True
    else Begin
      rangeStart := ExtractString (pageDesc,'-');                              // 1.Teil von <Seite> - <Seite>
      If TryStrToInt (pageDesc,q) And TryStrToInt (rangeStart,p) Then // Prüfen, ob zwei Nummern vorhanden sind
        For pageNumber := p to q Do                            // Ja, also alle Zahlen in die Liste hinzufügen
          aPagesToPrint[pageNumber] := True
      Else                                                                     // Keine gültige Bereichsangabe
        Raise Exception.Create('Ungültige Angabe');                                      // Exception und Ende
    End
  End
End;
Wie Du siehst, habe ich die Funktion vollständig vom Programm und ihren Strukturen getrennt. Damit ist diese Funktion allgemeingültig und kann in jedem Projekt unmittelbar wiederverwendet werden. Durch die Verlagerung der Problemlösung 'Teilstring extrahieren' ist die Routine auch lesbarer, finde ich.

P.S.: Die Prüfung, ob bei einer Bereichsangabe 'a-b' der Wert a < b ist, habe ich mir erspart, das kannst Du gerne noch implementieren.

sx2008 20. Jul 2009 08:56

Re: Parsen von Seitenzahlen
 
Es gibt da noch die Klasse TBits, die IMHO angenehmer und platzsparender ist.

Aber ganz perfekt wäre es so:
1.) string in einzelstücke zerlegen (explode & co)
2.)
Delphi-Quellcode:
TSeitenEintrag = record von,bis:integer end;
ein array of TSeitenEintrag anlegen und befüllen.
3.) dieses Array sortieren (einfaches Selection Sort reicht aus)
4.) Optimieren. Array von hinten nach vorne durchgehen und schauen,
ob man anliegende oder überschneidende Seiten von zwei auf einen Eintrag reduzieren kann
dieser Schritt ist optional
5.) Suchfunktion schreiben, die array durchläuft und schaut ob es einen Treffer gibt
6.) Punkt 1.) bis 5.) als wiederverwendbare Klassse implementieren

Diese Vorgehensweise kann auch folgende Anweisung problemlos umsetzen:
Code:
1-5;60-
Das wären die Seiten 1 bis 5 und 60 bis Ende.

Nachtrag:
die Klasse bekommt zusätzlich die Boolean Properties EvenPages und OddPages.
Die Suchfunktion von 5.) berücksichtigt diese Flags.
Somit können auch "nur gerade Seiten" oder "nur ungerade Seiten" von nur einer
Klasse behandelt werden.

alzaimar 20. Jul 2009 18:57

Re: Parsen von Seitenzahlen
 
Zitat:

Zitat von sx2008
Aber ganz perfekt wäre es so:
...

:gruebel: Definiere "perfekt" :shock:

Zitat:

Zitat von sx2008
Diese Vorgehensweise kann auch folgende Anweisung problemlos umsetzen:
Code:
1-5;60-

Das können die anderen -nach entsprechender Modifizierung- auch.
Bei mir wäre dies eine Zeile:
Delphi-Quellcode:
...
    else Begin
      rangeStart := ExtractString (pageDesc,'-');                            
      If pageDesc='' Then pageDesc := IntToStr (TotalPageCount); // <<--- Die Erweiterung
...
End;
Die Erweiterung der Spezifikation um gerade/ungerade Seiten ließe sich durch eine einfache Fallunterscheidung ebenfalls leicht implementieren.

Wie gesagt: Einen Vorteil Deiner Methode sehe ich nicht. Aber das heißt ja nicht, das es ihn nicht gibt. Erstelle doch einfach mal so eine Klasse. Ich befürchte, sie ist ein wenig komplexer als mein 10-Zeiler. Ich befürchte auch, das sie wesentlich langsamer ist. Obwohl das wohl keine große Rolle spielen dürfte.

Jens01 17. Aug 2012 15:40

AW: Parsen von Seitenzahlen
 
Da ich derartiges in meinem Projekt benötige, habe ich den Ansatz von alzaimar übernommen. Ich nutze aber eine Integerliste. Zusätzlich habe ich eine Umkehrung mit CreateNumberString hinzugefügt. Hier wird aus einer Integerliste ein String erstellt.
Vielleicht kann es jemand irgendwann mal gebrauchen.
Gruss Jens

Delphi-Quellcode:
unit tools.ParseNumbers;
// http://www.delphipraxis.net/934335-post7.html

interface

uses
  System.Generics.Collections;

function ExtractString(Var aString: string; const aDelimiter: string): string;
function CreateNumberList(aNumberDesc: string; aNumberList: TList<Integer>): Boolean;
procedure CreateNumberString(var aNumberDesc: string; aNumberList: TList<Integer>);

const
  ValidCharsString = ' ,;-0123456789';

implementation

uses
  System.SysUtils, System.StrUtils;

function ExtractString(Var aString: string; const aDelimiter: string): string;
Var
  P: Integer;
Begin
  P := Pos(aDelimiter, aString);
  If P = 0 Then
  Begin
    Result := Trim(aString);
    aString := '';
  End
  Else
  Begin
    Result := Trim(Copy(aString, 1, p - 1));
    aString := Trim(Copy(aString, p + Length(aDelimiter), MaxInt));
  End
End;

procedure TrimString(var s: string);
begin
  s := Trim(s);
  s := ReplaceStr(s, ' -', '-');
  s := ReplaceStr(s, '- ', '-');
  s := ReplaceStr(s, ',', ';');
  s := ReplaceStr(s, ' ;', ';');
  s := ReplaceStr(s, '; ', ';');
  s := ReplaceStr(s, ' ', ';');
end;

function CreateNumberList(aNumberDesc: string; aNumberList: TList<Integer>): Boolean;
Var
  numberDesc, rangeStart: String;
  number, p, q         : Integer;
Begin
  aNumberList.Clear;
  TrimString(aNumberDesc);

  While aNumberDesc <> '' do
  Begin
    numberDesc := ExtractString(aNumberDesc, ';');

    If TryStrToInt(numberDesc, number) Then
      aNumberList.Add(number)
    else
    Begin
      rangeStart := ExtractString(numberDesc, '-');
      If TryStrToInt(numberDesc, q) And TryStrToInt(rangeStart, p) and (p <= q) Then
        For number := p to q Do
          aNumberList.Add(number)
      Else
        Exit(False);
    End
  End;
  Result := True;
End;

procedure CreateNumberString(var aNumberDesc: string; aNumberList: TList<Integer>);
var
  i: Integer;
begin
  if aNumberList.Count = 0 then
    Exit;
  aNumberDesc := '';
  aNumberList.Sort;
  i := 0;
  while (i < aNumberList.Count) do
  begin
    if (i + 2 < aNumberList.Count) and (aNumberList[i] + 1 = aNumberList[i + 1]) and
      (aNumberList[i] + 2 = aNumberList[i + 2]) then
    begin
      aNumberDesc := aNumberDesc + IntToStr(aNumberList[i]) + '-';
      while (i + 1 < aNumberList.Count) and (aNumberList[i] + 1 = aNumberList[i + 1]) do
        Inc(i);
      aNumberDesc := aNumberDesc + IntToStr(aNumberList[i]);
    end
    else
    begin
      aNumberDesc := aNumberDesc + IntToStr(aNumberList[i]);
    end;

    Inc(i);
    if i + 1 < aNumberList.Count then
      aNumberDesc := aNumberDesc + ';';
  end;
end;

end.

himitsu 17. Aug 2012 15:48

AW: Parsen von Seitenzahlen
 
Zitat:

Delphi-Quellcode:
function CreateNumberList(aNumberDesc: string; var aNumberList: TList<Integer>): Boolean;

Weg mit dem VAR, denn du willst ja nur den Objektinhalt ändern und nicht den Instanzzeiger.

Popov 17. Aug 2012 16:29

AW: Parsen von Seitenzahlen
 
Ich hab so Just4Fun das Problem im ersten Post gelöst. Es funktioniert relativ gut, zumindest bei der Aufgabenstellung

Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
    function IntList(s: String; List: TStrings): Boolean;
        function IsValidInteger(S: String): Boolean;
        var
          I, Code: Integer;
        begin
          Val(S, I, Code);
          Result := Code = 0;
        end;
    var
      a, b: String;
      i: Integer;
    begin
      Result := False;
      List.Clear;

      if Pos('-', s) = 0 then //für einzelne Seitenzahlen, z. B. "1"
      begin
        if IsValidInteger(s) then
        begin
          List.Add(Trim(s));
          Result := True;
        end;
        Exit;
      end;

      a := Copy(s, 1, Pos('-', s) - 1); //für Seitenzahlen von bis, z. B. "1-3"
      Delete(s, 1, Pos('-', s));
      b := s;

      if IsValidInteger(a) and IsValidInteger(b) then
      begin
        for i := StrToInt(a) to StrToInt(b) do
          List.Add(IntToStr(i));
        Result := True;
      end;
    end;
var
  s: String;
  sl, slList, slResult: TStringList;
  i: Integer;
begin
  s := '1-3; 10; 17; 21-23';

  sl := TStringList.Create;
  slList := TStringList.Create;
  slResult := TStringList.Create;
  try
    sl.Delimiter := ';';
    sl.DelimitedText := s;
    for i := 0 to sl.Count - 1 do
      if IntList(sl[i], slList) then
        slResult.AddStrings(slList);

    ListBox1.Items.Text := slResult.Text;
  finally
    sl.Free;
    slList.Free;
    slResult.Free;
  end;
end;

Furtbichler 17. Aug 2012 18:45

AW: Parsen von Seitenzahlen
 
Das wäre dann die dritte funktionierende Lösung.

Popov 17. Aug 2012 19:04

AW: Parsen von Seitenzahlen
 
Wie gesagt, nicht wirklich eine Lösung (Problem von 2009), aber etwas Just4Fun Programmierung.

Furtbichler 18. Aug 2012 08:32

AW: Parsen von Seitenzahlen
 
IsValidNumber durch 'TryStrToInt' ersetzen und dann noch das dreimalige Ermitteln der Position von '-' durch Refaktorisieren entfernen. Dann wird das auch kompakter. Das Ziel sollte ein Boolean-Array oder ein Integer-Array sein, keine ListBox.

Deine Lösung unterscheidet sich eigentlich nicht von den anderen, außer, das Du sie nicht allgemeingültig gemacht (Ausgabe in eine ListBox) und als Buttonclick implementiert hast. Einzig das separieren der durch Semikolon getrennten Einzelbereiche bewerkstelligst Du über eine Stringliste.

Aber falsch ist es nicht, nur die Fehlerbehandlung ist ungenügend, finde ich.

@Jens01: TrimString ist überflüssig. Das trimmen übernimmt die ExtractString-Funktion

Sir Rufo 18. Aug 2012 10:44

AW: Parsen von Seitenzahlen
 
Wäre es nicht wesentlich schicker dieses mit dem State-Pattern zu lösen?

State-Pattern: CSV-Parser

Bjoerk 18. Aug 2012 12:14

AW: Parsen von Seitenzahlen
 
Hab' auch noch einen (mit Syntaxprüfung, allerdings ungetestet) und auch noch eine Frage dazu. Ist es möglich herauszufinden, ob (wie in meinem Fall) der AdobeRader diese Seiten tatsächlich gedruckt hat (Logdatei o.ä.) ?

Delphi-Quellcode:
function TrimAll(const S: string): string;
var
  I: integer;
begin
  Result := S;
  for I := Length(Result) downto 1 do
    if Result[I] = ' ' then
      Delete(Result, I, 1);
end;

function IsInPrintOrder(const AValue: integer; const ASet: string): boolean;
type
  TStatus = (stWaitForInteger1, stWaitForInteger2, stError,
    stReadyForInteger1, stReadyForInteger2);
var
  Status: TStatus;
  T, T1, T2: string;
  I, I1, I2: integer;
begin
  T1 := '';
  T2 := '';
  Result := false;
  T := TrimAll(ASet) + ';';
  Status := stWaitForInteger1;
  for I := 1 to Length(T) do
    if (Status <> stError) then
    begin
      case Status of
        stWaitForInteger1:
          if T[I] in ['0'..'9'] then
            T1 := T1 + T[I]
          else
            if T[I] = '-' then
              Status := stWaitForInteger2
            else
              if T[I] in [',', ';'] then
                Status := stReadyForInteger1
              else
                Status := stError;
        stWaitForInteger2:
          if T[I] in ['0'..'9'] then
            T2 := T2 + T[I]
          else
            if T[I] in [',', ';'] then
              Status := stReadyForInteger2
            else
              Status := stError;
      end;
      case Status of
        stReadyForInteger1:
          if TryStrToInt(T1, I1) then
          begin
            Status := stWaitForInteger1;
            if AValue = I1 then
            begin
              Result := true;
              Break;
            end
            else
              T1 := '';
          end
          else
            Status := stError;
        stReadyForInteger2:
          if TryStrToInt(T1, I1) and TryStrToInt(T2, I2) and (I2 >= I1) then
          begin
            Status := stWaitForInteger1;
            if (AValue >= I1) and (AValue <= I2) then
            begin
              Result := true;
              Break;
            end
            else
            begin
              T1 := '';
              T2 := '';
            end;
          end
          else
            Status := stError;
      end;
    end;
  if Status = stError then
    raise Exception.Create('Invalid Syntax');
end;

end.

Jens01 18. Aug 2012 12:32

AW: Parsen von Seitenzahlen
 
Zitat:

@Jens01: TrimString ist überflüssig. Das trimmen übernimmt die ExtractString-Funktion
Dies Trimmen ist doch etwas anderes als was ExtractString macht. Ich nehme dort alle überflüssigen Leerzeichen raus und kann die restlichen dann durch ein ';' ersetzen. So hat der Anwender die Möglichkeit, Leerzeichen, Komma und Semikolon als Trennzeichen zu nutzten.

Furtbichler 18. Aug 2012 13:04

AW: Parsen von Seitenzahlen
 
Oh, dann habe ich nicht richtig hingeschaut, was die Funktion macht.

Furtbichler 18. Aug 2012 13:06

AW: Parsen von Seitenzahlen
 
Zitat:

Zitat von Sir Rufo (Beitrag 1178681)
Wäre es nicht wesentlich schicker dieses mit dem State-Pattern zu lösen?

Schicker vielleicht, aber nicht unbedingt lesbarer und ein klein wenig Overkill.
Ich hätte noch RegEx anzubieten.

Sir Rufo 18. Aug 2012 15:39

AW: Parsen von Seitenzahlen
 
Zitat:

Zitat von Furtbichler (Beitrag 1178698)
Zitat:

Zitat von Sir Rufo (Beitrag 1178681)
Wäre es nicht wesentlich schicker dieses mit dem State-Pattern zu lösen?

Schicker vielleicht, aber nicht unbedingt lesbarer und ein klein wenig Overkill.

Aber wesentlich angenehmer, wenn es um die Erweiterbarkeit geht.
Hier der Ansatz mit dem State-Pattern:
Delphi-Quellcode:
unit PageStrParser;

interface

{$DEFINE UseStateInstanceCache }

uses
  Classes
{$IFDEF UseStateInstanceCache}
    , Generics.Collections
{$ENDIF};

type
  TPageStrParser          = class;
  TPageStrParserStateClass = class of TPageStrParserState;

  TPageStrParserState = class
  private
    FParser : TPageStrParser;
    procedure ChangeState( NewState : TPageStrParserStateClass );
    procedure AddCharToCurrPage1( Ch : Char );
    procedure AddCharToCurrPage2( Ch : Char );
    procedure AddCurrPagesToList;
  public
    constructor Create( AParser : TPageStrParser );

    procedure ProcessChar( Ch : Char; Pos : Integer ); virtual; abstract;
  end;

  TPageStrParser = class
  private
    FState : TPageStrParserState;
{$IFDEF UseStateInstanceCache}
    FStateCache : TDictionary<TPageStrParserStateClass, TPageStrParserState>;
{$ENDIF}
    FCurrPageFrom : string;
    FCurrPageTo :  string;
    FPageList :    TStrings;
    function GetState : TPageStrParserStateClass;
    procedure SetState( const Value : TPageStrParserStateClass );
  protected
    procedure AddCharToCurrPage1( Ch : Char );
    procedure AddCharToCurrPage2( Ch : Char );
    procedure AddCurrPagesToList;

    property State : TPageStrParserStateClass read GetState write SetState;
  public
    constructor Create;
    destructor Destroy; override;

    procedure ExtractPages( const s : string; const aPageList : TStrings );
  end;

implementation

uses
  SysUtils;

type
  TPageStrParserPageFromStartState = class( TPageStrParserState )
  public
    procedure ProcessChar( Ch : Char; Pos : Integer ); override;
  end;

  TPageStrParserPageFromState = class( TPageStrParserState )
  public
    procedure ProcessChar( Ch : Char; Pos : Integer ); override;
  end;

  TPageStrParserPageFromEndState = class( TPageStrParserState )
  public
    procedure ProcessChar( Ch : Char; Pos : Integer ); override;
  end;

  TPageStrParserPageToStartState = class( TPageStrParserState )
  public
    procedure ProcessChar( Ch : Char; Pos : Integer ); override;
  end;

  TPageStrParserPageToState = class( TPageStrParserState )
  public
    procedure ProcessChar( Ch : Char; Pos : Integer ); override;
  end;

  TPageStrParserPageToEndState = class( TPageStrParserState )
  public
    procedure ProcessChar( Ch : Char; Pos : Integer ); override;
  end;

  TPageStrParserErrorState = class( TPageStrParserState )
  public
    procedure ProcessChar( Ch : Char; Pos : Integer ); override;
  end;

resourcestring
  EParserError = 'Error in line at position %d: Invalid or Unexpected Char "%s"';

  { TPageStrParser }

procedure TPageStrParser.AddCharToCurrPage1( Ch : Char );
begin
  FCurrPageFrom := FCurrPageFrom + Ch;
end;

procedure TPageStrParser.AddCharToCurrPage2( Ch : Char );
begin
  FCurrPageTo := FCurrPageTo + Ch;
end;

procedure TPageStrParser.AddCurrPagesToList;
var
  lFrom, lTo : Integer;
  lPage :     Integer;
  lPageNr :   string;
begin
  lFrom := StrToInt( FCurrPageFrom );
  lTo  := StrToIntDef( FCurrPageTo, lFrom );

  if lFrom <= lTo
  then
    for lPage := lFrom to lTo do
      begin
        lPageNr := IntToStr( lPage );
        FPageList.Add( lPageNr );
      end
  else
    for lPage := lFrom downto lTo do
      begin
        lPageNr := IntToStr( lPage );
        FPageList.Add( lPageNr );
      end;

  FCurrPageFrom := '';
  FCurrPageTo  := '';
end;

constructor TPageStrParser.Create;
begin
  inherited;
{$IFDEF UseStateInstanceCache}
  FStateCache := TObjectDictionary<TPageStrParserStateClass, TPageStrParserState>.Create( [doOwnsValues] );
{$ENDIF}
end;

destructor TPageStrParser.Destroy;
begin
{$IFDEF UseStateInstanceCache}
  FStateCache.Free;
{$ELSE}
  FState.Free;
{$ENDIF}
  inherited;
end;

procedure TPageStrParser.ExtractPages( const s : string; const aPageList : TStrings );
var
  i : Integer;
  Ch : Char;
begin
  FPageList := aPageList;

  FPageList.BeginUpdate;
  try
    FPageList.Clear;

    State := TPageStrParserPageFromStartState;

    FCurrPageFrom := '';
    FCurrPageTo  := '';

    for i := 1 to Length( s ) do
      begin
        Ch := s[i];
        FState.ProcessChar( Ch, i );

        if State = TPageStrParserErrorState
        then
          FState.ProcessChar( Ch, i );

      end;

    if FCurrPageFrom <> ''
    then
      AddCurrPagesToList;

  finally
    FPageList.EndUpdate;
  end;
end;

function TPageStrParser.GetState : TPageStrParserStateClass;
begin
  Result := TPageStrParserStateClass( FState.ClassType );
end;

procedure TPageStrParser.SetState( const Value : TPageStrParserStateClass );
begin
{$IFDEF UseStateInstanceCache}
  if FStateCache.ContainsKey( Value )
  then
    FState := FStateCache.Items[Value]
  else
    begin
      FState := Value.Create( Self );
      FStateCache.Add( Value, FState );
    end;
{$ELSE}
  FState.Free;
  FState := Value.Create( Self );
{$ENDIF}
end;

{ TPageStrParserState }

procedure TPageStrParserState.AddCharToCurrPage1( Ch : Char );
begin
  FParser.AddCharToCurrPage1( Ch );
end;

procedure TPageStrParserState.AddCharToCurrPage2( Ch : Char );
begin
  FParser.AddCharToCurrPage2( Ch );
end;

procedure TPageStrParserState.AddCurrPagesToList;
begin
  FParser.AddCurrPagesToList;
end;

procedure TPageStrParserState.ChangeState( NewState : TPageStrParserStateClass );
begin
  FParser.State := NewState;
end;

constructor TPageStrParserState.Create( AParser : TPageStrParser );
begin
  inherited Create;
  FParser := AParser;
end;

{ TPageStrParserErrorState }

procedure TPageStrParserErrorState.ProcessChar( Ch : Char; Pos : Integer );
begin
  raise Exception.Create( Format( EParserError, [Pos, Ch] ) );
end;

{ TPageStrParserPageFromStartState }

procedure TPageStrParserPageFromStartState.ProcessChar( Ch : Char; Pos : Integer );
begin
  case Ch of
    ' ' :
      ;
    '0' .. '9' :
      begin
        AddCharToCurrPage1( Ch );
        ChangeState( TPageStrParserPageFromState );
      end;
  else
    ChangeState( TPageStrParserErrorState );
  end;
end;

{ TPageStrParserPageFromState }

procedure TPageStrParserPageFromState.ProcessChar( Ch : Char; Pos : Integer );
begin
  case Ch of
    ' ' :
      ChangeState( TPageStrParserPageFromEndState );
    '0' .. '9' :
      begin
        AddCharToCurrPage1( Ch );
      end;
    '-' :
      begin
        ChangeState( TPageStrParserPageToStartState );
      end;
    ';', ',' :
      begin
        AddCurrPagesToList;
        ChangeState( TPageStrParserPageFromStartState );
      end;
  else
    ChangeState( TPageStrParserErrorState );
  end;
end;

{ TPageStrParserPageFromEndState }

procedure TPageStrParserPageFromEndState.ProcessChar( Ch : Char; Pos : Integer );
begin
  case Ch of
    ' ' :
      ;
    '-' :
      ChangeState( TPageStrParserPageToStartState );
    ',', ';' :
      begin
        AddCurrPagesToList;
        ChangeState( TPageStrParserPageFromStartState );
      end
  else
    ChangeState( TPageStrParserErrorState );
  end;
end;

{ TPageStrParserPageToStartState }

procedure TPageStrParserPageToStartState.ProcessChar( Ch : Char; Pos : Integer );
begin
  case Ch of
    ' ' :
      ;
    '0' .. '9' :
      begin
        AddCharToCurrPage2( Ch );
        ChangeState( TPageStrParserPageToState );
      end;
  else
    ChangeState( TPageStrParserErrorState );
  end;
end;

{ TPageStrParserPageToState }

procedure TPageStrParserPageToState.ProcessChar( Ch : Char; Pos : Integer );
begin
  case Ch of
    ' ' :
      ChangeState( TPageStrParserPageToEndState );
    '0' .. '9' :
      begin
        AddCharToCurrPage2( Ch );
      end;
    ';', ',' :
      begin
        AddCurrPagesToList;
        ChangeState( TPageStrParserPageFromStartState );
      end;
  else
    ChangeState( TPageStrParserErrorState );
  end;
end;

{ TPageStrParserPageToEndState }

procedure TPageStrParserPageToEndState.ProcessChar( Ch : Char; Pos : Integer );
begin
  case Ch of
    ' ' :
      ;
    ',', ';' :
      begin
        AddCurrPagesToList;
        ChangeState( TPageStrParserPageFromStartState );
      end
  else
    ChangeState( TPageStrParserErrorState );
  end;
end;

end.

Furtbichler 18. Aug 2012 15:49

AW: Parsen von Seitenzahlen
 
:shock:

Und was willst Du erweitern? Lass uns doch mal checken, ob das wirklich erweiterbarer ist. Gib mal was vor? :mrgreen:

Sir Rufo 18. Aug 2012 15:54

AW: Parsen von Seitenzahlen
 
Zitat:

Zitat von Furtbichler (Beitrag 1178713)
:shock:

Und was willst Du erweitern? Lass uns doch mal checken, ob das wirklich erweiterbarer ist. Gib mal was vor? :mrgreen:

Das weiß ich ja noch nicht, sonst wäre ich ja Hellseher, wenn ich wüßte, wie die Anforderung in x Tagen sich ändern :mrgreen:

Furtbichler 18. Aug 2012 17:01

AW: Parsen von Seitenzahlen
 
Na, ich wüsste schon so Einiges. Deshalb bin ich ein erfahrener Entwickler ;-) Lässt sich alles mit den 10-Zeilern hier erledigen.

Aber ich gehe mal davon aus, das Du deinen Code nicht als minimalistisch-kompaktes Beispiel hier eingestellt hast, das dieses Piddy-Problem realitätsnah umsetzt.

Dein Beispiel taugt aber durchaus als gutes Beispiel, denn im Kleinen soll man üben was einem im Großen gelingen soll.

Früher hieß das 'State-Pattern' übrigens DEA, und daran hatte ich auch gedacht, es aber verworfen, weil die Syntax einfach zu simpel ist und es fürs Parsen von Strings nun wirklich Standardlösungen gibt (aka RegEx).

Aber egal. Viele Wege führen nach Rom.


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:41 Uhr.

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