Creating MS Word docx files in Go - UniOffice Golang

Whether you’re a business owner reaching out to your clients or a marketer reaching out to potential customers, you write letters and articles to grab their attention. One important factor in grabbing the reader’s attention is the personalization of the letter. Using the reader’s name and other personal information can grab their attention and make it clear that you are actually interested in them.

Unfortunately, creating personalized documents manually would take hours upon hours, even for the simple ones. For this reason, you need a smart word processor library that can create personalized documents for you.

UniOffice is one such library based on Go language that can help you streamline your document creation process. Alongside personalized letters, UniOffice can also be used to create letters that might contain dynamic information such as the current price of shares of a company or the weather update.

Flexible and Agile

You can use UniOffice for various applications, it depends on you. You can easily edit the code to meet your requirements. You can add in beautifully designed tables or you can add images, the UniOffice library supports all of these functionalities and many more.

The library is quite well optimized and while testing, it was determined that it can create 10 customized letters following a template in less than ~500ms. This means that a task that might previously take hours to complete can be completed in a matter of seconds using UniOffice.

The results and the code used for testing has been discussed in the proceeding section.

The customized documents created using UniOffice have a .docx extension and can be opened using any of the readily available word processors. These documents can then further be edited if required or used as needed.

UniOffice is unique in the Go marketplace, it is the only word processor based on Go Language. There are no direct competitors that provide the same functionality as UniOffice.

Creating Template Based Letters Using UniOffice

At UniOffice’s GitHub repository, we have code samples discussing various use cases that will help you get started. We are going to work on a particular example, which discusses how we can edit a template document to fill it with personalized document.

If you want to skip the tutorial, you can go ahead and access the unedited example directly at our GitHub repository.

Simply clone the whole UniOffice repository and visit the "/document/edit-document/" directory. You just have to run the main.go file using the following command:

go run main.go

Code Breakdown

Let’s break our code down into chunks so that it is easier to comprehend. The first few lines of the code mention the packages that have to be included to run the code. The key packages for this example are:

import (
    "fmt"
    "log"
    "time"
    "github.com/unidoc/unioffice/document"
)

The fmt package implements the basic I/O operations that help in reading documents and printing out helpful messages on the console. The log package implements a simple log package and the time package can be used for any time measurements.

type person struct{
    Fname string
    Lname string
    Address string
}

The next step is to define the person structure that will store the information of the people whose letters have to be generated. We kept it simple and only stored their names and addresses. The code can be edited to meet your needs.

func main() {
    start := time.Now()

    jack := person{
        Fname: "Jack",
        Lname: "Jones",
        Address: "London",
    }

    jonas := person{
        Fname: "Jonas",
        Lname: "Nick",
        Address: "NewYork",
    }

    adam := person{
        Fname: "Adam",
        Lname: "Black",
        Address: "Paris",
    }

    abraham := person{
        Fname: "Abraham",
        .
        .
        .

    //Declaring array of persons
    people := []person{jack, jonas, adam, abraham, ...}

At the start of the main function, we use the time package to get the time at which the program’s execution has started. Then we create different variables of the structure person. You can create as many people as you want. All the persons are then stored in a slice named people.

    for _, personList := range people {
        doc, err := document.Open("document.docx")
        if err != nil {
            log.Fatalf("error opening document: %s", err)
        }

        paragraphs := []document.Paragraph{}
        for _, p := range doc.Paragraphs() {
            paragraphs = append(paragraphs, p)
        }

        // This sample document uses structured document tags, which are not common
        // except for in document templates.  Normally you can just iterate over the
        // document's paragraphs.
        for _, sdt := range doc.StructuredDocumentTags() {
            for _, p := range sdt.Paragraphs() {
                paragraphs = append(paragraphs, p)
            }
        }

After the people slice has been created, we iterate it using a for loop and store each person in the personList variable one by one. For each person, we read the template document then read its paragraph and get its tags.

    for _, p := range paragraphs {
        for _, r := range p.Runs() {
            switch r.Text() {
                case"FIRST NAME":
                    // ClearContent clears both text and line breaks within a run,
                    // so we need to add the line break back
                    r.ClearContent()
                    r.AddText(personList.Fname)
                    r.AddBreak()

                    para := doc.InsertParagraphBefore(p)
                    para.AddRun().AddText("Mr.")
                    para.SetStyle("Name") // Name is a default style in this template file
                    para = doc.InsertParagraphAfter(p)
                    para.AddRun().AddText("III")
                    para.SetStyle("Name")
                case"LAST NAME":
                    r.ClearContent()
                    r.AddText(personList.Lname)
                case"Address | Phone | Email":
                    r.ClearContent()
                    r.AddText(personList.Address)
                case"Date":
                    r.ClearContent()
                    r.AddText(time.Now().For
                    .
                    .
                    .

After that, we go through each paragraph and look out for our flags. If a flag is found then that is replaced by the appropriate information of the person. Note that the above code is not complete, we cut out the rest and you can add as many cases as needed.

    doc.SaveToFile("results/"+personList.Fname+"_Letter"+".docx")
    }
    diff := time.Now().Sub(start)
    fmt.Printf("Time taken: %s\n", diff)
}

After all the cases have been iterated, the new document is created inside the results sub-directory. We also print out the time it has taken for executing the code.

Example Document

We test the above given code on a total of ten people structures. The execution was completed in:

Time taken: 475.7302ms

The document created for the jack structure is shown below:

Fig: Sample document of Jack Jones

The image below represents the results directory. Which stores the result documents after the code has been executed.

Fig: The Results Directory

Conclusion

UniOffice is a strong Go-based library that can be used for editing, formatting and creating documents. The library offers a lot more than what we have achieved in this article and you can check out the examples on the GitHub repository.