diff --git a/CHANGELOG.md b/CHANGELOG.md index d4b991fc..33dc5d12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### 3.52.0 + +Add `syntax.allowUnclosedTag` option. + +This allows to write : `Hello {user` and not have an error in your template. + ### 3.51.2 Improve typescript typings : diff --git a/es6/docxtemplater.d.ts b/es6/docxtemplater.d.ts index a0c9d518..d023edc2 100644 --- a/es6/docxtemplater.d.ts +++ b/es6/docxtemplater.d.ts @@ -82,6 +82,7 @@ declare namespace DXT { interface Syntax { allowUnopenedTag?: boolean; + allowUnclosedTag?: boolean; changeDelimiterPrefix?: string | null; } diff --git a/es6/docxtemplater.test-d.ts b/es6/docxtemplater.test-d.ts index da57f2d6..6b790881 100644 --- a/es6/docxtemplater.test-d.ts +++ b/es6/docxtemplater.test-d.ts @@ -189,6 +189,7 @@ const doc8 = new Docxtemplater(new PizZip("hello"), { const doc9 = new Docxtemplater(new PizZip("hello"), { syntax: { allowUnopenedTag: true, + allowUnclosedTag: true, changeDelimiterPrefix: null, }, }); diff --git a/es6/lexer.js b/es6/lexer.js index 1d26fa2e..003d5f84 100644 --- a/es6/lexer.js +++ b/es6/lexer.js @@ -126,9 +126,22 @@ function getDelimiterErrors(delimiterMatches, fullText, syntaxOptions) { lastDelimiterOffset, delimiterOffset - lastDelimiterOffset + lastDelimiterLength + 4 ); + if (!syntaxOptions.allowUnclosedTag) { + errors.push( + getDuplicateOpenTagException({ + xtag, + offset: lastDelimiterOffset, + }) + ); + lastDelimiterMatch = currDelimiterMatch; + delimiterAcc.push({ ...currDelimiterMatch, error: true }); + return delimiterAcc; + } + } + if (!syntaxOptions.allowUnclosedTag) { errors.push( - getDuplicateOpenTagException({ - xtag, + getUnclosedTagException({ + xtag: wordToUtf8(xtag), offset: lastDelimiterOffset, }) ); @@ -136,15 +149,7 @@ function getDelimiterErrors(delimiterMatches, fullText, syntaxOptions) { delimiterAcc.push({ ...currDelimiterMatch, error: true }); return delimiterAcc; } - errors.push( - getUnclosedTagException({ - xtag: wordToUtf8(xtag), - offset: lastDelimiterOffset, - }) - ); - lastDelimiterMatch = currDelimiterMatch; - delimiterAcc.push({ ...currDelimiterMatch, error: true }); - return delimiterAcc; + delimiterAcc.pop(); } if (!inDelimiter && position === "end") { @@ -177,7 +182,7 @@ function getDelimiterErrors(delimiterMatches, fullText, syntaxOptions) { return delimiterAcc; } - inDelimiter = !inDelimiter; + inDelimiter = position === "start"; lastDelimiterMatch = currDelimiterMatch; delimiterAcc.push(currDelimiterMatch); return delimiterAcc; @@ -191,12 +196,16 @@ function getDelimiterErrors(delimiterMatches, fullText, syntaxOptions) { lastDelimiterOffset, fullText.length - lastDelimiterOffset ); - errors.push( - getUnclosedTagException({ - xtag: wordToUtf8(xtag), - offset: lastDelimiterOffset, - }) - ); + if (!syntaxOptions.allowUnclosedTag) { + errors.push( + getUnclosedTagException({ + xtag: wordToUtf8(xtag), + offset: lastDelimiterOffset, + }) + ); + } else { + delimiterWithErrors.pop(); + } } return { diff --git a/es6/tests/e2e/fixtures.js b/es6/tests/e2e/fixtures.js index f198d11c..4d3ae164 100644 --- a/es6/tests/e2e/fixtures.js +++ b/es6/tests/e2e/fixtures.js @@ -1656,47 +1656,47 @@ const fixtures = [ }, { it: "should work well with $index angular parser", - content: "{#todos}{#$index==0}FIRST {/}{text} {/todos}", + content: "{#list}{#$index==0}FIRST {/}{text} {/list}", ...noInternals, options: { parser: expressionParser, }, - scope: { todos: [{ text: "Hello" }, { text: "Other todo" }] }, - result: 'FIRST Hello Other todo ', + scope: { list: [{ text: "Hello" }, { text: "Other item" }] }, + result: 'FIRST Hello Other item ', }, { it: "should work well with $index inside condition angular parser", content: - "{#todos}{#important}!!{$index+1}{text}{/}{^important}?{$index+1}{text}{/}{/}", + "{#list}{#important}!!{$index+1}{text}{/}{^important}?{$index+1}{text}{/}{/}", ...noInternals, options: { parser: expressionParser, }, scope: { - todos: [ + list: [ { important: true, text: "Hello" }, - { text: "Other todo" }, + { text: "Other item" }, { important: true, text: "Bye" }, ], }, - result: '!!1Hello?2Other todo!!3Bye', + result: '!!1Hello?2Other item!!3Bye', }, { it: "should work well with $index inside condition angular parser", content: - "{#todos}{#important}!!{$index+1}{text}{/}{^important}?{$index+1}{text}{/}{/}", + "{#list}{#important}!!{$index+1}{text}{/}{^important}?{$index+1}{text}{/}{/}", ...noInternals, options: { parser: angularParserIE11, }, scope: { - todos: [ + list: [ { important: true, text: "Hello" }, - { text: "Other todo" }, + { text: "Other item" }, { important: true, text: "Bye" }, ], }, - result: '!!1Hello?2Other todo!!3Bye', + result: '!!1Hello?2Other item!!3Bye', }, { it: "should work well with nested conditions inside table", @@ -2136,6 +2136,70 @@ http://errors.angularjs.org/"NG_VERSION_FULL"/$parse/lexerr?p0=Unexpected%20next world + `, + }, + { + it: "should not fail on triple open tag if allowUnclosedTag is true", + ...noInternals, + content: ` + + Hello {{{ + + + lastName + + + } world + + `, + options: { + syntax: { + allowUnclosedTag: true, + }, + }, + scope: { firstName: "John", lastName: "Doe" }, + result: ` + + Hello {{Doe + + + + + + world + + `, + }, + { + it: "should not fail on SPACED unclosed tag if allowUnclosedTag is true", + ...noInternals, + content: ` + + Hello {firstName { + + + lastName + + + } world + + `, + options: { + syntax: { + allowUnclosedTag: true, + }, + }, + scope: { firstName: "John", lastName: "Doe" }, + result: ` + + Hello {firstName Doe + + + + + + world + `, }, { @@ -2168,6 +2232,65 @@ http://errors.angularjs.org/"NG_VERSION_FULL"/$parse/lexerr?p0=Unexpected%20next } world} } } + `, + }, + { + it: "should not fail if allowUnclosedTag on 'Hello {' string", + ...noInternals, + content: "Hello {", + options: { + syntax: { + allowUnclosedTag: true, + allowUnopenedTag: true, + }, + }, + scope: { firstName: "John", lastName: "Doe" }, + result: "Hello {", + }, + { + it: "should not fail if allowUnclosedTag on 'Hello }' string", + ...noInternals, + content: "Hello }", + options: { + syntax: { + allowUnclosedTag: true, + allowUnopenedTag: true, + }, + }, + scope: { firstName: "John", lastName: "Doe" }, + result: "Hello }", + }, + { + it: "should not fail on double delimiters if allowUnclosedTag and allowUnopenedTag is true", + ...noInternals, + content: ` + + Hello {{ + + + lastName + + + }} + + `, + options: { + syntax: { + allowUnclosedTag: true, + allowUnopenedTag: true, + }, + }, + scope: { firstName: "John", lastName: "Doe" }, + result: ` + + Hello {Doe + + + + + + } + `, }, {