Difference between revisions of "Zeno file format"

From openZIM
Jump to: navigation, search
(New page: Nach dem Öffnen der Datei muss der Header eingelesen werden, er steht am Anfang der Datei. <pre> TZenoLibraryHeaderFlag = (zlfIsIndex); TZenoLibraryHeaderFlags = set of TZenoLibraryHe...)
 
Line 1: Line 1:
 +
ZenoReader/Library
 +
Aus DirectWiki
 +
Wechseln zu: Navigation, Suche
 +
Die Dokumentation wurde für das Zeno-2.0-Format aktualisiert!
 +
Inhaltsverzeichnis
 +
1 Aufbau einer .zeno-Datei
 +
1.1 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.  
 
 
<pre>
 
<pre>
 
  TZenoLibraryHeaderFlag = (zlfIsIndex);
 
  TZenoLibraryHeaderFlag = (zlfIsIndex);
 
  TZenoLibraryHeaderFlags = set of TZenoLibraryHeaderFlag;
 
  TZenoLibraryHeaderFlags = set of TZenoLibraryHeaderFlag;
 +
 
  TZenoLibraryHeader = record
 
  TZenoLibraryHeader = record
 
   rMagicNumber: integer;            // Erkennungsmarke, muss immer den Wert 1439867043 haben
 
   rMagicNumber: integer;            // Erkennungsmarke, muss immer den Wert 1439867043 haben
Line 23: Line 36:
 
  end;
 
  end;
 
</pre>
 
</pre>
 +
 +
[bearbeiten]  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).
 +
TWPVLibrary = class (TObject)
 +
private
 +
  FBuffer: array of byte;  // das Inhaltsverzeichnis als Byte-Stream mit Records variabler Länge
 +
  FBufferSize: integer;
 +
 +
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.
 +
  FList: TList;           
 +
 +
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:
 +
TZenoLibraryCompressionType = (zenocompDefault, zenocompNone, zenocompZip);
 +
TZenoLibraryMimeType = (ZenomimeTextHtml, ZenomimeTextPlain, ZenomimeImageJpeg, ZenoMimeImagePng,
 +
                        ZenoMimeImageTiff, ZenoMimeTextCss, ZenoMimeImageGif, ZenoMimeIndex,
 +
                        ZenoMimeApplicationJavaScript, ZenoMimeImageIcon, ZenoMimeTextXml)
 +
 +
RZenoArticle = packed record
 +
  rFilePos: Int64;                            // 8 Byte, absolute Position der Artikeldaten im Stream
 +
  rFileLen: cardinal;                        // 4 Byte, Läge der Artikeldaten
 +
  rCompression: TZenoLibraryCompressionType;  // 1 Byte
 +
  rMime: TWPVLibraryMimeType;                // 1 Byte
 +
  rRedirectFlag: char;                        // wird demnächst verwendet
 +
  rNamespace: char;                          // A, B, ...
 +
  rRedirectIndex: integer;                    // 4 Byte, wenn rRedirectFlag dann hier der Artikelindex des Hauptartikels
 +
  rLogicalNumber: integer;                    // 4 Byte, in wp nicht benutzt
 +
  rExtraLen: word;                            // 2-Byte-Länge des nachfolgenden Strings inklusive Nullbyte
 +
  rExtra: array [0..$FFF] of char;            // Artikeltitel, Nullbyte, evtl. Parameter wie h=200, durch Nullbyte getrennt
 +
end;
 +
 +
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
 +
Die Artikel der Indexdateien gehören zu folgenden Namespaces:
 +
V/xxx: alle Artikel der Kategorie xxx, alphabetisch sortiert
 +
W/xxx: alle Artikel der Kategorie xxx, 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)
 +
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).
 +
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.
 +
 +
 +
Für jede Gewichtung folgt ein Eintrag:
 +
len: Länge des Streams in der Datei in Bytes
 +
firstArticleIndex: bei V, W, 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.
 +
[bearbeiten]  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.
 +
RNamespaceStartCount = record
 +
  rNamespaceStart: integer;  // Startindex ist 1, nicht 0
 +
  rNamespaceCount: integer;
 +
end;
 +
 +
RNamespaceStartCountArray = array [TLibNamespaceChar] of RNamespaceStartCount;
 +
 +
[bearbeiten]  Integer-Komprimierung
 +
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.
 +
1 Byte : Wertebereich 0 bis 26-1
 +
2 Bytes: Wertebereich 26 bis 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
 +
 +
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:
 +
TIntegerCompressionType =
 +
  (ictNone,        // keine Komprimierung, je 4 Bytes pro Integer
 +
  ict2Bits,      // Komprimierung wie oben beschrieben, 1-4 Bytes, verwendet für den
 +
                  // Namespace V, da dort die Artikel alphabetisch sortiert sind sowie
 +
                  // für die Struktur in rExtra
 +
  ict2BitsSeq1,  // Namespace W: es wird nur die Differenz zwischen dem vorherigen Wert
 +
                  // und dem aktuellen gespeichert, was die Zahl "kleiner" hält
 +
  ict2BitsSeq2);  // Namespace X: da es sich um Paare von Artikel- und Wortindex handelt
 +
                  // wird die Different des Artikelindex gespeichert, ist dieser Null
 +
                  // wird die Differenz des Wortindex gespeichert, ansonsten die absolute
 +
                  // Zahl
 +
 +
Beispiel für ict2BitsSeq2: Es soll diese Folge codiert werden:
 +
(3,27), (5,3), (5,20), (12,25)
 +
 +
