From 9d880716446f1b4c465145a0832925fee35efc1c Mon Sep 17 00:00:00 2001 From: Edwin Hoogerbeets Date: Tue, 14 Nov 2017 11:58:13 -0800 Subject: [PATCH] Add support for Javascript Template (JST) files When parsing attribute values that had no quotes in the source, pass that info to the attribute callback by passing in an empty string for the quote parameter. This way, the callback can decide to add the missing quote or not as it sees fit. --- package.json | 3 +- src/parser.js | 92 ++++++++++++++++++++++++++++------ tests/attribute-tests.js | 21 +++++--- tests/files/good-expected.html | 15 +++++- tests/files/good.html | 13 ++++- tests/sanitization-tests.js | 2 +- tests/xml-prolog-tests.js | 4 +- 7 files changed, 120 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 8a7449e..fcd7354 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "contributors": [ { "name": "jdponomarev" }, { "name": "fiatjaf" }, - { "name": "Sergii Kliuchnyk" } + { "name": "Sergii Kliuchnyk" }, + { "name": "Edwin Hoogerbeets" } ], "main": "./src/parser.js", diff --git a/src/parser.js b/src/parser.js index 6ae5539..57a8eac 100644 --- a/src/parser.js +++ b/src/parser.js @@ -3,20 +3,43 @@ var fs = require('fs'); function readAttribute(context) { var name = context.readRegex(context.regex.attribute); - var value = null; + var value = null, quote = ''; if (context.current === '=' || context.peekIgnoreWhitespace() === '=') { context.readRegex(/\s*=\s*/); - var quote = /['"]/.test(context.current) ? context.current : ''; - var attributeValueRegex = !quote - ? /(.*?)(?=[\s>])/ - : new RegExp(quote + '(.*?)' + quote); + var attributeValueRegex; + switch (context.current) { + case "'": + attributeValueRegex = /('(\\'|<%.*?%>|[^'])*?')/; + quote = "'"; + break; + case '"': + attributeValueRegex = /("(\\"|<%.*?%>|[^"])*?")/; + quote = '"'; + break; + case '<': + attributeValueRegex = (context.peek() === '%') ? + /(<%.*?%>)/ : + /(.*?)(?=[\s><])/; + break; + default: + attributeValueRegex = /(.*?)(?=[\s><])/; + break; + } var match = attributeValueRegex.exec(context.substring) || [0, '']; value = match[1]; context.read(match[0].length); + + if (value[0] === '"' || value[0] === "'") { + value = value.substring(1); + } + + if (value[value.length-1] === '"' || value[value.length-1] === "'") { + value = value.substring(0, value.length-1); + } } - context.callbacks.attribute(name, value); + context.callbacks.attribute(name, value, quote); } function readAttributes(context, isXml) { @@ -28,14 +51,50 @@ function readAttributes(context, isXml) { return context.current === '>' || (context.current === '/' && context.peekIgnoreWhitespace() === '>'); } - var next = context.current; + var next = context.current, handled; while (!context.isEof() && !isClosingToken()) { - if (context.regex.attribute.test(next)) { - readAttribute(context); - next = context.current; + handled = false; + if (context.current === '<') { + for (var callbackName in context.regex.dataElements) { + if (!context.regex.dataElements.hasOwnProperty(callbackName)) { + continue; + } + + var dataElement = context.regex.dataElements[callbackName], + start = dataElement.start, + isValid = false; + + switch (typeof start) { + case 'string': + isValid = context.substring.slice(0, start.length) === start; + break; + case 'object': + isValid = start.test(context.substring); + break; + case 'function': + isValid = start(context.substring) > -1; + break; + } + + if (isValid) { + callbackText(context); + context.callbacks[callbackName](parseDataElement(context, dataElement)); + next = context.current; + handled = true; + break; + } + next = context.current; + } } - else { - next = context.read(); + + if (!handled) { + if (context.regex.attribute.test(next)) { + readAttribute(context); + next = context.current; + } + else { + next = context.read(); + } } } } @@ -73,7 +132,7 @@ function parseOpenElement(context) { readAttributes(context, false); readCloserForOpenedElement(context, name); - if (!/^(script|xmp)$/i.test(name)) { + if (!/^(script|xmp|style)$/i.test(name)) { return; } @@ -432,7 +491,7 @@ exports.sanitize = function(htmlString, removalCallbacks) { sanitized += ''; }, - attribute: function(name, value) { + attribute: function(name, value, quote) { if (ignoreStack.length) { return; } @@ -444,7 +503,8 @@ exports.sanitize = function(htmlString, removalCallbacks) { sanitized += ' ' + name; if (value) { - sanitized += '="' + value.replace(/"/g, '"') + '"'; + // reuse the existing quote style if possible + sanitized += '=' + quote + ((quote === '"') ? value.replace(/"/g, '"') : value.replace(/'/g, ''')) + quote; } }, @@ -468,7 +528,7 @@ exports.sanitize = function(htmlString, removalCallbacks) { } for (var i = tagStack.length - 1; i >= 0; i--) { - if (tagStack[i] === 'script' || tagStack[i] === 'xmp') { + if (tagStack[i] === 'script' || tagStack[i] === 'xmp' || tagStack[i] === 'style') { sanitized += value; return; } diff --git a/tests/attribute-tests.js b/tests/attribute-tests.js index 05bb962..7538b1c 100644 --- a/tests/attribute-tests.js +++ b/tests/attribute-tests.js @@ -29,9 +29,10 @@ describe('attributes', function() { openCount++; }, - attribute: function(name, value) { + attribute: function(name, value, quote) { name.should.equal('bar'); value.should.equal('baz'); + quote.should.equal('"'); attrCount++; } }); @@ -48,9 +49,10 @@ describe('attributes', function() { openCount++; }, - attribute: function(name, value) { + attribute: function(name, value, quote) { name.should.equal('bar'); value.should.equal('baz'); + quote.should.equal("'"); attrCount++; } }); @@ -105,9 +107,10 @@ describe('attributes', function() { openCount++; }, - attribute: function(name, value) { + attribute: function(name, value, quote) { name.should.equal(attrCount === 0 ? 'width' : 'height'); value.should.equal(attrCount === 0 ? '0' : '12'); + quote.should.equal(attrCount === 0 ? '' : '"'); attrCount++; } }); @@ -124,9 +127,10 @@ describe('attributes', function() { openCount++; }, - attribute: function(name, value) { + attribute: function(name, value, quote) { name.should.equal('bar'); value.should.equal('baz'); + quote.should.equal('"'); attrCount++; } }); @@ -143,9 +147,10 @@ describe('attributes', function() { openCount++; }, - attribute: function(name, value) { + attribute: function(name, value, quote) { name.should.equal('bar'); value.should.equal('baz'); + quote.should.equal('"'); attrCount++; } }); @@ -162,9 +167,10 @@ describe('attributes', function() { openCount++; }, - attribute: function(name, value) { + attribute: function(name, value, quote) { name.should.equal('bar:baz'); value.should.equal('bat'); + quote.should.equal('"'); attrCount++; } }); @@ -181,9 +187,10 @@ describe('attributes', function() { openCount++; }, - attribute: function(name, value) { + attribute: function(name, value, quote) { name.should.equal('[bar]'); value.should.equal('baz'); + quote.should.equal('"'); attrCount++; } }, { diff --git a/tests/files/good-expected.html b/tests/files/good-expected.html index 2fd9831..b400d8f 100644 --- a/tests/files/good-expected.html +++ b/tests/files/good-expected.html @@ -2,18 +2,29 @@ tommy montgomery - + +
-
+
diff --git a/tests/files/good.html b/tests/files/good.html index fa1f8ad..7ae1fa3 100644 --- a/tests/files/good.html +++ b/tests/files/good.html @@ -10,13 +10,24 @@ href="/favicon.ico"/> +
-
+
diff --git a/tests/sanitization-tests.js b/tests/sanitization-tests.js index 924cfcc..5b50ff3 100644 --- a/tests/sanitization-tests.js +++ b/tests/sanitization-tests.js @@ -160,7 +160,7 @@ describe('Sanitization', function() { it('should sanitize based on attribute value', function() { var html = ''; var sanitized = helpers.parser.sanitize(html, { - attributes: function(name, value) { + attributes: function(name, value, quote) { return value === 'abc'; } }); diff --git a/tests/xml-prolog-tests.js b/tests/xml-prolog-tests.js index 22fec64..5eeb847 100644 --- a/tests/xml-prolog-tests.js +++ b/tests/xml-prolog-tests.js @@ -8,7 +8,7 @@ describe('XML Prologs', function() { xmlProlog: function() { prologCount++; }, - attribute: function(name, value) { + attribute: function(name, value, quote) { name.should.equal('version'); value.should.equal('1.0'); attributeCount++; @@ -64,7 +64,7 @@ describe('XML Prologs', function() { name.should.equal('foo'); closeCount++; }, - attribute: function(name, value) { + attribute: function(name, value, quote) { openCount.should.equal(1); closeCount.should.equal(0); name.should.equal('version');