Article
· Jul 26 8m read

Creating advanced Word documents with IRIS

Word documents are widely used in the market. Users frequently create contracts, memos, resumes, reports, analyses, and other documents that may require data from or captured by InterSystems IRIS. However, IRIS does not have an API, SDK, library, or adapter for this. This limitation no longer exists. 

The new Open Exchange library iris4word (https://openexchange.intersystems.com/package/iris4word) delivers an ObjectScript SDK where the developer passes any %DynamicObject as a parameter, a Word file template and then receives a ready document, with the structure and formatting defined in its template.


To use iris4word

This article will use a REST API to get the content, but it is possible query database also. To install the iris4word and the sample using it, proceed with these tasks:

  1. If you use IPM/ZPM: zpm:USER>install iris4word
  2. If you use Docker: 
    1. git clone https://github.com/yurimarx/iris4word.git
    2. docker-compose up -d --build
  3. Open the Postman (to execute the REST API sample)
  4. Import the sample collection from (https://raw.githubusercontent.com/yurimarx/iris4word/refs/heads/master/iris4word.postman_collection.json): 
  5. Upload the file template template.docx (in the sample/template.docx path of this repository or from https://raw.githubusercontent.com/yurimarx/iris4word/refs/heads/master/sample/template.docx). To upload, fill the file field into the Body tab: 
  6.  The upload process will send the template to the server to be used by the iris4word.
  7. Open the 2. Download Word Document and copy and past to the body this JSON content: 
    {
      "company": {
        "name": "ACM Ltda.",
        "address": "Main Street, 123",
        "city": "New York",
        "state": "NY"
      },
      "projects": [
        {
          "name": "System Development X",
          "beginDate": "2024-01-01",
          "endDate": "2024-06-06",
          "team": [
            {"name": "John Star", "role": "Senior Developer"},
            {"name": "Marie Loo", "role": "BDM"}
          ],
          "tasks": [
            {"description": "Requirements", "status": "Done"},
            {"description": "Development", "status": "Doing"}
          ]
        },
        {
          "name": "ERP Development Y",
          "beginDate": "2024-03-03",
          "endDate": "2025-12-12",
          "team": [
            {"name": "Peter Rogers", "role": "Project Manager"},
            {"name": "Robert Plant", "role": "ERP Specialist"}
          ],
          "tasks": [
            {"description": "ERP configuration", "status": "Done"},
            {"description": "User training", "status": "Doing"}
          ]
        }
      ],
      "principalContact": {
        "name": "Carlos Olivera",
        "email": "carlos.olivera@company.com",
        "phone": "+1 555 555-555"
      }
    }
     
  8. On the Send button, select the option Send and Download:
  9. See the results:
  10. Compare the JSON content with the template.docx and see the marks and tags used.

Behind the Scenes

It's very easy, with the template file saved into /tmp/template.docx, just call it:

ClassMethod DownloadDoc(template As %String) As %Status
{
    Set tUUID = $System.Util.CreateGUID() 
    Set filePath = "/tmp/"_tUUID_".docx"
    Set jsonContentString = {}.%FromJSON(%request.Content)
    Set sc = ##class(dc.iris4word.WordUtil).GenerateWordFileFromJSON(jsonContentString.%ToJSON(), "/tmp/"_template_".docx", filePath)

    Set %response.NoCharSetConvert=1
    Set %response.Headers("Access-Control-Allow-Origin")="*"
    Do %response.SetHeader("Content-Type","application/vnd.openxmlformats-officedocument.wordprocessingml.document")
    Do %response.SetHeader("Content-Disposition","attachment;filename="""_tUUID_".docx"_"""")
    
    Set stream=##class(%Stream.FileBinary).%New()
    Set sc=stream.LinkToFile(filePath)
    Do stream.OutputToDevice()
    
    Return sc
}

O ClassMethod ##class(dc.iris4word.WordUtil).GenerateWordFileFromJSON recebe os dados na forma de um %DynamicObject, the word template path and the file path where the final word document must be created. Now you have a word file to send to the user as response. Very easy!!

The Template language

The iris4word delivers to you a flexible template language to compose dynamic word documents using the Microsoft Word editor.

Text tag

The text tag is the most basic tag type in the Word template. {{name}} will be replaced by the value of key name in the data model. If the key is not exist, the tag will be cleared(The program can configure whether to keep the tag or throw an exception).

Template:

{{name}} always said life was like a box of {{thing}}.

Output:

Mama always said life was like a box of chocolates.

Picture tag

The image tag starts with @, for example, {{@logo}} will look for the value with the key of logo in the data model, and then replace the tag with the image. The data corresponding to the image tag can be a simple URL or Path string, or a structure containing the width and height of the image.

Template:

Fruit Logo:
watermelon {{@watermelon}}
lemon {{@lemon}}
banana {{@banana}}
Output:

Fruit Logo:
watermelon 🍉
lemon 🍋
banana 🍌

Numbering

The list tag corresponds to Word's symbol list or numbered list, starting with *, such as {{*number}}.

Template:

{{*list}}
Output:

● Plug-in grammar
● Supports word text, pictures, table...
● Templates, not just templates, but also style templates

Sections

A section is composed of two tags before and after, the start tag is identified by ?, and the end tag is identified by /, such as {{?section}} as the start tag of the sections block, {{/section} } is the end tag, and section is the name of this section.

Sections are very useful when processing a series of document elements. Document elements (text, pictures, tables, etc.) located in a section can be rendered zero, one or N times, depending on the value of the section.

False Values or Empty collection

If the value of the section is null, false or an empty collection, all document elements located in the section will not be displayed, similar to the condition of the if statement is false.

Datamodel:

{
  "announce": false
}
Template:

Made it,Ma!{{?announce}}Top of the world!{{/announce}}
Made it,Ma!
{{?announce}}
Top of the world!🎋
{{/announce}}
Output:

Made it,Ma!
Made it,Ma!

Non-False Values and Not a collection

If the value of the section is not null, false, and is not a collection, all document elements in the section will be rendered once, similar to the condition of the if statement is true.

Datamodel:

{
  "person": { "name": "Sayi" }
}
Template:

{{?person}}
  Hi {{name}}!
{{/person}}
Output:

  Hi Sayi!

Non-Empty collection

If the value of the section is a non-empty collection, the document elements in the section will be looped once or N times, depending on the size of the collection, similar to the foreach syntax.

Datamodel:

{
  "songs": [
    { "name": "Memories" },
    { "name": "Sugar" },
    { "name": "Last Dance" }
  ]
}
Template:

{{?songs}}
{{name}}
{{/songs}}
Output:

Memories
Sugar
Last Dance
In the loop, a special tag {{=#this}} can be used to directly refer to the object of the current iteration.

Datamodel:

{
  "produces": [
    "application/json",
    "application/xml"
  ]
}
Template:

{{?produces}}
{{=#this}}
{{/produces}}
Output:

application/json
application/xml

The Java code developed to create the iris4word library

The iris4word was created using Java. The InterSystems IRIS supports use Java libraries with External Java Gateway feature:

ClassMethod GenerateWordFileFromJSON(jsonContent As %String, wordTemplateFileName As %String, wordFileName As %String) As %Status
{
    Set tSC = $$$OK

    Try {
        
        set javaGate = $system.external.getJavaGateway()  
        do javaGate.addToPath($SYSTEM.Util.BinaryDirectory()_"iris4word-1.0.0-jar-with-dependencies.jar")
        set wordUtil = javaGate.new("iris4word.WordUtil")
...
}

The instruction $system.external.getJavaGateway returned a Java engine and the addToPath allow us to use the file iris4word-1.0.0.0-jar-with-dependencies.jar. This library has the iris4word implementation:

package iris4word;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.Map;

public class WordUtil {

    public void createFromFile(String jsonFileName, String wordTemplateFileName, String wordFileName) {
        
    	java.util.logging.Logger julLogger2 = java.util.logging.Logger.getLogger("com.deepoove.poi.render.processor.LogProcessor");
    	julLogger2.setLevel(java.util.logging.Level.OFF);
    	
    	java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger("org.slf4j.Logger");
        julLogger.setLevel(java.util.logging.Level.OFF);
        
    	ObjectMapper objectMapper = new ObjectMapper(); 
    	XWPFTemplate template = null;
    	
        try {
            File jsonFile = new File(jsonFileName);
            
            @SuppressWarnings("unchecked")
			Map<String, Object> data = objectMapper.readValue(jsonFile, Map.class);

            Configure config = Configure.builder().build();

            template = XWPFTemplate.compile(wordTemplateFileName, config).render(data);

            template.writeToFile(wordFileName);
            

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        	if(template != null) {        		
        		try {
					template.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
        	}
		}
    }
	
}

The iris4word used the open source library poi-tl (package com.deepoove.poi) to render word templates with dynamic data. The iris4word transform JSON data to the poi-tl format, set the data to poi-tl, execute the rendering process and get the word document. The document is saved to the disk to be used into ObjectScript code:

ClassMethod GenerateWordFile(jsonFileName As %String, wordTemplateFileName As %String, wordFileName As %String) As %Status
{
    Set tSC = $$$OK

    Try {
        
        set javaGate = $system.external.getJavaGateway()  
        do javaGate.addToPath($SYSTEM.Util.BinaryDirectory()_"iris4word-1.0.0-jar-with-dependencies.jar")
        set wordUtil = javaGate.new("iris4word.WordUtil") 
        
        do wordUtil.createFromFile(jsonFileName, wordTemplateFileName, wordFileName)
        
    }
    Catch ex {
        Set tSC=ex.AsStatus()
    }

    return tSC
}

This project demonstrates how not only Python, but also Java, can significantly extend IRIS's functionality. Enjoy!

Discussion (0)1
Log in or sign up to continue