![]() |
Gedcom-Datei parsen
Hallo,
ich schreibe heute, da ich mir von der Community meiner alten Lieblingsprogrammiersprache Hilfe erhoffe, die mir die Kollegen des aktuellen Frameworks ![]() Was soll gepasst werden? Eine Gedcom-Datei. ![]() Ich möchte eine Anwendung erstellen, die bestehende Gedcom-Dateien einlesen kann, um so mit den Daten zu arbeiten. Wie? Mein Gedanke war folgender: Erst einmal die Datei einlesen. Zum Beispiel in eine Stringlist. Anschließend alle Zeilen durchlaufen. Sollte die Zeile ein Hauptobjekt kennzeichnen, so soll die Zeilennummer als Anfangszeile in einer Variable gespeichert werden. Nennen wir sie A. Anschließend geht die Schleife weiter, bis zum nächsten Hauptobjekt. Nun soll in Variable B die Zeilennummer vor dem neuen Hauptobjekt gespeichert werden (B = Zeilennummer -1) Haben A und B einen Wert, sollen diese als TPoint in einer Objektliste gespeichert werden. Später, wenn die Springlist durchgelaufen ist, sollte man eine Liste haben, die alle Hauptobjekte, oder z.b. nur Personen enthält. Für jeden Eintrag hat man nun die Start- und die Endzeilennummer und kann nun ebenfalls in einer Schleife jedes Objekt einzeln passen. Darum gehts. Was? Ich suche Hilfe für den Algorithmus, der an sich sehr einfach sein sollte. Ich fände klasse, wenn man konzeptionelle Gedanken austauschen würde und/oder gemeinsam ein Strukturramm für diesen ersten Schritt zum einlesen einer Gedcom-Datei macht. Wichtig: Das Xoxo-Framework bietet keine StringStreams.
Delphi-Quellcode:
Das war so ein erster Gedankenansatz. Ich hoffe, ihr konntet mir folgen.
procedure Parse(Gedcom As Stringlist)
var AktuelleZeile: String; AktuellesObjektName, AktuellesObjekt: String; // INDI, FAM, SOUR, OBJ, NOTE i, Start, Ende: Integer; istObjekt: Boolean = False; begin for i:=1 to Gedcom.Count do begin AktuelleZeile:= Gedcom.Items[i]; if LeftStr(AktuelleZeile, 1) = "0" then AktuellesObjektName = Copy(AktuelleZeile, 3, 4); AktuellesObjektName = Trim(AktuellesObjektName); // solange diese nicht der Header ist if AktuellesObjektName <> "HEAD" then // Typ auslesen (INDI, FAM etc.) AktuellesObjekt = RightStr(AktuelleZeile, 4); AktuellesObjekt = Trim(AktuellesObjekt); case AktuellesObjekt of "INDI": begin if istObjekt then B = i-1 istObjekt = False // an dieser Stelle würde ich nun einem Point A und B zuweisen und das Objekt in einer Liste speichern else A = i; istObjekt = True end; end; end; end; end; end; end; Eines ist noch zu bedenken: Das Ende einer Gedcom-Datei wird ebenfalls mit einer 0 am Beginn gekennzeichnet ("0 TRLR"). Sollte istObjekt = True sein, muss als B Gedcom.Count-1 gesetzt werden. Ich freue mich über einen regen Austausch. Gute Nacht! |
AW: Gedcom-Datei parsen
Mir ist nicht so recht klargeworden, welche Hilfe du erwartest. Die Vorgehensweise zum Parsen der genannten Textdatei hast du doch bereits ausführlich beschrieben. Woran hapert es also noch? Funktioniert dein Code nicht? Erhältst du Fehlermeldungen? Wenn ja, welche?
Auch deinen Hinweis, daß das Xoxo-Framework keine StringStreams biete, verstehe ich nicht so ganz: Inwiefern ist das ausschlaggebend, wenn du doch gar nicht mit diesem Framework arbeiten willst, sondern lediglich mit der bezeichneten Textdatei? |
AW: Gedcom-Datei parsen
Ist das jetzt nur für dich als Spielerei gedacht oder soll das ein ernsthaftes Produkt werden?
Bei so übermächtigen Konkurrenten wie MyHeritage braucht ihr schon ein dickes Alleinstellungsmerkmal, um gegenüber denen zu bestehen. |
AW: Gedcom-Datei parsen
Ich glaube, er hat nicht gesagt, dass er nicht mit dem Framework arbeiten möchte, sondern dass es egal sei, weil es nur um den Algorithmus und nicht die konkrete Implementierung gehe.
Sprich: er will Hilfe zum Algorithmus, und meint auch hier gut aufgehoben zu sein, obwohl wir Pascal sprechen :) Zur Idee an sich: Zeilennummern merken wird sehr komplex. Was, wenn einer Person ein Feld hinzugefügt wird? Dann müssen alle Indizes danach angepasst werden. Ditto umgekehrt für Löschen. Zum Parsen Personenblöcke übergeben mag noch gehen. Pro Personenobjekt den Personenblock im Speicher halten auch, um unbekannte Felder uminterpretiert mitschleifen zu können. Aber globale Indizes halte ich für zu unnötig umständlich. Eine alternative Idee für das Umgehen mit dateiweiten Indizes wäre, jede Zeile als Objekt zu betrachten, denn Zeiger wären im Gegensatz zu Indizes bei Einfügeoperationen etc. stabil. |
AW: Gedcom-Datei parsen
Ich würde mich einfach mal durch die Quelltexte dieser Open Source Projekte lesen:
![]() Die müssen ja auch alle irgendwie die GEDCOM-Datei parsen. |
AW: Gedcom-Datei parsen
Das sieht doch ganz wie eine Baumstruktur aus. Klar, wenn der Level (erste Ziffer) größer wird, hängt ein Kind dran. Wird er wieder kleiner, ist es ein weiterer Knoten im letzten Knoten des gleichen Levels. Nicht weiter wild, das einzulesen. Ich glaube, die Notation heißt 'Infix' (im Gegensatz zu Postfix oder Prefix)
Und es ist auch nicht weiter wild, das wieder abzuspeichern (Wieder Infix). Ich würde mir also keine Gedanken darüber machen, die einzelnen Zeilennummern mitzuspeichern, denn sobald Du irgendwo etwas umsortiertst, entfernst oder hinzupackst, stimmten die Nummern ja nicht mehr. Ich würde mir also eine Klasse für einen abstrakten Knoten definieren und davon die einzelnen Datentypen/Knotentypen ableiten (INDI, SOUR etc.) Ein Knoten ist ein Ding mit Nutzdaten und eine Liste von Unterknoten. Die Nutzdaten ist Text, Datum, ein Dokument vielleicht, ein Bild(?) etc. Xojo kann Klassen? Wenn ja: Fein. Wenn nicht. Dann... Oh je, wie war das doch gleich? Ach ja, wir bauen uns dann selbst einen Baum zusammen. Auf abstrakte Knoten etc. müssen wir dann eben pfeifen und alles über Arrays abbilden. |
AW: Gedcom-Datei parsen
Zitat:
Zitat:
Zitat:
Zitat:
Mit der Notation "Infix" kann ich leider nichts anfangen. Was ist das? |
AW: Gedcom-Datei parsen
Infix bedeutet beim Aufzählen aller Knoten eines Baumes: Erst 'mich', dann meine Kinder.
Prefix bedeutet (beim binären Baum): Erst linkes Kind, dann mich, dann rechtes Kind Postfix 'erst rechts, dann ich, dann links' Vielleicht war ich zu voreilig, und es gibt Pre- und Postfix-Notation bei t-ären(t>2) Bäumen gar nicht. (t-ärer Baum: Jeder Knoten kann max t Kinder haben). Beim Aufzählen (und damit beim *erzeugen* der GEDCOM-Datei) würde man in etwa so vorgehen:
Code:
und der Aufruf startet mit
print-gedcom (node, level):
print level, node.type, node.description for each child in node.children print-gedcom(child, level + 1)
Code:
Wie Du siehst, haben wir es hier mit einer rekursiven Datenstruktur zu tun, d.h. die Klasse 'Knoten' beinhaltet widerum 'Knoten' (nämlich die Liste der Kindknoten).
foreach node in root-nodes
print-gedcom (node, 0) Mal das mal auf: Oben die Wurzel (hier: Level: -1) darunter alle Hauptknoten (Level 0) Unter jeden Hauptknoten kommen seine Unterknoten (Level 1) und unter jeden Unterknoten (level k) wieder sein Unterknoten (Level k+1) usw. |
AW: Gedcom-Datei parsen
Zitat:
Die Wurzel ist immer das erste INDI-Record eine Gedcom-Datei (z.B. "0 @I1@ INDI"). So richtig hilft mir der Gedankenansatz nicht weiter ;) |
AW: Gedcom-Datei parsen
Wenn schon, dann solltest du die ganze Datei parsen. Dann beginnt es schon mal beim HEAD. Aber das ist schon der erste Knoten, die wirkliche Root-Node ist die Datei selber.
Die Zeile fängt immer mit einer Zahl an und die referenziert auf den Parent im aktuellen Kontext. Du brauchst also schon mal ein Konstrukt, wo du immer auf die aktuelle Parents zugreifen kannst.
Code:
0 ...
Delphi-Quellcode:
// Node einfügen
AddNode( TNode.Create( GetParent( 0 ) ); // diese Node wird ab nun bei GetParent( 1 ) zurückgeliefert // GetParent( n ) mit n > 1 liefert nun eine Exception!
Code:
1 ...
2 ... 3 ... 1 ...
Delphi-Quellcode:
Das würde ich zunächst umsetzen. Wenn das funktioniert, dann weitere Ableitungen von der Node-Basisklasse erstellen und spezialisieren.
AddNode( TNode.Create( GetParent( 1 ) );
AddNode( TNode.Create( GetParent( 2 ) ); AddNode( TNode.Create( GetParent( 3 ) ); AddNode( TNode.Create( GetParent( 1 ) ); // Ab nun kann man nicht mehr auf Parent 2/3 zugreifen |
AW: Gedcom-Datei parsen
Zitat:
Suchst du nach der passenden Baum- und/oder Klassenstruktur? |
AW: Gedcom-Datei parsen
Zitat:
![]() Das war an sich auch nicht das Problem, da ich ja nun die Datenstruktur/Modell habe. Es geht einzig und allein darum, wie ich eine bestehende Gedcom-Datei schnell einlesen kann. Und ich befürchte, dass das bei den ganzen Stringoperationen und bei Gedcom-Dateien mit 10000 Personen und/oder Familien und mehr etc. gar nicht so einfach ist, einen guten, schnellen Algorithmus zum parsen zu schreiben. |
AW: Gedcom-Datei parsen
Hier mal so ein Minimalprogramm zum Veranschaulichen
Delphi-Quellcode:
Ausgabe ist dann wie folgt
program dp_183093;
{$APPTYPE CONSOLE} {$R *.res} uses System.Generics.Collections, System.SysUtils; type TNode = class private FParent: TNode; FChildren: TList<TNode>; function GetLastChild: TNode; function GetChildCount: Integer; function GetChild( Index: Integer ): TNode; public constructor Create( AParent: TNode ); destructor Destroy; override; procedure AddChild( ANode: TNode ); property ChildCount: Integer read GetChildCount; property Children[Index: Integer]: TNode read GetChild; property Parent: TNode read FParent; property LastChild: TNode read GetLastChild; end; TGedFile = class( TNode ) private function GetParent( Index: Integer ): TNode; public constructor Create( AValues: TArray<Integer> ); end; { TNode } procedure TNode.AddChild( ANode: TNode ); begin 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 TNode.Create( AParent: TNode ); begin inherited Create; FChildren := TObjectList<TNode>.Create; if Assigned( AParent ) then AParent.AddChild( Self ); end; destructor TNode.Destroy; begin FChildren.Free; inherited; end; function TNode.GetChild( Index: Integer ): TNode; begin Result := FChildren[Index]; end; function TNode.GetChildCount: Integer; begin Result := FChildren.Count; end; function TNode.GetLastChild: TNode; begin Result := FChildren.Last; end; { TGedFile } constructor TGedFile.Create( AValues: TArray<Integer> ); var LValue: Integer; begin inherited Create( nil ); for LValue in AValues do begin TNode.Create( GetParent( LValue ) ); end; 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 OutputNode( ANode: TNode; ALevel: Integer = 0 ); var LIdx: Integer; begin Write( '':ALevel, ANode.ToString ); if ALevel > 0 then Write( ' (', ALevel - 1, ')' ); WriteLn; for LIdx := 0 to ANode.ChildCount - 1 do OutputNode( ANode.Children[LIdx], ALevel + 1 ); end; procedure Main; var LFile: TGedFile; begin LFile := TGedFile.Create( TArray<Integer>.Create( 0, 1, 2, 3, 1, 1, 1, 2, 3, 3, 3, 3 ) ); try OutputNode( LFile ); finally LFile.Free; end; end; begin try Main; except on E: Exception do WriteLn( E.ClassName, ': ', E.Message ); end; ReadLn; end.
Code:
TGedFile
TNode (0) TNode (1) TNode (2) TNode (3) TNode (1) TNode (1) TNode (1) TNode (2) TNode (3) TNode (3) TNode (3) TNode (3) |
AW: Gedcom-Datei parsen
Zitat:
Wie oft man das für eine GEDCOM-Datei? => Einmal! Das reine GEDCOM auslesen ist ja nicht das Problem, oder? Viel schwieriger stelle ich mir vor, die logische Verknüpfung und Visualisierung hinzugekommen. Meine Ur-ur-ur-Großeltern waren beispielsweise entfernte Cousins und hatten einen gemeinsamen Ur-ur- bzw. Ur-ur-ur-Großvater. Wie erkennt man solche "geschlossenen Kreise" im Stammbaum? Gerade bei 10.000 Personen, die sonst wie miteinander verwandt sein können, hast du zwangsläufig die unmöglichsten Konstellationen. |
AW: Gedcom-Datei parsen
[QUOTE=TiGü;1283119]
Zitat:
Die Personen/Familien werden alle in einer eigenen Liste gespeichert. Auf sie zugreifen kann man über deren ![]() |
AW: Gedcom-Datei parsen
Die GEDCOM-Datei ist aber nun mal in einer Baum-Struktur abgelegt. Um das vernünftig aufbrechen zu können packt man den Dateiinhalt in so einen Baum und kann sich dann aus diesem Baum einfach bedienen.
Der Baum ist nur für das Einlesen gedacht. Kann aber, wenn der Baum durch die Daten befüllt wird auch dazu dienen wieder eine GEDCOM-Datei zu erstellen. Das muss man ganz klar trennen. Beispiel der Knoten NOTE kann Unterknoten CONT und der Unterknoten CONC haben. Da alle zugehörigen aber an NOTE hängen, kann ich aus dem Knoten NOTE den gesamten Text auslesen und in ein Memo-Feld einer Datenbank speichern. |
AW: Gedcom-Datei parsen
OK, soweit habe ich das verstanden.
Ich lese die Datei via TextInputStream ein (in Delphi TStringList) - und zwar Zeilenweise. Ich überprüfe, ob die Zeile mit 0 beginnt und extrahiere dann den Typ. Soweit klappt das. Wenn Das Objekt vom z.B. Typ "INDI" ist, Erstelle ich dementsprechend eine neues IndividualRecord und rufe dessen Parser-Methode auf. Auch das klappt. ABER, und jetzt wieder zum Algorithmus, wie kann ich die Folgezeilen auslesen, ohne, dass ich unendlich viele und unnötige Schleifen aufmache? |
AW: Gedcom-Datei parsen
Also das hier lädt mir eine komplette StressTest-Demo-Datei ein
Delphi-Quellcode:
Damit habe ich die im Speicher und kann auf jeden Knoten ganz gemütlich zugreifen um die Daten nun auszulesen und sonstwie zu verarbeiten. Die erzeugten Nodes parsen hier gar nichts ... wozu auch :)
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; |
AW: Gedcom-Datei parsen
Zitat:
Was macht LValue.DataIsReference? |
AW: Gedcom-Datei parsen
Zitat:
|
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? |
AW: Gedcom-Datei parsen
Ja, der StringBuilder ist nur schneller und rührt nicht so im Speicher herum.
Delphi-Quellcode:
ist analog zu
LStr := '';
for LIdx := 1 to 1000 do LStr := LStr + 'a'; Result := LStr;
Delphi-Quellcode:
LSB := TStringBuilder.Create();
try for LIdx := 1 to 1000 do LSB.Append( 'a' ); Result := LSB.ToString; finally LSB.Free; end; |
AW: Gedcom-Datei parsen
...ach verdammt, ich kriege es nicht zum laufen.
Was wäre denn noch ein Konzept so eine Datei, ressourcenschonend, einzulesen? |
AW: Gedcom-Datei parsen
Hallo,
ist zwar schon eine Weile her, dass die Frage diskutiert wurde, aber ich doktere seit Jahren an demselben Problem herum. Eigentlich will ich lediglich ein möglichst reines GEDCOM (Version 5.5.1) schreiben und benutze dazu das Programm AGES 2.0 ( ![]() Vor Jahren habe ich mich einmal mit dem GEDCOM-Parser für Delphi 'My Family Tree' ( ![]() Ein weiteres Programm, das LaTeX-Ausdrucke von GEDCOM-Dateien vorsieht, ist LifeLines, das aber seit Jahren nicht weiterentwickelt wird. Meine Frage an hansklok ist nun, ob er mit seinem Problem weitergekommen ist und mich eventuell an seinen Fortschritten teilhaben lässt, oder ob ich mich in das oldie LifeLines einarbeiten soll. Gruß ktweigel (Thomas) |
AW: Gedcom-Datei parsen
Zitat:
ja, ich bin noch an der Sache dran. Nur dauert meine Arbeit noch an, da wir es mit einer sehr komplexen Struktur zu tun haben und alle möglichen genealogischen Zusammensetzungen (Cousin/en-Ehe, Inzucht etc.) berücksichtigt werden müssen. Das ist alles, was ich bisher dazu sagen kann. Ich halte an dieser Stelle aber auf dem Laufenden. Bei genaueren Fragen, Anregungen und Wünschen, gerne eine persönliche Nachricht hier schreiben. Gruß hansklok |
AW: Gedcom-Datei parsen
Zitat:
das mit den komplexen Strukturen kann ich nur bestätigen. Ich habe mich deshalb auch bisher davor gescheut, die Daten in einer Datenbank zu halten. Aber wenn man GEDCOM benutzt wird man dafür mit x. rekursiven Strukturen 'belohnt'. Bin nur Hobbyprogrammierer. Werde mir jetzt noch mal das Programm 'Ancestromania' anschauen ( ![]() Am liebsten wäre es mir allerdings, man bekäme den Quellcode von AGES, das ja in DELPHI geschrieben wurde und könnte, falls man ihn versteht, die eigenen Bedürfnisse einprogrammieren. Gruß Thomas (ktweigel) |
AW: Gedcom-Datei parsen
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo hansklok,
Dieser Tread schweigt nun schon seit etlichen Jahren. Bist Du denn mit den komplexen Strukturen weitergekommen. Inzwischen hat ein Prof. Sturm eine LATEX-Package namens 'genealogytree.sty' entwickelt, die höchst qualitätvolle Stammbäume ausdrucken kann (Anhang 48759). Leider gibt es keine Anbindung an das GEDCOM-Format. Und jede einzelne Person in die LATEX-Datei eintragen ist keine Option. Gruß Thomas |
AW: Gedcom-Datei parsen
Hallo Thomas, habe via Persönlicher Nachricht geantwortet!
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:55 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