godocx

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 3, 2025 License: MIT Imports: 5 Imported by: 0

README

Godocx-templates

Template-based docx report creation. (See the blog post).

HEAVILY inspired (aka copy/pasted) from docx-templates 🙏

go get github.com/ArFnds/godocx-template

Why?

  • Write documents naturally using Word, just adding some commands where needed for dynamic contents

Features

  • Insert the data in your document (INS, = or just nothing)
  • Embed images and HTML (IMAGE, HTML). Dynamic images can be great for on-the-fly QR codes, downloading photos straight to your reports, charts… even maps!
  • Add loops with FOR/END-FOR commands, with support for table rows, nested loops, and JavaScript processing of elements (filter, sort, etc)
  • Define custom aliases for some commands (ALIAS) — useful for writing table templates!
  • Plenty of examples in this repo
  • Embed hyperlinks (LINK).
Not yet supported

Contributions are welcome!

Table of contents

Installation

$ go get github.com/ArFnds/godocx-template

Usage

Here is a simple example, with report data injected directly as an object:

import (
	"fmt"
	"log/slog"
	"os"
	"reflect"
	"time"

	. "github.com/ArFnds/godocx-template"
)

func main() {
   var data = ReportData{
		"dateOfDay":         time.Now().Local().Format("02/01/2006"),
		"acceptDate":        time.Now().Local().Format("02/01/2006"),
		"company":           "The company",
		"people": []any{
			map[string]any{"name": "John", "lastname": "Doe"},
			map[string]any{"name": "Barn", "lastname": "Simson"},
		},
   }

   options := CreateReportOptions{
      // mandatory
		LiteralXmlDelimiter: "||",
		// optionals
		ProcessLineBreaks: true,
   }

   outBuf, err := CreateReport("mytemplate.docx", &data, options)
	if err != nil {
		panic(err)
	}
	err = os.WriteFile("outdoc.docx", outBuf, 0644)
	if err != nil {
		panic(err)
	}
}

Writing templates

Create a word file, and write your template inside it.

dateOfDay: +++dateOfDay+++  acceptDate: +++acceptDate+++
company: +++company+++

+++FOR person IN people+++
  person:  +++INS $person.firstname+++  +++INS $person.lastname+++
+++END-FOR person+++

Custom command delimiters

You can use different left/right command delimiters by passing an object to CmdDelimiter:

options := CreateReportOptions{
	LiteralXmlDelimiter: "||",
	CmdDelimiter: &Delimiters{
		Open:  "{",
		Close: "}",
	},
}

This allows much cleaner-looking templates!

Then you can add commands in your template like this: {foo}, {project.name}, {FOR ...}.

Supported commands

Currently supported commands are defined below.

Insert data with the INS command ( or using =, or nothing at all)

Inserts the result of a given JavaScript snippet as follows.

Using code like this:

import (
	"fmt"
	"log/slog"
	"os"
	"time"

	. "github.com/ArFnds/godocx-template"
)

func main() {
   var data = ReportData{
		"name":    "John",
		"surname": "Appleseed",
   }

   options := CreateReportOptions{
      LiteralXmlDelimiter: "||",
   }

   outBuf, err := CreateReport("mytemplate.docx", &data, options)
	if err != nil {
		panic(err)
	}
	err = os.WriteFile("outdoc.docx", outBuf, 0644)
	if err != nil {
		panic(err)
	}
}

And a template like this:

+++name+++ +++surname+++

Will produce a result docx file that looks like this:

John Appleseed

Alternatively, you can use the more explicit INS (insert) command syntax.

+++INS name+++ +++INS surname+++

You can also use = as shorthand notation instead of INS:

+++= name+++ +++= surname+++

Even shorter (and with custom CmdDelimiter: &Delimiters{Open: "{", Close: "}"}):

{name} {surname}

Includes a hyperlink from a map[string]any with a url and label key, or *LinkPars:

data := ReportData {
	"projectLink": &LinkPars {
		Url: "https://theproject.url",
		Label: "The label"
	}
}
+++LINK projectLink+++

If the label is not specified, the URL is used as a label.

HTML

Takes the HTML resulting from evaluating a JavaScript snippet and converts it to Word contents.

Important: This uses altchunk, which is only supported in Microsoft Word, and not in e.g. LibreOffice or Google Docs.

+++HTML `
<meta charset="UTF-8">
<body>
  <h1>${$film.title}</h1>
  <h3>${$film.releaseDate.slice(0, 4)}</h3>
  <p>
    <strong style="color: red;">This paragraph should be red and strong</strong>
  </p>
</body>
`+++
IMAGE

