Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Binärdatei lesen und durchsuchen (https://www.delphipraxis.net/22154-binaerdatei-lesen-und-durchsuchen.html)

Torphyr 13. Mai 2004 09:54


Binärdatei lesen und durchsuchen
 
Hallo zusammen,
ich hätte ja nicht gedacht, daß ich mal einzelne Bits würde schubsen müssen, aber jetzt ist es wirklich soweit. Ich muss CGM-Dateien nach Text durchsuchen. Zur Erklärung: "CGM" steht für "Computer Grafics Metafile" und ist ein offener Standard zum Austausch von CAD-Daten. Beschrieben ist das ganze in einer ISO-Norm, die es aber glücklicherweise zum Download gibt.

Es gibt zwei Speicherformate für CGM, binär und ASCII. Die Textversion ist aber leider für mich nicht relevant, weil sich der Kunde aufgrund bereits vorhandener großer Datenbestände im Binärformat nicht auf ASCII-Codierung umstellen kann. Wie auch immer, das Binärformat ist so aufgebaut, daß jedes Grafikobjekt eine Elementklasse und eine Element-ID besitzt, über die sich die Art des Grafikelementes eindeutig bestimmen lässt. So ist beispielsweise Text immer mit der Klasse "4" und der ID "4" am Beginn der ersten 16 Bit des betreffenden Abschnittes zu identifizieren.

Ein Beispiel (aus der Norm zitiert):
Der Text "Hydrogen" an den Koordinaten x=0 und y=1
Code:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|    4     |       4       |   15   |   Header, Klasse 4 und ID 4, Länge der Parameterliste ist 15
|                  0                |   x=0
|                  1                |   y=1
|          8       |       'H'     |   Textlänge, String
|         'y'     |       'd'     |   String
|         'r'     |       'o'     |   String
|         'g'     |       'e'     |   String
|         'n'     |        0       |   String, auffüllende Null
Nun ist es mir zwar schon gelungen, eine Datei zu öffnen und *irgend etwas* zu lesen, aber von irgendeiner Kontrolle ist dieses Experiment noch weit entfernt:

Code:
procedure TForm1.BtnGoClick(Sender: TObject);
var
  Datei: file;
  NumRead: Integer;
  Buf: Integer;
begin
  if OpenDialog1.Execute then
  begin
    AssignFile(Datei, OpenDialog1.FileName);
    Reset(Datei, 1);
    Memo.Lines.Text := 'Dateigröße: ' + IntToStr(FileSize(Datei)) + ' Bytes';
      repeat
        BlockRead(Datei, Buf, 2, NumRead);
        if Buf = 4 then
        begin
          Memo.Lines.Text := Memo.Lines.Text + ' | ' + IntToStr(Buf);
        end;
      until (NumRead = 0);
    CloseFile(Datei);
  end;
end;
Wer kann hilfreiche Hinweise zum sinnvollen weiteren Vorgehen geben?

Besten Dank!
Torphyr

generic 13. Mai 2004 14:15

Re: Binärdatei lesen und durchsuchen
 
bau dir records mit denen du die daten lesen kannst.

Delphi-Quellcode:
type rheader = packed record
       klasseid: Byte;
       parameterlaenge: Byte;
     end;

// für andere typen machst du dir einfach noch weitere records
// welche du dann nach dem header einfach einliest.

var h : rheader;
    fs : TFilestream;
    count : integer;

    s : String;
begin
  fs := TFilestream.Create('c:\text.txt', fmOpenRead);
  try
    repeat
      count:=fs.Read(h, sizeof(rheader));
      if count>0 then
      begin
        case h.klasseid of
// andere typen hier einfügen
          $44 : begin
                  SetLength(s, h.parameterlaenge);
// hier den string lesen
                  count:=fs.Read(s[1], h.parameterlaenge);
                  MessageDlg(s, mtWarning, [mbOK], 0);
                end;
        end;
      end;
    until count=0;
  finally
    fs.free;
  end;
end;

Torphyr 13. Mai 2004 15:00

Re: Binärdatei lesen und durchsuchen
 
Hi generic,
Klasse, Danke für die schnelle und gehaltvolle Antwort. Ich mach mich gleich ans Ausprobieren, habe aber an einer Stelle noch meine Zweifel:

Zitat:

Zitat von generic

Delphi-Quellcode:
type rheader = packed record
       klasseid: Byte;
       parameterlaenge: Byte;
     end;

Kann das gutgehen? Das Problem ist doch, daß im Header die Klasse, die ID und die Parameterlänge ungleichmäßig auf die zwei Byte verteilt sind (und obendrein auch noch rückwärts geschrieben werden, Bitfolge 15 ... 0). Die Klasse ist also nur 4 Bit breit, die ID folgt mit 7 Bit Breite, hängt also rüber ins zweite Byte, und dann kommen schließlich nochmal 5 Bit für die Länge der Parameterliste.
Wenn ich die Typen jetzt als volle Bytes definiere, schnippel ich doch den Header mittendurch, oder? :gruebel:

Mit dem Editor zwischen den Zähnen,
Torphyr

Christian Seehase 13. Mai 2004 17:31

Re: Binärdatei lesen und durchsuchen
 
Moin Torphyr,

erst einmal herzlich willkommen hier in der Delphi-PRAXiS.

Was erhältst Du, wenn Du erst einmal nur den Header ausliest, und Dir diesen mal hexadezimal anzeigen lässt?
(bezogen auf Deine Beispieldatei)

Beispiel:

Delphi-Quellcode:
var
  fsIN   : TFileStream;
  wHeader : WORD;

begin
  fsIN := TFileStream('PfadZuDerCGMDatei',fmOpenRead);
  try
    fsIN.Read(wHeader,2);
    ShowMessage(IntToHex(wHeader,4));
  finally
    FreeAndNil(fsIN);
  end;
end;

Torphyr 14. Mai 2004 10:55

Re: Binärdatei lesen und durchsuchen
 
Hallo Christian,
Danke für den Vorschlag, soweit klappt das schon mal. Bloß: Was ich da zu sehen bekomme ist natürlich nur der Header des ersten Wortes im Stream. Und wahrscheinlich stimmt der Wert nicht mal, weil ja die Bitfolge zuvor umgekehrt werden müsste (ich frage mich sowieso schon die ganze Zeit, warum die invertiert ist). Ich müsste also folgendes machen:

1. Den stream wortweise lesen.
2. Die Bitfolge des Wortes umkehren (von 15 ... 0 nach 0 ... 15)
3. Prüfen, ob die Bits 5 ... 11 und 12 ... 15 jeweils 4 ergeben
4. Wenn das der Fall ist, die weiteren Informationen auslesen (Position und Text)

Nun bin ich inzwischen auf "TBits" gestoßen, kann aber nicht viel dazu finden, außer daß das ein Array of Bits mit beliebiger Länge ist. Keine Ahnung, ob ich damit was zuwege bringe ...

Torphyr

Torphyr 14. Mai 2004 12:41

Interessante QuelleBin gerade
 
Bin gerade auf eine interessante Quelle zum Thema Byte-Manipulation gestoßen:
http://www.merlyn.demon.co.uk/del-bits.htm#View
Ist aber echt trockenes Brot :?
Was soll's, ich hab ja gesunde Zähne! :mrgreen:

UPDATE
Halt, hier, noch besser:
http://www.oszhdl.be.schule.de/gymna...load/index.htm
Ganz unten, bei "Klassenbibliotheken" der Link "Dlib". Ind der unit "ubits" steckt eine Menge tolles Zeug.
Allmählich fasse ich wieder Mut :)

Christian Seehase 14. Mai 2004 15:45

Re: Binärdatei lesen und durchsuchen
 
Moin Torphyr,

mir ging's eigentlich mal darum, dass Du kurz ein Beispiel zeigst, aus dem man dann ableiten kann, wie die gelesenen Daten zu verarbeiten sind.
Wenn ich das richtig sehe, ist ja nur das erste gelesene Word aufzubereiten, den Rest eines jeden Satzes kann man dann ja direkt auslesen, da hier nichts innerhalb eines Byte/Word codiert ist.
(und ich wollte mir nicht extra die Doku zu CGM besorgen ;-) )

Torphyr 28. Mai 2004 15:25

Re: Binärdatei lesen und durchsuchen
 
Liste der Anhänge anzeigen (Anzahl: 1)
So, das Gröbste ist geschafft. Noch mal zur Erinnerung: Ich wollte das Binär-CAD-Format CGM lesen, um die darin enthaltenen Texte und die jeweils zugehörigen X/Y-Koordinaten für weitere Verwendung zu extrahieren. Danke für die Anregungen! Nun bin selbst draufgekommen, wie's gemacht wird, auch wenn es eine Weile gedauert hat (und sicher nicht sehr elegant ist). Egal: Es tut. Einen Haken hat die Sache aber noch: Zwar komme ich jetzt an die Koordinaten, nicht jedoch aber zu einer vernünftigen Interpretation. Das scheint mir jedoch ein eigenes, eher mathematisches Problem zu sein, für das ich besser einen neuen Thread starte.

So, nun noch das kommentierte Listing:
Delphi-Quellcode:
unit CGM;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    BtnGo: TButton;
    OpenDialog1: TOpenDialog;
    Memo2: TMemo;
    Label1: TLabel;
    procedure BtnGoClick(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
Function BTstBit ( Zahl : Byte; Bitnr : Integer ) : Boolean;
(* -------------------------------------------------------------------- *)
Begin
  Bitnr := Bitnr And $0007;
  Btstbit := (( Zahl Shr Bitnr ) And 1 ) = 1
End;

Function BSetBit ( Zahl : Byte; Bitnr : Integer ) : Byte;
(* -------------------------------------------------------------------- *)
Var I : Byte;
Begin
  Bitnr := Bitnr And $0007;
  I := 1;
  Bsetbit := Zahl Or ( I Shl Bitnr )
End;

Function WSetBit ( Zahl : Word; Bitnr : Integer ) : Word;
(* -------------------------------------------------------------------- *)
Var I : Byte;
Begin
  Bitnr := Bitnr And $000F;
  I := 1;
  Wsetbit := Zahl Or ( I Shl Bitnr )
End;

Function ByteToBinStr ( Zahl : Byte ) : String;
(* -------------------------------------------------------------------- *)
Var I : Integer; W : String[8];
Begin
  W := '00000000';
  For I := 7 Downto 0 Do
  If Btstbit ( Zahl,I )
  Then Insert ('1',W,8 - I );
  ByteToBinStr := W
End;

Function BinStrToByte ( Str1 : String ) : Byte;
(* -------------------------------------------------------------------- *)
Var
  I,J : Integer;
  K  : Byte;
Begin
  K := 0;
  J := Length ( Str1 );
  If ( J > 8 )
  Then J := 8;
  For I := J Downto 1 Do
  If ( Str1[i] = '1')
  Then K := Bsetbit ( K, J - I );
  BinStrToByte := K
End;

Function BinStrToWord ( Str1 : String ) : Word;
(* -------------------------------------------------------------------- *)
Var
  I,J : Integer;
  W  : Word;
Begin
  W := 0;
  J := Length ( Str1 );
  If ( J > 16 )
  Then J := 16;
  For I := J Downto 1 Do
  If ( Str1[I] = '1')
  Then W := Wsetbit ( W, J - I );
  BinStrToWord := W
End;

procedure TForm1.BtnGoClick(Sender: TObject);
var
  fs : TFilestream;
  bHeader, ByteParam : Byte;
  WordParam : Word;
  boxb, boxh, posx, posy : Smallint;
  count, i, j, k, paramlist, len : Integer;
  bin, Byte01, Byte02, ausgabe, HClass, HID, PLLength, cgmtext, ding : String;


begin // 1
 if OpenDialog1.Execute then    { Öffnen-Dialogfeld anzeigen }
  begin // 2
    fs := TFileStream.Create(OpenDialog1.FileName,fmOpenRead); // Okay, wir lesen die Datei ein
    i := 0;            // Zähler für die Grafik-Objekte in der Datei
    bin := '';         // Variable für Binärstrings
    ausgabe := '';     // Kommt ins linke Textfenster: Die Header der Grafik-Objekte
    cgmtext := '';     // Kommt ins rechte Textfenster: Der Text und (bisher leider noch nicht) seine Koordinaten
    memo1.Text := '';  // Textfenster ...
    memo2.Text := '';  // ... nullen
    try // 3
      repeat // so lange wiederholen, bis die Datei alle ist :-)
        count:=fs.Read(bHeader, sizeof(bheader));
        Byte01 := ByteToBinStr(bHeader);           // Die ersten zwei Bytes enthalten den
        count:=fs.Read(bHeader, sizeof(bheader));  // Header. Wir wandelnb sie in einen
        Byte02 := ByteToBinStr(bHeader);           // Binärstring ...

        i := i + 1; // Grafikobjekt Nr.
        bin := Byte01 + Byte02;                    // ... und kleben sie aneinander.

        HClass := '';                              // CGM-Klasse
        HID := '';                                 // CGM - ID
        PLLength := '';                            // Parameter-Lauflänge

        // Dann gucken wir uns den Header genauer an:
        For j := 1 to 16 do
        begin
          case j of
          1..4:   HClass := HClass + bin[j];    // Die ersten vier Bit stehen für die Klasse
          5..11:  HID := HID + bin[j];          // Sieben Bit für die ID (meinzeit, vier hätten es auch getan)
          12..16: PLLength := PLLength + bin[j]; // Der Rest geht für die Lauflänge drauf
          end;
        end;
        HClass := IntToStr(BinStrToByte(HClass));    // Zurück zu etwas mehr lesbarem: Die CGM-Klasse ...
        HID := IntToStr(BinStrToByte(HID));          // ... die ID ..
        PLLength := IntToStr(BinStrToByte(PLLength)); // ... und die Lauflänge.

        // Bei Klasse 4 und ID 4 haben wir Text an einer X/Y-Position, alles andere
        // ist erstmal uninteressant.
        if (StrToInt(HClass) = 4) AND (StrToInt(HID) = 4) then
          begin // 4
              paramlist := StrToInt(PLLength);
              cgmtext := cgmtext + '*** ' + IntToStr(i) + #13#10; // Die laufende Nummer des Grafik-Objektes
              for j := 1 to paramlist do // So oft, wie's Parameter gibt
              begin // 5
                case j of // 6
                1: begin                                      //  ???????????????????????
                      ding := '';                              // Noch nicht entschlüsselt!
                      fs.Read(ByteParam, sizeof(ByteParam));   // Als erstes kommt die X-Koordinate mit einer
                      Byte01 := ByteToBinStr(ByteParam);       // Lauflänge von vier Bits, relevant sind wahrscheinlich
                      fs.Read(ByteParam, sizeof(ByteParam));   // die ersten zwei.
                      Byte02 := ByteToBinStr(ByteParam);

                      bin := Byte01 + Byte02;
                      For k := 1 to 16 do
                      begin
                       ding := ding + bin[k];
                      end;
                      cgmtext := cgmtext + 'Xbin: ' + ding + #13#10;
                      cgmtext := cgmtext + 'X: ' + IntToStr(BinStrToWord(ding)) + #13#10;

                      fs.Read(ByteParam, sizeof(ByteParam)); // Zwei Byte weiter in der Datei
                      fs.Read(ByteParam, sizeof(ByteParam));
                    end;
                2: begin
                      fs.Read(ByteParam, sizeof(ByteParam));                           // Als nächstes die Y-Koordinate, ...
                      cgmtext := cgmtext + 'Y1: ' + ByteToBinStr(ByteParam) + #13#10;  // ... zu Forschungszwecken diesmal ..
                      fs.Read(ByteParam, sizeof(ByteParam));                           // ... in Binärdarstellung (ich habe zum
                      cgmtext := cgmtext + 'Y2: ' + ByteToBinStr(ByteParam) + #13#10;  // Test CGM-Dateien mit X=Y fabriziert, die
                      fs.Read(ByteParam, sizeof(ByteParam));                           // Binärdartsellung der Y-Koordinate entspricht
                      cgmtext := cgmtext + 'Y3: ' + ByteToBinStr(ByteParam) + #13#10;  // also auch dem Wert für X).
                      fs.Read(ByteParam, sizeof(ByteParam));
                      cgmtext := cgmtext + 'Y4: ' + ByteToBinStr(ByteParam) + #13#10;
                    end;
                3: begin
                      fs.Read(WordParam, sizeof(WordParam));
                      cgmtext := cgmtext + 'Fin: ' + IntToStr(WordParam) + #13#10;     // Abschlussflag. Uninteressant.
                    end;
                4: begin
                      fs.Read(ByteParam, sizeof(ByteParam));  // Lauflänge des Textes.
                      len := ByteParam;                      // Wird bis zum nächsten Word-Ende mit Nullen gefüllt.
                      cgmtext := cgmtext + 'Len: ' + IntToStr(ByteParam) + #13#10 + 'Text: ';

                      for k := 1 to len do // So lange es Zeichen gibt.
                      begin
                        fs.Read(ByteParam, sizeof(ByteParam));
                        if (ByteParam > 33) and (ByteParam < 126) then // Sicherung gegen ungültige Chars.
                          cgmtext := cgmtext + char(ByteParam);
                      end;
                      if len / 2 = Int(len / 2) then   // Eventuelles Füllbyte überspringen
                        fs.Read(ByteParam, sizeof(ByteParam));
                    end;
                end; // 6
              end; // 5
              cgmtext := cgmtext + #13#10 + #13#10; // Leerzeile als Trennung zum nächsten Textobjekt
          end
          else begin // 4
          paramlist := StrToInt(PLLength); // Wie oben (ja, ja, schon gut :-) ), Lauflänge der Parameter
          for j := 1 to paramlist do               // Was jetzt kommt ist kein Text und ...
           fs.Read(ByteParam, sizeof(ByteParam));  // ... deshalb zu überspringen.
          end; // 4
          // Kumulation der CGM-Objektliste für das linke Textfenster
          ausgabe := ausgabe + IntToStr(i) + #9 + '| ' + HClass + ' | ' + HID + ' | ' + PLLength + #13#10;
          Label1.Caption := IntToStr(count); // Nicht weiter wichtig ;-)
      until (count = 0);     // Ende gut ...
      memo1.Text := ausgabe; // ... Programm tut ...
      memo2.Text := cgmtext; // ... Datenflut.
    finally
      FreeAndNil(fs); // Hier kommt keiner lebend raus!
    end; //3
  end; // 2
end; // 1
end.
Den Link zum weiterführenden Thread trage ich noch nach.

[edit=Luckie]Code- durch Delphi-Tags ersetzt. Mfg, Luckie[/edit]

dizzy 28. Mai 2004 15:33

Re: Binärdatei lesen und durchsuchen
 
Ich verstehe zwar nicht, worum es in dem QT letzten Endes geht, doch du solltest dich mal mit dem Borland Styleguide auseinandersetzen ;)
Und: Bei Delphi-QT bitte nicht die [ code]-, sonder die [ delphi]-Tags benutzen (ohne Leerzeichen). Dann klappt auch die Syntaxhervorhebung.
Der Code ist in seiner jetzigen Form nicht so gut lesbar. Z.B. wechselst du zwischen "Begin" und "begin", manchmal
Code:
for i := a to b do
begin
  //code
end;

// und manchmal:
for i := a to b do
 begin
   //code
 end;
\\edit: *gnarf* die Delphi-Tags formatieren schon selber ein wenig... also eben [ code]...
wobei oberes vorzuziehen wäre. Das hat den Grund, dass dann bei Verschachtelungen die "end;"'s nicht so krüppelig aussehen :)
Und der Kommentar zwischen Methodenkopf und dem begin ist *buäääää* :mrgreen:


Ich mag jetzt völlig über-pingelig klingen, doch hab ich in den letzten Tagen sehr stark gemerkt, dass ein einheitliches Code-Bild nach einem Semi-Standard (Styleguide) ganz erheblich zur Lesbarkeit beiträgt! Wollte ich dir nur ans Herz gelegt haben *patsch*


gruss,
dizzy

Torphyr 28. Mai 2004 15:38

Re: Binärdatei lesen und durchsuchen
 
Hallo dizzy,
was willst Du, 'n Killer oder 'n Dressman? :-D

Okay: Du hast Recht.
Und ich keine Zeit, jedenfalls so lange es um Forschung geht. Später dann, naja ...

Schüss,
Torphyr


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:39 Uhr.
Seite 1 von 2  1 2      

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