easy-template-x

repository·master·Indexed 19 days ago

https://github.com/alonrbar/easy-template-x

A library for generating .docx documents from templates in Node.js and browser environments. It features a plugin-based architecture for text replacement, loops, conditional rendering, image embedding, hyperlinks, data-driven charts, and raw XML insertion. The core functionality is provided via the TemplateHandler class and its process() method.

Tokens
8.3K
Snippets
29
Records
36
Agent score
68%

What's inside easy-template-x

  1. Generate docx documents with easy-template-x

    master

    easy-template-x is a library used to generate .docx documents from templates. It is designed to work in both Node.js and browser environments. The core workflow involves reading a template file (as a Buffer or Blob), providing a data object containing the values to be injected, and using the TemplateHandler to process the template and produce a new document.

    import { TemplateHandler } from 'easy-template-x';
    
    const handler = new TemplateHandler();
    const doc = await handler.process(templateFile, data);
  2. How easy-template-x plugins work

    master

    The easy-template-x library uses a plugin model to extend its template manipulation capabilities. Each plugin is responsible for handling a specific contentType defined in your input data.

    Bundled plugins include:

    • Text plugin: Simple text replacement.
    • Loop plugin: Iteration (text, tables, lists) and conditional rendering.
    • Image plugin: Embedding images via text tags or placeholder replacement.
    • Link plugin: Hyperlink creation.
    • Chart plugin: Handling various chart types.
    • Raw xml plugin: Custom XML insertion (e.g., for page breaks or special symbols).

    You can also write custom plugins by inheriting from the TemplatePlugin class.

  3. How tag data scoping works

    master

    easy-template-x supports data scoping, allowing you to reference "shallow" data from within deeper hierarchies (like loops). This enables you to declare top-level data (e.g., a company logo or name) in the outer scope and access it from within an {#Employees} loop.

    Example Scenario:

    Input Data:

    {
        "Company": "Contoso Ltd.",
        "Employees": [
            { "Surname": "Gates", "Given name": "William" },
            { "Surname": "Nadella", "Given name": "Satya" },
        ]
    }

    In the template, you can use the {Company} tag inside the {#Employees} loop to access the outer scope value.

  4. Use the Loop plugin for iteration and conditions

    master

    The Loop plugin allows you to iterate over arrays (text, table rows, columns, or lists) and render content conditionally.

    Syntax Requirements:

    • Opening tags must start with # (e.g., {#loop}).
    • Closing tags must start with / (e.g., {/loop}). The name of the closing tag is configurable and does not need to match the opening tag or even have a name (e.g., {/}).

    Conditions: Use the same syntax for boolean conditions. If the value in the data is truthy, the content between the tags is rendered; otherwise, it is omitted.

    Nested Logic: You can nest loops and conditions within each other. Ensure your data structure matches the nesting depth of your template.

    {
        "Beers": [
            { "Brand": "Carlsberg", "Price": 1 },
            { "Brand": "Leaf Blonde", "Price": 2 },
            { "Brand": "Weihenstephan", "Price": 1.5 }
        ]
    }
  5. Extend document manipulation with TemplateExtensions

    master

    To perform powerful document manipulations that go beyond standard plugin capabilities, you can use extensions. Extensions run either before or after the standard template processing.

    To create an extension, inherit from the TemplateExtension class. Extensions are registered via the extensions property in TemplateHandlerOptions.

    const handler = new TemplateHandler({
        extensions: {
            afterCompilation: [
                new DataBindingExtension()
            ]
        }
    });
  6. Use advanced syntax with custom scopeDataResolvers

    master

    By default, easy-template-x uses a simple syntax designed for non-technical users. To use more sophisticated expressions (like Angular-like syntax), you can provide a custom scopeDataResolver via the TemplateHandlerOptions.

    import { createResolver } from "easy-template-x-angular-expressions"
    
    const handler = new TemplateHandler({
        scopeDataResolver: createResolver()
    })
  7. Use easy-template-x in Node.js

    master

    To use easy-template-x in a Node.js environment, use the fs module to read your template file as a Buffer and write the resulting document back to the file system.

    1. Read the template file using fs.readFileSync.
    2. Initialize TemplateHandler.
    3. Call handler.process(templateFile, data) with your data object.
    4. Save the resulting Buffer using fs.writeFileSync.
    import * as fs from 'fs';
    import { TemplateHandler } from 'easy-template-x';
    
    // 1. read template file
    const templateFile = fs.readFileSync('myTemplate.docx');
    
    // 2. process the template
    const data = {
        posts: [
            { author: 'Alon Bar', text: 'Very important\ntext here!' },
            { author: 'Alon Bar', text: 'Forgot to mention that...' }
        ]
    };
    
    const handler = new TemplateHandler();
    const doc = await handler.process(templateFile, data);
    
    // 3. save output
    fs.writeFileSync('myTemplate - output.docx', doc);
  8. Write a custom TemplatePlugin

    master

    To create a custom plugin, extend the TemplatePlugin class. You must define a unique contentType that matches the _type property in your input data.

    Implement the simpleTagReplacements method to handle the logic for your specific tag type. You can use utilities from officeMarkup and xml to query and modify the document structure.

    Example Implementation:

    import { officeMarkup, xml } from "easy-template-x";
    
    export class RawXmlPlugin extends TemplatePlugin {
    
        // Declare the unique "content type" this plugin handles
        public readonly contentType = 'rawXml';
    
        public simpleTagReplacements(tag: Tag, data: ScopeData): void {
            
            const value = data.getScopeData<RawXmlContent>();
            if (value && typeof value.xml === 'string') {
    
                // Find the actual XML text node in MS Word
                const wordTextNode = officeMarkup.query.containingTextNode(tag.xmlTextNode);
    
                // Parse and insert the new XML content
                const newNode = xml.parser.parse(value.xml);
                xml.modify.insertBefore(newNode, wordTextNode);
            }
    
            // Remove the placeholder tag
            officeMarkup.modify.removeTag(tag);
        }
    }

    Required Content Interface:

    export interface RawXmlContent extends PluginContent {
        _type: 'rawXml';
        xml: string;
    }
    import { officeMarkup, xml } from "easy-template-x";
    
    /**
     * A plugin that inserts raw xml to the document.
     */
    export class RawXmlPlugin extends TemplatePlugin {
    
        // Declare the unique "content type" this plugin handles
        public readonly contentType = 'rawXml';
    
        // Plugin logic goes here:
        public simpleTagReplacements(tag: Tag, data: ScopeData): void {
            
            // Get the value to use from the input data.
            const value = data.getScopeData<RawXmlContent>();
            if (value && typeof value.xml === 'string') {
    
                // Tag.xmlTextNode always reference the actual xml text node.
                // In MS Word each text node is wrapped by a <w:t> node so we retrieve that.
                const wordTextNode = officeMarkup.query.containingTextNode(tag.xmlTextNode);
    
                // If the input data contains an "xml" string property, parse it and insert the content next to the placeholder tag.
                const newNode = xml.parser.parse(value.xml);
                xml.modify.insertBefore(newNode, wordTextNode);
            }
    
            // Remove the placeholder tag.
            officeMarkup.modify.removeTag(tag);
        }
    }
  9. Use easy-template-x in the Browser

    master

    In the browser, you can process templates by fetching a .docx file as a Blob or obtaining it via an HTML File Input. The processing logic remains identical to the Node.js implementation. To save the resulting document, you typically create a temporary URL from the resulting Blob and trigger a download via a link element.

    import { TemplateHandler } from 'easy-template-x';
    
    // 1. read template file (e.g., via fetch)
    const response = await fetch('http://somewhere.com/myTemplate.docx');
    const templateFile = await response.blob();
    
    // 2. process the template
    const data = {
        posts: [
            { author: 'Alon Bar', text: 'Very important\ntext here!' },
            { author: 'Alon Bar', text: 'Forgot to mention that...' }
        ]
    };
    
    const handler = new TemplateHandler();
    const doc = await handler.process(templateFile, data);
    
    // 3. save output
    saveFile('myTemplate - output.docx', doc);
    
    function saveFile(filename, blob) {
        const blobUrl = URL.createObjectURL(blob);
        let link = document.createElement("a");
        link.download = filename;
        link.href = blobUrl;
        document.body.appendChild(link);
        link.click();
        setTimeout(() => {
            link.remove();
            window.URL.revokeObjectURL(blobUrl);
            link = null;
        }, 0);
    }
  10. Control Loop behavior with loopOver

    master

    The Loop plugin uses heuristics to decide what to repeat. You can explicitly control this behavior using the loopOver option. Supported values are:

    • row: Repeats table rows.
    • column: Repeats table columns.
    • paragraph: Repeats paragraphs.
    • content: Repeats the content between the tags (default/naive approach).

    This option also applies to conditional rendering.

  11. Customize tag and container delimiters

    master

    You can change the default syntax for tags and containers (used for loops and conditions) using the delimiters option. For example, to switch from {tag} and {#loop}{/loop} to {{>>loop}} and {{<<loop}} style syntax:

    const handler = new TemplateHandler({
        delimiters: {
            tagStart: "{{",
            tagEnd: "}}",
            containerTagOpen: ">>",
            containerTagClose: "<<"
        },
    })
  12. Use the Text plugin for simple replacements

    master

    The Text plugin replaces a single tag with custom text while preserving the original text style. The input data should be a simple key-value mapping where the key matches the tag name in the template.

    {
        "First Tag": "Quis et ducimus voluptatum\nipsam id.",
        "Second Tag": "Dolorem sit voluptas magni dolorem molestias."
    }