Skip to content

Commit

Permalink
Add support for Javascript Template (JST) files
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Edwin Hoogerbeets authored and tmont committed Nov 19, 2017
1 parent adafef8 commit 9d88071
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 30 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"contributors": [
{ "name": "jdponomarev" },
{ "name": "fiatjaf" },
{ "name": "Sergii Kliuchnyk" }
{ "name": "Sergii Kliuchnyk" },
{ "name": "Edwin Hoogerbeets" }
],

"main": "./src/parser.js",
Expand Down
92 changes: 76 additions & 16 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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();
}
}
}
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -432,7 +491,7 @@ exports.sanitize = function(htmlString, removalCallbacks) {
sanitized += '</' + name + '>';
},

attribute: function(name, value) {
attribute: function(name, value, quote) {
if (ignoreStack.length) {
return;
}
Expand All @@ -444,7 +503,8 @@ exports.sanitize = function(htmlString, removalCallbacks) {

sanitized += ' ' + name;
if (value) {
sanitized += '="' + value.replace(/"/g, '&quot;') + '"';
// reuse the existing quote style if possible
sanitized += '=' + quote + ((quote === '"') ? value.replace(/"/g, '&quot;') : value.replace(/'/g, '&apos;')) + quote;
}
},

Expand All @@ -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;
}
Expand Down
21 changes: 14 additions & 7 deletions tests/attribute-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
}
});
Expand All @@ -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++;
}
});
Expand Down Expand Up @@ -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++;
}
});
Expand All @@ -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++;
}
});
Expand All @@ -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++;
}
});
Expand All @@ -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++;
}
});
Expand All @@ -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++;
}
}, {
Expand Down
15 changes: 13 additions & 2 deletions tests/files/good-expected.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@
<html>
<head>
<title>tommy montgomery </title>
<meta foo="bar" http-equiv="content-type" content="text/html;charset=utf-8"/>
<meta foo=bar http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="stylesheet" href="/media/css/global.css"/>
<link rel="shortcut icon" type="image/png" href="/favicon.ico"/>
</head>

<style>
/* comment with tricky <tags> embedded <in> it */
.header {
font: "Lucida Sans";
size: 14pt;
}

div>child-selector {
background-color: yellow;
}
</style>
<body>
<div id="wrapper">
<div id="header">
<h1><a href="/"><span>t</span>ommy <span>mont</span>gomery</a></h1>
</div>

<div id="main-content">
<div id='single-quote-class'>
<div id="menu"></div>
</div>
</div>
Expand Down
13 changes: 12 additions & 1 deletion tests/files/good.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,24 @@
href="/favicon.ico"/>
</head>

<style>
/* comment with tricky <tags> embedded <in> it */
.header {
font: "Lucida Sans";
size: 14pt;
}

div>child-selector {
background-color: yellow;
}
</style>
<body>
<div id="wrapper">
<div id="header">
<h1><a href="/"><span>t</span>ommy <span>mont</span>gomery</a></h1>
</div>

<div id="main-content">
<div id='single-quote-class'>
<div id="menu"></div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion tests/sanitization-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ describe('Sanitization', function() {
it('should sanitize based on attribute value', function() {
var html = '<foo id="abc"></foo><foo id="def"></foo>';
var sanitized = helpers.parser.sanitize(html, {
attributes: function(name, value) {
attributes: function(name, value, quote) {
return value === 'abc';
}
});
Expand Down
4 changes: 2 additions & 2 deletions tests/xml-prolog-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand Down Expand Up @@ -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');
Expand Down

0 comments on commit 9d88071

Please sign in to comment.