The value should be an ImagePars, containing:

  • width: desired width of the image on the page in cm. Note that the aspect ratio should match that of the input image to avoid stretching.
  • height desired height of the image on the page in cm.
  • data: an ByteArray with the image data
  • extension: one of '.png', '.gif', '.jpg', '.jpeg', '.svg'.
  • thumbnail [optional]: when injecting an SVG image, a fallback non-SVG (png/jpg/gif, etc.) image can be provided. This thumbnail is used when SVG images are not supported (e.g. older versions of Word) or when the document is previewed by e.g. Windows Explorer. See usage example below.
  • alt [optional]: optional alt text.
  • rotation [optional]: optional rotation in degrees, with positive angles moving clockwise.
  • caption [optional]: optional caption displayed below the image

In the .docx template:

+++IMAGE imageKey+++

Note that you can center the image by centering the IMAGE command in the template.

In the ReportData:

data := ReportData {
  "imageKey": &ImagePars{
			Width:     16.88,
			Height:    23.74,
			Data:      imageByteArray,
			Extension: ".png",
		},
}
FOR and END-FOR

Loop over a group of elements (can only iterate over Array).

+++FOR person IN peopleArray+++
+++INS $person.name+++ (since +++INS $person.since+++)
+++END-FOR person+++

Note that inside the loop, the variable relative to the current element being processed must be prefixed with $.

It is possible to get the current element index of the inner-most loop with the variable $idx, starting from 0. For example:

+++FOR company IN companies+++
Company (+++$idx+++): +++INS $company.name+++
Executives:
+++FOR executive IN $company.executives+++
-	+++$idx+++ +++$executive+++
+++END-FOR executive+++
+++END-FOR company+++

FOR loops also work over table rows:

----------------------------------------------------------
| Name                         | Since                   |
----------------------------------------------------------
| +++FOR person IN             |                         |
| project.people+++            |                         |
----------------------------------------------------------
| +++INS $person.name+++       | +++INS $person.since+++ |
----------------------------------------------------------
| +++END-FOR person+++         |                         |
----------------------------------------------------------

And let you dynamically generate columns:

+-------------------------------+--------------------+------------------------+
| +++ FOR row IN rows+++        |                    |                        |
+===============================+====================+========================+
| +++ FOR column IN columns +++ | +++INS $row+++     | +++ END-FOR column +++ |
|                               |                    |                        |
|                               | Some cell content  |                        |
|                               |                    |                        |
|                               | +++INS $column+++  |                        |
+-------------------------------+--------------------+------------------------+
| +++ END-FOR row+++            |                    |                        |
+-------------------------------+--------------------+------------------------+

Finally, you can nest loops (this example assumes a different data set):

+++FOR company IN companies+++
+++INS $company.name+++
+++FOR person IN $company.people+++
* +++INS $person.firstName+++
+++FOR project IN $person.projects+++
    - +++INS $project.name+++
+++END-FOR project+++
+++END-FOR person+++

+++END-FOR company+++
IF and END-IF

Include contents conditionally (support: ==, !=, >=, <=, >, <):

+++IF name == 'John'+++
 Name is John
+++END-IF+++

The IF command is implemented as a FOR command with 1 or 0 iterations, depending on the expression value.

ALIAS (and alias resolution with *)

Define a name for a complete command (especially useful for formatting tables):

+++ALIAS name INS $person.name+++
+++ALIAS since INS $person.since+++

----------------------------------------------------------
| Name                         | Since                   |
----------------------------------------------------------
| +++FOR person IN             |                         |
| project.people+++            |                         |
----------------------------------------------------------
| +++*name+++                  | +++*since+++            |
----------------------------------------------------------
| +++END-FOR person+++         |                         |
----------------------------------------------------------

Inserting literal XML

You can also directly insert Office Open XML markup into the document using the literalXmlDelimiter, which is by default set to ||.

E.g. if you have a template like this:

+++INS text+++
data := ReportData{ "text": "foo||<w:br/>||bar" }

See http://officeopenxml.com/anatomyofOOXML.php for a good reference of the internal XML structure of a docx file.

License (MIT)

This Project is licensed under the MIT License. See LICENSE for more information.

Documentation

Index

Constants

View Source
const (
	DEFAULT_CMD_DELIMITER = "+++"
	CONTENT_TYPES_PATH    = "[Content_Types].xml"
)

Variables

This section is empty.

Functions

func CreateReport

func CreateReport(templatePath string, data *ReportData, options CreateReportOptions) ([]byte, error)

CreateReport generates a report document based on a given template and data. It parses the template file, processes any commands within the template using provided data, and outputs the final document as a byte slice.

Parameters:

  • templatePath: The file path to the template document.
  • data: A pointer to ReportData containing data to be inserted into the template.

Returns:

  • A byte slice representing the generated document.
  • An error if any occurs during template parsing, processing, or document generation.

Types

type CreateReportOptions

type CreateReportOptions = internal.CreateReportOptions

type Delimiters added in v1.0.1

type Delimiters = internal.Delimiters

type Functions

type Functions = internal.Functions

map[string]func(args ...any) string

type ImagePars

type ImagePars = internal.ImagePars

type LinkPars added in v1.1.0

type LinkPars = internal.LinkPars

type ReportData

type ReportData = internal.ReportData

type VarValue

type VarValue = internal.VarValue

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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