From 81924f883ecc89a04f7c5dc92d211639210f792a Mon Sep 17 00:00:00 2001 From: Qing Ye Date: Sun, 21 Jul 2019 03:10:24 +0800 Subject: [PATCH] fix: further improve rendering for if and assign tag --- src/builtin/filters/string.ts | 2 +- src/builtin/tags/assign.ts | 14 +++++-------- src/builtin/tags/if.ts | 7 +++++-- src/render/syntax.ts | 13 ++++++------ src/util/underscore.ts | 24 ++++++++++++++++++++++ test/integration/builtin/filters/string.ts | 3 +++ test/integration/builtin/tags/assign.ts | 15 ++++++++++++++ 7 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/builtin/filters/string.ts b/src/builtin/filters/string.ts index 9792db0..374596b 100644 --- a/src/builtin/filters/string.ts +++ b/src/builtin/filters/string.ts @@ -13,7 +13,7 @@ export default { _.stringify(v).split(pattern).join(replacement), 'replace_first': (v: string, arg1: string, arg2: string) => _.stringify(v).replace(arg1, arg2), 'rstrip': (str: string) => _.stringify(str).replace(/\s+$/, ''), - 'split': (v: string, arg: string) => _.stringify(v).split(arg), + 'split': (v: string, arg: string) => _.stringify(v).replace(new RegExp(`(${_.escapeRegExp(arg)})+$`), '').split(arg), 'strip': (v: string) => _.stringify(v).trim(), 'strip_newlines': (v: string) => _.stringify(v).replace(/\n/g, ''), 'truncate': (v: string, l: number = 50, o: string = '...') => { diff --git a/src/builtin/tags/assign.ts b/src/builtin/tags/assign.ts index e5ae80f..2dd2042 100644 --- a/src/builtin/tags/assign.ts +++ b/src/builtin/tags/assign.ts @@ -5,7 +5,6 @@ import Context from '../../context/context' import ITagImplOptions from '../../template/tag/itag-impl-options' const re = new RegExp(`(${identifier.source})\\s*=\\s*([^]*)`) -const closingOutputRe = new RegExp(`\\}\\}`, 'g') export default { parse: function (token: TagToken) { @@ -13,15 +12,12 @@ export default { assert(match, `illegal token ${token.raw}`) this.key = match[1] this.value = match[2] + this.token = token }, render: async function (ctx: Context) { - let parsedValue - const closingCount = (this.value.match(closingOutputRe) || []).length - if (this.value.startsWith(ctx.opts.outputDelimiterLeft) && closingCount === 1) { - parsedValue = await this.liquid.parseAndRender(this.value, ctx.getAll()) - } else { - parsedValue = await this.liquid.evalValue(this.value, ctx) - } - ctx.front()[this.key] = parsedValue + const value = this.value + .replace(ctx.opts.outputDelimiterLeft, '') + .replace(ctx.opts.outputDelimiterRight, '') + ctx.front()[this.key] = await this.liquid.evalValue(value, ctx) } } as ITagImplOptions diff --git a/src/builtin/tags/if.ts b/src/builtin/tags/if.ts index e7a9204..dccff2b 100644 --- a/src/builtin/tags/if.ts +++ b/src/builtin/tags/if.ts @@ -1,4 +1,5 @@ import { evalExp, isTruthy } from '../../render/syntax' +import { escapeRegExp } from '../../util/underscore' import TagToken from '../../parser/tag-token' import Token from '../../parser/token' import Context from '../../context/context' @@ -6,8 +7,6 @@ import ITemplate from '../../template/itemplate' import ITagImplOptions from '../../template/tag/itag-impl-options' import ParseStream from '../../parser/parse-stream' -const re = new RegExp(`\\{\\{\\s*(.*?)\\s*\\}\\}`) - export default { parse: function (tagToken: TagToken, remainTokens: Token[]) { this.branches = [] @@ -36,6 +35,10 @@ export default { }, render: async function (ctx: Context) { + const re = new RegExp( + `${escapeRegExp(ctx.opts.outputDelimiterLeft)}\\s*(.*?)\\s*${escapeRegExp(ctx.opts.outputDelimiterRight)}` + ) + for (const branch of this.branches) { const parsedCond = branch.cond.replace(re, '$1') const cond = await evalExp(parsedCond, ctx) diff --git a/src/render/syntax.ts b/src/render/syntax.ts index 54dc514..bab18e2 100644 --- a/src/render/syntax.ts +++ b/src/render/syntax.ts @@ -1,7 +1,7 @@ import * as lexical from '../parser/lexical' import assert from '../util/assert' import Context from '../context/context' -import { range, last, isFunction } from '../util/underscore' +import { range, last, isFunction, escapeRegExp } from '../util/underscore' import { isComparable } from '../drop/icomparable' import { NullDrop } from '../drop/null-drop' import { EmptyDrop } from '../drop/empty-drop' @@ -87,6 +87,12 @@ async function parseValue (str: string | undefined, ctx: Context): Promise if (!isNaN(Number(str))) return Number(str) if ((str[0] === '"' || str[0] === "'") && str[0] === last(str)) return str.slice(1, -1) + // for Braze, strip {{ and }} + const re = new RegExp( + `${escapeRegExp(ctx.opts.outputDelimiterLeft)}\\s*(.*?)\\s*${escapeRegExp(ctx.opts.outputDelimiterRight)}` + ) + str = str.replace(re, '$1') + // extension for Braze attributes let match if ((match = str.match(lexical.attribute))) { @@ -94,11 +100,6 @@ async function parseValue (str: string | undefined, ctx: Context): Promise return ctx.get(match[2]) } - // for Braze, strip {{ and }} - str = str - .replace(ctx.opts.outputDelimiterLeft, '') - .replace(ctx.opts.outputDelimiterRight, '') - return ctx.get(str) } diff --git a/src/util/underscore.ts b/src/util/underscore.ts index e751963..bb03b77 100644 --- a/src/util/underscore.ts +++ b/src/util/underscore.ts @@ -1,5 +1,29 @@ const toStr = Object.prototype.toString +const specials = [ + '-', + '[', + ']', + '/', + '{', + '}', + '(', + ')', + '*', + '+', + '?', + '.', + '\\', + '^', + '$', + '|' +] +const regex = RegExp('[' + specials.join('\\') + ']', 'g') + +export function escapeRegExp (str: string) { + return str.replace(regex, '\\$&') +} + /* * Checks if value is classified as a String primitive or object. * @param {any} value The value to check. diff --git a/test/integration/builtin/filters/string.ts b/test/integration/builtin/filters/string.ts index 0839835..a259e2b 100644 --- a/test/integration/builtin/filters/string.ts +++ b/test/integration/builtin/filters/string.ts @@ -106,6 +106,9 @@ describe('filters/string', function () { '{% endfor %}', 'John Paul George Ringo ') }) + it('should ignore empty string at the end', function () { + return test('{% assign a = ",123,456,," | split: "," %}{{a}}', '["", "123", "456"]') + }) it('should support strip', function () { return test('{{ " So much room for activities! " | strip }}', 'So much room for activities!') diff --git a/test/integration/builtin/tags/assign.ts b/test/integration/builtin/tags/assign.ts index 9e332a6..a2dd442 100644 --- a/test/integration/builtin/tags/assign.ts +++ b/test/integration/builtin/tags/assign.ts @@ -55,11 +55,26 @@ describe('tags/assign', function () { const html = await liquid.parseAndRender(src, { bar: 'baz', a: { b: 'test bar' } }) return expect(html).to.equal('test baz') }) + it('should support object in variable', async function () { + const src = '{% assign foo = {{a}} %}{{foo.b}}' + const html = await liquid.parseAndRender(src, { a: { b: 'test' } }) + return expect(html).to.equal('test') + }) + it('should support variable with filter', async function () { + const src = '{% assign foo = {{a | slice: 0, 2 }} %}{{foo}}' + const html = await liquid.parseAndRender(src, { a: [1, 2, 3] }) + return expect(html).to.equal('[1, 2]') + }) it('should support spaces around =', async function () { const src = '{% assign foo = {{bar}} %}{{foo}}' const html = await liquid.parseAndRender(src, { bar: 'value_of_bar' }) return expect(html).to.equal('value_of_bar') }) + it('should support variable and filter', async function () { + const src = '{% assign foo = {{bar}} | split: "|" %}{{foo}}' + const html = await liquid.parseAndRender(src, { bar: '123|' }) + return expect(html).to.equal('["123"]') + }) it('should assign var-1', async function () { const src = '{% assign var-1 = 5 %}{{ var-1 }}' const html = await liquid.parseAndRender(src)