Skip to content

Commit c0f4e19

Browse files
committed
Add documentation, especially warc2zim doc about rewriting
1 parent b613fca commit c0f4e19

File tree

7 files changed

+186
-12
lines changed

7 files changed

+186
-12
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Example usage:
2222
zimscraperlib>=1.1,<1.2
2323
```
2424

25+
See [functional architecture](docs/functional_architecture.md), [software architecture](docs/software_architecture.md) and [technical architecture](docs/technical_architecture.md) for more details on scraperlib (not all aspects are covered yet, this is a WIP).
26+
2527
# Dependencies
2628

2729
* libmagic

docs/functional_architecture.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Functional Architecture
2+
3+
## Enrich libzim functions
4+
5+
zimscraperlib has primitives to enrich libzim functions with some operations which are known to be shared across scrapers. See `zim` module.
6+
7+
## Handle videos
8+
9+
zimscraperlib has primitives to manipulate videos with some operations which are known to be shared across scrapers. See `video` module.
10+
11+
## Handle pictures
12+
13+
zimscraperlib has primitives to manipulate pictures with some operations which are known to be shared across scrapers. See `image` module.
14+
15+
## Store and rewrite mostly unmodified HTML, CSS and JS from online website
16+
17+
zimscraperlib also contains primitives to rewrite HTML, CSS and JS fetched online, to proper operate within a ZIM without heavy modifications. While originaly developped for warc2zim, some of these primitives are now also used for mindtouch scraper and others might follow, so they are shared in zimscraperlib. See `rewriting` module.
18+
19+
## ZIM storage
20+
21+
While storing web resources in a ZIM is mostly straightforward (we just transfer the raw bytes, after some modification for URL rewriting if needed), the decision of the path where the resource will be stored is very important.
22+
23+
This is purely conventional, even if ZIM specification has to be respected for proper operation in readers.
24+
25+
This function is responsible to compute the ZIM path where a given web resource is going to be stored.
26+
27+
While the URL is the only driver of this computation for now, zimscraperlib might have to consider other contextual data in the future. E.g. the resource to serve might by dynamic, depending not only on URL query parameters but also header(s) value(s).
28+
29+
## Fuzzy rules
30+
31+
Unfortunately, it is not always possible / desirable to store the resource with a simple transformation.
32+
33+
A typical situation is that some query parameters are dynamically computed by some Javascript code to include user tracking identifier, current datetime information, ...
34+
35+
When running again the same javascript code inside the ZIM, the URL will hence be slightly different because context has changed, but the same content needs to be retrieved.
36+
37+
zimscraperlib hence relies on fuzzy rules to transform/simplify some URLs when computing the ZIM path.
38+
39+
## URL Rewriting
40+
41+
zimscraperlib transforms (rewrites) URLs found in documents (HTML, CSS, JS, ...) so that they are usable inside the ZIM.
42+
43+
### General case
44+
45+
One simple example is that we might have following code in an HTML document to load an image with an absolute URL:
46+
47+
```
48+
<img src="https://en.wikipedia.org/wiki/File:Kiwix_logo_v3.svg"></img>
49+
```
50+
51+
The URL `https://en.wikipedia.org/wiki/File:Kiwix_logo_v3.svg` has to be transformed to a URL that it is usable inside the ZIM.
52+
53+
For proper reader operation, openZIM prohibits using absolute URLs, so this has to be a relative URL. This relative URL is hence dependant on the location of the resource currently being rewriten.
54+
55+
The table below gives some examples of what the rewritten URL is going to be, depending on the URL of the rewritten document.
56+
57+
| HTML document URL | image URL rewritten for usage inside the ZIM |
58+
|--|--|
59+
| `https://en.wikipedia.org/wiki/Kiwix` | `./File:Kiwix_logo_v3.svg` |
60+
| `https://en.wikipedia.org/wiki` | `./wiki/File:Kiwix_logo_v3.svg` |
61+
| `https://en.wikipedia.org/waka/Kiwix` | `../wiki/File:Kiwix_logo_v3.svg` |
62+
| `https://fr.wikipedia.org/wiki/Kiwix` | `../../en.wikipedia.org/wiki/File:Kiwix_logo_v3.svg` |
63+
64+
As can be seen on the last line (but this is true for all URLs), this rewriting has to take into account the convention saying at which ZIM path a given web resource will be stored.
65+
66+
### Dynamic case
67+
68+
The explanation above more or less assumed that the transformations can be done statically, i.e zimscraperlib can open every known document, find existing URLs and replace them with their counterpart inside the ZIM.
69+
70+
While this is possible for HTML and CSS documents typically, it is not possible when the URL is dynamically computed. This is typically the case for JS documents, where in the general case the URL is not statically stored inside the JS code but computed on-the-fly by aggregating various strings and values.
71+
72+
Rewriting these computations is not deemed feasible due to the huge variety of situation which might be encountered.
73+
74+
A specific function is hence needed to rewrite URL **live in client browser**, intercept any function triggering a web request, transform the URL according to conventions (where we expect the resource to be located in the general case) and fuzzy rules.
75+
76+
_Spoiler: this is where we will rely on wombat.js from webrecorder team, since this dynamic interception is quite complex and already done quite neatly by them_
77+
78+
### Fuzzy rules
79+
80+
The same fuzzy rules that have been used to compute the ZIM path from a resource URL have to be applied again when rewriting URLs.
81+
82+
While this is expected to serve mostly for the dynamic case, we still applies them on both side (staticaly and dynamicaly) for coherency.
83+
84+
## Documents rewriten statically
85+
86+
For now zimscraperlib rewrites HTML, CSS and JS documents. For CSS and JS, this mainly consists in replacing URLs. For HTML, we also have more specific rewritting necessary (e.g. to handle base href or redirects with meta).
87+
88+
No domain specific (DS) rules are applied like it is done in wabac.JS because these rules are already applied in Browsertrix Crawler. For the same reason, JSON is not rewritten anymore (URL do not need to be rewritten in JSON because these URLs will be used by JS, intercepted by wombat and dynamically rewritten).
89+
90+
JSONP callbacks are supposed to be rewritten but this has not been heavily tested.
91+
92+
Other types of documents are supposed to be either not feasible / not worth it (e.g. URLs inside PDF documents), meaningless (e.g. images, fonts) or planned for later due to limited usage in the wild (e.g. XML).

