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.
Files changed (51) hide show
  1. word2html-0.5.0/.github/dependabot.yml +10 -0
  2. word2html-0.5.0/.github/workflows/ci.yml +116 -0
  3. word2html-0.5.0/.gitignore +33 -0
  4. {word2html-0.3.0 โ†’ word2html-0.5.0}/LICENSE.txt +1 -1
  5. word2html-0.5.0/PKG-INFO +225 -0
  6. word2html-0.5.0/README.md +204 -0
  7. word2html-0.5.0/docs/.nojekyll +0 -0
  8. word2html-0.5.0/docs/assets/style.css +370 -0
  9. word2html-0.5.0/docs/development.html +155 -0
  10. word2html-0.5.0/docs/index.html +90 -0
  11. word2html-0.5.0/docs/install.html +89 -0
  12. word2html-0.5.0/docs/limits.html +102 -0
  13. word2html-0.5.0/docs/python-api.html +146 -0
  14. word2html-0.5.0/docs/security.html +152 -0
  15. word2html-0.5.0/docs/troubleshooting.html +116 -0
  16. word2html-0.5.0/docs/upgrading.html +96 -0
  17. word2html-0.5.0/docs/usage.html +127 -0
  18. word2html-0.5.0/pyproject.toml +73 -0
  19. word2html-0.5.0/tests/conftest.py +199 -0
  20. word2html-0.5.0/tests/test_active_content.py +130 -0
  21. word2html-0.5.0/tests/test_cli.py +256 -0
  22. word2html-0.5.0/tests/test_convert.py +232 -0
  23. word2html-0.5.0/tests/test_docs.py +226 -0
  24. word2html-0.5.0/tests/test_hidden_text.py +162 -0
  25. word2html-0.5.0/tests/test_limits.py +162 -0
  26. word2html-0.5.0/tests/test_links.py +186 -0
  27. word2html-0.5.0/tests/test_malformed.py +259 -0
  28. word2html-0.5.0/tests/test_output.py +125 -0
  29. word2html-0.5.0/tests/test_paths.py +100 -0
  30. word2html-0.5.0/uv.lock +487 -0
  31. word2html-0.5.0/word2html/__init__.py +36 -0
  32. word2html-0.5.0/word2html/common.py +91 -0
  33. word2html-0.5.0/word2html/docx.py +537 -0
  34. word2html-0.5.0/word2html/errors.py +43 -0
  35. word2html-0.5.0/word2html/html_scan.py +114 -0
  36. word2html-0.5.0/word2html/main.py +244 -0
  37. word2html-0.5.0/word2html/pandoc.py +144 -0
  38. word2html-0.3.0/MANIFEST.in +0 -2
  39. word2html-0.3.0/PKG-INFO +0 -46
  40. word2html-0.3.0/README.md +0 -30
  41. word2html-0.3.0/setup.cfg +0 -4
  42. word2html-0.3.0/setup.py +0 -45
  43. word2html-0.3.0/word2html/__init__.py +0 -4
  44. word2html-0.3.0/word2html/main.py +0 -54
  45. word2html-0.3.0/word2html.egg-info/PKG-INFO +0 -46
  46. word2html-0.3.0/word2html.egg-info/SOURCES.txt +0 -13
  47. word2html-0.3.0/word2html.egg-info/dependency_links.txt +0 -1
  48. word2html-0.3.0/word2html.egg-info/entry_points.txt +0 -3
  49. word2html-0.3.0/word2html.egg-info/not-zip-safe +0 -1
  50. word2html-0.3.0/word2html.egg-info/requires.txt +0 -2
  51. word2html-0.3.0/word2html.egg-info/top_level.txt +0 -1
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ - package-ecosystem: uv
8
+ directory: /
9
+ schedule:
10
+ interval: weekly
@@ -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) 2017, Brad Montgomery <brad@bradmontgomery.net>
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
@@ -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
+ [![CI](https://github.com/bradmontgomery/word2html/actions/workflows/ci.yml/badge.svg)](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
+ [![CI](https://github.com/bradmontgomery/word2html/actions/workflows/ci.yml/badge.svg)](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