Skip to content
Fill a Template from Data

Fill a Template from Data

Once a template has the layouts you need, producing a deck per customer, region or period is a loop: unmarshal the data, clear the slides, add the layouts you want, write the values into the placeholders and save under a name derived from the record. The example does this with a sales report, generating one .pptx per area from a JSON payload.

The API is the same as Use a Template. What is worth saying here is how the loop is arranged, because the presentation object is reused across records rather than reopened.

The loop

ppt, err := presentation.OpenTemplate("template.pptx")
if err != nil {
    log.Fatalf("unable to open template: %s", err)
}
defer ppt.Close()

for _, data := range saleData {
    for _, s := range ppt.Slides() {
        if err := ppt.RemoveSlide(s); err != nil {
            log.Fatalf("error removing slide: %s", err)
        }
    }
    // add slides from layouts and fill their placeholders
    if err := ppt.SaveToFile(fmt.Sprintf("%s.pptx", data.Area)); err != nil {
        log.Fatalf("error saving presentation: %s", err)
    }
}

The clear-then-build step at the top of each iteration is what makes one template serve every record. On the first pass it removes the template’s own example slides; on later passes it removes the slides built for the previous record. Skip it and each file accumulates every record that came before.

SaveToFile can be called repeatedly on the same presentation. It writes the current state and leaves the object usable, so there is no need to reopen the template between records.

Writing values in

ph, err := sld.GetPlaceholder(pml.ST_PlaceholderTypeTitle)
if err != nil {
    log.Fatalf("error getting placeholder type title: %s", err)
}
ph.SetText(fmt.Sprintf("Sale Data For: %s", data.Area))

ph, err = sld.GetPlaceholderByIndex(1)
if err != nil {
    log.Fatalf("error getting placeholder by index: %s", err)
}
ph.ClearAll()
para := ph.AddParagraph()
para.AddRun().SetText(fmt.Sprintf("Number of Customers: %d", data.Customers))

SetText suits a title: one paragraph, one run, no formatting decisions. A body placeholder holding several lines wants ClearAll followed by one AddParagraph per line, since each paragraph becomes its own bullet under the layout’s list style.

Every placeholder lookup here checks its error. In a generator that runs unattended over data you have not seen, a layout that lacks the placeholder you expected is the failure you want reported rather than a nil dereference two lines later.

Limitations

Slides() builds a new slice on every call, so ranging over it while RemoveSlide shortens the presentation’s own list is safe. Hold on to the result across an add or a remove and it goes stale instead: the values in it still point at real slides, but the positions they were taken from have moved.

Output filenames come straight from the data in the example, which is fine for a fixed payload and unsafe for anything user-supplied. Sanitize the value before using it as a path.

Placeholder indices are per layout. GetPlaceholderByIndex(1) finds the content placeholder in this template’s “Title and Content” layout; another template can number them differently. If you are targeting templates you do not control, iterate PlaceHolders() and match on Type() instead.

Run the example

The example parses an embedded JSON document with three sales records and writes Michigan.pptx, Cincinnati.pptx and Washington.pptx, each with a caption slide and a content slide. It prints the template’s layouts first.

git clone https://github.com/unidoc/unioffice-examples.git
cd unioffice-examples/presentation/use-template-sales
go run main.go

If this is your first time using UniOffice, follow the getting started guide to create an API key and set up your development environment.

View the full source
Last updated on