Publishing DITA with MkDocs Material
Back in December of 2021, Radu Coravu posted an article on the <oXygen/>
blog about Publishing DITA Content Using the MKDocs Web Site Generator.
As Radu explains, MkDocs is a static site generator that makes it easy to publish a documentation website from a folder of Markdown files. His post shows how to install MkDocs, create a new project, and publish a simple DITA map via an oXygen transformation scenario.
With Radu’s approach, the DITA map file is first converted to a Markdown file, which is exported to HTML and then transformed via an XSLT stylesheet to the YAML format that defines the navigation structure of the MkDocs site.
This works well enough for simple maps like the oXygen flowers
sample (and in cases where manual steps are acceptable), but there are a few constraints that limit the utility of this process in larger projects and automated publishing scenarios.
This post explores how to work around those contraints and add local search to the site using additional resources from the MkDocs ecosystem, which have improved significantly since Radu first proposed this workflow.
MkDocs caveats
MkDocs is a great way to spin up a simple site from a handful of Markdown files, but there’s one limitation that can be a show-stopper in larger projects — you can’t nest topics in the navigation.
Out of the box, the MkDocs navigation only supports unlinked section headings (groups) and pages. There’s no way to nest pages that include editorial content in a hierarchy of parent/child link relationships.
Note that a section cannot have a page assigned to it.
Sections are only containers for child pages and sub-sections.
mkdocs.org/user-guide/writing-your-docs
This is fine for small doc sets with flat structures, but less suitable for deeper information hierarchies commonly modeled in DITA publications.
Navigation limitations
MkDocs can be configured to create navigation sections automatically based on the subfolder names in the docs
directory, but in typical DITA projects with folders like concept
, task
, and reference
, that might not be welcome or useful. Unless the source file structure was explicitly designed for the desired navigation structure, there’s little value in showing folders in the table of contents (ToC) in the navigation sidebar.
In DITA projects, maps facilitate abstraction and re-use and allow us to create multiple navigation structures from a single set of files, so binding the file structure to the ToC is often counterproductive.
Avoiding dead ends
Although the DITA specification includes the <topichead>
element to create a parent node in the hierarchy without a corresponding topic, it can be confusing if some links open topics, and others don’t.
Many teams and style guides discourage the use of topic heads to avoid unlinked navigation nodes. For a consistent experience, every navigation item should open a new page.
— So how can we implement this with MkDocs?
Using the Material theme
The Material theme for MkDocs is one of the most popular open-source themes for documentation sites, and the best-known MkDocs theme by far.
With over 200,000 downloads per month, the theme is widely used and well-maintained by Martin Donath (@squidfunk), who put thousands of hours into the project. The mkdocs-material repository has a long history of over 300 releases, and more than 5000 commits by nearly 250 contributors.
With support for 40-odd languages, the theme includes a broad range of documentation-specific features such as page ToCs, versioning, anchor links, and robust mobile support.
Many developers already use Material for MkDocs to build docs sites for their projects. Thanks to the Markdown export features in Jarno Elovirta’s Lightweight DITA plugin for DITA Open Toolkit, we can easily integrate content authored in DITA to sites like this without spending any money on proprietary portal software or services.
But how does this solve our ToC problem?
Section index pages
Material for MkDocs includes an option to enable section index pages, so if you have a topic that serves as a landing page for a group of related topics, you can link to the content from the navigation section.
Here’s an example based on the structure of the DITA Open Toolkit documentation:
theme:
name: material
features:
- navigation.indexes
nav:
- 'DITA Open Toolkit 4.0': 'index.md'
- 'Release Notes':
- 'release-notes/index.md'
- 'Release history': 'topics/release-history.md'
The navigation.indexes
option in the theme features
key enables the section index pages, so links to index
files in subfolders open the corresponding topics. In this example, the Release Notes entry in the ToC is linked to the contents of the release-notes/index.md
file, and the Release history topic appears as a child topic inside the Release Notes node.
— But as the name foretells, it only works for topics whose filenames are literally index
. You can’t use arbitrary page names as section landing pages. Links to folder/index.md
will work, but folder/other-filename.md
won’t.
A proposal was submitted to the MkDocs project to relax this constraint and allow any page name to be defined as a section index, but the maintainers were reluctant to add this implementation to the Material theme, or to MkDocs itself.
So what can we do?
Other pages as section indexes
Fortunately, in plugin-based ecosystems like DITA-OT or MkDocs, maintainers don’t need to support every edge case in the core project.
Those who need additional features can create custom plugins to extend processing without asking permission or increasing complexity for others who don’t need the extra code.
The mkdocs-section-index plugin was created based on the earlier proposal, and extends the Material theme implementation to enable section landing pages with arbitrary names.
To use the plugin, first install it via pip
:
pip install mkdocs-section-index
and then add it to the plugins
key in the MkDocs configuration file:
plugins:
- search
- section-index
With this setup, we can extend the previous example by linking to additional landing pages with other names:
nav:
- 'DITA Open Toolkit 4.0': 'index.md'
- 'Release Notes':
- 'release-notes/index.md'
- 'Release history': 'topics/release-history.md'
- 'Installing DITA-OT':
- 'topics/installing-client.md'
The Installing DITA-OT ToC entry now links as intended to /topics/installing-client/
.
This plugin replaces the section index pages feature from the Material theme, so we can remove that entry in the configuration:
theme:
name: material
- features:
- - navigation.indexes
Generating navigation from maps
We don’t want to maintain the navigation structure in the MkDocs configuration file by hand when it’s already in the DITA map. We can read it directly from the output generated by DITA Open Toolkit.
The Lightweight DITA plugin is already capable of converting the map structure to a format that can be used with Markdown-based publishing environments like GitBook or mdBook, which define the ToC structure in a SUMMARY.md
file.
To publish GitHub-Flavored Markdown and generate this file, use the markdown_gitbook
transtype:
dita --input=input-file --format=markdown_gitbook
Using the GitBook output
With the SUMMARY.md
file from the toolkit’s GitBook output, there’s no need to define the navigation hierarchy in the MkDocs configuration file.
The mkdocs-literate-nav
plugin can read the GitBook summary file for the navigation. As before, we first install the plugin via pip
:
pip install mkdocs-literate-nav
then add it to the MkDocs configuration file, and tell it which file contains the ToC hierarchy:
plugins:
- search
- section-index
+ - literate-nav:
+ nav_file: SUMMARY.md
Local search out of the box
One of the main reasons so many teams publish their documentation with Material for MkDocs is that it includes client-side search.
Whenever the MkDocs build
or serve
processes run, the local search index is automatically updated to reflect any changes in the Markdown source files.
This setup provides a viable open-source alternative to commercial webhelp solutions, and may be all you need for your own docs server.
In contrast to online search solutions that require a network connection to query the index, the MkDocs search works completely offline, so it can be used to build sites for secure environments and other scenarios where network access is not available.
Note: The search feature is currently based on Lunr.js, but Martin Donath is revisiting this approach, and plans to improve the search features in an upcoming version of Material for MkDocs.
If you’d like to support Martin’s work on the project, consider sponsoring him on GitHub, or subscribing to the insiders version, which provides early access to new features, and helps secure the future of the project with a sponsorware release strategy.
Demo sites
If you’d like to see the results of the approach outlined in this article, I built a demo site using the contents of the DITA Open Toolkit docs.
The demonstration output is available on the GitHub project site at infotexture.github.io/ot-mkdocs-demo, and hosted in the GitHub repository at infotexture/ot-mkdocs-demo.
The content was generated from the DITA-OT 4.0.2 docs as hosted on the DITA Open Toolkit project website at www.dita-ot.org/4.0/.
The repository includes a basic continuous integration setup that automatically publishes any content changes via GitHub Actions. You can borrow this setup to automate your own MkDocs publishing processes.
Note: I published a similar demo with GitBook output a few years ago, but never got around to writing about it. If you’re curious, you’ll find the repository at infotexture/ot-docs-mdbook.