HTML export diverges from upstream vimwiki :VimwikiAll2HTML output (headings, code fences, tag ids) #6

Closed
opened 2026-06-23 13:30:35 +00:00 by gffranco · 3 comments
Owner

Summary

When rendering the same .wiki sources with the same templates, the HTML produced by nuwiki's export (nuwiki.export.allToHtmlForce / :VimwikiAll2HTML) is not identical to the HTML produced by upstream vimwiki's :VimwikiAll2HTML. The same set of files is generated (no page missing or extra), but every file differs, in several systematic ways.

Raising this to check whether byte-for-byte parity with vimwiki is a goal, or whether these are intentional (and generally cleaner) reimplementation choices. A couple of the differences are user-visible with a custom template.

Environment

  • nuwiki v0.3.0 + nuwiki-ls (x86_64-unknown-linux-gnu release binary), Neovim 0.12
  • Compared against upstream vimwiki (latest master) under Vim 9.2
  • Identical inputs: same *.wiki files, same template_path/template_default/template_ext, same g:vimwiki_list and g:vimwiki_* globals (global_ext=0, toc_header_level=2, html_header_numbering=0)

What matches

Inter-page links (href="foo.html"), tables (<table>/<tr>/<th>/<td>), blockquotes, todo-list classes (done0..4, rejected), class="tag", and the flat heading ids used by intra-page TOC links.

Differences

1. Heading markup (structural)

<!-- vimwiki -->
<div id="Title-Section"><h2 id="Section" class="header"><a href="#Title-Section">Section</a></h2></div>
<!-- nuwiki -->
<h2 id="Section">Section</h2>

nuwiki drops the wrapping <div> with its hierarchical (parent-scoped) id, the class="header", and the in-heading self-link <a href="#...">. Result: no clickable header anchor links, nothing for CSS to hook via .header, and deep-links to the hierarchical ids no longer resolve. (Flat ids are preserved, so normal TOC links still work.)

2. Code fences (most visible difference)

