docgen

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: Apache-2.0 Imports: 2 Imported by: 0

README

Overview

Docgen automates generating high-quality CX deliverable documents from our tools.

See the Quick Start to get started immediately.


Writing our deliverable documents is often a time-consuming and error-prone process. Even with good templates, e.g. in SCDP, populating customer-specific information can be a consderable effort. In CX, we've done a great job with automating other aspects of delivery, but deliverable document generation is still often harder than it should be.

This tool consumes the output of our existing automation tools, e.g. analysis tools, build tools, etc., and generates deliverable documentation from this output. It does this accurately, efficiently, and reliably, and generates high-quality documentation that conforms to our CX templating standards.

As a tool owner, you can integrate this tool into your existing tool to bolt on document generation or improve on existing, difficult to maintain solutions. As an end-user doing CX delivery, this tool will transparently generate your deliverable documents providing significant time and cost savings delivery.

See the Comparison section for comparison with other solutions, which will further illustrate the unique value this tool provides.

Quick Start

Configuration Options

Docgen supports two ways to configure its behavior:

  1. Command-line arguments (default): Pass parameters directly via CLI
  2. YAML configuration file: Use a config file for easier management and reusability
Using Command-Line Arguments

The traditional way to use docgen is with command-line arguments:

docgen --input input.html --output out.docx --context context.json --verbose

Available command-line options:

docgen --help
Using a Configuration File

For easier management and reusability, you can use a YAML configuration file:

docgen --config config.yaml

Important: When a config file is provided, all command-line arguments (except --config) are ignored.

Example Configuration File

Create a file named config.yaml:

# Path to the tag context JSON file
context: context.json

# Path to the input file (.html, .md, or .markdown)
input: input.html

# Path to the output Word document file
output: out.docx

# Path to a custom Word document template (optional)
template: ""

# Bookmark name to render content into
bookmark: main

# Path to the log file
logfile: docgen.log

# Enable verbose (debug level) logging
verbose: false

# Working directory for relative paths
workdir: ""

See config-example.yaml for a complete example with documentation.

Benefits of Using Configuration Files
  • Reusability: Save different configurations for different projects
  • Version control: Check configuration files into git alongside your templates
  • Simplicity: Avoid long command lines with many arguments
  • Documentation: YAML comments can document your configuration choices

Simple Example

Download the latest docgen release from GitHub releases.

Clone the example repo:

git clone --depth 1 https://github.com/cisco-open/docgen-example.git

Copy or move the docgen (docgen.exe for Windows) file into your docgen-example folder that you just cloned.

Run the development tool while in that folder: ./docgen --dev. This starts the development environment, listens for changes to the files, validates your templates for correctness, and opens a document preview in your default browser.

Understanding the Simple Example

Two things are provided in this example:

  1. An example input.html file. This is the HTML content that will be inserted into the deliverable document. In a real workflow, you generate this file from your existing automation tool using a templating engine suited to your language — e.g. Jinja2 for Python, Handlebars for JavaScript, or Templ for Go. See the Generating Input Content section for examples.
  2. Optionally, a context.json file for populating {{tag}} placeholders directly in the Word document template (e.g. cover page fields, headers, footers). See Direct Template Tags.

Running the development server does the following:

  1. Reads the input.html (or .md) file as the document body content.
  2. Inserts that content into the main bookmark in the deliverable document. Docgen uses a built-in CX document template by default, but you can supply your own custom template with pre-existing content.
  3. Optionally renders {{tag}} placeholders in the Word template using values from context.json.

See the Comprehensive Guide for detailed usage and examples.

Comprehensive Guide

Generating Input Content

Docgen accepts a pre-rendered HTML or Markdown file as its input. You generate this file using whatever templating solution is idiomatic for your tool's language. This keeps docgen focused on document conversion while letting you use mature, well-documented templating engines you already know.

Python — Jinja2

Jinja2 is the standard templating engine for Python tools.

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("."))
template = env.get_template("report.html.j2")

data = {
    "devices": [
        {"name": "switch01", "ip": "10.0.0.1"},
        {"name": "switch02", "ip": "10.0.0.2"},
    ]
}

with open("input.html", "w") as f:
    f.write(template.render(**data))

report.html.j2

<h1>Device Report</h1>
<table>
  <tr><th>Name</th><th>IP</th></tr>
  {% for device in devices %}
  <tr><td>{{ device.name }}</td><td>{{ device.ip }}</td></tr>
  {% endfor %}
</table>

Alternatively, FastHTML can generate HTML directly from Python objects without a separate template file.

JavaScript / Node.js — Handlebars

Handlebars is a popular choice for JavaScript tools.

