Search code examples
javascriptblobjszipdocxtemplater

How can I output a docx from docxtemplater.js for input in jszip


Using the examples from jszip and docxtemplater I am trying to add multiple docx-Documents to a zip archive and I am failing. My archive only contains docx-files with 0 Bytes that are not readable.

I think I don't understand how I can add the docx-files in binary mode to the archive. I added the rather long example code that I fused from the examples mentioned above.

Can you tell me how I have to modify the output of the generate()-function or the addition of the files to the zip-archive?

<html>
<body>
    <button onclick="zip_docx()">Generate document</button>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/docxtemplater/3.17.9/docxtemplater.js"></script>
<script src="https://unpkg.com/[email protected]/dist/pizzip.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.js"></script>
<script src="https://unpkg.com/[email protected]/dist/pizzip-utils.js"></script>
<script src="jszip.min.js"></script>
<!--
Mandatory in IE 6, 7, 8 and 9.
-->
<!--[if IE]>
    <script type="text/javascript" src="https://unpkg.com/[email protected]/dist/pizzip-utils-ie.js"></script>
<![endif]-->
<script>
function loadFile(url,callback){
    PizZipUtils.getBinaryContent(url,callback);
}
function zip_docx(){
  var zip = new JSZip();
  var file1 = generate("file1");
  var file2 = generate("file2");
  var file3 = generate("file3");
  zip.file("file1.docx", file1);
  zip.file("file2.docx", file2);
  zip.file("file3.docx", file3);

  zip.generateAsync({type:"blob"})
  .then(function(content) {
      // see FileSaver.js
      saveAs(content, "example.zip");
  });
  }
function generate() {
    loadFile("https://docxtemplater.com/tag-example.docx",function(error,content){
        if (error) { throw error };

        // The error object contains additional information when logged with JSON.stringify (it contains a properties object containing all suberrors).
        function replaceErrors(key, value) {
            if (value instanceof Error) {
                return Object.getOwnPropertyNames(value).reduce(function(error, key) {
                    error[key] = value[key];
                    return error;
                }, {});
            }
            return value;
        }

        function errorHandler(error) {
            console.log(JSON.stringify({error: error}, replaceErrors));

            if (error.properties && error.properties.errors instanceof Array) {
                const errorMessages = error.properties.errors.map(function (error) {
                    return error.properties.explanation;
                }).join("\n");
                console.log('errorMessages', errorMessages);
                // errorMessages is a humanly readable message looking like this :
                // 'The tag beginning with "foobar" is unopened'
            }
            throw error;
        }

        var zip = new PizZip(content);
        var doc;
        try {
            doc=new window.docxtemplater(zip);
        } catch(error) {
            // Catch compilation errors (errors caused by the compilation of the template : misplaced tags)
            errorHandler(error);
        }

        doc.setData({
            first_name: 'John',
            last_name: 'Doe',
            phone: '0652455478',
            description: 'New Website'
        });
        try {
            // render the document (replace all occurences of {first_name} by John, {last_name} by Doe, ...)
            doc.render();
        }
        catch (error) {
            // Catch rendering errors (errors relating to the rendering of the template : angularParser throws an error)
            errorHandler(error);
        }

        var out=doc.getZip().generate({
            type:"blob",
            mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        }) //Output the document using Data-URI
        //saveAs(out,"output.docx")
        return out
    })
}
</script>

Solution

  • After researching the comment of muellermarkus, I was consulting with the leader of the project and I opened an issue on github. There is an answer there which I copy here as a reference. The main hurdle was the addition of {base64: true} as option when the docx-file is added to the archive.

    <html lang="en">
    <body>
        <button onclick="generate()">Generate document</button>
    </body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/docxtemplater/3.17.9/docxtemplater.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/pizzip.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/pizzip-utils.js"></script>
    
    <script>
    var zipDocs = new PizZip();
    function loadFile(url,callback){
        PizZipUtils.getBinaryContent(url,callback);
    }
    
    function generate() {
    
        loadFile("https://docxtemplater.com/tag-example.docx",function(error,content){
            if (error) { throw error };
    
            // The error object contains additional information when logged with JSON.stringify (it contains a properties object containing all suberrors).
            function replaceErrors(key, value) {
                if (value instanceof Error) {
                    return Object.getOwnPropertyNames(value).reduce(function(error, key) {
                        error[key] = value[key];
                        return error;
                    }, {});
                }
                return value;
            }
    
            function errorHandler(error) {
                console.log(JSON.stringify({error: error}, replaceErrors));
    
                if (error.properties && error.properties.errors instanceof Array) {
                    const errorMessages = error.properties.errors.map(function (error) {
                        return error.properties.explanation;
                    }).join("\n");
                    console.log('errorMessages', errorMessages);
                    // errorMessages is a humanly readable message looking like this :
                    // 'The tag beginning with "foobar" is unopened'
                }
                throw error;
            }
            const array = ['John','Jane']
            array.forEach(function(name){
              var zip = new PizZip(content);
              var doc;
              try {
                  doc=new window.docxtemplater(zip);
              } catch(error) {
                  // Catch compilation errors (errors caused by the compilation of the template : misplaced tags)
                  errorHandler(error);
              }
              console.log(name);
              doc.setData({
                  first_name: name,
                  last_name: 'Doe',
                  phone: '0652455478',
                  description: 'New Website'
              });
    
              try {
                // render the document (replace all occurences of {first_name} by John, {last_name} by Doe, ...)
                doc.render();
              }
              catch (error) {
                // Catch rendering errors (errors relating to the rendering of the template : angularParser throws an error)
                errorHandler(error);
              }
    
              var out=doc.getZip().generate() //Output the document using Data-URI
    
              zipDocs.file(name+".docx", out, {base64: true});
              console.log(name, " wurde gezippt.");
    
          })
          var content = zipDocs.generate({ type: "blob" });
          // see FileSaver.js
          saveAs(content, "example.zip");
        })
    }
    </script>