Unkomprimiert, aber mit Differenzenbildung reduziert sich das zu:
 +
(3,27), (2,3), (0,17), (7, 25)

Revision as of 16:16, 21 February 2009

ZenoReader/Library Aus DirectWiki Wechseln zu: Navigation, Suche Die Dokumentation wurde für das Zeno-2.0-Format aktualisiert! Inhaltsverzeichnis 1 Aufbau einer .zeno-Datei 1.1 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.

 TZenoLibraryHeaderFlag = (zlfIsIndex);
 TZenoLibraryHeaderFlags = set of TZenoLibraryHeaderFlag;

 TZenoLibraryHeader = record
   rMagicNumber: integer;            // Erkennungsmarke, muss immer den Wert 1439867043 haben
   rVersion: integer;                // wp2006=2, wp2007=3, bei Formatänderungen wird hochgezählt
   rCount: integer;                  // Anzahl der Artikel
   rUnused1: integer;                // da Delphi anscheinend Int64 auf 8-Byte-Grenzen legt entsteht diese Lücke
   rIndexPos: Int64;                 // Position des Inhaltsverzeichnisses
   rIndexLen: integer;               // Länge des Inhaltsverzeichnisses
   rUnused2: integer;                // vormals rFlags 
   rIndexPtrPos: Int64;              // Position der Zeigerliste auf das Inhaltsverzeichnis
   rIndexPtrLen: integer;            // Länge der Zeigerliste auf das Inhaltsverzeichnis, also 4*rCount
   rTreeDataPos: Int64;              // bei wp nicht benutzt
   rTreeDataLen: integer;            // bei wp nicht benutzt
   rIndexTotalArticleCount: integer; // nur für die Indexdatei
   rIsIndexCompressed: boolean;      // in der ausgelieferten Version immer true bei der Indexdatei
   rNamespaceCountPos: int64;        // Fileposition der Tabelle, die Infos über die Namespaces hat, siehe unten
   rNamespaceCountLen: integer;      // Länge dieser Tabelle, z.Zt. fix auf 368 Bytes (8 Bytes * 46 Namespaces)
   rUnused: array [0..57] of integer;// mehr Luft als hier vorher war
 end;

[bearbeiten] 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).

TWPVLibrary = class (TObject)
private
  FBuffer: array of byte;   // das Inhaltsverzeichnis als Byte-Stream mit Records variabler Länge
  FBufferSize: integer;

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.

  FList: TList;             

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:

TZenoLibraryCompressionType = (zenocompDefault, zenocompNone, zenocompZip);
TZenoLibraryMimeType = (ZenomimeTextHtml, ZenomimeTextPlain, ZenomimeImageJpeg, ZenoMimeImagePng, 
                        ZenoMimeImageTiff, ZenoMimeTextCss, ZenoMimeImageGif, ZenoMimeIndex, 
                        ZenoMimeApplicationJavaScript, ZenoMimeImageIcon, ZenoMimeTextXml)
RZenoArticle = packed record
  rFilePos: Int64;                            // 8 Byte, absolute Position der Artikeldaten im Stream 
  rFileLen: cardinal;                         // 4 Byte, Läge der Artikeldaten
  rCompression: TZenoLibraryCompressionType;  // 1 Byte
  rMime: TWPVLibraryMimeType;                 // 1 Byte
  rRedirectFlag: char;                        // wird demnächst verwendet
  rNamespace: char;                           // A, B, ...
  rRedirectIndex: integer;                    // 4 Byte, wenn rRedirectFlag dann hier der Artikelindex des Hauptartikels
  rLogicalNumber: integer;                    // 4 Byte, in wp nicht benutzt
  rExtraLen: word;                            // 2-Byte-Länge des nachfolgenden Strings inklusive Nullbyte
  rExtra: array [0..$FFF] of char;            // Artikeltitel, Nullbyte, evtl. Parameter wie h=200, durch Nullbyte getrennt
end;

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 Die Artikel der Indexdateien gehören zu folgenden Namespaces: V/xxx: alle Artikel der Kategorie xxx, alphabetisch sortiert W/xxx: alle Artikel der Kategorie xxx, 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) 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). 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.


Für jede Gewichtung folgt ein Eintrag: len: Länge des Streams in der Datei in Bytes firstArticleIndex: bei V, W, 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. [bearbeiten] 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.

RNamespaceStartCount = record
  rNamespaceStart: integer;  // Startindex ist 1, nicht 0
  rNamespaceCount: integer;
end;
RNamespaceStartCountArray = array [TLibNamespaceChar] of RNamespaceStartCount;

[bearbeiten] Integer-Komprimierung 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. 1 Byte : Wertebereich 0 bis 26-1 2 Bytes: Wertebereich 26 bis 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

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: TIntegerCompressionType =

 (ictNone,        // keine Komprimierung, je 4 Bytes pro Integer
  ict2Bits,       // Komprimierung wie oben beschrieben, 1-4 Bytes, verwendet für den
                  // Namespace V, da dort die Artikel alphabetisch sortiert sind sowie
                  // für die Struktur in rExtra
  ict2BitsSeq1,   // Namespace W: es wird nur die Differenz zwischen dem vorherigen Wert
                  // und dem aktuellen gespeichert, was die Zahl "kleiner" hält
  ict2BitsSeq2);  // Namespace X: da es sich um Paare von Artikel- und Wortindex handelt
                  // wird die Different des Artikelindex gespeichert, ist dieser Null
                  // wird die Differenz des Wortindex gespeichert, ansonsten die absolute
                  // Zahl

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

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