<!-- vimwiki: {{{python -->
<pre python>def fizzbuzz(...): ...

<!-- nuwiki: {{{python -->
<pre><code class="language-python">def fizzbuzz(...): ...</code></pre>

nuwiki's form is arguably more correct (a real language-* class that highlight.js consumes directly), but it is a different DOM shape, so syntax highlighting renders differently between the two.

3. Tag anchor ids

:tag: inline tags:

<!-- vimwiki -->  <span class="tag" id="wiki">wiki</span>
<!-- nuwiki  -->  <span class="tag" id="tag-wiki">wiki</span>

nuwiki prefixes the anchor id with tag-, so deep-links to a tag anchor (#wiki) break.

4. Same-page anchor hrefs

href="index.html#Contents" (vimwiki) vs href="#Contents" (nuwiki). Both resolve; the format differs.

5. Entity encoding (stricter in nuwiki)

' -> &#39; in attribute values; " -> &quot; inside <code>. Same rendering, different bytes.

6. List markup

nuwiki closes <li>/</ul> and is compact (one item per line); vimwiki leaves <li> unclosed with blank lines. Both are valid HTML5.

7. Auto-generated style.css

vimwiki emits its full ~187-line default stylesheet; nuwiki emits a ~1-line minimal one. Different content (no impact when a template supplies its own CSS).

8. Whitespace / size

nuwiki output is roughly half the line count for the same page (e.g. 401 -> 216 lines), with much less inter-element whitespace.

Question

Are 1–3 (headings without class="header"/self-anchor/hierarchical id, the <pre><code class="language-*"> code-fence shape, and the tag- id prefix) intended, or should nuwiki match vimwiki's output? Happy to provide a full per-file unified diff if useful.

## Summary When rendering the **same** `.wiki` sources with the **same** templates, the HTML produced by nuwiki's export (`nuwiki.export.allToHtmlForce` / `:VimwikiAll2HTML`) is not identical to the HTML produced by upstream vimwiki's `:VimwikiAll2HTML`. The same set of files is generated (no page missing or extra), but **every file differs**, in several systematic ways. Raising this to check whether byte-for-byte parity with vimwiki is a goal, or whether these are intentional (and generally cleaner) reimplementation choices. A couple of the differences are user-visible with a custom template. ## Environment - nuwiki **v0.3.0** + `nuwiki-ls` (x86_64-unknown-linux-gnu release binary), Neovim 0.12 - Compared against upstream **vimwiki** (latest `master`) under Vim 9.2 - Identical inputs: same `*.wiki` files, same `template_path`/`template_default`/`template_ext`, same `g:vimwiki_list` and `g:vimwiki_*` globals (`global_ext=0`, `toc_header_level=2`, `html_header_numbering=0`) ## What matches Inter-page links (`href="foo.html"`), tables (`<table>/<tr>/<th>/<td>`), blockquotes, todo-list classes (`done0..4`, `rejected`), `class="tag"`, and the flat heading `id`s used by intra-page TOC links. ## Differences ### 1. Heading markup (structural) ```html <!-- vimwiki --> <div id="Title-Section"><h2 id="Section" class="header"><a href="#Title-Section">Section</a></h2></div> <!-- nuwiki --> <h2 id="Section">Section</h2> ``` nuwiki drops the wrapping `<div>` with its hierarchical (parent-scoped) `id`, the `class="header"`, and the in-heading self-link `<a href="#...">`. Result: no clickable header anchor links, nothing for CSS to hook via `.header`, and deep-links to the hierarchical ids no longer resolve. (Flat `id`s are preserved, so normal TOC links still work.) ### 2. Code fences (most visible difference) ```html <!-- vimwiki: {{{python --> <pre python>def fizzbuzz(...): ... <!-- nuwiki: {{{python --> <pre><code class="language-python">def fizzbuzz(...): ...</code></pre> ``` nuwiki's form is arguably *more* correct (a real `language-*` class that highlight.js consumes directly), but it is a different DOM shape, so syntax highlighting renders differently between the two. ### 3. Tag anchor ids `:tag:` inline tags: ```html <!-- vimwiki --> <span class="tag" id="wiki">wiki</span> <!-- nuwiki --> <span class="tag" id="tag-wiki">wiki</span> ``` nuwiki prefixes the anchor id with `tag-`, so deep-links to a tag anchor (`#wiki`) break. ### 4. Same-page anchor hrefs `href="index.html#Contents"` (vimwiki) vs `href="#Contents"` (nuwiki). Both resolve; the format differs. ### 5. Entity encoding (stricter in nuwiki) `'` -> `&#39;` in attribute values; `"` -> `&quot;` inside `<code>`. Same rendering, different bytes. ### 6. List markup nuwiki closes `<li>`/`</ul>` and is compact (one item per line); vimwiki leaves `<li>` unclosed with blank lines. Both are valid HTML5. ### 7. Auto-generated `style.css` vimwiki emits its full ~187-line default stylesheet; nuwiki emits a ~1-line minimal one. Different content (no impact when a template supplies its own CSS). ### 8. Whitespace / size nuwiki output is roughly half the line count for the same page (e.g. 401 -> 216 lines), with much less inter-element whitespace. ## Question Are 1–3 (headings without `class="header"`/self-anchor/hierarchical id, the `<pre><code class="language-*">` code-fence shape, and the `tag-` id prefix) intended, or should nuwiki match vimwiki's output? Happy to provide a full per-file unified diff if useful.
Author
Owner

Fixed in v0.4.0 (commit 8fdfa9e). The exported HTML now matches upstream vimwiki's :VimwikiAll2HTML on the structural differences you flagged:

  1. Headings — restored the full structure: <div id="{hierarchical-id}"><h{n} id="{flat-id}" class="header"><a href="#{hierarchical-id}">…</a></h{n}></div>. The flat id stays on the heading so intra-page TOC / #anchor links keep resolving, and the hierarchical id + .header + self-anchor are back for CSS and deep-links.
  2. Code fences — now <pre python> (the verbatim text after {{{ dropped in as raw attributes), instead of <pre><code class="language-python">.
  3. Tag ids — bare id="wiki", no more tag- prefix. (This also fixed a real inconsistency: the LSP reported [[Page#wiki]] as a valid link while the exported HTML emitted id="tag-wiki", so that link was actually broken.)
  4. Same-page anchors[[#Contents]] now resolves to index.html#Contents rather than a bare #Contents.
  5. style.css — the auto-generated default is now vimwiki's stock stylesheet verbatim, so a fresh export looks identical out of the box and the .header / .tag / .toc / done* classes are styled.

Left intentionally unchanged: #5 (entity encoding — stricter is safer), #6 (list whitespace) and #8 (overall whitespace) are valid HTML5 with no template impact, so I didn't chase byte-for-byte parity there. Centered headings and the <div class="toc"> Contents wrapper also keep their current form.

To pick it up, update the plugin and reinstall the server binary:

  • Neovim/lazy: :Lazy update nuwiki, then :NuwikiInstall (or :lua require('nuwiki').install()), then restart.
  • The new nuwiki-ls binaries are attached to the v0.4.0 release.

If you still have your comparison setup handy, a fresh :VimwikiAll2HTML diff against v0.4.0 would be very welcome — especially to confirm the exact markup for the Contents heading and any multi-word/centered headings, since my test cases used single-word headings. Thanks for the thorough report!

Fixed in **v0.4.0** (commit 8fdfa9e). The exported HTML now matches upstream vimwiki's `:VimwikiAll2HTML` on the structural differences you flagged: 1. **Headings** — restored the full structure: `<div id="{hierarchical-id}"><h{n} id="{flat-id}" class="header"><a href="#{hierarchical-id}">…</a></h{n}></div>`. The flat `id` stays on the heading so intra-page TOC / `#anchor` links keep resolving, and the hierarchical id + `.header` + self-anchor are back for CSS and deep-links. 2. **Code fences** — now `<pre python>` (the verbatim text after `{{{` dropped in as raw attributes), instead of `<pre><code class="language-python">`. 3. **Tag ids** — bare `id="wiki"`, no more `tag-` prefix. (This also fixed a real inconsistency: the LSP reported `[[Page#wiki]]` as a valid link while the exported HTML emitted `id="tag-wiki"`, so that link was actually broken.) 4. **Same-page anchors** — `[[#Contents]]` now resolves to `index.html#Contents` rather than a bare `#Contents`. 7. **`style.css`** — the auto-generated default is now vimwiki's stock stylesheet verbatim, so a fresh export looks identical out of the box and the `.header` / `.tag` / `.toc` / `done*` classes are styled. Left intentionally unchanged: #5 (entity encoding — stricter is safer), #6 (list whitespace) and #8 (overall whitespace) are valid HTML5 with no template impact, so I didn't chase byte-for-byte parity there. Centered headings and the `<div class="toc">` Contents wrapper also keep their current form. To pick it up, update the plugin and reinstall the server binary: - Neovim/lazy: `:Lazy update nuwiki`, then `:NuwikiInstall` (or `:lua require('nuwiki').install()`), then restart. - The new `nuwiki-ls` binaries are attached to the [v0.4.0 release](https://code.gfran.co/gffranco/nuwiki/releases/tag/v0.4.0). If you still have your comparison setup handy, a fresh `:VimwikiAll2HTML` diff against v0.4.0 would be very welcome — especially to confirm the exact markup for the `Contents` heading and any multi-word/centered headings, since my test cases used single-word headings. Thanks for the thorough report!
Author
Owner

Re-ran the same comparison against v0.4.0 (cloned v0.4.0, the v0.4.0 nuwiki-ls binary, identical .wiki sources/templates, diffed against upstream vimwiki under Vim 9.2). Thanks — the structural fixes are confirmed.

Confirmed fixed (now identical to vimwiki, ignoring the intentional encoding/whitespace below)

  • Headings (incl. multi-word) — e.g. Current Tasks, Tips and Tricks, Element gallery all now emit <div id="{Parent}-{Section}"><h2 id="{Section}" class="header"><a href="#{Parent}-{Section}">…</a></h2></div>. Your worry about single-word-only test cases was unfounded — multi-word headings match.
  • Code fences<pre python>, <pre shell>, <pre java> — exact match.
  • Tag idsid="wiki" / id="meta" / id="work", no tag- prefix.
  • Same-page anchorshref="index.html#Contents" again.

One structural difference remains: the Contents / TOC heading

This is the exact markup you asked about — it still diverges:

<!-- vimwiki -->
<div id="Contents" class="toc"><h2 id="Contents" class="header"><a href="#Contents">Contents</a></h2></div>
<!-- nuwiki v0.4.0 -->
<div class="toc"><h2 id="Contents">Contents</h2></div>

Unlike every other heading (which now gets the full treatment), the TOC header is missing three things vs vimwiki:

  1. id="Contents" on the wrapping <div> (v0.4.0's div has only class="toc"),
  2. class="header" on the <h2>,
  3. the self-anchor <a href="#Contents">…</a> inside it.

The #Contents anchor still resolves (the <h2> keeps id="Contents"), so it isn't broken — just inconsistent with how regular headings now render.

Notes

  • Centered headings: my corpus has none either, so I couldn't verify that case — same gap as your test suite.
  • style.css: now vimwiki's stock 187-line sheet. Not quite byte-verbatim — the embedded base64 for two checkbox icons (.rejected, .done4) differs slightly from current upstream, looks like a vimwiki-version skew in the bundled images. Cosmetic, and moot when a template supplies its own CSS.
  • Intentional/expected remainders: entity encoding ('&#39;, "&quot;) and list/whitespace — consistent within each output, anchors resolve fine.

Happy to share the full per-file diff if helpful. Nice turnaround on this!

Re-ran the same comparison against **v0.4.0** (cloned `v0.4.0`, the v0.4.0 `nuwiki-ls` binary, identical `.wiki` sources/templates, diffed against upstream vimwiki under Vim 9.2). Thanks — the structural fixes are confirmed. ✅ ## Confirmed fixed (now identical to vimwiki, ignoring the intentional encoding/whitespace below) - **Headings (incl. multi-word)** — e.g. `Current Tasks`, `Tips and Tricks`, `Element gallery` all now emit `<div id="{Parent}-{Section}"><h2 id="{Section}" class="header"><a href="#{Parent}-{Section}">…</a></h2></div>`. Your worry about single-word-only test cases was unfounded — multi-word headings match. - **Code fences** — `<pre python>`, `<pre shell>`, `<pre java>` — exact match. - **Tag ids** — `id="wiki"` / `id="meta"` / `id="work"`, no `tag-` prefix. - **Same-page anchors** — `href="index.html#Contents"` again. ## One structural difference remains: the `Contents` / TOC heading This is the exact markup you asked about — it still diverges: ```html <!-- vimwiki --> <div id="Contents" class="toc"><h2 id="Contents" class="header"><a href="#Contents">Contents</a></h2></div> <!-- nuwiki v0.4.0 --> <div class="toc"><h2 id="Contents">Contents</h2></div> ``` Unlike every *other* heading (which now gets the full treatment), the TOC header is missing three things vs vimwiki: 1. `id="Contents"` on the wrapping `<div>` (v0.4.0's div has only `class="toc"`), 2. `class="header"` on the `<h2>`, 3. the self-anchor `<a href="#Contents">…</a>` inside it. The `#Contents` anchor still resolves (the `<h2>` keeps `id="Contents"`), so it isn't broken — just inconsistent with how regular headings now render. ## Notes - **Centered headings:** my corpus has none either, so I couldn't verify that case — same gap as your test suite. - **`style.css`:** now vimwiki's stock 187-line sheet. Not quite byte-verbatim — the embedded base64 for two checkbox icons (`.rejected`, `.done4`) differs slightly from current upstream, looks like a vimwiki-version skew in the bundled images. Cosmetic, and moot when a template supplies its own CSS. - **Intentional/expected remainders:** entity encoding (`'` → `&#39;`, `"` → `&quot;`) and list/whitespace — consistent within each output, anchors resolve fine. Happy to share the full per-file diff if helpful. Nice turnaround on this!
Author
Owner

Two follow-up rendering bugs found while reviewing the exported pages are now fixed in v0.4.1 (commit a653903):

  • Keyword headings — a header whose text is a keyword (== TODO ==) was rendering as an inline <span class="todo"> badge with an empty id, instead of a styled header. Now renders as a normal header (<div id="TODO"><h2 id="TODO" class="header"><a href="#TODO">TODO</a></h2></div>); keyword text is kept in the id (= My TODO list =id="My TODO list"). Keyword badges still render in body text.
  • Brackets in wikilink descriptions[[page|Task [B-1]]] closed at the first ]], truncating the description to Task [B-1 and leaking a stray ]. The close-scan is now bracket-aware, so the [B-1] is preserved and the link closes correctly.

Combined with the earlier parity work (#1–4, #7), this closes out the issue. Grab the new nuwiki-ls from the v0.4.1 release (:NuwikiInstall) and re-run the export. Thanks again for the detailed reports — please reopen if anything still looks off.

Two follow-up rendering bugs found while reviewing the exported pages are now fixed in **v0.4.1** (commit a653903): - **Keyword headings** — a header whose text is a keyword (`== TODO ==`) was rendering as an inline `<span class="todo">` badge with an empty `id`, instead of a styled header. Now renders as a normal header (`<div id="TODO"><h2 id="TODO" class="header"><a href="#TODO">TODO</a></h2></div>`); keyword text is kept in the id (`= My TODO list =` → `id="My TODO list"`). Keyword badges still render in body text. - **Brackets in wikilink descriptions** — `[[page|Task [B-1]]]` closed at the first `]]`, truncating the description to `Task [B-1` and leaking a stray `]`. The close-scan is now bracket-aware, so the `[B-1]` is preserved and the link closes correctly. Combined with the earlier parity work (#1–4, #7), this closes out the issue. Grab the new `nuwiki-ls` from the [v0.4.1 release](https://code.gfran.co/gffranco/nuwiki/releases/tag/v0.4.1) (`:NuwikiInstall`) and re-run the export. Thanks again for the detailed reports — please reopen if anything still looks off.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gffranco/nuwiki#6