diff --git a/src/twig.expression.js b/src/twig.expression.js index e58322c6..60040bdf 100644 --- a/src/twig.expression.js +++ b/src/twig.expression.js @@ -576,14 +576,14 @@ module.exports = function (Twig) { }, { type: Twig.expression.type.slice, - regex: /^\[(\d*:\d*)\]/, + regex: /^\[(-?\w*:-?\w*)\]/, next: Twig.expression.set.operationsExtended, compile(token, stack, output) { const sliceRange = token.match[1].split(':'); // SliceStart can be undefined when we pass parameters to the slice filter later - const sliceStart = (sliceRange[0]) ? parseInt(sliceRange[0], 10) : undefined; - const sliceEnd = (sliceRange[1]) ? parseInt(sliceRange[1], 10) : undefined; + const sliceStart = sliceRange[0]; + const sliceEnd = sliceRange[1]; token.value = 'slice'; token.params = [sliceStart, sliceEnd]; @@ -596,11 +596,39 @@ module.exports = function (Twig) { output.push(token); }, - parse(token, stack) { + parse(token, stack, context) { const input = stack.pop(); - const {params} = token; + let {params} = token; const state = this; + if (parseInt(params[0], 10).toString() === params[0]) { + params[0] = parseInt(params[0], 10); + } else { + const value = context[params[0]]; + if (state.template.options.strictVariables && value === undefined) { + throw new Twig.Error('Variable "' + params[0] + '" does not exist.'); + } + + params[0] = value; + } + + if (params[1]) { + if (parseInt(params[1], 10).toString() === params[1]) { + params[1] = parseInt(params[1], 10); + } else { + const value = context[params[1]]; + if (state.template.options.strictVariables && value === undefined) { + throw new Twig.Error('Variable "' + params[1] + '" does not exist.'); + } + + if (value === undefined) { + params = [params[0]]; + } else { + params[1] = value; + } + } + } + stack.push(Twig.filter.call(state, token.value, input, params)); } }, diff --git a/src/twig.filters.js b/src/twig.filters.js index 18b3fd89..30ad66a8 100644 --- a/src/twig.filters.js +++ b/src/twig.filters.js @@ -644,9 +644,13 @@ module.exports = function (Twig) { // Default to start of string const start = params[0] || 0; // Default to length of string - const length = params.length > 1 ? params[1] : value.length; + let length = params.length > 1 ? params[1] : value.length; // Handle negative start values const startIndex = start >= 0 ? start : Math.max(value.length + start, 0); + // Handle negative length values + if (length < 0) { + length = value.length - startIndex + length; + } if (Twig.lib.is('Array', value)) { const output = []; diff --git a/test/test.expressions.js b/test/test.expressions.js index 3563bd80..1552ab51 100644 --- a/test/test.expressions.js +++ b/test/test.expressions.js @@ -467,6 +467,36 @@ describe('Twig.js Expressions ->', function () { output.should.equal('23'); }); + it('should support slice shorthand (full form) with negative start', function () { + const testTemplate = twig({data: '{{ "12345"[-2:1] }}'}); + const output = testTemplate.render(); + output.should.equal('4'); + }); + + it('should support slice shorthand (full form) with negative lenght', function () { + const testTemplate = twig({data: '{{ "12345"[2:-1] }}'}); + const output = testTemplate.render(); + output.should.equal('34'); + }); + + it('should support slice shorthand (full form) with variables as arguments', function () { + const testTemplate = twig({data: '{{ "12345"[start:length] }}'}); + const output = testTemplate.render({start: 2, length: 3}); + output.should.equal('345'); + }); + + it('should support slice shorthand (full form) with variable as argument (omit first)', function () { + const testTemplate = twig({data: '{{ "12345"[:length] }}'}); + const output = testTemplate.render({length: 3}); + output.should.equal('123'); + }); + + it('should support slice shorthand (full form) variable as argument (omit last)', function () { + const testTemplate = twig({data: '{{ "12345"[start:] }}'}); + const output = testTemplate.render({start: 2}); + output.should.equal('345'); + }); + it('should support slice shorthand (omit first)', function () { const testTemplate = twig({data: '{{ "12345"[:2] }}'}); const output = testTemplate.render(); diff --git a/test/test.filters.js b/test/test.filters.js index a0323ab0..c832d2c8 100644 --- a/test/test.filters.js +++ b/test/test.filters.js @@ -640,6 +640,10 @@ describe('Twig.js Filters ->', function () { const testTemplate = twig({data: '{{ \'12345\'|slice(1, 2) }}'}); testTemplate.render().should.equal('23'); }); + it('should slice a string with variables as arguments', function () { + const testTemplate = twig({data: '{{ \'12345\'|slice(start, length) }}'}); + testTemplate.render({start: 2, length: 3}).should.equal('345'); + }); it('should slice a string to the end', function () { const testTemplate = twig({data: '{{ \'12345\'|slice(2) }}'}); testTemplate.render().should.equal('345');