Markdown Word draws two directives. Both are real Word features rather than Markdown dressed up, which is the reason they exist at all.
::toc — a table of contents Word can build
A block directive. Word's TOC element cannot draw one of these: it writes a PAGEREF for every
title registered, and this library never registers any — it draws a heading as a paragraph, not
through addTitle(). With none registered it writes a closing field character and no opening one,
so the document carries a field that starts nowhere. The field is therefore built directly.
Attributes
| Attribute | Type | Default | Notes |
|---|---|---|---|
depth |
integer | maxHeadingLevel, which is 6 |
clamped to 1–6 |
numbers |
boolean | true |
false is Word's \n |
title |
string | none | a heading drawn above the field |
::toc{depth=2, title="Contents"}
Names are matched case-insensitively and trimmed, so ::TOC and :: toc both work.
What comes out
An empty field, marked dirty, with w:updateFields set so Word builds it on open. Between
separate and end it says:
Press F9 to build the table of contents.
That is deliberate. PHPWord's cached page numbers are all 1, and a reader who never updates a
field shows them as they are — a table of contents in which every entry claims to be on page 1 is
worse than one that says plainly what to do. F9 is what both Word and LibreOffice Writer update a
field with, so it names the way rather than the menu, which is not the same in either.
The field instruction it writes is exactly:
TOC \o "1-<depth>" \h \z \u
and with numbers=false, a \n before the \h.
The Markdown
# Quarterly report
Contents below are built by Word on open.
::toc{depth=2, title="Contents"}
## Highlights
The figure was provisional and has since been confirmed.
## Notes
Word will build the table on open. If it does not, select it and press F9.Limitations
- Only
.docxcarries it..odtand.rtfget the text::tocinstead — and the library says so inpendingLosses()rather than letting the document quietly lose a feature. - Heading styles must be the built-in names.
LookAndFeelsetsHeading1…Heading6for exactly this reason; Word collects a table of contents by style name. - A template gets the source form. Through a
.dotxthe handler is constructed detached, so the fallback applies and::tocis written as a paragraph.
:footnote[…] — a real Word footnote
An inline directive. The text in the brackets is the note; there are no attributes, and the body is inserted as text rather than parsed as Markdown.
The figure was provisional[^1] and has since been confirmed.
[^1]: Source: the regional ledger, March.
Word's own footnote syntax is not what is parsed here. :footnote[…] is this suite's spelling.
Measured, and it does not currently reach a footnote. Building
Body with a note:footnote[Source: the ledger.]with the library's own default parser produced a.docxwith noword/footnotes.xmlin it at all, and the literal text:footnote[Source: the ledger.]in the body. The same happened with an explicit parser carryingGenericDirectiveExtension. This page says so rather than describing the intended behaviour, because the build on this site checks the difference and would fail the example otherwise.
- The mark sits in the run the directive stood in, so no post-pass is needed.
- The note is set in the
paragraphslot's character half, not in whatever heading the mark sits in. - An empty body —
:footnoteor:footnote[]— draws the source characters instead. - Only
.docxcarries it;.odtand.rtfget the text back.
Everything else
Any other name parses and is written out as its own source text. That is the policy, and it is the opposite of the presentation package's:
Status :green[go] before lunch.
parses, and Status :green[go] before lunch. is what reaches the document.
The reasoning is that Status :green[go] is a sentence, and a parser nobody asked to change
should not change the syntax of every document converted after it. A directive that used to be
opt-in became on by default, which was a behaviour change and is written down as one.
Two consequences worth knowing:
- The container form of a drawn name is also claimed.
:::toc…:::matches the block map, the handler returnstrue, and the body is silently dropped. There is no test pinning this either way, so treat::tocas the only supported spelling. - Block and inline are separate maps.
::footnoteis not drawn — footnotes are inline — and comes out as the text the author wrote.
Strict mode is not reachable from here
GenericDirectiveExtension takes a DirectiveRegistry that makes the parser strict. Through
CommonMarkParser that mode cannot be reached: the extension list is a list of class names and
each is built with new, so handing over an instance with a registry on it gets a fresh one
without it. If you want strict directives, build the environment yourself — see
How directives work.
Every example on this page was built by the library it documents while this page was built. This page as Markdown.
