Difference between revisions of "ZIM file format"

Jump to navigation Jump to search
1,258 bytes removed ,  10:12, 16 July 2019
correction, see ZIM File Example
(correction, see ZIM File Example)
(5 intermediate revisions by 3 users not shown)
Line 8: Line 8:
Length in bytes, all types are little-endian.
Length in bytes, all types are little-endian.


{|{{Prettytable}}
All integers are unsigned integers (uint_16, uint_32, uint_64).
! Field Name !! Type !!Offset!!Length!! Description                 
 
{| class="sortable" style="border-width:1px; border-style:solid; border-color:#888888; background-color:#eeeeee; border-collapse:collapse; empty-cells:show" cellspacing="0" cellpadding="4" {{Prettytable}}
! Field Name !! Type !! Offset !! Length !! Description                 
|-
| magicNumber || integer || 0 || 4 || Magic number to recognise the file format, must be 72173914 (0x44D495A)
|-
|-
| magicNumber || integer || 0 || 4 || Magic number to recognise the file format, must be 72173914                   
|majorVersion
|integer
|4
|2
|Major version of the ZIM file format (5 or 6)
|-
|-
| version || integer || 4 || 4 || ZIM=5, bytes 1-2: major, bytes 3-4: minor version of the ZIM file format                         
| minorVersion || integer || 6 || 2 || Minor version of the ZIM file format                         
|-
|-
| uuid || integer || 8 || 16 || unique id of this zim file                           
| uuid || integer || 8 || 16 || unique id of this zim file                           
Line 34: Line 42:
|-
|-
| checksumPos || integer || 72 || 8 || pointer to the md5checksum of this file without the checksum itself. This points always 16 bytes before the end of the file.
| checksumPos || integer || 72 || 8 || pointer to the md5checksum of this file without the checksum itself. This points always 16 bytes before the end of the file.
|-
| geoIndexPos || integer || 80 || 8 || pointer to the geo index (optional). Present if mimeListPos is at least 80.
|}
|}
Major version is updated when an incompatible change is integrated in the format (a lib made for a version N will probably not be able to read a version N+1)
Minor version is updated when an compatible change is integrated (a lib made for a minor version n will be able to read a version n+1)
There are currently 2 major versions :
* The version 5
* The version 6 (the same that version 5 + potential extended cluster)


== MIME Type List (mimeListPos) ==
== MIME Type List (mimeListPos) ==
Line 43: Line 57:
The MIME types in this list are zero terminated strings. An empty string marks the end of the MIME type list.
The MIME types in this list are zero terminated strings. An empty string marks the end of the MIME type list.


{|{{Prettytable}}
{| class="sortable" style="border-width:1px; border-style:solid; border-color:#888888; background-color:#eeeeee; border-collapse:collapse; empty-cells:show" cellspacing="0" cellpadding="4" {{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description                 
! Field Name !! Type !!Offset!!Length!! Description                 
|-
|-
Line 58: Line 72:
The URL pointer list is a list of 8 byte offsets to the directory entries.
The URL pointer list is a list of 8 byte offsets to the directory entries.


The directory entries are always ordered by URL (including the namespace prefix). Ordering is simply done by comparing the URL strings in binary.
The directory entries are always ordered by URL. Ordering is simply done by comparing the URL strings.


Since directory entries have variable sizes this is needed for random access.
Since directory entries have variable sizes this is needed for random access.


{|{{Prettytable}}
{| class="sortable" style="border-width:1px; border-style:solid; border-color:#888888; background-color:#eeeeee; border-collapse:collapse; empty-cells:show" cellspacing="0" cellpadding="4" {{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description                 
! Field Name !! Type !!Offset!!Length!! Description                 
|-
|-
Line 81: Line 95:
To get the offset of an article from the title pointer list, you have to look it up in the URL pointer list.
To get the offset of an article from the title pointer list, you have to look it up in the URL pointer list.


{|{{Prettytable}}
{| class="sortable" style="border-width:1px; border-style:solid; border-color:#888888; background-color:#eeeeee; border-collapse:collapse; empty-cells:show" cellspacing="0" cellpadding="4" {{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description                 
! Field Name !! Type !!Offset!!Length!! Description                 
|-
|-
Line 103: Line 117:


=== Article Entry ===
=== Article Entry ===
{|{{Prettytable}}
{| class="sortable" style="border-width:1px; border-style:solid; border-color:#888888; background-color:#eeeeee; border-collapse:collapse; empty-cells:show" cellspacing="0" cellpadding="4" {{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description                 
! Field Name !! Type !!Offset!!Length!! Description                 
|-
|-
Line 126: Line 140:


=== Redirect Entry ===
=== Redirect Entry ===
{|{{Prettytable}}
{| class="sortable" style="border-width:1px; border-style:solid; border-color:#888888; background-color:#eeeeee; border-collapse:collapse; empty-cells:show" cellspacing="0" cellpadding="4" {{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description                 
! Field Name !! Type !!Offset!!Length!! Description                 
|-
|-
Line 147: Line 161:


=== Linktarget or deleted Entry ===
=== Linktarget or deleted Entry ===
{|{{Prettytable}}
{| class="sortable" style="border-width:1px; border-style:solid; border-color:#888888; background-color:#eeeeee; border-collapse:collapse; empty-cells:show" cellspacing="0" cellpadding="4" {{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description                 
! Field Name !! Type !!Offset!!Length!! Description                 
|-
|-
Line 168: Line 182:
The cluster pointer list is a list of 8 byte offsets which point to all data clusters in a ZIM file.
The cluster pointer list is a list of 8 byte offsets which point to all data clusters in a ZIM file.


{|{{Prettytable}}
{| class="sortable" style="border-width:1px; border-style:solid; border-color:#888888; background-color:#eeeeee; border-collapse:collapse; empty-cells:show" cellspacing="0" cellpadding="4" {{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description                 
! Field Name !! Type !!Offset!!Length!! Description                 
|-
|-
Line 183: Line 197:
The clusters contain the actual data of the directory entries. Clusters can be compressed or uncompressed. The purpose of the clusters are that data of more than one directory entry can be compressed inside one cluster, making the compression much more efficient. Typically clusters have a size of about 1 MB.
The clusters contain the actual data of the directory entries. Clusters can be compressed or uncompressed. The purpose of the clusters are that data of more than one directory entry can be compressed inside one cluster, making the compression much more efficient. Typically clusters have a size of about 1 MB.


The first byte of the cluster identifies if it is compressed (4) or not (0). The default is uncompressed indicated by a value of 0 or 1 (obsoleted, inherited by Zeno) while compressed clusters are indicated by a value of 4 which indicates [[LZMA2 compression]] (or more precisely XZ, since there is a XZ header). There have been other compression algorithms used before (2: zlib, 3: bzip2) which have been removed. The zimlib uses [http://tukaani.org/xz/ xz-utils] as a C++ implementation of lzma2, for Java see [http://tukaani.org/xz/java.html XZ-Java].
The first byte of the cluster identifies some information about the cluster.
 
The first fourth low bits identifies if the cluster is compressed (4) or not (0). The default is uncompressed indicated by a value of 0 or 1 (obsoleted, inherited by Zeno) while compressed clusters are indicated by a value of 4 which indicates [[LZMA2 compression]] (or more precisely XZ, since there is a XZ header). There have been other compression algorithms used before (2: zlib, 3: bzip2) which have been removed. The zimlib uses [http://tukaani.org/xz/ xz-utils] as a C++ implementation of lzma2, for Java see [http://tukaani.org/xz/java.html XZ-Java].
 
The firth bit identifies if the cluster is extended or not :
* By default (5th bit == 0) the cluster is not extended. It means that the offsets are stored in a 4 bytes length integer. Thus contents stored in the cluster cannot exceed 4Go.
* If the cluster is extended (5th bit == 1), the offsets are stored in 8 bytes length integer. Thus contents stored in the cluster can exceed 4Go.
A cluster can be extended only if the zim major version is 6. Else (major version == 5) cluster will always be not extended.


To find the data of a specific directory entry within a cluster the uncompressed cluster has a list of pointers to blobs within the uncompressed cluster after the first byte.
To find the data of a specific directory entry within a cluster the uncompressed cluster has a list of pointers to blobs within the uncompressed cluster after the first byte.


{|{{Prettytable}}
{| class="sortable" style="border-width:1px; border-style:solid; border-color:#888888; background-color:#eeeeee; border-collapse:collapse; empty-cells:show" cellspacing="0" cellpadding="4" {{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description                 
! Field Name !! Type !!Offset!!Length!! Description                 
|-
|-
| compression type || integer || 0 || 1 || 0: default (no compression), 1: none (inherited from Zeno), 4: LZMA2 compressed               
| cluster information || integer || 0 || 1 || Fourth low bits : 0: default (no compression), 1: none (inherited from Zeno), 4: LZMA2 compressed
Firth bits : 0: normal (OFFSET_SIZE=4) 1: extended (OFFSET_SIZE=8)                
|-
|-
|colspan=5| The following data bytes have to be uncompressed!
| colspan="5" | The following data bytes have to be uncompressed!
|-
|-
| <1st Blob> || integer || 1 || 4 || offset to the <1st Blob>                     
| <1st Blob> || integer || 1 || OFFSET_SIZE || offset to the <1st Blob>                     
|-
|-
| <2nd Blob> || integer || 5 || 4 || offset to the <2nd Blob>                     
| <2nd Blob> || integer || 1+OFFSET_SIZE || OFFSET_SIZE || offset to the <2nd Blob>                     
|-
|-
| <nth Blob> || integer ||(n-1)*4+1|| 4 || offset to the <nth Blob>             
| <nth Blob> || integer ||(n-1)*OFFSET_SIZE+1|| OFFSET_SIZE || offset to the <nth Blob>             
|-
|-
| ... || integer || ... || 4 || ...                           
| ... || integer || ... || OFFSET_SIZE || ...                           
|-
|-
| <last blob / end> || integer || n/a || 4 || offset to the end of the cluster             
| <last blob / end> || integer || n/a || OFFSET_SIZE || offset to the end of the cluster             
|-
|-
| <1st Blob> || data || n/a || n/a || data of the <1st Blob>                     
| <1st Blob> || data || n/a || n/a || data of the <1st Blob>                     
Line 211: Line 233:
|}
|}


The offset addresses uncompressed data. The last pointer points to the end of the data area. So there is always one more offset than blobs. Since the first offset points to the start of the first data, the number of offsets can be determined by dividing this offset by 4. The size of one blob is calculated by the difference of two consecutive offsets.
The offset addresses uncompressed data. The last pointer points to the end of the data area. So there is always one more offset than blobs. Since the first offset points to the start of the first data, the number of offsets can be determined by dividing this offset by OFFSET_SIZE. The size of one blob is calculated by the difference of two consecutive offsets.
 
== Geo Index ==
 
The geo index contains a spatially indexed data structure so that it is possible to search for articles by their position on the globe. Coordinates are assumed to be WGS84 coordinates, but encoded into binary in the following way: Both latitudes and longitudes use the full span of 32 bit unsigned big endian integers, which means that the south pole has an encoded latitude of
0 and the north pole an encoded latitude of 0xffffffff (actually 0x100000000). This means that the scaling between degree and encoded integers
is different for latitudes and longitudes. The coordinate of Berlin, which is approximately at 52° 31′ N, 13° 23′ E or
52.516667, 13.383333 is encoded as latitude (52.516667 + 90) / 180 * 0x100000000 = 3400580132 and longitude
(13.383333 + 180) / 360 * 0x100000000 = 2307153030.
 
The index actually consists of a list of indices assumed to be ordered by importance. The idea is that the indices
are searched in order until a certain amount of search results is found, such that at low zoom levels, only the
important articles (countries, large cities) are displayed.
 
All offsets in the geo index are counted relative to the start of the geo index. This means that in order to get
absolute file offsets, you have to add the value of the geoIndexPos field in the file header.
 
At geoIndexPos, the geo index starts with the following table:
 
{|{{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description               
|-
| indicesCount || integer || 0 || 4 || number of geo indices (=n)
|-
| <1st offset> || integer || 4 || 4 || offset to the data area of the 1st geo index
|-
| <2nd offset> || integer || 8 || 4 || offset to the data area of the 2nd geo index
|-
| ... || integer || ... || 4 || ...                         
|-
| <nth offset> || integer ||n*4|| 4 || offset to the data area of the nth geo index
|-
| <end offset> || integer ||(n+1)*4|| 4 || offset to the end of the data area of the nth geo index
|}
 
Each geo index is a binary tree of coordinates alternating between latitudes and longitudes (starting with latitudes)
ending in a list of coordinates and article indices as leaves. A non-leaf node is encoded as follows:
 
{|{{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description               
|-
| value || integer || ? || 4 || discriminator coordinate, cannot be zero (would indicate a leaf node)
|-
| greaterOffset || integer || ? + 4 || 4 || offset to the node containing coordinates with latitude / longitude greater than the discriminator
|-
| <smaller or equal coordinates> || ... || ... || ... || node leading to coordinates with latitude / longitude smaller or equal than the discriminator
|-
| <larger coordinates> || ... || greaterOffset || ... || node leading to coordinates with latitude / longitude greater than the discriminator
|}
 
Leaf nodes containing the actual coordinates and links to the articles are encoded as follows:
 
{|{{Prettytable}}
! Field Name !! Type !!Offset!!Length!! Description               
|-
| 0 || integer || ? || 4 || value identifying leaf nodes
|-
| numCoordinates || integer || ? + 4 || 4 || number of coordinates that follow
|-
| <1st latitude> || integer || ? + 8 || 4 || 1st latitude
|-
| <1st longitude> || integer || ? + 12 || 4 || 1st longitude
|-
| <1st title index> || integer || ? + 16 || 4 || 1st title index
|-
| ... || ... || ... || ... || ...
|-
| <nth latitude> || integer || ? + n * 12 - 4 || 4 || nth latitude
|-
| <nth longitude> || integer || ? + n * 12 || 4 || nth longitude
|-
| <nth title index> || integer || ? + n * 12 + 4 || 4 || nth title index
|}
 
The title indices in the above table are article numbers just like in the title pointer list.
 
 
It can be assumed that the index is generated in the following way: All coordinates are sorted
by latitude. The median is stored as discriminator value, the first half of the coordinates is
stored in a "lower node", the second half in an upper node. For each of the two nodes, the
coordinates are sorted by longitude and are split in much the same way until there are
less than a specific number of articles (e.g. 10) left at which point they are
stored as a list in a leaf node.


== Namespaces ==
== Namespaces ==
Line 300: Line 240:
They can be distinguished by prepending the article namespace before the article name in the URL path, eg. ''http://localhost/A/Articlename''.
They can be distinguished by prepending the article namespace before the article name in the URL path, eg. ''http://localhost/A/Articlename''.


{|{{Prettytable}}
{| class="sortable" style="border-width:1px; border-style:solid; border-color:#888888; background-color:#eeeeee; border-collapse:collapse; empty-cells:show" cellspacing="0" cellpadding="4" {{Prettytable}}
! Namespace !! Description   
! Namespace !! Description   
|-
|-
Line 328: Line 268:
ZIM contents are addressed using URLs fitting the following pattern: <namespace>/<article_url>. The references in articles HTML code (''<a href=""></a>'', ''<img src="">'', etc.) are URL-encoded following the [http://www.ietf.org/rfc/rfc1738.txt RFC 1738] rules.
ZIM contents are addressed using URLs fitting the following pattern: <namespace>/<article_url>. The references in articles HTML code (''<a href=""></a>'', ''<img src="">'', etc.) are URL-encoded following the [http://www.ietf.org/rfc/rfc1738.txt RFC 1738] rules.


Absolute URLs, ie. with a leading slash (''/'') are forbidden, because this avoid including the ZIM contents in any HTTP sub-hierachy. ZIM contents URLs must consequently be relative. Be careful, <article_url> may itself contain slashes (for example "BMW_501/502").
Absolute URLs, ie. with a leading slash (''/'') are forbidden, because this avoid including the ZIM contents in any HTTP sub-hierachy. ZIM contents URLs must consequently be relative.


The URLs in the UrlPointerlist are not encoded. Some readers process the requests that already do the decoding internally whereas most readers will handle the URLs directly. In this case you have to do the decoding before you pass the parameter to zimlib, but zimlib already provides a method to do so.
The URLs in the UrlPointerlist are not encoded. Some readers process the requests that already do the decoding internally whereas most readers will handle the URLs directly. In this case you have to do the decoding before you pass the parameter to zimlib, but zimlib already provides a method to do so.
14

edits

Navigation menu