Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loop rendering bug - inner loop props are rendered with root level data #523

Closed
shaharsol opened this issue Jun 7, 2020 · 6 comments
Closed
Labels

Comments

@shaharsol
Copy link

Environment

  • Version of docxtemplater : 3.17.6
  • Used docxtemplater-modules : all the basic
  • Runner : Node.JS

How to reproduce my problem :

My template is the following :
template.zip

{#products}
    {name}, {price} €
{/products}

With the following js file :

var PizZip = require('pizzip');
var Docxtemplater = require('docxtemplater');

var fs = require('fs');
var path = require('path');

// 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;
}

//Load the docx file as a binary
var content = fs
    .readFileSync('/Users/shaharsol/Downloads/templ.docx', 'binary');

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

//set the templateVariables
doc.setData({
    name: 'Santa Katerina',
    products: [
      {
        price: '$3.99'
      }
    ]
});

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 buf = doc.getZip()
             .generate({type: 'nodebuffer'});

// buf is a nodejs buffer, you can either write it to a file or do anything else with it.
fs.writeFileSync('/Users/shaharsol/Downloads/ret2.docx', buf);

This is the result:
ret.zip

Screen Shot 2020-06-07 at 17 27 26

The problem is that the name property, which is set in the root data element, is rendered as if it was part of products. I would expect it not to render there, and let the nullGetter handle that value since it supposed to be null.

@shaharsol
Copy link
Author

BTW, I currently deal with it using a custom parser:

if (context.num === 0 && context.scopePath.length > 0) {
  return '';
}

But I am not sure I'm not breaking anything else...

@edi9999 edi9999 closed this as completed in 9d573e0 Jun 8, 2020
@edi9999
Copy link
Member

edi9999 commented Jun 8, 2020

Hello @shaharsol , I've just added it to the FAQ.

If you use the following condition it should work :

    parser(tag) {
        return {
            get(scope, context) {
                if (context.num < context.scopePath.length) {
                    return null;
                }
                // You can customize your parser here instead of scope[tag] of course
                return scope[tag];
            },
        };
    },

@edi9999
Copy link
Member

edi9999 commented Jun 8, 2020

I think your condition will also work, it is just a bit more specific.

With my condition, it will work regardless of the "nesting", eg it will never use a scope that is not the "deepest one". With your condition, it will return null only when doing the last evaluation on the rootScope, but it won't circuit-break if having 2 levels of loops.

@shaharsol
Copy link
Author

@edi9999 thanks for your quick response. However, don't you think it's a bug? Is this the intended behavior? This "scope inheritance" is actually thought of?

@edi9999
Copy link
Member

edi9999 commented Jun 8, 2020

No, I don't think it is a bug, most users expect that the upper scope can be accessed from within a loop.

Docxtemplater is inspired by mustache and I think in most mustache implementation, it works like this (altough I'm not sure whether it is specified explicitly). See janl/mustache.js#399 for a discussion.

Docxtemplater is actually less strict then that, since, with the custom parser, it allows you to change the behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants