Difference between revisions of "Zeno file format"

Jump to navigation Jump to search
369 bytes added ,  18:22, 11 January 2013
m
(9 intermediate revisions by one other user not shown)
Line 1: Line 1:
ZenoReader/Library
{{Box|'''Please note:''' This page only exists for historical reasons, therefore it has not been translated to English.
Aus DirectWiki
 
Wechseln zu: Navigation, Suche
The Zeno file format has been obsoleted by the ZIM (Zeno IMproved) file format. The ZIM library is not compatible to Zeno anymore as there are major differences between Zeno and ZIM concerning features and compression. Zeno was never widespread and is not used anymore.
 
The successor of Zeno is the [[ZIM File Format]] which is used in many [[ZIM Readers|reader applications on different platforms]] and offers an easy to use [[zimlib]].}}
 
Documentation by Erwin Jurschitza (DirectMedia Verlagsgesellschaft Berlin, http://www.directmedia.de/).
 
= ZenoReader/Library =
Die Dokumentation wurde für das Zeno-2.0-Format aktualisiert!  
Die Dokumentation wurde für das Zeno-2.0-Format aktualisiert!  
Inhaltsverzeichnis
 
1 Aufbau einer .zeno-Datei  
== Aufbau einer .zeno-Datei ==
1.1 Header
== Header ==
1.2 Inhaltsverzeichnis
1.3 Zusatzdaten bei Indexdateien
2 NamespaceCounts-Tabelle
3 Integer-Komprimierung
  [bearbeiten]  Aufbau einer .zeno-Datei
[bearbeiten]  Header  
Nach dem Öffnen der Datei muss der Header eingelesen werden, er steht am Anfang der Datei.  
Nach dem Öffnen der Datei muss der Header eingelesen werden, er steht am Anfang der Datei.  


Line 36: Line 36:
  end;
  end;


[bearbeiten]  Inhaltsverzeichnis  
=== Inhaltsverzeichnis ===
Um den Code erstmal einfach zu halten, wird das ganze Inhaltsverzeichnis auf einen Schlag eingelesen (kann mit dem Parameter /f erzwungen werden, bringt Geschwindigkeitsgewinn auf Kosten von RAM).  
Um den Code erstmal einfach zu halten, wird das ganze Inhaltsverzeichnis auf einen Schlag eingelesen (kann mit dem Parameter /f erzwungen werden, bringt Geschwindigkeitsgewinn auf Kosten von RAM).  
<pre>
  TWPVLibrary = class (TObject)
  TWPVLibrary = class (TObject)
  private
  private
   FBuffer: array of byte;  // das Inhaltsverzeichnis als Byte-Stream mit Records variabler Länge
   FBuffer: array of byte;  // das Inhaltsverzeichnis als Byte-Stream mit Records variabler Länge
   FBufferSize: integer;
   FBufferSize: integer;
</pre>


Auf jeden Fall ganz eingelesen werden muss die Zeigerliste auf das Inhaltsverzeichnis. Sie wird in einer Listenstruktur gehalten, könnte aber auch ein array of integer sein. Die Liste ist vorsortiert, siehe ZenoReader/Qunicode.  
Auf jeden Fall ganz eingelesen werden muss die Zeigerliste auf das Inhaltsverzeichnis. Sie wird in einer Listenstruktur gehalten, könnte aber auch ein array of integer sein. Die Liste ist vorsortiert, siehe ZenoReader/Qunicode.  
<pre>
   FList: TList;             
   FList: TList;             
</pre>


Beispiel: Das nullte Element der Liste hat den Wert 0 und zeigt auf Nullte Byte in FBuffer. Das erste Element hat z.B. den Wert 56 und zeigt auf das 56. Byte in FBuffer, was einem Zeiger auf den zweiten Eintrag des Inhaltsverzeichnisses entspricht, usw. Dabei ist der Inhalt von FBuffer folgendermaßen zu interpretieren:  
Beispiel: Das nullte Element der Liste hat den Wert 0 und zeigt auf Nullte Byte in FBuffer. Das erste Element hat z.B. den Wert 56 und zeigt auf das 56. Byte in FBuffer, was einem Zeiger auf den zweiten Eintrag des Inhaltsverzeichnisses entspricht, usw. Dabei ist der Inhalt von FBuffer folgendermaßen zu interpretieren:  
<pre>
  TZenoLibraryCompressionType = (zenocompDefault, zenocompNone, zenocompZip);
  TZenoLibraryCompressionType = (zenocompDefault, zenocompNone, zenocompZip);
  TZenoLibraryMimeType = (ZenomimeTextHtml, ZenomimeTextPlain, ZenomimeImageJpeg, ZenoMimeImagePng,  
  TZenoLibraryMimeType = (ZenomimeTextHtml, ZenomimeTextPlain, ZenomimeImageJpeg, ZenoMimeImagePng,  
Line 64: Line 72:
   rExtra: array [0..$FFF] of char;            // Artikeltitel, Nullbyte, evtl. Parameter wie h=200, durch Nullbyte getrennt
   rExtra: array [0..$FFF] of char;            // Artikeltitel, Nullbyte, evtl. Parameter wie h=200, durch Nullbyte getrennt
  end;
  end;
</pre>


Es ist zu beachten, dass der Record auf Bytegrenzen gepackt ist, d.h. ohne rExtra genau 26 Byte groß ist. Durch das zwingene Nullbyte in rExtra auch bei einem leeren Artikeltitel ist somit jeder Eintrag mindestens 27 Byte groß.  
Es ist zu beachten, dass der Record auf Bytegrenzen gepackt ist, d.h. ohne rExtra genau 26 Byte groß ist. Durch das zwingene Nullbyte in rExtra auch bei einem leeren Artikeltitel ist somit jeder Eintrag mindestens 27 Byte groß.
[bearbeiten]  Zusatzdaten bei Indexdateien  
 
=== Zusatzdaten bei Indexdateien ===
Die Artikel der Indexdateien gehören zu folgenden Namespaces:  
Die Artikel der Indexdateien gehören zu folgenden Namespaces:  
V/xxx: alle Artikel der Kategorie xxx, alphabetisch sortiert
* V/xxx: alle Artikel der Kategorie xxx, alphabetisch sortiert
W/xxx: alle Artikel der Kategorie xxx, nach Artikelindex sortiert
* W/xxx: alle Artikel der Kategorie xxx, nach Artikelindex sortiert
X/xxx: alle Artikel in denen das Wort xxx vorkommt, nach Artikelindex sortiert
* X/xxx: alle Artikel in denen das Wort xxx vorkommt, nach Artikelindex sortiert
Y/xxx: reserviert für Weblinks (in WP-zeno nicht vorhanden)
* Y/xxx: reserviert für Weblinks (in WP-zeno nicht vorhanden)
Z/xxx: reserviert für hervorgehobene Wortgruppen (in WP-zeno nicht vorhanden)
* Z/xxx: reserviert für hervorgehobene Wortgruppen (in WP-zeno nicht vorhanden)


Ein Eintrag für V und W ist ein 4-Byte-Integer (Artikelindex), ein Eintrag für X ein 8-Byte-Integer (Artikelindex und Wortindex).  
Ein Eintrag für V und W ist ein 4-Byte-Integer (Artikelindex), ein Eintrag für X ein 8-Byte-Integer (Artikelindex und Wortindex).  
Nach dem abschließenden Nullbyte des Wortes in rExtra kommt ein Längenbyte für die nachfolgende Struktur, das stets <= 255 ist. Alle Integers sind komprimiert, siehe unten.  
Nach dem abschließenden Nullbyte des Wortes in rExtra kommt ein Längenbyte für die nachfolgende Struktur, das stets <= 255 ist. Alle Integers sind komprimiert, siehe unten.  
flags: die ersten vier Bit geben an, ob es einen Eintrag für die Gewichtung (0..3) gibt,
      auch dieses Byte ist (unnötigerweise) schon komprimiert, d.h. in komprimiertem
      Zustand sind die Bits um 2 nach links verschoben.


* flags: die ersten vier Bit geben an, ob es einen Eintrag für die Gewichtung (0..3) gibt, auch dieses Byte ist (unnötigerweise) schon komprimiert, d.h. in komprimiertem Zustand sind die Bits um 2 nach links verschoben.


Für jede Gewichtung folgt ein Eintrag:  
Für jede Gewichtung folgt ein Eintrag:  
len: Länge des Streams in der Datei in Bytes
* len: Länge des Streams in der Datei in Bytes
firstArticleIndex: bei V, W, X
* firstArticleIndex: bei V, W, X
firstWordIndex: nur bei X
* firstWordIndex: nur bei X


Der erste Eintrag einer jeden Gewichtung befindet sich somit in RZenoArticle und ist schnell zu lesen. Gibt es nur diesen einen Eintrag, ist len=0, ansonsten gibt es weitere Einträge in der Indexdatei.  
Der erste Eintrag einer jeden Gewichtung befindet sich somit in RZenoArticle und ist schnell zu lesen. Gibt es nur diesen einen Eintrag, ist len=0, ansonsten gibt es weitere Einträge in der Indexdatei.
[bearbeiten]  NamespaceCounts-Tabelle  
 
== NamespaceCounts-Tabelle ==
Gültige Namespace-Characters (TLibNamespaceChar) gehen von '-' bis 'Z', also 46 verschiedene Namespaces. Da die Artikel aufsteigend nach Namespace angelegt sind, kann man mit dem Startindex und der Anzahl der Artikel für diesen Namespace gut auf einen bestimmten Namespace zugreifen, z.B. wenn die Wildcardsuche über den ganzen Namespace 'X' (und nur über den) laufen soll.  
Gültige Namespace-Characters (TLibNamespaceChar) gehen von '-' bis 'Z', also 46 verschiedene Namespaces. Da die Artikel aufsteigend nach Namespace angelegt sind, kann man mit dem Startindex und der Anzahl der Artikel für diesen Namespace gut auf einen bestimmten Namespace zugreifen, z.B. wenn die Wildcardsuche über den ganzen Namespace 'X' (und nur über den) laufen soll.  
<pre>
  RNamespaceStartCount = record
  RNamespaceStartCount = record
   rNamespaceStart: integer;  // Startindex ist 1, nicht 0
   rNamespaceStart: integer;  // Startindex ist 1, nicht 0
Line 95: Line 106:


  RNamespaceStartCountArray = array [TLibNamespaceChar] of RNamespaceStartCount;
  RNamespaceStartCountArray = array [TLibNamespaceChar] of RNamespaceStartCount;
</pre>


[bearbeiten]  Integer-Komprimierung  
== Integer-Komprimierung ==
Bei komprimierten Indexdateien sind sowohl die Zusatzdaten (s.o.) als auch die Daten in der Indexdatei wie folgt komprimiert:  
Bei komprimierten Indexdateien sind sowohl die Zusatzdaten (s.o.) als auch die Daten in der Indexdatei wie folgt komprimiert:  
Die ersten beiden Bits des ersten Bytes sind reserviert und codieren ob es sich insgesamt um 1, 2, 3 oder 4 Byte handelt, die einen (Pseudo-)-Integer zwischen 0 und $4040403F codieren.  
Die ersten beiden Bits des ersten Bytes sind reserviert und codieren ob es sich insgesamt um 1, 2, 3 oder 4 Byte handelt, die einen (Pseudo-)-Integer zwischen 0 und $4040403F codieren.  
1 Byte : Wertebereich 0 bis 26-1
* 1 Byte : Wertebereich 0 bis 26-1
2 Bytes: Wertebereich 26 bis 214+26-1
* 2 Bytes: Wertebereich 26 bis 214+26-1
3 Bytes: Wertebereich 214+26 bis 222+214+26-1
* 3 Bytes: Wertebereich 214+26 bis 222+214+26-1
3 Bytes: Wertebereich 222+214+26 bis 230+222+214+26-1
* 3 Bytes: Wertebereich 222+214+26 bis 230+222+214+26-1


Ferner wird bei manchen Streams von Interegers noch die Eigenschaft ausgenutzt, dass sie aufsteigend sortiert sind, was bei der Indexdatei für die Namespaces "W" und "X" gilt. Der Integer-Compressor/Decompressor hat eine Property, die das Verhalten steuert:  
Ferner wird bei manchen Streams von Interegers noch die Eigenschaft ausgenutzt, dass sie aufsteigend sortiert sind, was bei der Indexdatei für die Namespaces "W" und "X" gilt. Der Integer-Compressor/Decompressor hat eine Property, die das Verhalten steuert:  
<pre>
TIntegerCompressionType =  
TIntegerCompressionType =  
   (ictNone,        // keine Komprimierung, je 4 Bytes pro Integer
   (ictNone,        // keine Komprimierung, je 4 Bytes pro Integer
Line 116: Line 130:
                   // wird die Differenz des Wortindex gespeichert, ansonsten die absolute
                   // wird die Differenz des Wortindex gespeichert, ansonsten die absolute
                   // Zahl
                   // Zahl
</pre>


Beispiel für ict2BitsSeq2: Es soll diese Folge codiert werden:  
Beispiel für ict2BitsSeq2: Es soll diese Folge codiert werden:  
(3,27), (5,3), (5,20), (12,25)
:(3,27), (5,3), (5,20), (12,25)


Unkomprimiert, aber mit Differenzenbildung reduziert sich das zu:  
Unkomprimiert, aber mit Differenzenbildung reduziert sich das zu:  
(3,27), (2,3), (0,17), (7, 25)
:(3,27), (2,3), (0,17), (7, 25)


 
= ZenoReader/Qunicode =
 
== Codierung ==
=ZenoReader/Qunicode=
Aus DirectWiki
Wechseln zu: Navigation, Suche
Inhaltsverzeichnis
1 Codierung
2 Vergleichsroutine
3 Folding
4 Testroutine
  [bearbeiten]  Codierung  
Die Artikeltitel sind in einem eigenen Format codiert, da UTF8 oder ähnliches Geschwindigkeitsnachteile beim Sortieren und Matchen bringt. Es ist eine Ad-hoc-Codierung, die davon ausgeht, dass Characters mit den Werten 1 und 2 nicht im Titel vorkommen. Ein Byte mit dem Wert 1 zeigt an, dass die nachfolgenden 2 Bytes als 2-Byte-Unicode zu interpretieren sind. Um Nullbytes im String zu vermeiden wird der Wert 2 genommen, wenn das niedrige Byte des 2-Byte-Unicode null ist, das dann als 1 (?) codiert ist. Das hohe Byte kann niemals Null sein, da $20..$FF sowieso als ein Byte codiert werden.  
Die Artikeltitel sind in einem eigenen Format codiert, da UTF8 oder ähnliches Geschwindigkeitsnachteile beim Sortieren und Matchen bringt. Es ist eine Ad-hoc-Codierung, die davon ausgeht, dass Characters mit den Werten 1 und 2 nicht im Titel vorkommen. Ein Byte mit dem Wert 1 zeigt an, dass die nachfolgenden 2 Bytes als 2-Byte-Unicode zu interpretieren sind. Um Nullbytes im String zu vermeiden wird der Wert 2 genommen, wenn das niedrige Byte des 2-Byte-Unicode null ist, das dann als 1 (?) codiert ist. Das hohe Byte kann niemals Null sein, da $20..$FF sowieso als ein Byte codiert werden.  
Beispielhaft sei die Funktion QunicodeToXML angeführt:  
Beispielhaft sei die Funktion QunicodeToXML angeführt:  
<pre>
function QunicodeToXML (const qunicode: string): string;
function QunicodeToXML (const qunicode: string): string;
type
type
Line 175: Line 182:
   SetLength(Result, l);
   SetLength(Result, l);
end;
end;
</pre>


Diese Codierung erlaubt ein schnelleres Sortieren und Matchen und ist - bei westeuropäischen Texten - meist sogar kürzer als UTF-8, was daran liegt, dass Umlaute weiterhin nur ein Byte benötigen.  
Diese Codierung erlaubt ein schnelleres Sortieren und Matchen und ist - bei westeuropäischen Texten - meist sogar kürzer als UTF-8, was daran liegt, dass Umlaute weiterhin nur ein Byte benötigen.  
[bearbeiten]  Vergleichsroutine  
 
== Vergleichsroutine ==
Für das Sortieren und Finden benötigt man eine Vergleichsroutine zweier Qunicode-Strings:  
Für das Sortieren und Finden benötigt man eine Vergleichsroutine zweier Qunicode-Strings:  
<pre>
function PCharCompareUseTable (p1, p2: PChar): integer;
function PCharCompareUseTable (p1, p2: PChar): integer;
var
var
Line 228: Line 239:
  end;
  end;
end;
end;
</pre>


Entscheidend ist dabei das Folding von 2-Byte-Zeichen auf 1-Byte-Zeichen, um Umlaute und diakritische Zeichen "richtiger" zu sortieren als es per Bytevergleich wäre. "Richtiger" deshalb, da "richtig" einen enormen Aufwand darstellen würde, siehe Unicode Collation Algorithm.  
Entscheidend ist dabei das Folding von 2-Byte-Zeichen auf 1-Byte-Zeichen, um Umlaute und diakritische Zeichen "richtiger" zu sortieren als es per Bytevergleich wäre. "Richtiger" deshalb, da "richtig" einen enormen Aufwand darstellen würde, siehe Unicode Collation Algorithm.  
PCharCompareUseTable  besteht aus zwei Teilen: Zuerst wird der String gefaltet verglichen, wenn er gleich ist, wird der Unicode-Wert genommen. Dies hat folgende Vorteile:  
PCharCompareUseTable  besteht aus zwei Teilen: Zuerst wird der String gefaltet verglichen, wenn er gleich ist, wird der Unicode-Wert genommen. Dies hat folgende Vorteile:  
A, a, Ä, a, Ā, ā usw. stehen vor B, b, ...  
* A, a, Ä, a, Ā, ā usw. stehen vor B, b, ...  
A, a, Ä, a, Ā, ā sortiert ergibt genau die Reihenfolge, die hier steht  
* A, a, Ä, a, Ā, ā sortiert ergibt genau die Reihenfolge, die hier steht  
 
Wichtig: Diese Sortierung entspricht keiner Norm - sie ist eher so etwas wie eine intuitive Sotierung, die für Sprachen im Latin-Alphabet brauchbare Ergebnisse liefert. Da Offline-Wikipedia in allen Sprachen funktionieren soll, wäre es wünschenswert, doch den Unicode Collation Algorithm zu implementieren - es muss erstmal eine Aufwands- und Runtimeverhalten-Abschätzung her.  
Wichtig: Diese Sortierung entspricht keiner Norm - sie ist eher so etwas wie eine intuitive Sotierung, die für Sprachen im Latin-Alphabet brauchbare Ergebnisse liefert. Da Offline-Wikipedia in allen Sprachen funktionieren soll, wäre es wünschenswert, doch den Unicode Collation Algorithm zu implementieren - es muss erstmal eine Aufwands- und Runtimeverhalten-Abschätzung her.  
[bearbeiten]  Folding  
 
== Folding ==
_ZZZ ist ein array [0..$FFFF], in dem jedem 2-Byte-Unicode ein ASCII-Zeichen 0..$7F zugeordnet wird. Die Tabelle: qunicode.zip.  
_ZZZ ist ein array [0..$FFFF], in dem jedem 2-Byte-Unicode ein ASCII-Zeichen 0..$7F zugeordnet wird. Die Tabelle: qunicode.zip.  
Diese Tabelle kann für alle 2-Byte-Unicodes erweitert werden; das Folding von z.B. chinesischen Zeichen auf ASCII-Zeichen stellt dann eine Art Hashfunktion dar, mit der bei Eingabe eines chinesischen Strings ein passender Match (aber u.U. noch mehr) gefunden werden kann. Die Tabelle sollte Bestandteil einer Zeno-Datei sein!  
Diese Tabelle kann für alle 2-Byte-Unicodes erweitert werden; das Folding von z.B. chinesischen Zeichen auf ASCII-Zeichen stellt dann eine Art Hashfunktion dar, mit der bei Eingabe eines chinesischen Strings ein passender Match (aber u.U. noch mehr) gefunden werden kann. Die Tabelle sollte Bestandteil einer Zeno-Datei sein!  
[bearbeiten]  Testroutine  
 
== Testroutine ==
Da die Artikel in einer Zeno-Datei vorsortiert sind, ist es ein guter Test festzustellen, ob mit der selbst geschriebenen Compare-Routine dies auch weiterhin stimmt. Das muss zwingend der Fall sein, damit beim Laden des Headers der Zeno-Datei nicht jedesmal sortiert werden muss.
Da die Artikel in einer Zeno-Datei vorsortiert sind, ist es ein guter Test festzustellen, ob mit der selbst geschriebenen Compare-Routine dies auch weiterhin stimmt. Das muss zwingend der Fall sein, damit beim Laden des Headers der Zeno-Datei nicht jedesmal sortiert werden muss.

Navigation menu