From 5cf39ff88cb8467af054dd34c54d4dfa84e2bde5 Mon Sep 17 00:00:00 2001 From: Duncan Beevers Date: Sat, 5 Jul 2014 14:18:38 -0500 Subject: [PATCH] Attributes can be wrapped with custom markup Allows for recognizing attributes wrapped by handlebars conditionals --- src/htmlminifier.js | 6 ++-- src/htmlparser.js | 72 ++++++++++++++++++++++++++++++++++++++++----- tests/minifier.js | 13 ++++++++ 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/src/htmlminifier.js b/src/htmlminifier.js index 5e160d3..060447e 100644 --- a/src/htmlminifier.js +++ b/src/htmlminifier.js @@ -337,7 +337,7 @@ attrFragment = attrName + '=' + attrValue; } - return (' ' + attrFragment); + return (' ' + attr.customOpen + attrFragment + attr.customClose); } function setDefaultTesters(options) { @@ -595,7 +595,9 @@ }, doctype: function(doctype) { buffer.push(options.useShortDoctype ? '' : collapseWhitespace(doctype)); - } + }, + customAttrOpen: options.customAttrOpen, + customAttrClose: options.customAttrClose }); results.push.apply(results, buffer); diff --git a/src/htmlparser.js b/src/htmlparser.js index d6e19c6..cecd65a 100644 --- a/src/htmlparser.js +++ b/src/htmlparser.js @@ -32,10 +32,12 @@ 'use strict'; // Regular Expressions for parsing tags and attributes - var startTag = /^<([\w:-]+)((?:\s*[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, + var startTagOpen = /^<([\w:-]+)/, + startTagAttrs = /(?:\s*[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*/, + startTagClose = /\s*(\/?)>/, endTag = /^<\/([\w:-]+)[^>]*>/, endingSlash = /\/>$/, - attr = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g, + singleAttr = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/, doctype = /^]+>/i, startIgnore = /<(%|\?)/, endIgnore = /(%|\?)>/; @@ -67,6 +69,36 @@ return this[ this.length - 1 ]; }; + var startTag, attr; + var customStartTagAttrs; + if (handler.customAttrOpen && handler.customAttrClose) { + customStartTagAttrs = new RegExp( + '(' + + '(?:' + + // Capture the custom attribute opening and closing markup surrounding the standard attribute rules + '(?:\\s*' + handler.customAttrOpen.source + startTagAttrs.source + handler.customAttrClose.source + ')' + + '|' + + // Or capture just the standard attribute rules + startTagAttrs.source + + ')*' + + ')' + ); + attr = new RegExp( + '(?:' + + '(' + handler.customAttrOpen.source + ')' + + singleAttr.source + + '(' + handler.customAttrClose.source + ')' + + ')|(?:' + singleAttr.source + ')', 'g' + ); + } + else { + // No custom attribute wrappers specified, so just capture the standard attribute rules + customStartTagAttrs = new RegExp('(' + startTagAttrs.source + ')'); + attr = new RegExp(singleAttr.source, 'g'); + } + + startTag = new RegExp(startTagOpen.source + customStartTagAttrs.source + startTagClose.source); + while ( html ) { chars = true; @@ -225,15 +257,39 @@ if ( handler.start ) { var attrs = []; - rest.replace(attr, function(match, name) { - var value = arguments[2] ? arguments[2] : - arguments[3] ? arguments[3] : - arguments[4] ? arguments[4] : - fillAttrs[name] ? name : arguments[2]; + rest.replace(attr, function() { + var name, value, fallbackValue, customOpen, customClose; + + if (arguments.length === 7) { + name = arguments[1]; + fallbackValue = arguments[2]; + value = fallbackValue + || arguments[3] + || arguments[4]; + } + else { + name = arguments[2] || arguments[7]; + fallbackValue = arguments[3]; + value = fallbackValue + || arguments[4] + || arguments[5] + || arguments[8] + || arguments[9] + || arguments[10]; + customOpen = arguments[1]; + customClose = arguments[6]; + } + + if ( value === undefined ) { + value = fillAttrs[name] ? name : fallbackValue; + } + attrs.push({ name: name, value: value, - escaped: value && value.replace(/(^|[^\\])"/g, '$1"') + escaped: value && value.replace(/(^|[^\\])"/g, '$1"'), + customOpen: customOpen || '', + customClose: customClose || '' }); }); diff --git a/tests/minifier.js b/tests/minifier.js index 7242e45..31f3a95 100644 --- a/tests/minifier.js +++ b/tests/minifier.js @@ -502,6 +502,19 @@ equal(minify(input, { removeAttributeQuotes: true }), '

'); }); + test('preserving custom attribute-wrapping markup', function() { + var customAttrOptions = { + customAttrOpen: /\{\{#if\s+\w+\}\}/, + customAttrClose: /\{\{\/if\}\}/ + }; + + input = ''; + equal(minify(input, customAttrOptions), ''); + + input = ''; + equal(minify(input, customAttrOptions), ''); + }); + test('collapsing whitespace', function() { input = '