![]() |
AW: Gedcom-Datei parsen
Zitat:
Delphi-Quellcode:
Wenn man sich die Sätze anschaut, dann sind die alle nach dem gleichen Muster gestrickt
// Wenn der Data-String mit einem @ startet, dann ist es eine Referenz
Result.DataIsReference := Result.Data.StartsWith( '@' );
Code:
Das war es schon.
<LEVEL>[<sep>@<REFERENCE>@]<sep><TYPE>[<sep><DATA>]
<LEVEL> = numerisch <sep> = SPACE <TYPE> = alphanumerisch ohne <sep> <DATA> = alles bis zum Ende der Zeile In den Record
Delphi-Quellcode:
fülle ich einfach die Werte aus der Zeile ein.
TDataRecord
Hier der gesamte Code:
Delphi-Quellcode:
program dp_183093;
{$APPTYPE CONSOLE} {$R *.res} uses System.Generics.Collections, System.IOUtils, System.Classes, System.SysUtils; type TDataRecord = record Level: Integer; Reference: string; NodeTypeStr: string; Data: string; DataIsReference: Boolean; end; TNodeType = record private FTypeName: string; public constructor Create( const ATypeName: string ); function ToString: string; property TypeName: string read FTypeName; end; TNode = class private FNodeType: TNodeType; FParent: TNode; protected function GetData: string; virtual; function GetLastChild: TNode; virtual; abstract; function GetChildCount: Integer; virtual; abstract; function GetChild( Index: Integer ): TNode; virtual; abstract; public constructor Create( AParent: TNode; ANodeType: TNodeType ); procedure AddChild( ANode: TNode ); virtual; abstract; function ToString: string; override; property Data: string read GetData; property ChildCount: Integer read GetChildCount; property Children[Index: Integer]: TNode read GetChild; property Parent: TNode read FParent; property LastChild: TNode read GetLastChild; end; TParentNode = class( TNode ) private FChildren: TList<TNode>; protected function GetLastChild: TNode; override; function GetChildCount: Integer; override; function GetChild( Index: Integer ): TNode; override; public constructor Create( AParent: TNode; ANodeType: TNodeType ); destructor Destroy; override; procedure AddChild( ANode: TNode ); override; end; TRefNode = class( TParentNode ) private FDataReference: string; FReferenceDict: TDictionary<string, TNode>; protected function GetChildCount: Integer; override; function GetChild( Index: Integer ): TNode; override; function GetData: string; override; public constructor Create( AParent: TNode; ANodeType: TNodeType; const ADataReference: string; AReferenceDict: TDictionary<string, TNode> ); end; TDataNode = class( TParentNode ) private FData: string; protected function GetData: string; override; public constructor Create( AParent: TNode; ANodeType: TNodeType; const AData: string ); end; TGedFile = class( TParentNode ) private FReferenceDict: TDictionary<string, TNode>; function GetParent( Index: Integer ): TNode; function ParseLine( const ALine: string ): TDataRecord; public constructor Create( ); destructor Destroy; override; procedure LoadFromFile( const Filename: string ); end; { TNode } constructor TNode.Create( AParent: TNode; ANodeType: TNodeType ); begin inherited Create; FNodeType := ANodeType; if Assigned( AParent ) then AParent.AddChild( Self ); end; function TNode.GetData: string; begin Result := ''; end; function TNode.ToString: string; begin if Data.IsEmpty then Result := FNodeType.ToString else Result := FNodeType.ToString + ' ' + Data.QuotedString; end; { TGedFile } constructor TGedFile.Create( ); begin inherited Create( nil, TNodeType.Create( 'FILE' ) ); FReferenceDict := TDictionary<string, TNode>.Create; end; destructor TGedFile.Destroy; begin FReferenceDict.Free; inherited; end; function TGedFile.GetParent( Index: Integer ): TNode; begin Result := Self; while Index > 0 do begin if not Assigned( Result ) then raise Exception.Create( 'Fehlermeldung' ); Result := Result.LastChild; Dec( Index ); end; end; procedure TGedFile.LoadFromFile( const Filename: string ); var LValue: TDataRecord; LCurrent: TNode; LCurrentIdx: Integer; LLines: TStringList; LLine: string; begin LCurrent := Self; LCurrentIdx := 0; LLines := TStringList.Create; try LLines.LoadFromFile( Filename ); for LLine in LLines do begin LValue := ParseLine( LLine ); while LCurrentIdx <> LValue.Level do begin if LCurrentIdx < LValue.Level then begin LCurrent := LCurrent.LastChild; Inc( LCurrentIdx ); end else begin LCurrent := LCurrent.Parent; Dec( LCurrentIdx ); end; end; if LValue.DataIsReference then LCurrent := TRefNode.Create( LCurrent, TNodeType.Create( LValue.NodeTypeStr ), LValue.Data, FReferenceDict ) else LCurrent := TDataNode.Create( LCurrent, TNodeType.Create( LValue.NodeTypeStr ), LValue.Data ); Inc( LCurrentIdx ); if not LValue.Reference.IsEmpty then FReferenceDict.Add( LValue.Reference, LCurrent ); end; finally LLines.Free; end; end; function TGedFile.ParseLine( const ALine: string ): TDataRecord; var LValues: TArray<string>; LPrefix: string; begin LValues := ALine.Split( [' '], 3 ); Result.Level := LValues[0].ToInteger; // Reference gefunden? if LValues[1].StartsWith( '@' ) then begin Result.Reference := LValues[1]; Result.NodeTypeStr := LValues[2]; end else begin Result.Reference := ''; Result.NodeTypeStr := LValues[1]; SetLength( LValues, 2 ); end; LPrefix := string.Join( ' ', LValues ); Result.Data := ALine.Substring( LPrefix.Length + 1 ); Result.DataIsReference := Result.Data.StartsWith( '@' ); end; { TNodeType } constructor TNodeType.Create( const ATypeName: string ); begin FTypeName := ATypeName.ToUpper; end; function TNodeType.ToString: string; begin Result := FTypeName; end; { TParentNode } procedure TParentNode.AddChild( ANode: TNode ); begin inherited; if Assigned( ANode.Parent ) and ( ANode.Parent <> nil ) then raise Exception.Create( 'Fehlermeldung' ); ANode.FParent := Self; if not FChildren.Contains( ANode ) then FChildren.Add( ANode ); end; constructor TParentNode.Create( AParent: TNode; ANodeType: TNodeType ); begin inherited Create( AParent, ANodeType ); FChildren := TObjectList<TNode>.Create( ); end; destructor TParentNode.Destroy; begin FChildren.Free; inherited; end; function TParentNode.GetChild( Index: Integer ): TNode; begin Result := FChildren[Index]; end; function TParentNode.GetChildCount: Integer; begin Result := FChildren.Count; end; function TParentNode.GetLastChild: TNode; begin Result := FChildren.Last; end; { TRefNode } constructor TRefNode.Create( AParent: TNode; ANodeType: TNodeType; const ADataReference: string; AReferenceDict: TDictionary<string, TNode> ); begin inherited Create( AParent, ANodeType ); FDataReference := ADataReference; FReferenceDict := AReferenceDict; end; function TRefNode.GetChild( Index: Integer ): TNode; var LRefNode: TNode; begin LRefNode := FReferenceDict[FDataReference]; if index < LRefNode.ChildCount then Result := LRefNode.Children[Index] else Result := inherited GetChild( Index - LRefNode.ChildCount ); end; function TRefNode.GetChildCount: Integer; begin Result := inherited GetChildCount + FReferenceDict[FDataReference].ChildCount;; end; function TRefNode.GetData: string; begin Result := FDataReference; end; { TDataNode } constructor TDataNode.Create( AParent: TNode; ANodeType: TNodeType; const AData: string ); begin inherited Create( AParent, ANodeType ); FData := AData; end; function TDataNode.GetData: string; begin Result := FData; end; function OutputNode( ANode: TNode; ALevel: Integer = 0; AFollowRef: Boolean = True ): string; var LIdx: Integer; LSB: TStringBuilder; begin LSB := nil; try LSB := TStringBuilder.Create; LSB.Append( ' ', ALevel ); LSB.Append( ANode.ToString ); if ( ANode is TRefNode ) then begin LSB.Append( ' (Ref)' ); end; if ( ANode is TRefNode ) and not AFollowRef then begin LSB.Append( ' [NOT FOLLOWED]' ); LSB.AppendLine; end else begin LSB.AppendLine; for LIdx := 0 to ANode.ChildCount - 1 do LSB.Append( {} OutputNode( {} ANode.Children[LIdx], {} ALevel + 1, {} not( ANode is TRefNode ) {} ) ); end; Result := LSB.ToString; finally LSB.Free; end; end; procedure Main; var LFile: TGedFile; LFilename: string; begin LFile := TGedFile.Create( ); try LFilename := '..\..\TestGED.ged'; LFile.LoadFromFile( LFilename ); TFile.WriteAllText( TPath.ChangeExtension( LFilename, '.txt' ), OutputNode( LFile ) ); finally LFile.Free; end; end; begin try Main; except on E: Exception do WriteLn( E.ClassName, ': ', E.Message ); end; ReadLn; end. |
AW: Gedcom-Datei parsen
Liste der Anhänge anzeigen (Anzahl: 1)
Aus der verlinkten Spezi:
Zitat:
Es werden doch einfach nur Datensätze(!) geschickt die u.U. miteinander verknüpft sind (über die Querverweis ID) und deren Inhalte/Daten hierarchisch angeordnet sind. Sprich, wenn dann haben die einzelenen Datensätze eine Baumstruktur aber nicht unbedingt die Datei (Hat der LKW eine Baumstruktur?). Irgendwann müssen doch die konkreten Objekte für die Geschäftslogik erzeugt werden, was ist da der Vorteil der Baumstruktur bzw. das ich mir vorher die Mühe mache "abstrakte" (im Sinne von unkonkrete) Objekte in Baumstruktur zu erzeugen? Das der Zugriff einfacher ist? |
AW: Gedcom-Datei parsen
@Jumpy
Der Inhalt der Datei ist wie ein Baum aufgebaut. Ich überführe nur den Inhalt 1:1 in eine entsprechend auswertebare Struktur. Mehr mache ich nicht. Ausserdem muss man im Zweifelsfall erst die gesamte Datei ausgelesen haben, um auf die Referenzen auch zugreifen zu können. Hier ein Auszug aus ![]()
Code:
Wie bitteschön, soll denn jetzt die Referenz zu @F1@ und @F42@ behandelt werden? Die kommen in der Datei erst ein paar Zeilen später ...
0 @I1@ INDI
1 NAME Victoria /Hanover/ 1 TITL Queen of England 1 SEX F 1 BIRT 2 DATE 24 MAY 1819 2 PLAC Kensington,Palace,London,England 1 DEAT 2 DATE 22 JAN 1901 2 PLAC Osborne House,Isle of Wight,England 1 BURI 2 PLAC Royal Mausoleum,Frogmore,Berkshire,England 1 REFN 1 1 FAMS @F1@ 1 FAMC @F42@ Ab Zeile 23285 kommt
Code:
0 @F1@ FAM
1 HUSB @I2@ 1 WIFE @I1@ 1 CHIL @I3@ 1 CHIL @I4@ 1 CHIL @I5@ 1 CHIL @I6@ 1 CHIL @I7@ 1 CHIL @I8@ 1 CHIL @I9@ 1 CHIL @I10@ 1 CHIL @I11@ 1 DIV N 1 MARR 2 DATE 10 FEB 1840 2 PLAC Chapel Royal,St. James Palace,England |
AW: Gedcom-Datei parsen
Ich verstehe glaub ich wie du das meinst. Aber wenn du jetzt anfängst die Objekte der Geschäftslogik zu erstellen (also der nächste Schritt) machst du das ja dann in dem du dem Baum Schritt für Schritt abgehst. Hast du dann nicht das selbe Problem, wenn du an die Stelle kommst, dass die "konkreten" Objekte auf die verweisen wird noch nicht existieren weil die "abstrakte" Objekte mit der Info darüber im Baum noch ein paar Zweige tiefer hängen?
OK, du wirst sie leichter finden und nach Bedarf erzeugen können. Wobei ich gerade überlege (ich hab allerdings keine Ahnung von Genealogie-Programmen) dass es eh nicht möglich(?)/einfach ist diese Objekte OOP-mäßig miteinander zu verknüpfen (assoziieren?). Hätte jetzt gedacht, dass die Objekte sortenrein in entsprechenden Collections liegen und in anderen Objekten gerade mal die Referenz-ID auf diese gespeichert wird und man dann die Collection fragen muss: Gib mir mal die Familie mir der ID F12, oder so. (Zumindest die Objekte auf Ebene 0). Objekte auf höheren Ebenen würden wahrscheinlich mit dem übergeordneten Objekt erzeugt und im übergeordneten Objekt "verwaltet". Referenziert würden die glaub ich ala @I12!P2@ oder so, hab da aber bisher kein konkretes Beispiel für gesehen nur die Spezi so verstanden, das es das geben könnte. |
AW: Gedcom-Datei parsen
@Jumpy
Nein habe ich nicht, denn dafür wird bei einem Referenz-Verweis auch ein
Delphi-Quellcode:
erstellt mit direktem Zugriff auf die referenzierte Node (müsste noch leicht verfeinert werden).
TRefNode
Auf jeden Fall kann man gleich prüfen, ob man diese Referenz schon gespeichert hat, wenn nicht speichert man die. |
AW: Gedcom-Datei parsen
Ich glaube ich habe bei der Beschreibung des Zeilenaufbaus einen kleinen Fehler gemacht.
So ist die korrekte Logik für die Zeile
Code:
Wenn die Zeile also eine Referenz ist
<LEVEL> -- <SEP> -+- <TYPE> -- <SEP> -+- <DATA> ---+- <EOL>
| +- <REFTO> --+ +- <REF> -- <SEP> -- <TYPE> ------- <EOL>
Code:
dann folgen keine Daten mehr.
0 @foo@ INDI
Wenn die Zeile auf eine Referenz verweist, dann beinhalten die Daten die Referenz
Code:
Das müsste also alles falsch sein
0 INDI @foo@
Code:
0 @foo@ INDI @foo@
0 @foo@ INDI Some data 0 INDI @foo@ and some data |
AW: Gedcom-Datei parsen
Zitat:
Das kommt ja letztlich daher, dass die Datensätze in beliebiger Reihenfolge in der Datei stehen können und daher dass zirkuläre Referenzen erlaubt sind, d.h. es wäre gar nicht möglich die Daten so zu speichern, dass man daraus die Objekte in einer Reihenfolge erzeugt die bewirkt, das Referenzierte Objekte bereits zuvor erstellt werden. ---------- Kann das sein das
Code:
auch falsch ist?
0 INDI @foo@
Code:
aber OK?
1 INDI @foo@
Muss der Eintrag auf Ebene 0 nicht eine Ref sein, entspricht doch quasi einem Primary Key, den jeder Datensatz braucht? |
AW: Gedcom-Datei parsen
Code:
Ist nicht falsch, aber in dem Kontext unsinnig.
0 INDI @foo@
Code:
macht schon mehr Sinn
0 NOTE @foo@
|
AW: Gedcom-Datei parsen
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:
Zitat:
Zitat:
0 @I123@ INDI 0 @F123@ FAM 0 @N123@ NOTE 0 @S123@ SOUR 0 @O123@ OBJ Header und Trailer (kommen einmalig pro Datei vor!): 0 HEAD 0 TRLR Ich hänge mal den übersetzten Xojo-Code (Basic) ran. Allerdings kommt es zu Exceptions (habe das kommentiert). Auch, was nicht geht. Vielleicht findet Ihr den Fehler. Achso: Xoxo kennt keine StringBuilder-Klasse. ich habe jetzt einfach einen String definiert und ihm immer wieder die neuen Teile angehängt. Das ist doch dasselbe, oder? Siehe OutputNode(). Danke für diesen angeregten, spannenden Thread. |
AW: Gedcom-Datei parsen
Das fehlende List.Contains(ANode) musst du wohl selber nachbauen, wenn die Collection das nicht kann. Im einfachsten Fall einfach eine Prozedur, der man Collection und Element übergibt und die dann durch alle Elemente iteriert und schaut, ob es das Element schon gibt.
Oder aber Dictionary statt Collection nehmen, vllt. kann das mehr? |
Alle Zeitangaben in WEZ +1. Es ist jetzt 06:44 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