const Handlebars = require("handlebars");
const fs = require("fs");

const template = Handlebars.compile(fs.readFileSync("report.html.hbs", "utf8"));

const data = {
  devices: [
    { name: "switch01", ip: "10.0.0.1" },
    { name: "switch02", ip: "10.0.0.2" },
  ],
};

fs.writeFileSync("input.html", template(data));

report.html.hbs

<h1>Device Report</h1>
<table>
  <tr><th>Name</th><th>IP</th></tr>
  {{#each devices}}
  <tr><td>{{name}}</td><td>{{ip}}</td></tr>
  {{/each}}
</table>
Go — Templ

Templ is a type-safe HTML templating library for Go, and is the recommended approach for Go-based tools.

// devices.templ
package report

templ DeviceTable(devices []Device) {
    <h1>Device Report</h1>
    <table>
        <tr><th>Name</th><th>IP</th></tr>
        for _, d := range devices {
            <tr><td>{ d.Name }</td><td>{ d.IP }</td></tr>
        }
    </table>
}
// main.go
f, _ := os.Create("input.html")
defer f.Close()
report.DeviceTable(devices).Render(context.Background(), f)

Go's standard html/template or text/template packages also work if you prefer not to add a dependency.

Markdown

If your content is simple prose with basic tables or lists, generating Markdown directly (e.g. with string formatting or a Markdown builder library) is often the simplest approach. Docgen accepts .md and .markdown files in addition to .html.

Direct Template Tags

In addition to inserting content from an input file, you can embed {{tag}} placeholders directly in your Word document template. These are rendered using a JSON context file and are useful for fields that appear in the document template itself — such as cover page fields, headers, or footers — rather than in the body content.

Pass a JSON context file with --context context.json:

{
  "customer": "Acme Corp",
  "date": "26 Jun 2026",
  "author": "Jane Smith"
}

Any {{customer}}, {{date}}, or {{author}} tags in the Word template are replaced with the corresponding values. These tags use Go's text/template syntax, so simple expressions and conditionals are supported, but complex logic belongs in your input generation step.

Advanced HTML Examples
Column widths

Table columns widths can be specified with the colgroup HTML element:

<table>
  <colgroup>
    <col style="width: 30%" />
    <col style="width: 70%" />
  </colgroup>
  <!--rest of table-->
</table>

All tables will be rendered at full page width. Without colgroup columns will be evenly distributed, e.g. three columns will render at 33% each.

Rowspan and Colspan

rowspan and colspan allow merging table cells horizontally and vertically respectively. The Mozilla development docs explain these fields.

colspan is pretty straightforward. The following example illustrates a table with three columns, where two of the header cells are merged into a single header:

<table>
  <tr>
    <th>Name</th>
    <th colspan="2">Address</th>
  </tr>
  <tr>
    <td>switch01</td>
    <td>10.0.0.1</td>
    <td>/24</td>
  </tr>
</table>

rowspan offers a unique challenge. Namely, the spanned cell is only rendered in the first row (the one with the rowspan attribute). From there on, that cell is excluded in the HTML. This is an example of correctly formatted HTML using rowspan:

<table>
  <tr>
    <th>Model</th>
    <th>Switches</th>
  </tr>
  <tr>
    <th rowspan="2">93180YC-EX</th>
    <th>switch01</th>
  </tr>
  <tr>
    <th>switch02</th>
  </tr>
</table>

Note, specifically, that the spanned row indicating the model is excluded from the second row. That's because this field is spanned across two rows. When generating this HTML from your templating engine, you need to emit the rowspan cell only in the first row of each group and omit it from subsequent rows.

Here's how that looks in Jinja2:

<table>
  <tr><th>Model</th><th>Switch</th></tr>
  {% for model, switches in devices_by_model.items() %}
    {% for switch in switches %}
    <tr>
      {% if loop.first %}
      <td rowspan="{{ switches|length }}">{{ model }}</td>
      {% endif %}
      <td>{{ switch.name }}</td>
    </tr>
    {% endfor %}
  {% endfor %}
</table>
Images

Images are supported with the HTML image tag:

<img src="/router.png" />

Images must be in either png or jpeg format. The preceeding slash will be read by the development server, which will try to read the image in relation to the current working directory. In this example, router.png is expected to exist in the current folder.

Comparison to other solutions

This solution was created because of a distinct need for better document automation for CX delivery. The following solutions were considered, and have all been used in production leading up to this tool:

SCDP is our Atlasian Confluence deployment, and is a collaborative templating, document creation, and document sharing platform. Document templates can be reused and tailored to the needs of each project.

Challenges:

  • SCDP is a document collaboration and templating solution; not an automation solution. There is no clear solution for automating content into SCDP templates.

Programmtic document creation is where you (the tool owner) use a docx library to progressively build the document throughout your code. For example, the python-docx library provides method calls for adding paragraphs, tables, headers, formmating, etc. This is a very common solution and is how most existing automated documentation creation is performed.

Challenges:

  • Mixing code logic with presentation logic makes it very difficult to write and maintain quality documentation. Programming languges such as Python were not designed for writing visual documentation.

Pandoc is a universal document transformation library which will translate from Markdown or HTML to docx. Other solutions can be used to create an HTML or Markdown document (e.g. Jinja2, Handlebars, Templ, etc), and then Pandoc can be used to transform this into a Microsoft Word document. This was the predecesor to docgen and is the closest in architecture.

Challenges:

  • Getting started, developing, and deploying a tool that uses this solution requires a lot more dependencies.
  • Invalid Markdown or HTML can produce a broken/invalid Word document.
  • Pandoc is not as fast or efficient as a purpose-built tool such as docgen.

Feature Comparison

Feature docgen SCDP Library Pandoc
Automated
Separation of business and presentation logic na
Declarative na
Easy to integrate with new tools ¹ ²
Git-friendly na ³
Will not create a broken Word document
Fast / memory efficient na
Developer Friendly

¹ A docx library such as python-docx can be very easy to get started with, but writing quality documentation in Python can be very challenging.

² Several independent components are required for this solution, making it much more difficult for an end-user (conulting engineer) to run.

³ Mixing business and presentation logic makes it very difficult to determine which commits changes are documentation changes vs core code changes. It also makes it more difficult to wind these changes back independently.

Templating + pandoc + a web server + live reload requires installing multiple components or running the development environment in docker, which significantly increases the "getting started" burden for a new developer.

Contributing

This is a community tool. Contributions, bug fixes, and feature requests are welcome.

Testing

Unit Tests

Run the standard unit tests:

go test ./...
Integration Tests

Integration tests validate that generated .docx files conform to the OOXML standard. See INTEGRATION_TESTING.md for detailed instructions on running integration tests locally.

Quick start:

# Install Node.js dependencies (local development)
cd tools/ooxml-validator-node
npm install
cd ../..

# Run integration tests
go test -tags=integration -v ./...

Architecture

Architectural Overview
Deep Dive

Coding Standards

License

SPDX-License-Identifier: Apache-2.0

Copyright 2026 Cisco Systems, Inc. and their affiliates

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Documentation

Overview

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Document

type Document struct {
	// contains filtered or unexported fields
}

Document represents a Microsoft Word document in Office Open XML format. It provides methods for manipulating the document content, including inserting HTML, rendering template tags, and writing the document to disk.

A Document is created using NewDocument with a template document in .docx format. Once created, you can modify the document using its methods and then write it back to disk using WriteFile or Write.

func NewDocument

func NewDocument(template []byte) (Document, error)

NewDocument creates a new Word document from a template.

The template parameter should contain the bytes of a valid .docx file (Microsoft Word document in Office Open XML format). The template file serves as the base document which can then be modified using the returned Document's methods.

The template must contain valid OOXML structure including:

  • word/document.xml - the main document content
  • word/_rels/document.xml.rels - relationship definitions
  • word/numbering.xml - numbering definitions for lists

Optional components that will be loaded if present:

  • word/header1.xml - document header
  • word/footer1.xml - document footer

Returns a pointer to a Document that can be used to manipulate the Word document, or an error if the template is invalid or cannot be parsed.

Example:

template, err := os.ReadFile("template.docx")
if err != nil {
    return err
}

doc, err := docgen.NewDocument(template)
if err != nil {
    return err
}

// Use the document...
err = doc.WriteFile("output.docx")
return err

func (Document) GetBookmark

func (doc Document) GetBookmark(name string) *ooxml.Element

GetBookmark finds and returns the bookmark element with the specified name.

Bookmarks are named locations in a Word document that can be used as insertion points or references. This method searches the document for a bookmark with the given name and returns an Element representing the location.

The name parameter specifies the bookmark name to search for. Bookmark names are case-sensitive and must match exactly.

Returns a pointer to an Element representing the bookmark location, or nil if the bookmark is not found or is not within the document body.

Example:

bookmark := doc.GetBookmark("main")
if bookmark == nil {
    log.Fatal("Bookmark 'main' not found")
}

func (Document) InsertHTML

func (doc Document) InsertHTML(input io.Reader, bookmark string) error

InsertHTML inserts HTML content into the document at the specified bookmark location.

This method parses HTML from the provided reader and converts it to corresponding Word document elements (paragraphs, runs, formatting, etc.), inserting them at the bookmark location.

The input parameter is a reader containing HTML content. The HTML will be parsed and converted to OOXML elements.

The bookmark parameter specifies where in the document to insert the HTML. The bookmark must exist in the template document (created using Insert > Bookmark in Microsoft Word). If the bookmark is not found, an error is returned.

Supported HTML elements include:

  • Text formatting: <b>, <i>, <u>, <s>, <sub>, <sup>
  • Paragraphs: <p>, <div>
  • Lists: <ul>, <ol>, <li>
  • Tables: <table>, <tr>, <td>, <th>
  • Links: <a href="...">
  • Images: <img src="...">
  • Headings: <h1> through <h6>
  • Line breaks: <br>

Returns an error if the bookmark is not found or if there are issues parsing the HTML or modifying the document.

Example:

html := strings.NewReader("<p>This is <b>bold</b> text.</p>")
err := doc.InsertHTML(html, "content")
if err != nil {
    log.Fatal("Failed to insert HTML:", err)
}

func (Document) InsertMarkdown

func (doc Document) InsertMarkdown(input io.Reader, bookmark string) error

InsertMarkdown inserts Markdown content into the document at the specified bookmark location.

This method parses Markdown from the provided reader, converts it to HTML using goldmark, and then converts the HTML to corresponding Word document elements (paragraphs, runs, formatting, etc.), inserting them at the bookmark location.

The input parameter is a reader containing Markdown content. The Markdown will be parsed and converted to OOXML elements.

The bookmark parameter specifies where in the document to insert the Markdown. The bookmark must exist in the template document (created using Insert > Bookmark in Microsoft Word). If the bookmark is not found, an error is returned.

Supported Markdown elements include:

  • Text formatting: **bold**, *italic*, ~~strikethrough~~
  • Paragraphs and line breaks
  • Lists: ordered and unordered
  • Tables
  • Links: [text](url)
  • Images: ![alt](src)
  • Headings: # through ######
  • Code blocks and inline code

Returns an error if the bookmark is not found or if there are issues parsing the Markdown or modifying the document.

Example:

markdown := strings.NewReader("# Title\n\nThis is **bold** text.")
err := doc.InsertMarkdown(markdown, "content")
if err != nil {
    log.Fatal("Failed to insert Markdown:", err)
}

func (Document) RenderTags

func (doc Document) RenderTags(ctx any) error

RenderTags renders all template tags in the document using the provided context.

Template tags are text placeholders in the format {{tagName}} that appear anywhere in the document (body, headers, footers). This method finds all such tags and replaces them with values from the context.

The ctx parameter is the template context, typically a map or struct containing the values to substitute. The context is evaluated using Go template syntax, so you can use any valid Go template expression within the {{ }} delimiters.

Tags can appear in:

  • Document body
  • Headers
  • Footers

Tags are evaluated independently, so each tag is treated as a separate template. Complex expressions like {{.user.name}} or {{add .a .b}} are supported if you register the appropriate template functions.

Returns an error if there are issues parsing or rendering any template tag.

Example:

ctx := map[string]interface{}{
    "title": "My Document",
    "author": "John Doe",
    "date": "2024-01-01",
}
err := doc.RenderTags(ctx)
if err != nil {
    log.Fatal("Failed to render tags:", err)
}

func (*Document) Write

func (doc *Document) Write(w io.Writer) error

Write writes the document to the provided io.Writer.

This method serializes the entire Word document (including all modifications) to the Office Open XML format and writes it to the provided writer. This is useful for streaming the document to HTTP responses, buffers, or other destinations without writing to disk.

The writer will receive a complete .docx file (ZIP archive containing XML files).

Returns an error if there are issues serializing or writing the document.

Example:

var buf bytes.Buffer
err := doc.Write(&buf)
if err != nil {
    log.Fatal("Failed to write document:", err)
}
// buf now contains the complete .docx file

func (Document) WriteFile

func (doc Document) WriteFile(path string) error

WriteFile writes the document to a file at the specified path.

This method serializes the entire Word document (including all modifications) back to the Office Open XML format and writes it to disk. The resulting file will be a valid .docx file that can be opened in Microsoft Word or other compatible applications.

The path parameter specifies where to write the file. If the file already exists, it will be overwritten. Parent directories must exist.

Returns an error if the file cannot be created or if there are issues serializing the document.

Example:

err := doc.WriteFile("output.docx")
if err != nil {
    log.Fatal("Failed to write document:", err)
}

Directories

Path Synopsis
cmd
docgen command
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
internal
html
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
log
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
ooxml
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
templates
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
pkg
config
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL