Entwickler-Ecke
Delphi Language (Object-Pascal) / CLX - Komprimieren von Daten innerhalb des Programms, z.B. mit ZIP
UweK - Fr 29.06.12 15:06
Titel: Komprimieren von Daten innerhalb des Programms, z.B. mit ZIP
Guten Tag,
Ich möchte in einem ASCII-File mit der Zeilenstruktur "<Keywort> = <Wert>" bei einem <Wert> binäre Daten unterbringen, die ich natürlich MIME codiere, damit der File problemlos in einem Editor bearbeitbar bleibt.
Die binären Daten sind groß (über 100 kB), und nach MIME noch ein ganzes Stück größer. Deshalb würde ich die Daten vor dem MIME gern komprimieren. ZIP wäre dafür z.B. gut geeignet. Ich habe die Daten mal testweise in eine Extradatei geschrieben und diese dann mit einem normalen ZIP-Programm komprimiert. Durch viele "symmetrische Muster" in meinen Daten schrumpfte das Ergebnis dann auf 1 kB Größe. Es könnte aber sicherlich auch irgendein anderer Kompressor als ZIP sein, denn ich möchte den <Wert> nur innerhalb meines Programmes in die ASCII-Files schreiben und wieder zurück lesen. Es sollte aber möglichst keine extra DLL sein, damit ich mein Programm bequem weitergeben kann.
Im Idealfall stelle ich mir etwas ganz Simples in dieser Form vor:
Delphi-Quelltext
1: 2:
| ZippedString:= ZIP(DatenString); ErgebnisString:= MIME(ZippedString); |
Ich habe mir bereits diverse Webseiten mit ZIP Tools angeschaut (ZipForge, Abbrevia, TZipMaster usw.), bin bei deren Beschreibungen aber in der Fülle von Funktionen ertrunken, die ich alle nicht brauche, ohne dabei einen Hinweis auf meinen gewünschen Mini-Zweck zu finden.
Ich würde mich sehr freuen, wenn mir jemand von Euch einen Tipp aus der eigenen Erfahrung geben könnte.
Moderiert von
Narses: Delphi-Tags hinzugefügt
Martok - Fr 29.06.12 15:22
Da du eigentlich ja nicht das Datenformat brauchst, dürfte T(De)CompressionStream (Unit zlib) das sein, was du suchst.
Ungefähr so (ungetestet, im Beitragseditor getippt):
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| uses zlib; function Zip(S:String): string; var src,dst: TStringStream; comp: TCompressionStream; begin src:= TStringStream.Create(S); dst:= TStringStream.Create(''); comp:= TCompressionStream.Create(dst); try comp.CopyFrom(src, -1); Result:= dst.DataString; finally end end; |
Narses - Fr 29.06.12 15:44
Moin!
UweK hat folgendes geschrieben : |
die ich natürlich MIME codiere |
Ich nehme mal an, du meinst
base64 [
http://de.wikipedia.org/wiki/Base64], weil
MIME [
http://de.wikipedia.org/wiki/Multipurpose_Internet_Mail_Extensions] ist keine Codierung, sondern ein Protokoll. :les:
Hier mein Vorschlag (getestet :mrgreen:):
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34:
| uses ..., zlib, base64;
procedure TForm1.btnEncodeClick(Sender: TObject); var Data: AnsiString; CompressedData: TStringStream; Compressor: TCompressionStream; begin Data := Memo1.Text; CompressedData := TStringStream.Create(''); Compressor := TCompressionStream.Create(clMax, CompressedData); Compressor.Write(PAnsiChar(Data)^, Length(Data)); Compressor.Free; Memo2.Text := B64Encode(CompressedData.DataString); CompressedData.Free; end;
procedure TForm1.btnDecodeClick(Sender: TObject); var CompressedData: TStringStream; Decompressor: TDecompressionStream; C: AnsiChar; Data: AnsiString; begin CompressedData := TStringStream.Create(B64Decode(Trim(Memo2.Text))); Decompressor := TDecompressionStream.Create(CompressedData); Data := ''; while Decompressor.Read(C, 1) = 1 do Data := Data +C; Memo1.Text := Data; Decompressor.Free; CompressedData.Free; end; |
Ich gestehe, ich habe das jetzt nicht auf Unicode-String-tauglichkeit hin gemacht, sondern einfach so, wie mein D7 es erlaubt. Also, bei Unicode-Delphi-Versionen Vorsicht mit dem TStringStream, da müsste man dann den Umweg über einen TMemoryStream gehen oder so. Laut
jaenicke geht das sogar unter Unicode-Delphi-Versionen. :nixweiss:
cu
Narses
Martok - Fr 29.06.12 17:38
Narses hat folgendes geschrieben : |
Also, bei Unicode-Delphi-Versionen Vorsicht mit dem TStringStream, da müsste man dann den Umweg über einen TMemoryStream gehen oder so. :idea: |
Eigentlich hab ich extra deswegen den StringStream genommen - in der Hoffnung, dass mal irgendwas richtig portiert wurde. Kann das mal jemand verifizieren?
jaenicke - Fr 29.06.12 19:53
Der Quelltext von
Narses funktioniert auch unter XE2 problemlos. ;-)
Narses - Fr 29.06.12 21:23
Moin!
Martok hat folgendes geschrieben : |
Narses hat folgendes geschrieben : | Also, bei Unicode-Delphi-Versionen Vorsicht mit dem TStringStream, da müsste man dann den Umweg über einen TMemoryStream gehen oder so. :idea: | Eigentlich hab ich extra deswegen den StringStream genommen |
What? :shock: Der String im TStringStream unter Unicode-Delphi ist ein AnsiString?! :suspect: Das kann ich kaum glauben... :gruebel:
jaenicke hat folgendes geschrieben : |
Der Quelltext von Narses funktioniert auch unter XE2 problemlos. ;-) |
Das wundert mich... :lol:
cu
Narses
jaenicke - Sa 30.06.12 04:55
Narses hat folgendes geschrieben : |
What? :shock: Der String im TStringStream unter Unicode-Delphi ist ein AnsiString?! :suspect: Das kann ich kaum glauben... :gruebel: |
Doch, ist es standardmäßig. ;-)
Möchte man Unicode drin haben, muss man das explizit beim Erstellen angeben:
Delphi-Quelltext
1:
| Foo := TStringStream.Create('', TEncoding.Unicode); |
Der Grund dürfte sein, dass viele an solchen Stellen Ansi benutzt haben und man das Verhalten nicht ändern wollte. Intern benutzt der Stream das default encoding (
TEncoding.Default), wenn man keins angibt, und das ist bei Delphi unter Windows Ansi und unter unixoiden Systemen UTF8.
Narses hat folgendes geschrieben : |
jaenicke hat folgendes geschrieben : | Der Quelltext von Narses funktioniert auch unter XE2 problemlos. ;-) | Das wundert mich... :lol: |
DataString ist zwar ein UnicodeString, aber das wird alles automatisch per Compilermagic konvertiert, wenn z.B. die Base64-Funktionen AnsiStrings erwarten. Und deshalb kommt da auch das richtige heraus.
UweK - Mi 11.07.12 15:33
Hallo,
Vielen Dank für eure Tipps. Ich kam erst jetzt dazu, mich damit zu beschäftigen.
Zunächst einmal funktionierte bei mir leider gar nichts davon, bis ich feststellte: Das Zippen geht tatsächlich fehlerfrei und so wie von euch beschrieben. Aber das Problem liegt bei mir noch beim base64. Dieses habe ich nicht in meinem Delphi 2010, und benutze deshalb ersatzweise IdEncoderMime/IdDecoderMime von Indy in dieser Form (meine Änderungen an eurem obigen Quelltext habe ich gekennzeichnet):
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:
| uses zlib, IdCoderMime; procedure TTestForm.btnEncodeClick(Sender: TObject); var Data: AnsiString; CompressedData: TStringStream; Compressor: TCompressionStream; IdEncoderMIME: TIdEncoderMIME; begin IdEncoderMime:= TIdEncoderMIME.Create; Data:= Memo1.Text; CompressedData := TStringStream.Create(''); Compressor := TCompressionStream.Create(clMax, CompressedData); Compressor.Write(PAnsiChar(Data)^, Length(Data)); Compressor.Free; Memo2.Text := IDEncoderMIME.EncodeString(CompressedData.DataString); CompressedData.Free; IdEncoderMIME.Free; end;
procedure TTestForm.btnDecodeClick(Sender: TObject); var CompressedData: TStringStream; Decompressor: TDecompressionStream; C: AnsiChar; Data: AnsiString; IdDecoderMIME: TIdDecoderMIME; begin IdDecoderMime:= TIdDecoderMIME.Create; CompressedData := TStringStream.Create(IdDecoderMime.DecodeString(Trim(Memo2.Text))); Decompressor := TDecompressionStream.Create(CompressedData); Data := ''; while Decompressor.Read(C, 1) = 1 do Data := Data +C; Memo3.Text := Data; Decompressor.Free; CompressedData.Free; IdDecoderMIME.Free; end; |
Das produziert bei bestimmten in Memo1 eingetippten Strings Fehlerabbrüche in Zeile (***) schon beim Decodieren des ersten Zeichens, während sehr ähnliche Strings korrekt bearbeitet werden. Zum Beispiel (jeweils ohne die " eingetippt):
OK: "Normaler Text!!!" (mit 3x Ausrufezeichen), "!!!" (3x Ausrufezeichen), "Text!!!!" (mit 4x Ausrufezeichen)
Fehler: "Normaler Text!!!!" (mit 4x Ausrufezeichen), "!!!!" (4x Ausrufezeichen), "Text!!!" (mit 3x Ausrufezeichen)
Das sieht alles irgendwie unsystematisch aus, wie ein Bug in Indy. Oder benutze ich Indy da falsch?
Der Effekt speziell mit den mehrfachen Ausrufezeichen fiel mir auf, weil meine Strings immer solche Zeichenfolgen enthalten. Ein typischer String wäre:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| MeinString:= ''; for I:= 0 to 31 do MeinString:= MeinString + Chr(255); for I:= 0 to 111 do for J:= 0 to 7 do MeinString:= MeinString + Chr(I); for I:= 928 to 1023 do MeinString:= MeinString + Chr(255); |
Meine zu verarbeitenden Strings sind immer genau 1024 Zeiichen lang, und immer von dieser Struktur mit aufsteigendem Chr(I), vorn und hinten mit Chr(255), nur gelegentliche Variationen in der Anzahl jedes Zeichens, also z.B. von irgendeinem Chr(I) mal einer weniger, und dafür von Chr(I+1) einer mehr.
Es wäre schön, wenn ihr mir auch noch diese Nuss knacken könntet.
Moderiert von
Martok: Delphi-Tags hinzugefügt
Narses - Mi 11.07.12 22:52
Moin!
UweK hat folgendes geschrieben : |
Aber das Problem liegt bei mir noch beim base64. Dieses habe ich nicht in meinem Delphi 2010 |
Such mal nach einer base64-Unit für Delphi, da gibt´s bestimmt was. Indy kennt auch base64, ich weiß aber nicht, ob das die Kompo ist, die du da verwendest. :nixweiss:
UweK hat folgendes geschrieben : |
Das produziert bei bestimmten in Memo1 eingetippten Strings Fehlerabbrüche in Zeile (***) schon beim Decodieren des ersten Zeichens |
Kannst du bitte mal die
exakte Fehlermeldung zeigen?
cu
Narses
UweK - Do 12.07.12 13:46
Hallo Narses,
Kurz zuvor: Ich habe in den Testprogrammen noch eine kleine Änderung gemacht: "Memo.Text" => "Memo.Lines[0]", damit ich nicht immer die Steuerzeichen zum Zeilenwechsel mitschleppe, wer weiß, was die noch anrichten.
1. Mit IdEncoderMime/IdDecorderMime von Indy:
Es kommt nur eine ganz dünne Fehlermeldung "data error" ohne weitere Angaben. Das passiert immer in der Zeile
while Decompressor.Read(C, 1) = 1 do
z.B. (im Debugger angeschaut) bei "Normaler Text!!!!" (4 Ausrufezeichen) beim decodieren des 4. Ausrufezeichens. Bis dahin wird der String in der Variablen "Data" korrekt zusammengesetzt.
2. Alternativen
Ich habe eine Unit "base64" hier gefunden. Ist das die Unit, die du kennst? Oder haben vielleicht mehrere Autoren Units mit gleicher Funktion und gleichem Namen aber verschiedem Innenleben in Umlauf gebracht?
http://www.koders.com/delphi/fidE7154E13CF5685897559967F6F9BD0483330D256.aspx
Mit dieser hier wird auf jeden Fall der Quatsch noch quätscher. Z.B. liefert das Encodieren des Strings Memo1 (steht schon beim Programmstart als Voreinstellung im Memo) den encodierten String eAigTQr-Nwx BQ "wA== . Der scheint keinen Sinn zu machen, weil er 1x "-" (Minus), 1x """ (Anführungszeichen oben) und 2x " " (Leerzeichen) enthält, die im Base64 Zeichensatz gar nicht enthalten sind.
Und das Wunderlichste: Ich kann zig-mal den btnEncodeClick drücken, und erhalte - logischerweise - jedes Mal im Memo2 denselben encodierten String wie angegeben. Wenn ich jetzt aber mal - ohne Veränderung des Inhalts von Memo1 - von meiner Anwendung weg zu einem anderen Programm wechsle und wieder zurück, und drücke dann erneut auf btnEncodeClick, so sieht der encodierte String in Memo2 plötzlich ganz anders aus: eAteTQorNwalBQrawA== . Und so bleibt er auch bei wiederholtem Drücken. Er enthält zwar jetzt keine unpassenden Zeichen mehr, aber ein Decodieren ist weder in der ersten noch in der zweiten Version möglich. Es tritt immer ein "Fehler bei Bereichsprüfung" auf.
=> Fragezeichen?
Danke, Gruß Uwe
Narses - Do 12.07.12 14:13
Moin!
UweK hat folgendes geschrieben : |
Ich habe in den Testprogrammen noch eine kleine Änderung gemacht: "Memo.Text" => "Memo.Lines[0]", damit ich nicht immer die Steuerzeichen zum Zeilenwechsel mitschleppe, wer weiß, was die noch anrichten. |
Das würde ich nicht machen, es geht ja genau darum, auch nicht darstellbare Zeichen korrekt transportieren zu können. :idea: Es sollte aber letztlich egal sein und keinen Einfluss haben.
UweK hat folgendes geschrieben : |
1. Mit IdEncoderMime/IdDecorderMime von Indy: |
Ich habe mir mal gerade diese Kompo bei der Indy-Version, die ich drauf habe, angesehen: ja, es ist ein base64-En/Decode. Immerhin. ;)
UweK hat folgendes geschrieben : |
Es kommt nur eine ganz dünne Fehlermeldung "data error" ohne weitere Angaben. |
Hm, und "dünne" Fehlermeldungen kann man nicht als Screenshot abbilden? :gruebel:
UweK hat folgendes geschrieben : |
Das passiert immer in der Zeile
while Decompressor.Read(C, 1) = 1 do
z.B. (im Debugger angeschaut) bei "Normaler Text!!!!" (4 Ausrufezeichen) beim decodieren des 4. Ausrufezeichens. Bis dahin wird der String in der Variablen "Data" korrekt zusammengesetzt. |
In deinem vorletzen Beitrag hast du aber geschrieben, es passiere schon beim ersten Zeichen, nicht beim letzen! :lupe:
Ja, das liegt daran, dass diese Unit nicht Unicode-save ist (ist übrigens genau die, die ich hier auch habe). Die kannst du nicht ohne Anpassungen verwenden.
UweK hat folgendes geschrieben : |
Und das Wunderlichste: ... |
Kurz gesagt: ich halte das für ein Unicode-Problem. Schau dir mal die Methodendeklaration an, Beispiel:
Was für ein Datentyp wird dir da an den markierten Stellen angezeigt?
cu
Narses
jaenicke - Do 12.07.12 14:58
Die Base64 Unit, die ich gefunden hatte, ließ sich mit wenigen Änderungen umschreiben. Aber die braucht man ja nicht, da Indy das kann.
Was ich mich frage ist, warum immer wieder mit Strings als Datencontainern angefangen wird. Die sind dafür schlicht nicht gedacht.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57:
| procedure TTestForm.btnEncodeClick(Sender: TObject); var Data: String; CompressedData: TMemoryStream; Compressor: TCompressionStream; IdEncoderMIME: TIdEncoderMIME; begin IdEncoderMime := TIdEncoderMIME.Create; try Data := Memo1.Text; CompressedData := TMemoryStream.Create; try Compressor := TCompressionStream.Create(clMax, CompressedData); Compressor.Write(PChar(Data)^, Length(Data) * SizeOf(Char)); Compressor.Free; CompressedData.Position := 0; Memo2.Text := IDEncoderMIME.Encode(CompressedData); finally CompressedData.Free; end; finally IdEncoderMIME.Free; end; end;
procedure TTestForm.btnDecodeClick(Sender: TObject); var CompressedData: TMemoryStream; Decompressor: TDecompressionStream; C: Char; Data: String; IdDecoderMIME: TIdDecoderMIME; begin CompressedData := TMemoryStream.Create; try IdDecoderMime:= TIdDecoderMIME.Create; try IdDecoderMime.DecodeBegin(CompressedData); IdDecoderMIME.Decode(Memo2.Text); IdDecoderMIME.DecodeEnd; finally IdDecoderMime.Free; end; CompressedData.Position := 0; Decompressor := TDecompressionStream.Create(CompressedData); try Data := ''; while Decompressor.Read(C, SizeOf(Char)) = SizeOf(Char) do Data := Data + C; Memo3.Text := Data; finally Decompressor.Free; end; finally CompressedData.Free; end; end; |
Ausprobieren kann ich das leider nicht, da ich im Büro bin, hab das nur kurz in den Browser getippt. Aber das sollte so passen.
Martok - Do 12.07.12 15:53
Indy deklariert(e?) übrigens irgendwo den Datentyp "Binary", der immer dem 1-Byte-String ohne Charset entspricht (meistens also AnsiString). Sie benutzen ihn nur selber nicht mehr ;)
Narses - Fr 13.07.12 10:31
Moin!
Mir fällt da gerade noch was auf: :gruebel:
UweK hat folgendes geschrieben : |
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| MeinString:= ''; for I:= 0 to 31 do MeinString:= MeinString + Chr(255); for I:= 0 to 111 do for J:= 0 to 7 do MeinString:= MeinString + Chr(I); for I:= 928 to 1023 do MeinString:= MeinString + Chr(255); |
Meine zu verarbeitenden Strings sind immer genau 1024 Zeiichen lang, und immer von dieser Struktur mit aufsteigendem Chr(I), vorn und hinten mit Chr(255), nur gelegentliche Variationen in der Anzahl jedes Zeichens, also z.B. von irgendeinem Chr(I) mal einer weniger, und dafür von Chr(I+1) einer mehr. |
Du könntest dir auch diese "Strings" als Generator-Patterns "merken", also eine Art Code ausdenken, der beschreibt, wie man den String bildet. Ich mach mal ein Beispiel:
Übersetzt: 32x $FF, 112x (8x Schleifenindex i), 92x $FF, 0 als Terminator
Ist jetzt nur eine Idee, kann man sicher auch noch anders angehen, aber allemal besser, als die Daten selbst zu speichern, wenn die Erzeugung selbst recht einfach ist. :idea:
cu
Narses
UweK - Fr 13.07.12 12:59
Hallo allerseits,
Da habe ich ja eine ganz schöne Diskussion losgetreten. Auf jeden Fall: Problem ist gelöst!
Der Vorschlag von jaenicke mit TMemoryStream erschlägt die letzten Fehler. Wobei aber die sehr ähnliche Version mit TStringStream von Narses zuvor auch funktioniert, wenn das gewünschte Ziel ein gezippter String mit Binärzeichen ohne zusätzliche Base64 Codierung ist. Habe beide Versionen in meinem Programm durchgetestet. Warum es bei bestimmten Strings nicht klappt, wenn man das Ergebnis des Zip von Narses einfach in IdDecoderMIME steckt, ist mir zwar weiterhin ein Ministerium, macht aber nichts.
Die letzte Idee von Narses mit der selbstgemachten Codierung wäre noch ein Plan B gewesen. Aber so ist es mir lieber, denn jetzt habe ich allgemein wiederverwendbare Prozeduren für Zip mit oder ohne Base64, die vielleicht mal wieder nützlich sind.
Gruß + Danke, Uwe
jaenicke - Fr 13.07.12 13:50
UweK hat folgendes geschrieben : |
Warum es bei bestimmten Strings nicht klappt, wenn man das Ergebnis des Zip von Narses einfach in IdDecoderMIME steckt, ist mir zwar weiterhin ein Ministerium, macht aber nichts. |
Ich weiß nicht so genau warum, aber ich habe gesehen, dass da nach dem Komprimieren und vor dem Base64 etwas anderes drin steht als nach dem Dekodieren des Base64 Strings. Ob das an den Strings liegt oder an der Base64-Funktion weiß ich nicht, danach habe ich nicht geschaut. (Weil der Stream ohnehin sinnvoller ist.)
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!