docs/software_architecture.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Software architecture
2+
3+
## HTML rewriting
4+
5+
HTML rewriting is purely static (i.e. before resources are written to the ZIM). HTML code is parsed with the [HTML parser from Python standard library](https://docs.python.org/3/library/html.parser.html).
6+
7+
A small header script is inserted in HTML code to initialize wombat.js which will wrap all JS APIs to dynamically rewrite URLs comming from JS.
8+
9+
This header script is generated using [Jinja2](https://pypi.org/project/Jinja2/) template since it needs to populate some JS context variables needed by wombat.js operations (original scheme, original url, ...).
10+
11+
## CSS rewriting
12+
13+
CSS rewriting is purely static (i.e. before resources are written to the ZIM). CSS code is parsed with the [tinycss2 Python library](https://pypi.org/project/tinycss2/).
14+
15+
## JS rewriting
16+
17+
### Static
18+
19+
Static JS rewriting is simply a matter of pure textual manipulation with regular expressions. No parsing is done at all.
20+
21+
### Dynamic
22+
23+
Dynamic JS rewriting is done with [wombat JS library](https://github.com/webrecorder/wombat). The same fuzzy rules that are used for static rewritting are injected into wombat configuration. Code to rewrite URLs is an adapted version of the code used to compute ZIM paths.
24+
25+
For wombat setup, including the URL rewriting part, we need to pass wombat configuration info. This code is developed in the `javascript` folder. For URL parsing, it relies on the [uri-js library](https://www.npmjs.com/package/uri-js). This javascript code is bundled into a single `wombatSetup.js` file with [rollup bundler](https://rollupjs.org), the same bundler used by webrecorder team to bundle wombat.

docs/technical_architecture.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Technical architecture
2+
3+
## Fuzzy rules
4+
5+
Fuzzy rules are stored in `rules/rules.yaml`. This configuration file is then used by `rules/generateRules.py` to generate Python and JS code.
6+
7+
Should you update these fuzzy rules, you hence have to:
8+
- regenerate Python and JS files by running `python rules/generateRules.py`
9+
- bundle again Javascript `wombatSetup.js` (see below).
10+
11+
## Wombat configuration
12+
13+
Wombat configuration contains some static configuration and the dynamic URL rewriting, including fuzzy rules.
14+
15+
It is bundled by rollup with `cd javascript && yarn build-prod` and the result is pushed to proper scraper location for inclusion at build time.
16+
17+
Tests are available and run with `cd javascript && yarn test`.
18+
19+
## Transformation of URL into ZIM path
20+
21+
Transforming a URL into a ZIM path has to respect the ZIM specification: path must not be url-encoded (i.e. it must be decoded) and it must be stored as UTF-8.
22+
23+
WARC record stores the items URL inside a header named "WARC-Target-URI". The value inside this header is encoded, or more exactly it is "exactly what the browser sent at the HTTP level" (see https://github.com/webrecorder/browsertrix-crawler/issues/492 for more details).
24+
25+
It has been decided (by convention) that we will drop the scheme, the port, the username and password from the URL. Headers are also not considered in this computation.
26+
27+
Computation of the ZIM path is hence mostly straightforward:
28+
- decode the hostname which is puny-encoded
29+
- decode the path and query parameter which might be url-encoded
30+
31+
## URL rewriting
32+
33+
In addition to the computation of the relative path from the current document URL to the URL to rewrite, URL rewriting also consists in computing the proper ZIM path (with same operation as above) and properly encoding it so that the resulting URL respects [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986). Some important stuff has to be noted in this encoding.
34+
35+
- since the original hostname is now part of the path, it will now be url-encoded
36+
- since the `?` and following query parameters are also part of the path (we do not want readers to drop them like kiwix-serve would do), they are also url-encoded
37+
38+
Below is an example case of the rewrite operation on an image URL found in an HTML document.
39+
40+
- Document original URL: `https://kiwix.org/a/article/document.html`
41+
- Document ZIM path: `kiwix.org/a/article/document.html`
42+
- Image original URL: `//xn--exmple-cva.com/a/resource/image.png?foo=bar`
43+
- Image rewritten URL: `../../../ex%C3%A9mple.com/a/resource/image.png%3Ffoo%3Dbar`
44+
- Image ZIM Path: `exémple.com/a/resource/image.png?foo=bar`
45+
46+
## JS Rewriting
47+
48+
JS Rewriting is a bit special because rules to apply are different wether we are using "classic" Javascript or "module" Javascript.
49+
50+
Detection of Javascript modules starts at the HTML level where we have a `<script type="module" src="...">` tag. This tells us that file at src location is a Javascript module. From there we now that its subresources are also Javascript module.
51+
52+
Currently this detection is done on-the-fly, based on the fact that WARC items are processed in the same order that they have been fetched by the browser, and we hence do not need a multi-pass approach. Meaning that HTML will be processed first, then parent JS, then its dependencies, ... **This is a strong assumption**.

openzim.toml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,3 @@ execute_after=[
88
action="get_file"
99
source="https://cdn.jsdelivr.net/npm/@webrecorder/wombat@3.8.2/dist/wombat.js"
1010
target_file="wombat.js"
11-
12-
# wombatSetup.js is supposed to be built locally from files in javascript folder.
13-
# Should someone not have proper skills / tooling / knowledge, or simply install from
14-
# sdist / Github repo directly, without any advanced knowledge of this specificity, the
15-
# configuration below ensures that wombatSetup.js is downloaded from dev.kiwix.org,
16-
# where we have the latest version from `main` branch. wheel contains the wombatSetup.js
17-
# which was built at the same time than the wheel. (reminder: get_file action does not
18-
# overwrite a file which already exists)
19-
[files.assets.actions."wombatSetup.js"]
20-
action="get_file"
21-
source="https://dev.kiwix.org/zimscraperlib/wombatSetup.js"
22-
target_file="wombatSetup.js"

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ artifacts = [
9191
"tests/rewriting/test_fuzzy_rules.py",
9292
]
9393

94+
[tool.hatch.build.targets.sdist]
95+
include = [
96+
"src/zimscraperlib/rewriting/statics/**",
97+
"src/zimscraperlib/rewriting/rules.py",
98+
"tests/rewriting/test_fuzzy_rules.py",
99+
]
100+
94101
[tool.hatch.envs.default]
95102
features = ["dev"]
96103

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
This folder must contain two files which are not under Git version control:
2+
- wombat.js, a webrecorder software
3+
- wombatSetup.js, a custom configuration script for wombat.js, which is built in this
4+
project from files in the javascript folder
5+
6+
If you install zimscraperlib from sdist or wheel, we've pre-packaged these files for
7+
convenience and also so that your version of wombatSetup.js ais "aligned" (i.e. if you
8+
install zimscraperlib x.y.z, we are sure which version wombatSetup.js you have).

0 commit comments

Comments
 (0)