word2html 0.3.0__tar.gz โ 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- word2html-0.5.0/.github/dependabot.yml +10 -0
- word2html-0.5.0/.github/workflows/ci.yml +116 -0
- word2html-0.5.0/.gitignore +33 -0
- {word2html-0.3.0 โ word2html-0.5.0}/LICENSE.txt +1 -1
- word2html-0.5.0/PKG-INFO +225 -0
- word2html-0.5.0/README.md +204 -0
- word2html-0.5.0/docs/.nojekyll +0 -0
- word2html-0.5.0/docs/assets/style.css +370 -0
- word2html-0.5.0/docs/development.html +155 -0
- word2html-0.5.0/docs/index.html +90 -0
- word2html-0.5.0/docs/install.html +89 -0
- word2html-0.5.0/docs/limits.html +102 -0
- word2html-0.5.0/docs/python-api.html +146 -0
- word2html-0.5.0/docs/security.html +152 -0
- word2html-0.5.0/docs/troubleshooting.html +116 -0
- word2html-0.5.0/docs/upgrading.html +96 -0
- word2html-0.5.0/docs/usage.html +127 -0
- word2html-0.5.0/pyproject.toml +73 -0
- word2html-0.5.0/tests/conftest.py +199 -0
- word2html-0.5.0/tests/test_active_content.py +130 -0
- word2html-0.5.0/tests/test_cli.py +256 -0
- word2html-0.5.0/tests/test_convert.py +232 -0
- word2html-0.5.0/tests/test_docs.py +226 -0
- word2html-0.5.0/tests/test_hidden_text.py +162 -0
- word2html-0.5.0/tests/test_limits.py +162 -0
- word2html-0.5.0/tests/test_links.py +186 -0
- word2html-0.5.0/tests/test_malformed.py +259 -0
- word2html-0.5.0/tests/test_output.py +125 -0
- word2html-0.5.0/tests/test_paths.py +100 -0
- word2html-0.5.0/uv.lock +487 -0
- word2html-0.5.0/word2html/__init__.py +36 -0
- word2html-0.5.0/word2html/common.py +91 -0
- word2html-0.5.0/word2html/docx.py +537 -0
- word2html-0.5.0/word2html/errors.py +43 -0
- word2html-0.5.0/word2html/html_scan.py +114 -0
- word2html-0.5.0/word2html/main.py +244 -0
- word2html-0.5.0/word2html/pandoc.py +144 -0
- word2html-0.3.0/MANIFEST.in +0 -2
- word2html-0.3.0/PKG-INFO +0 -46
- word2html-0.3.0/README.md +0 -30
- word2html-0.3.0/setup.cfg +0 -4
- word2html-0.3.0/setup.py +0 -45
- word2html-0.3.0/word2html/__init__.py +0 -4
- word2html-0.3.0/word2html/main.py +0 -54
- word2html-0.3.0/word2html.egg-info/PKG-INFO +0 -46
- word2html-0.3.0/word2html.egg-info/SOURCES.txt +0 -13
- word2html-0.3.0/word2html.egg-info/dependency_links.txt +0 -1
- word2html-0.3.0/word2html.egg-info/entry_points.txt +0 -3
- word2html-0.3.0/word2html.egg-info/not-zip-safe +0 -1
- word2html-0.3.0/word2html.egg-info/requires.txt +0 -2
- word2html-0.3.0/word2html.egg-info/top_level.txt +0 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
env:
|
|
17
|
+
UV_VERSION: "0.13.7"
|
|
18
|
+
PANDOC_VERSION: "3.13"
|
|
19
|
+
# Keep in sync with MIN_PANDOC_VERSION in word2html/pandoc.py.
|
|
20
|
+
PANDOC_MINIMUM: "3.6"
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
lint:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
timeout-minutes: 10
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
28
|
+
with:
|
|
29
|
+
persist-credentials: false
|
|
30
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
31
|
+
with:
|
|
32
|
+
version: ${{ env.UV_VERSION }}
|
|
33
|
+
enable-cache: true
|
|
34
|
+
- run: uv lock --check
|
|
35
|
+
- run: uv run --locked ruff check
|
|
36
|
+
- run: uv run --locked ruff format --check
|
|
37
|
+
- run: uv run --locked mypy
|
|
38
|
+
|
|
39
|
+
test:
|
|
40
|
+
runs-on: ${{ matrix.os }}
|
|
41
|
+
timeout-minutes: 15
|
|
42
|
+
strategy:
|
|
43
|
+
fail-fast: false
|
|
44
|
+
matrix:
|
|
45
|
+
os: [ubuntu-latest, macos-latest]
|
|
46
|
+
python: ["3.12", "3.13", "3.14", "3.15"]
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
49
|
+
with:
|
|
50
|
+
persist-credentials: false
|
|
51
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
52
|
+
with:
|
|
53
|
+
version: ${{ env.UV_VERSION }}
|
|
54
|
+
python-version: ${{ matrix.python }}
|
|
55
|
+
enable-cache: true
|
|
56
|
+
- uses: pandoc/actions/setup@86321b6dd4675f5014c611e05088e10d4939e09e # v1.1.1
|
|
57
|
+
if: runner.os == 'Linux'
|
|
58
|
+
with:
|
|
59
|
+
version: ${{ env.PANDOC_VERSION }}
|
|
60
|
+
# The setup action installs an x86_64 build on macOS, but macos-latest is arm64.
|
|
61
|
+
- name: Install native pandoc on macOS
|
|
62
|
+
if: runner.os == 'macOS'
|
|
63
|
+
shell: bash
|
|
64
|
+
env:
|
|
65
|
+
GH_TOKEN: ${{ github.token }}
|
|
66
|
+
run: |
|
|
67
|
+
gh release download "$PANDOC_VERSION" --repo jgm/pandoc \
|
|
68
|
+
--pattern '*-arm64-macOS.zip' --dir "$RUNNER_TEMP/pandoc"
|
|
69
|
+
unzip -q "$RUNNER_TEMP"/pandoc/*.zip -d "$RUNNER_TEMP/pandoc"
|
|
70
|
+
dirname "$(find "$RUNNER_TEMP/pandoc" -type f -name pandoc | head -n 1)" >> "$GITHUB_PATH"
|
|
71
|
+
- run: pandoc --version
|
|
72
|
+
- run: uv run --locked pytest --cov
|
|
73
|
+
|
|
74
|
+
pandoc-minimum:
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
timeout-minutes: 15
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
79
|
+
with:
|
|
80
|
+
persist-credentials: false
|
|
81
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
82
|
+
with:
|
|
83
|
+
version: ${{ env.UV_VERSION }}
|
|
84
|
+
python-version: "3.12"
|
|
85
|
+
enable-cache: true
|
|
86
|
+
- uses: pandoc/actions/setup@86321b6dd4675f5014c611e05088e10d4939e09e # v1.1.1
|
|
87
|
+
with:
|
|
88
|
+
version: ${{ env.PANDOC_MINIMUM }}
|
|
89
|
+
- run: pandoc --version
|
|
90
|
+
- run: uv run --locked pytest
|
|
91
|
+
|
|
92
|
+
package:
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
timeout-minutes: 10
|
|
95
|
+
steps:
|
|
96
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
97
|
+
with:
|
|
98
|
+
persist-credentials: false
|
|
99
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
100
|
+
with:
|
|
101
|
+
version: ${{ env.UV_VERSION }}
|
|
102
|
+
enable-cache: true
|
|
103
|
+
- uses: pandoc/actions/setup@86321b6dd4675f5014c611e05088e10d4939e09e # v1.1.1
|
|
104
|
+
with:
|
|
105
|
+
version: ${{ env.PANDOC_VERSION }}
|
|
106
|
+
- run: uv build
|
|
107
|
+
- name: Install the wheel into a clean environment and convert a document
|
|
108
|
+
shell: bash
|
|
109
|
+
run: |
|
|
110
|
+
uv venv "$RUNNER_TEMP/clean"
|
|
111
|
+
uv pip install --python "$RUNNER_TEMP/clean/bin/python" dist/*.whl
|
|
112
|
+
cd "$RUNNER_TEMP"
|
|
113
|
+
"$RUNNER_TEMP/clean/bin/word2html" --version
|
|
114
|
+
printf '# Hello\n\nCaf\xc3\xa9\n' | pandoc --from=markdown --to=docx --output=sample.docx
|
|
115
|
+
"$RUNNER_TEMP/clean/bin/word2html" --strict sample.docx
|
|
116
|
+
grep -q 'Cafรฉ' sample.html
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Virtual environments
|
|
2
|
+
env/
|
|
3
|
+
.venv/
|
|
4
|
+
venv/
|
|
5
|
+
|
|
6
|
+
# Python artifacts
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
*.so
|
|
11
|
+
.Python
|
|
12
|
+
|
|
13
|
+
# Distribution / packaging
|
|
14
|
+
build/
|
|
15
|
+
dist/
|
|
16
|
+
*.egg-info/
|
|
17
|
+
.eggs/
|
|
18
|
+
|
|
19
|
+
# IDE
|
|
20
|
+
.vscode/
|
|
21
|
+
.idea/
|
|
22
|
+
*.swp
|
|
23
|
+
*.swo
|
|
24
|
+
*~
|
|
25
|
+
|
|
26
|
+
# Testing
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.coverage
|
|
29
|
+
htmlcov/
|
|
30
|
+
|
|
31
|
+
# OS
|
|
32
|
+
.DS_Store
|
|
33
|
+
Thumbs.db
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright (c)
|
|
1
|
+
Copyright (c) 2025-2026, Brad Montgomery <brad@bradmontgomery.net>
|
|
2
2
|
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
4
|
this software and associated documentation files (the "Software"), to deal in
|
word2html-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: word2html
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Convert a Word (docx) document to HTML, safely, using pandoc.
|
|
5
|
+
Project-URL: Homepage, https://github.com/bradmontgomery/word2html
|
|
6
|
+
Author-email: Brad Montgomery <brad@bradmontgomery.net>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE.txt
|
|
9
|
+
Keywords: docx,html,pandoc,word
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
|
+
Classifier: Topic :: Text Processing
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# ๐ Convert a Word Doc to html
|
|
23
|
+
|
|
24
|
+
[](https://github.com/bradmontgomery/word2html/actions/workflows/ci.yml)
|
|
25
|
+
|
|
26
|
+
`word2html` turns a `.docx` file into a single, self-contained HTML5 file. It uses
|
|
27
|
+
[pandoc](https://pandoc.org/) for the conversion, and adds checks for malformed
|
|
28
|
+
documents, unsafe links, and hidden text.
|
|
29
|
+
|
|
30
|
+
## ๐ Requirements
|
|
31
|
+
|
|
32
|
+
- **pandoc 3.6 or newer.** This is a separate program: `uvx` and `pip` do **not** install it.
|
|
33
|
+
- Python 3.12 or newer.
|
|
34
|
+
|
|
35
|
+
Install pandoc with one of these:
|
|
36
|
+
|
|
37
|
+
| Platform | Command |
|
|
38
|
+
|---|---|
|
|
39
|
+
| macOS | `brew install pandoc` |
|
|
40
|
+
| Debian / Ubuntu | `sudo apt install pandoc` |
|
|
41
|
+
| Windows | `winget install JohnMacFarlane.Pandoc` |
|
|
42
|
+
| Anything else | See the [pandoc installation guide](https://pandoc.org/installing.html) |
|
|
43
|
+
|
|
44
|
+
Then check the version, because Linux distribution packages can be older than word2html needs:
|
|
45
|
+
|
|
46
|
+
pandoc --version
|
|
47
|
+
|
|
48
|
+
For example, `apt` doesn't install a new enough pandoc on Debian 12 or 13, or on Ubuntu 24.04.
|
|
49
|
+
If yours is too old, use an installer from the
|
|
50
|
+
[pandoc releases page](https://github.com/jgm/pandoc/releases). word2html is tested on Linux and
|
|
51
|
+
macOS on Python 3.12 to 3.15; Windows is not tested.
|
|
52
|
+
|
|
53
|
+
## ๐ Quick Start with uvx
|
|
54
|
+
|
|
55
|
+
The easiest way to run word2html without installing it is using `uvx`:
|
|
56
|
+
|
|
57
|
+
uvx word2html /path/to/MyGloriousDoc.docx
|
|
58
|
+
|
|
59
|
+
This will give you a new file, `/path/to/MyGloriousDoc.html`.
|
|
60
|
+
|
|
61
|
+
## ๐พ Installation
|
|
62
|
+
|
|
63
|
+
To install with uv:
|
|
64
|
+
|
|
65
|
+
uv tool install word2html
|
|
66
|
+
|
|
67
|
+
Or with pip:
|
|
68
|
+
|
|
69
|
+
pip install word2html
|
|
70
|
+
|
|
71
|
+
Then run:
|
|
72
|
+
|
|
73
|
+
word2html /path/to/MyGloriousDoc.docx
|
|
74
|
+
|
|
75
|
+
## ๐ ๏ธ Usage
|
|
76
|
+
|
|
77
|
+
word2html [-h] [--version] [--strict] [--timeout SECONDS]
|
|
78
|
+
[--max-memory SIZE] [--max-file-size SIZE] path
|
|
79
|
+
|
|
80
|
+
| Option | Default | What it does |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `--strict` | off | Refuse documents with unsafe links or hidden text instead of warning |
|
|
83
|
+
| `--timeout SECONDS` | `120` | Stop pandoc after this long |
|
|
84
|
+
| `--max-memory SIZE` | `2G` | Memory limit for pandoc, such as `512M` |
|
|
85
|
+
| `--max-file-size SIZE` | `200M` | Largest input file to accept |
|
|
86
|
+
| `--version` | | Show the version |
|
|
87
|
+
|
|
88
|
+
The output is written next to the input, with the same name and an `.html` extension. **An
|
|
89
|
+
existing file with that name is replaced.** The replacement is atomic: if conversion fails or is
|
|
90
|
+
refused, an existing file is left untouched. If the output path is a symlink, the symlink itself
|
|
91
|
+
is replaced and its target isn't changed.
|
|
92
|
+
|
|
93
|
+
### Exit codes
|
|
94
|
+
|
|
95
|
+
| Code | Meaning |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `0` | Converted. Any warnings were printed to stderr |
|
|
98
|
+
| `1` | Invalid input, a limit was exceeded, or pandoc or the output write failed |
|
|
99
|
+
| `2` | Bad command line |
|
|
100
|
+
| `3` | Refused for safety. Nothing was written |
|
|
101
|
+
|
|
102
|
+
## ๐ Security
|
|
103
|
+
|
|
104
|
+
Word documents can contain things you don't want to publish. Here's what word2html does about
|
|
105
|
+
them:
|
|
106
|
+
|
|
107
|
+
| Found in the document | Default | With `--strict` |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| Unsafe links | Warn on stderr, convert | Refuse (exit 3) |
|
|
110
|
+
| Hidden text | Warn on stderr, convert | Refuse (exit 3) |
|
|
111
|
+
| Active content | Refuse (exit 3) | Refuse (exit 3) |
|
|
112
|
+
|
|
113
|
+
> โ ๏ธ **By default, flagged links and hidden text are still in the HTML.** The warning is the only
|
|
114
|
+
> protection. Use `--strict` in scripts and automation, where nobody may read stderr.
|
|
115
|
+
|
|
116
|
+
**Unsafe links** are any link or source URL that doesn't use `http`, `https`, `mailto`, `tel`, or
|
|
117
|
+
`ftp`, and isn't a relative path or `#fragment`. That covers `javascript:`, `data:`, and `file:`
|
|
118
|
+
links, including disguised forms such as `JaVaScRiPt:` or `java<tab>script:`. A warning looks like:
|
|
119
|
+
|
|
120
|
+
word2html: warning: unsafe link: javascript:alert(1)
|
|
121
|
+
|
|
122
|
+
**Hidden text** is text formatted as hidden in Word, whether directly or through a style. pandoc
|
|
123
|
+
publishes it as ordinary visible text. The warning says how much was found and where, but never
|
|
124
|
+
repeats the text, so it can't leak into logs:
|
|
125
|
+
|
|
126
|
+
word2html: warning: 1 run(s) of text hidden in Word will be visible in the HTML (word/document.xml)
|
|
127
|
+
|
|
128
|
+
To remove it, open the document in Word and use **File โบ Info โบ Check for Issues โบ Inspect
|
|
129
|
+
Document**, or show hidden text and delete it.
|
|
130
|
+
|
|
131
|
+
**Active content** is anything that could run without being clicked, such as an "image" that is
|
|
132
|
+
really an HTML or JavaScript file. Word documents don't normally contain it, so it is always
|
|
133
|
+
refused. Embedded images (including SVG, which can't run scripts inside `<img>`), PDFs, video and
|
|
134
|
+
audio are fine.
|
|
135
|
+
|
|
136
|
+
### Converting documents you don't trust
|
|
137
|
+
|
|
138
|
+
word2html's checks are not a complete HTML sanitizer. If you publish HTML made from documents you
|
|
139
|
+
don't trust, use `--strict` and also run the result through an HTML sanitizer, as
|
|
140
|
+
[pandoc's security notes](https://pandoc.org/MANUAL.html#a-note-on-security) recommend.
|
|
141
|
+
|
|
142
|
+
## ๐ก๏ธ Malformed documents and limits
|
|
143
|
+
|
|
144
|
+
Every document is checked before pandoc sees it. These are rejected with a clear error (exit 1):
|
|
145
|
+
|
|
146
|
+
- Files that aren't really `.docx`, including empty, truncated, and non-zip files
|
|
147
|
+
- Password-protected and legacy `.doc` files
|
|
148
|
+
- Archives with encrypted members, duplicate members, or unsafe paths such as `../evil`
|
|
149
|
+
- Missing required parts, or XML that isn't well-formed
|
|
150
|
+
- XML with a `DOCTYPE`, which blocks external-entity and "billion laughs" attacks
|
|
151
|
+
|
|
152
|
+
These limits stop small files from running away with your machine's memory or time:
|
|
153
|
+
|
|
154
|
+
| Limit | Value | Change with |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| Input file size | 200 MiB | `--max-file-size` |
|
|
157
|
+
| pandoc memory | 2 GiB | `--max-memory` |
|
|
158
|
+
| pandoc run time | 120 seconds | `--timeout` |
|
|
159
|
+
| Total uncompressed size | 1 GiB | Fixed |
|
|
160
|
+
| Size of one XML part | 64 MiB | Fixed |
|
|
161
|
+
| Files in the archive | 10,000 | Fixed |
|
|
162
|
+
| XML nesting depth | 256 levels | Fixed |
|
|
163
|
+
|
|
164
|
+
The memory limit is a cap, not a target: a small hostile file can still use up to the limit before
|
|
165
|
+
it's stopped, so lower `--max-memory` if you convert untrusted documents on a shared machine.
|
|
166
|
+
|
|
167
|
+
A very long document can also reach it legitimately, because embedding images makes pandoc use
|
|
168
|
+
about 200 MB of memory per MB of document XML. With the default limit, that's about 10 MB of XML
|
|
169
|
+
in a document with images, or the full 64 MiB without them. Large images themselves cost little.
|
|
170
|
+
If you hit the limit, the error tells you, and `--max-memory 8G` (for example) allows more.
|
|
171
|
+
|
|
172
|
+
## ๐ Known behaviour
|
|
173
|
+
|
|
174
|
+
- **Tracked changes:** insertions are published as if accepted, while deletions are dropped.
|
|
175
|
+
- **Comments and document properties** (such as author) are not published.
|
|
176
|
+
- **Linked images**, meaning images stored outside the document, are dropped with a warning. They
|
|
177
|
+
are never fetched or read from disk. Images whose file is missing from the document also warn.
|
|
178
|
+
- **File size:** images are embedded in the HTML, so output can be larger than the `.docx`, and
|
|
179
|
+
documents with images need more memory to convert.
|
|
180
|
+
- **Styling:** output uses pandoc's default CSS.
|
|
181
|
+
|
|
182
|
+
## โฌ๏ธ Upgrading from 0.4
|
|
183
|
+
|
|
184
|
+
- pypandoc, pytidylib, and libtidy are no longer used, and there are no Python dependencies. You
|
|
185
|
+
now need only pandoc 3.6 or newer.
|
|
186
|
+
- Output is now HTML5 with a declared charset and pandoc's default CSS, rather than tidied HTML 4.
|
|
187
|
+
Highlights and footnote sections survive, and images are embedded rather than broken.
|
|
188
|
+
- Exit code `3` is new, and warnings on stderr have a new format.
|
|
189
|
+
- For Python callers, `convert_to_html()` now returns a `ConversionResult` and no longer prints.
|
|
190
|
+
Warnings are in `result.warnings`. Its options are `strict=` and `limits=`, and errors derive
|
|
191
|
+
from `WordToHtmlError`. `FileNotFoundError` is unchanged, and invalid files still raise a
|
|
192
|
+
`ValueError` subclass.
|
|
193
|
+
|
|
194
|
+
## ๐งช Development
|
|
195
|
+
|
|
196
|
+
You'll need [uv](https://docs.astral.sh/uv/) and pandoc. The tests fail rather than skip if pandoc
|
|
197
|
+
isn't installed.
|
|
198
|
+
|
|
199
|
+
uv sync # install dependencies
|
|
200
|
+
uv run pytest --cov # run tests with coverage
|
|
201
|
+
uv run ruff check # lint
|
|
202
|
+
uv run ruff format --check # check formatting
|
|
203
|
+
uv run mypy # check types
|
|
204
|
+
uv build # build the package
|
|
205
|
+
|
|
206
|
+
Every pull request runs all of these on GitHub Actions, across Linux and macOS, every supported
|
|
207
|
+
Python version, and the oldest supported pandoc.
|
|
208
|
+
|
|
209
|
+
## ๐ Note on Licenses
|
|
210
|
+
|
|
211
|
+
This code is MIT-licensed. It runs [pandoc](https://pandoc.org/), a separate program released under
|
|
212
|
+
the GPL, which you install yourself. word2html doesn't include or link to any pandoc code.
|
|
213
|
+
|
|
214
|
+
## โญ Star History
|
|
215
|
+
|
|
216
|
+
This project _really_ started out as a joke -- although this is something I've done too many times. I'm always
|
|
217
|
+
amazed at how many people have starred this repo. ๐คฉ
|
|
218
|
+
|
|
219
|
+
<a href="https://www.star-history.com/#bradmontgomery/word2html&type=date&legend=top-left">
|
|
220
|
+
<picture>
|
|
221
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=bradmontgomery/word2html&type=date&theme=dark&legend=top-left" />
|
|
222
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=bradmontgomery/word2html&type=date&legend=top-left" />
|
|
223
|
+
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=bradmontgomery/word2html&type=date&legend=top-left" />
|
|
224
|
+
</picture>
|
|
225
|
+
</a>
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# ๐ Convert a Word Doc to html
|
|
2
|
+
|
|
3
|
+
[](https://github.com/bradmontgomery/word2html/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
`word2html` turns a `.docx` file into a single, self-contained HTML5 file. It uses
|
|
6
|
+
[pandoc](https://pandoc.org/) for the conversion, and adds checks for malformed
|
|
7
|
+
documents, unsafe links, and hidden text.
|
|
8
|
+
|
|
9
|
+
## ๐ Requirements
|
|
10
|
+
|
|
11
|
+
- **pandoc 3.6 or newer.** This is a separate program: `uvx` and `pip` do **not** install it.
|
|
12
|
+
- Python 3.12 or newer.
|
|
13
|
+
|
|
14
|
+
Install pandoc with one of these:
|
|
15
|
+
|
|
16
|
+
| Platform | Command |
|
|
17
|
+
|---|---|
|
|
18
|
+
| macOS | `brew install pandoc` |
|
|
19
|
+
| Debian / Ubuntu | `sudo apt install pandoc` |
|
|
20
|
+
| Windows | `winget install JohnMacFarlane.Pandoc` |
|
|
21
|
+
| Anything else | See the [pandoc installation guide](https://pandoc.org/installing.html) |
|
|
22
|
+
|
|
23
|
+
Then check the version, because Linux distribution packages can be older than word2html needs:
|
|
24
|
+
|
|
25
|
+
pandoc --version
|
|
26
|
+
|
|
27
|
+
For example, `apt` doesn't install a new enough pandoc on Debian 12 or 13, or on Ubuntu 24.04.
|
|
28
|
+
If yours is too old, use an installer from the
|
|
29
|
+
[pandoc releases page](https://github.com/jgm/pandoc/releases). word2html is tested on Linux and
|
|
30
|
+
macOS on Python 3.12 to 3.15; Windows is not tested.
|
|
31
|
+
|
|
32
|
+
## ๐ Quick Start with uvx
|
|
33
|
+
|
|
34
|
+
The easiest way to run word2html without installing it is using `uvx`:
|
|
35
|
+
|
|
36
|
+
uvx word2html /path/to/MyGloriousDoc.docx
|
|
37
|
+
|
|
38
|
+
This will give you a new file, `/path/to/MyGloriousDoc.html`.
|
|
39
|
+
|
|
40
|
+
## ๐พ Installation
|
|
41
|
+
|
|
42
|
+
To install with uv:
|
|
43
|
+
|
|
44
|
+
uv tool install word2html
|
|
45
|
+
|
|
46
|
+
Or with pip:
|
|
47
|
+
|
|
48
|
+
pip install word2html
|
|
49
|
+
|
|
50
|
+
Then run:
|
|
51
|
+
|
|
52
|
+
word2html /path/to/MyGloriousDoc.docx
|
|
53
|
+
|
|
54
|
+
## ๐ ๏ธ Usage
|
|
55
|
+
|
|
56
|
+
word2html [-h] [--version] [--strict] [--timeout SECONDS]
|
|
57
|
+
[--max-memory SIZE] [--max-file-size SIZE] path
|
|
58
|
+
|
|
59
|
+
| Option | Default | What it does |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `--strict` | off | Refuse documents with unsafe links or hidden text instead of warning |
|
|
62
|
+
| `--timeout SECONDS` | `120` | Stop pandoc after this long |
|
|
63
|
+
| `--max-memory SIZE` | `2G` | Memory limit for pandoc, such as `512M` |
|
|
64
|
+
| `--max-file-size SIZE` | `200M` | Largest input file to accept |
|
|
65
|
+
| `--version` | | Show the version |
|
|
66
|
+
|
|
67
|
+
The output is written next to the input, with the same name and an `.html` extension. **An
|
|
68
|
+
existing file with that name is replaced.** The replacement is atomic: if conversion fails or is
|
|
69
|
+
refused, an existing file is left untouched. If the output path is a symlink, the symlink itself
|
|
70
|
+
is replaced and its target isn't changed.
|
|
71
|
+
|
|
72
|
+
### Exit codes
|
|
73
|
+
|
|
74
|
+
| Code | Meaning |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `0` | Converted. Any warnings were printed to stderr |
|
|
77
|
+
| `1` | Invalid input, a limit was exceeded, or pandoc or the output write failed |
|
|
78
|
+
| `2` | Bad command line |
|
|
79
|
+
| `3` | Refused for safety. Nothing was written |
|
|
80
|
+
|
|
81
|
+
## ๐ Security
|
|
82
|
+
|
|
83
|
+
Word documents can contain things you don't want to publish. Here's what word2html does about
|
|
84
|
+
them:
|
|
85
|
+
|
|
86
|
+
| Found in the document | Default | With `--strict` |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| Unsafe links | Warn on stderr, convert | Refuse (exit 3) |
|
|
89
|
+
| Hidden text | Warn on stderr, convert | Refuse (exit 3) |
|
|
90
|
+
| Active content | Refuse (exit 3) | Refuse (exit 3) |
|
|
91
|
+
|
|
92
|
+
> โ ๏ธ **By default, flagged links and hidden text are still in the HTML.** The warning is the only
|
|
93
|
+
> protection. Use `--strict` in scripts and automation, where nobody may read stderr.
|
|
94
|
+
|
|
95
|
+
**Unsafe links** are any link or source URL that doesn't use `http`, `https`, `mailto`, `tel`, or
|
|
96
|
+
`ftp`, and isn't a relative path or `#fragment`. That covers `javascript:`, `data:`, and `file:`
|
|
97
|
+
links, including disguised forms such as `JaVaScRiPt:` or `java<tab>script:`. A warning looks like:
|
|
98
|
+
|
|
99
|
+
word2html: warning: unsafe link: javascript:alert(1)
|
|
100
|
+
|
|
101
|
+
**Hidden text** is text formatted as hidden in Word, whether directly or through a style. pandoc
|
|
102
|
+
publishes it as ordinary visible text. The warning says how much was found and where, but never
|
|
103
|
+
repeats the text, so it can't leak into logs:
|
|
104
|
+
|
|
105
|
+
word2html: warning: 1 run(s) of text hidden in Word will be visible in the HTML (word/document.xml)
|
|
106
|
+
|
|
107
|
+
To remove it, open the document in Word and use **File โบ Info โบ Check for Issues โบ Inspect
|
|
108
|
+
Document**, or show hidden text and delete it.
|
|
109
|
+
|
|
110
|
+
**Active content** is anything that could run without being clicked, such as an "image" that is
|
|
111
|
+
really an HTML or JavaScript file. Word documents don't normally contain it, so it is always
|
|
112
|
+
refused. Embedded images (including SVG, which can't run scripts inside `<img>`), PDFs, video and
|
|
113
|
+
audio are fine.
|
|
114
|
+
|
|
115
|
+
### Converting documents you don't trust
|
|
116
|
+
|
|
117
|
+
word2html's checks are not a complete HTML sanitizer. If you publish HTML made from documents you
|
|
118
|
+
don't trust, use `--strict` and also run the result through an HTML sanitizer, as
|
|
119
|
+
[pandoc's security notes](https://pandoc.org/MANUAL.html#a-note-on-security) recommend.
|
|
120
|
+
|
|
121
|
+
## ๐ก๏ธ Malformed documents and limits
|
|
122
|
+
|
|
123
|
+
Every document is checked before pandoc sees it. These are rejected with a clear error (exit 1):
|
|
124
|
+
|
|
125
|
+
- Files that aren't really `.docx`, including empty, truncated, and non-zip files
|
|
126
|
+
- Password-protected and legacy `.doc` files
|
|
127
|
+
- Archives with encrypted members, duplicate members, or unsafe paths such as `../evil`
|
|
128
|
+
- Missing required parts, or XML that isn't well-formed
|
|
129
|
+
- XML with a `DOCTYPE`, which blocks external-entity and "billion laughs" attacks
|
|
130
|
+
|
|
131
|
+
These limits stop small files from running away with your machine's memory or time:
|
|
132
|
+
|
|
133
|
+
| Limit | Value | Change with |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| Input file size | 200 MiB | `--max-file-size` |
|
|
136
|
+
| pandoc memory | 2 GiB | `--max-memory` |
|
|
137
|
+
| pandoc run time | 120 seconds | `--timeout` |
|
|
138
|
+
| Total uncompressed size | 1 GiB | Fixed |
|
|
139
|
+
| Size of one XML part | 64 MiB | Fixed |
|
|
140
|
+
| Files in the archive | 10,000 | Fixed |
|
|
141
|
+
| XML nesting depth | 256 levels | Fixed |
|
|
142
|
+
|
|
143
|
+
The memory limit is a cap, not a target: a small hostile file can still use up to the limit before
|
|
144
|
+
it's stopped, so lower `--max-memory` if you convert untrusted documents on a shared machine.
|
|
145
|
+
|
|
146
|
+
A very long document can also reach it legitimately, because embedding images makes pandoc use
|
|
147
|
+
about 200 MB of memory per MB of document XML. With the default limit, that's about 10 MB of XML
|
|
148
|
+
in a document with images, or the full 64 MiB without them. Large images themselves cost little.
|
|
149
|
+
If you hit the limit, the error tells you, and `--max-memory 8G` (for example) allows more.
|
|
150
|
+
|
|
151
|
+
## ๐ Known behaviour
|
|
152
|
+
|
|
153
|
+
- **Tracked changes:** insertions are published as if accepted, while deletions are dropped.
|
|
154
|
+
- **Comments and document properties** (such as author) are not published.
|
|
155
|
+
- **Linked images**, meaning images stored outside the document, are dropped with a warning. They
|
|
156
|
+
are never fetched or read from disk. Images whose file is missing from the document also warn.
|
|
157
|
+
- **File size:** images are embedded in the HTML, so output can be larger than the `.docx`, and
|
|
158
|
+
documents with images need more memory to convert.
|
|
159
|
+
- **Styling:** output uses pandoc's default CSS.
|
|
160
|
+
|
|
161
|
+
## โฌ๏ธ Upgrading from 0.4
|
|
162
|
+
|
|
163
|
+
- pypandoc, pytidylib, and libtidy are no longer used, and there are no Python dependencies. You
|
|
164
|
+
now need only pandoc 3.6 or newer.
|
|
165
|
+
- Output is now HTML5 with a declared charset and pandoc's default CSS, rather than tidied HTML 4.
|
|
166
|
+
Highlights and footnote sections survive, and images are embedded rather than broken.
|
|
167
|
+
- Exit code `3` is new, and warnings on stderr have a new format.
|
|
168
|
+
- For Python callers, `convert_to_html()` now returns a `ConversionResult` and no longer prints.
|
|
169
|
+
Warnings are in `result.warnings`. Its options are `strict=` and `limits=`, and errors derive
|
|
170
|
+
from `WordToHtmlError`. `FileNotFoundError` is unchanged, and invalid files still raise a
|
|
171
|
+
`ValueError` subclass.
|
|
172
|
+
|
|
173
|
+
## ๐งช Development
|
|
174
|
+
|
|
175
|
+
You'll need [uv](https://docs.astral.sh/uv/) and pandoc. The tests fail rather than skip if pandoc
|
|
176
|
+
isn't installed.
|
|
177
|
+
|
|
178
|
+
uv sync # install dependencies
|
|
179
|
+
uv run pytest --cov # run tests with coverage
|
|
180
|
+
uv run ruff check # lint
|
|
181
|
+
uv run ruff format --check # check formatting
|
|
182
|
+
uv run mypy # check types
|
|
183
|
+
uv build # build the package
|
|
184
|
+
|
|
185
|
+
Every pull request runs all of these on GitHub Actions, across Linux and macOS, every supported
|
|
186
|
+
Python version, and the oldest supported pandoc.
|
|
187
|
+
|
|
188
|
+
## ๐ Note on Licenses
|
|
189
|
+
|
|
190
|
+
This code is MIT-licensed. It runs [pandoc](https://pandoc.org/), a separate program released under
|
|
191
|
+
the GPL, which you install yourself. word2html doesn't include or link to any pandoc code.
|
|
192
|
+
|
|
193
|
+
## โญ Star History
|
|
194
|
+
|
|
195
|
+
This project _really_ started out as a joke -- although this is something I've done too many times. I'm always
|
|
196
|
+
amazed at how many people have starred this repo. ๐คฉ
|
|
197
|
+
|
|
198
|
+
<a href="https://www.star-history.com/#bradmontgomery/word2html&type=date&legend=top-left">
|
|
199
|
+
<picture>
|
|
200
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=bradmontgomery/word2html&type=date&theme=dark&legend=top-left" />
|
|
201
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=bradmontgomery/word2html&type=date&legend=top-left" />
|
|
202
|
+
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=bradmontgomery/word2html&type=date&legend=top-left" />
|
|
203
|
+
</picture>
|
|
204
|
+
</a>
|
|
File without changes
|