diff --git a/README.md b/README.md index 7a3463cc35..93b8eb0034 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,10 @@ Otherwise, undefined variables will cause an exception. Defaults to `false`. * `trim_output_left` is similiar to `trim_output_right`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details. +* `tag_delimiter_left` and `tag_delimiter_right` are used to override the delimiter for liquid tags. + +* `output_delimiter_left` and `output_delimiter_right` are used to override the delimiter for liquid outputs. + * `greedy` is used to specify whether `trim_left`/`trim_right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`. ## Register Filters diff --git a/src/liquid-options.ts b/src/liquid-options.ts index 5088e55a12..25fc5d2487 100644 --- a/src/liquid-options.ts +++ b/src/liquid-options.ts @@ -23,6 +23,12 @@ export interface LiquidOptions { trim_output_right?: boolean /** `trim_output_left` is similar to `trim_output_right`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */ trim_output_left?: boolean + /** `tag_delimiter_left` and `tag_delimiter_right` are used to override the delimiter for liquid tags **/ + tag_delimiter_left?: string, + tag_delimiter_right?: string, + /** `output_delimiter_left` and `output_delimiter_right` are used to override the delimiter for liquid outputs **/ + output_delimiter_left?: string, + output_delimiter_right?: string, /** `greedy` is used to specify whether `trim_left`/`trim_right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`. */ greedy?: boolean } @@ -55,6 +61,10 @@ const defaultOptions: NormalizedFullOptions = { trim_output_right: false, trim_output_left: false, greedy: true, + tag_delimiter_left: '{%', + tag_delimiter_right: '%}', + output_delimiter_left: '{{', + output_delimiter_right: '}}', strict_filters: false, strict_variables: false } diff --git a/src/parser/delimited-token.ts b/src/parser/delimited-token.ts index 6b48b4c95f..b2b4e32b88 100644 --- a/src/parser/delimited-token.ts +++ b/src/parser/delimited-token.ts @@ -3,10 +3,15 @@ import Token from './token' export default class DelimitedToken extends Token { trimLeft: boolean trimRight: boolean - constructor (raw, pos, input, file, line) { + constructor (raw, value, pos, input, file, line) { super(raw, pos, input, file, line) - this.trimLeft = raw[2] === '-' - this.trimRight = raw[raw.length - 3] === '-' - this.value = raw.slice(this.trimLeft ? 3 : 2, this.trimRight ? -3 : -2).trim() + this.trimLeft = value[0] === '-' + this.trimRight = value[value.length - 1] === '-' + this.value = value + .slice( + this.trimLeft ? 1 : 0, + this.trimRight ? -1 : value.length + ) + .trim() } } diff --git a/src/parser/output-token.ts b/src/parser/output-token.ts index 294399e54d..294ddc57b6 100644 --- a/src/parser/output-token.ts +++ b/src/parser/output-token.ts @@ -1,8 +1,8 @@ import DelimitedToken from './delimited-token' export default class OutputToken extends DelimitedToken { - constructor (raw, pos, input, file, line) { - super(raw, pos, input, file, line) + constructor (raw, value, pos, input, file, line) { + super(raw, value, pos, input, file, line) this.type = 'output' } } diff --git a/src/parser/tag-token.ts b/src/parser/tag-token.ts index 4d6b8abaec..2bc6c303dd 100644 --- a/src/parser/tag-token.ts +++ b/src/parser/tag-token.ts @@ -5,8 +5,8 @@ import * as lexical from './lexical' export default class TagToken extends DelimitedToken { name: string args: string - constructor (raw, pos, input, file, line) { - super(raw, pos, input, file, line) + constructor (raw, value, pos, input, file, line) { + super(raw, value, pos, input, file, line) this.type = 'tag' const match = this.value.match(lexical.tagLine) if (!match) { diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts index f38d1165ca..7db681379f 100644 --- a/src/parser/tokenizer.ts +++ b/src/parser/tokenizer.ts @@ -15,6 +15,10 @@ export default class Tokenizer { } tokenize (input: string, file?: string) { const tokens = [] + const tagL = this.options.tag_delimiter_left + const tagR = this.options.tag_delimiter_right + const outputL = this.options.output_delimiter_left + const outputR = this.options.output_delimiter_right let p = 0 let curLine = 1 let state = ParseState.HTML @@ -28,30 +32,37 @@ export default class Tokenizer { curLine++ lineBegin = p + 1 } - const bin = input.substr(p, 2) if (state === ParseState.HTML) { - if (bin === '{{' || bin === '{%') { + if (input.substr(p, outputL.length) === outputL) { if (buffer) tokens.push(new HTMLToken(buffer, col, input, file, line)) - buffer = bin + buffer = outputL line = curLine col = p - lineBegin + 1 - p += 2 - state = bin === '{{' ? ParseState.OUTPUT : ParseState.TAG + p += outputL.length + state = ParseState.OUTPUT + continue + } else if (input.substr(p, tagL.length) === tagL) { + if (buffer) tokens.push(new HTMLToken(buffer, col, input, file, line)) + buffer = tagL + line = curLine + col = p - lineBegin + 1 + p += tagL.length + state = ParseState.TAG continue } - } else if (state === ParseState.OUTPUT && bin === '}}') { - buffer += '}}' - tokens.push(new OutputToken(buffer, col, input, file, line)) - p += 2 + } else if (state === ParseState.OUTPUT && input.substr(p, outputR.length) === outputR) { + buffer += outputR + tokens.push(new OutputToken(buffer, buffer.slice(outputL.length, -outputR.length), col, input, file, line)) + p += outputR.length buffer = '' line = curLine col = p - lineBegin + 1 state = ParseState.HTML continue - } else if (bin === '%}') { - buffer += '%}' - tokens.push(new TagToken(buffer, col, input, file, line)) - p += 2 + } else if (input.substr(p, tagR.length) === tagR) { + buffer += tagR + tokens.push(new TagToken(buffer, buffer.slice(tagL.length, -tagR.length), col, input, file, line)) + p += tagR.length buffer = '' line = curLine col = p - lineBegin + 1 diff --git a/src/parser/whitespace-ctrl.ts b/src/parser/whitespace-ctrl.ts index d5612116ad..99a8e13793 100644 --- a/src/parser/whitespace-ctrl.ts +++ b/src/parser/whitespace-ctrl.ts @@ -1,9 +1,9 @@ import DelimitedToken from 'src/parser/delimited-token' import Token from 'src/parser/token' import TagToken from 'src/parser/tag-token' -import { LiquidOptions } from 'src/liquid-options' +import { NormalizedFullOptions } from 'src/liquid-options' -export default function whiteSpaceCtrl (tokens: Token[], options: LiquidOptions) { +export default function whiteSpaceCtrl (tokens: Token[], options: NormalizedFullOptions) { options = { greedy: true, ...options } let inRaw = false diff --git a/test/unit/liquid/delimiter.ts b/test/unit/liquid/delimiter.ts new file mode 100644 index 0000000000..2f97870d6d --- /dev/null +++ b/test/unit/liquid/delimiter.ts @@ -0,0 +1,31 @@ +import { expect } from 'chai' +import Liquid from 'src/liquid' + +describe('LiquidOptions#*_delimiter_*', function () { + it('should respect tag_delimiter_*', async function () { + const engine = new Liquid({ + tag_delimiter_left: '<%=', + tag_delimiter_right: '%>' + }) + const html = await engine.parseAndRender('<%=if true%>foo<%=endif%> ') + return expect(html).to.equal('foo ') + }) + it('should respect output_delimiter_*', async function () { + const engine = new Liquid({ + output_delimiter_left: '<<', + output_delimiter_right: '>>' + }) + const html = await engine.parseAndRender('<< "liquid" | capitalize >>') + return expect(html).to.equal('Liquid') + }) + it('should support trimming with tag_delimiter_* set', async function () { + const engine = new Liquid({ + tag_delimiter_left: '<%=', + tag_delimiter_right: '%>', + trim_tag_left: true, + trim_tag_right: true + }) + const html = await engine.parseAndRender(' <%=if true%> \tfoo\t <%=endif%> ') + return expect(html).to.equal('foo') + }) +})