AGB  ·  Datenschutz  ·  Impressum  







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

Gedcom-Datei parsen

Ein Thema von hansklok · begonnen am 11. Dez 2014 · letzter Beitrag vom 7. Apr 2018
Antwort Antwort
hansklok

Registriert seit: 14. Apr 2004
Ort: Karlsruhe
318 Beiträge
 
Delphi 2010 Architect
 
#1

AW: Gedcom-Datei parsen

  Alt 11. Dez 2014, 18:33
[QUOTE=TiGü;1283119]
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.
Genau das ist der Grund, warum ich mich so schwer tue, alles in einer Binärbaum-Struktur unterzubringen.

Die Personen/Familien werden alle in einer eigenen Liste gespeichert. Auf sie zugreifen kann man über deren UUID. Dann ist es auch möglich Spezialfälle wie Inzucht oder Cousin- & Cousinenehen darzustellen.
  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
 
#2

AW: Gedcom-Datei parsen

  Alt 11. Dez 2014, 18:50
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.
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
hansklok

Registriert seit: 14. Apr 2004
Ort: Karlsruhe
318 Beiträge
 
Delphi 2010 Architect
 
#3

AW: Gedcom-Datei parsen

  Alt 11. Dez 2014, 18:58
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?
  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
 
#4

AW: Gedcom-Datei parsen

  Alt 11. Dez 2014, 22:35
Also das hier lädt mir eine komplette StressTest-Demo-Datei ein
Delphi-Quellcode:
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;
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
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
hansklok

Registriert seit: 14. Apr 2004
Ort: Karlsruhe
318 Beiträge
 
Delphi 2010 Architect
 
#5

AW: Gedcom-Datei parsen

  Alt 11. Dez 2014, 23:04
Also das hier lädt mir eine komplette StressTest-Demo-Datei ein
Kannst Du den Quellcode bitte bisschen kommentieren? Was ist bei Dir TDataRecord?

Was macht LValue.DataIsReference?

Geändert von hansklok (11. Dez 2014 um 23:07 Uhr)
  Mit Zitat antworten Zitat
hansklok

Registriert seit: 14. Apr 2004
Ort: Karlsruhe
318 Beiträge
 
Delphi 2010 Architect
 
#6

AW: Gedcom-Datei parsen

  Alt 11. Dez 2014, 23:52
Also das hier lädt mir eine komplette StressTest-Demo-Datei ein
Kannst Du bitte deinen gesamten Source posten, dann kann ich ersten besser nachvollziehen und zweitens versuchen in Xoxo zu übersetzen, an sich nicht schwer.
  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
 
#7

AW: Gedcom-Datei parsen

  Alt 12. Dez 2014, 00:07
Also das hier lädt mir eine komplette StressTest-Demo-Datei ein
Kannst Du den Quellcode bitte bisschen kommentieren? Was ist bei Dir TDataRecord?

Was macht LValue.DataIsReference?
Eigentlich habe ich doch recht sprechenden Quelltext:
Delphi-Quellcode:
// Wenn der Data-String mit einem @ startet, dann ist es eine Referenz
Result.DataIsReference := Result.Data.StartsWith( '@' );
Wenn man sich die Sätze anschaut, dann sind die alle nach dem gleichen Muster gestrickt
Code:
<LEVEL>[<sep>@<REFERENCE>@]<sep><TYPE>[<sep><DATA>]

<LEVEL> = numerisch
<sep> = SPACE
<TYPE> = alphanumerisch ohne <sep>
<DATA> = alles bis zum Ende der Zeile
Das war es schon.

In den Record TDataRecord fülle ich einfach die Werte aus der Zeile ein.

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.
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
Jumpy

Registriert seit: 9. Dez 2010
Ort: Mönchengladbach
1.739 Beiträge
 
Delphi 6 Enterprise
 
#8

AW: Gedcom-Datei parsen

  Alt 12. Dez 2014, 09:02
Aus der verlinkten Spezi:

Zitat:
Eine GEDCOM Übertragung beinhaltet eine Datenbank in Form eines sequenziellen Datenstromes aus
mit einander verbundenenDatensätzen. Ein Datensatz wird durch eine Abfolge von gekennzeichneten
Zeilen variabler Länge repräsentiert, die in einer Hierarchie angeordnet sind. Eine Zeile besteht immer aus der Nummer einer Hierarchieebene, einem Kennzeichen (Tag) und optional einem Wert. Eine Zeile kann außerdem eine Querverweis-ID oder Querverweiszeiger beinhalten.
[...]
Eine Reihe von einer oder mehr Zeilen erzeugt einen Datensatz. Der Anfang eines neuen
Datensatzes wird durch eine Zeile gekennzeichnet, die mit der Ebenennummer 0 (Null) beginnt.
Nur weil es um Stammbäume geht und der Inhalt der Datei aussieht wie eine Baumstruktur, erhöht es mMn nur die Komplexität, das beim einlesen unbedingt in eine Baumstruktur zu schaufeln, da in dem Baum dann Äpfel, Birnen und Nüsse hängen (Familien, Personen, Beziehungen, Sourcen, ...) die anschließend extra wieder gepflückt und auf ihre Körbe verteilt werden müssen. Da kann ich die doch direkt so wie die von der Palette/der gemischten Kiste (der Datei) kommen auf die Körbe verteilen.

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?
Angehängte Grafiken
Dateityp: jpg Baumstruktur.jpg (14,4 KB, 10x aufgerufen)
Ralph

Geändert von Jumpy (12. Dez 2014 um 09:12 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 03:23 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