html-minifier 0.6.5 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,30 +1,53 @@
1
- ## [0.6.5](https://github.com/kangax/html-minifier/compare/v0.6.4...0.6.5)
1
+ ## [0.6.7](https://github.com/kangax/html-minifier/compare/v0.6.6...v0.6.7)
2
+
3
+ * Preserve empty script with `src` attribute
4
+ * Make `draggable` enumerated attribute
5
+ * Add option to preserve a single line break on collapse
6
+ * Do not treat draggable as a boolean attribute
7
+ * Add `minifyURLs` option
8
+ * Add `customAttrCollapse` option
9
+ * `collapseBoolean` is case-insensitive
10
+ * Remove removable attrs with unspecified values
11
+ * `removeComments` affects htmlmin:ignore comments
12
+ * Quote terminal attribute value by unary slash
13
+ * Closing tag search is case-insensitive
14
+ * `s` tag has been reinstated
15
+ * Remove `i`, `b`, `u` tags from linter as problematic
16
+
17
+ ## [0.6.6](https://github.com/kangax/html-minifier/compare/v0.6.5...v0.6.6)
18
+
19
+ * Fix Firefox attribute bug
20
+ * Add `customAttrAssign` option
21
+ * Add `maxLineLength` option
22
+ * Add `customAttrSurround` option
23
+
24
+ ## [0.6.5](https://github.com/kangax/html-minifier/compare/v0.6.4...v0.6.5)
2
25
 
3
26
  * `caseSensitive` option now applies to tags and attributes
4
27
 
5
- ## [0.6.4](https://github.com/kangax/html-minifier/compare/v0.6.3...0.6.4)
28
+ ## [0.6.4](https://github.com/kangax/html-minifier/compare/v0.6.3...v0.6.4)
6
29
 
7
30
  * Support for mixed-case tags
8
31
  * Support IE downlevel-revealed conditional comments
9
32
  * Add support for `wbr` element
10
33
 
11
- ## [0.6.3](https://github.com/kangax/html-minifier/compare/v0.6.2...0.6.3)
34
+ ## [0.6.3](https://github.com/kangax/html-minifier/compare/v0.6.2...v0.6.3)
12
35
 
13
36
  * Do not treat `translate` attribute as boolean
14
37
  * Add `svg` element in inline list
15
38
  * Do not remove special spaces like ` `
16
39
  * Make `collapseWhitespace` more resilient
17
40
 
18
- ## [0.6.2](https://github.com/kangax/html-minifier/compare/v0.6.1...0.6.2)
41
+ ## [0.6.2](https://github.com/kangax/html-minifier/compare/v0.6.1...v0.6.2)
19
42
 
20
43
  * Fix bug introduced by `processScripts` option
21
44
 
22
- ## [0.6.1](https://github.com/kangax/html-minifier/compare/v0.6.0...0.6.1)
45
+ ## [0.6.1](https://github.com/kangax/html-minifier/compare/v0.6.0...v0.6.1)
23
46
 
24
47
  * Add CLI
25
48
  * Fix error with valueless attributes (e.g. `<a href>`). [#169](https://github.com/kangax/html-minifier/issues/169)
26
49
 
27
- ## [0.6.0](https://github.com/kangax/html-minifier/compare/v0.5.6...0.6.0)
50
+ ## [0.6.0](https://github.com/kangax/html-minifier/compare/v0.5.6...v0.6.0)
28
51
 
29
52
  * Minify meta viewport value [#159](https://github.com/kangax/html-minifier/issues/159)
30
53
  * Add support for ignoring markup via `<!-- htmlmin:ignore -->` [#89](https://github.com/kangax/html-minifier/issues/89)
package/README.md CHANGED
@@ -9,17 +9,17 @@ See [corresponding blog post](http://perfectionkills.com/experimenting-with-html
9
9
 
10
10
  [Test suite is available online](http://kangax.github.io/html-minifier/tests/).
11
11
 
12
- Also see corresponding [Ruby wrapper](https://github.com/stereobooster/html_minifier), and for Node.js, [Grunt plugin](https://github.com/gruntjs/grunt-contrib-htmlmin) & [Gulp module](https://github.com/jonschlinkert/gulp-htmlmin).
12
+ Also see corresponding [Ruby wrapper](https://github.com/stereobooster/html_minifier), and for Node.js, [Grunt plugin](https://github.com/gruntjs/grunt-contrib-htmlmin), [Gulp module](https://github.com/jonschlinkert/gulp-htmlmin), and [Koa middleware wrapper](https://github.com/koajs/html-minifier).
13
13
 
14
14
  How does HTMLMinifier compare to [another solution](http://www.willpeavy.com/minifier/) — HTML Minifier from Will Peavy (1st result in [google search for "html minifier"](https://www.google.com/#q=html+minifier)) as well as htmlcompressor.com?
15
15
 
16
16
  | Site | Original size _(KB)_ | HTMLMinifier _(KB)_ | Will Peavy _(KB)_ | htmlcompressor.com _(KB)_ |
17
17
  | --------------------------------------------------------------------------- |:-----------:| ----------------:| ------------:| ----------------:|
18
18
  | [HTMLMinifier page](https://github.com/kangax/html-minifier) | 48.8 | <b>37.3</b> | 43.3 | 41.9 |
19
- | [ES6 table](http://kangax.github.io/es5-compat-table/es6/) | 117.9 | <b>79.9</b> | 92 | 91.9 |
19
+ | [ES6 table](http://kangax.github.io/es5-compat-table/es6/) | 117.9 | <b>79.9</b> | 92 | 91.9 |
20
20
  | [MSN](http://msn.com) | 156.6 | <b>133</b> | 145 | 138.3 |
21
21
  | [Stackoverflow](http://stackoverflow.com) | 200.4 | <b>159.5</b> | 168.3 | 163.3 |
22
- | [Amazon](http://amazon.com) | 245.9 | <b>206.3</b> | 225 | 218.5 |
22
+ | [Amazon](http://amazon.com) | 245.9 | <b>206.3</b> | 225 | 218.5 |
23
23
  | [Wikipedia](http://en.wikipedia.org/wiki/President_of_the_United_States) | 401.4 | <b>380.6</b> | 396.3 | n/a |
24
24
  | [Eloquent Javascript](http://eloquentjavascript.net/print.html) | 869.5 | <b>830</b> | 872 | n/a |
25
25
 
@@ -34,52 +34,69 @@ How does HTMLMinifier compare to [another solution](http://www.willpeavy.com/min
34
34
  | `removeCDATASectionsFromCDATA` | [Remove CDATA sections from script and style elements](http://perfectionkills.com/experimenting-with-html-minifier/#remove_cdata_sections) | `false` |
35
35
  | `collapseWhitespace` | [Collapse white space that contributes to text nodes in a document tree.](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_whitespace) | `false` |
36
36
  | `conservativeCollapse` | Always collapse to 1 space (never remove it entirely). Must be used in conjunction with `collapseWhitespace=true` | `false` |
37
+ | `preserveLineBreaks` | Always collapse to 1 line break (never remove it entirely) when whitespace between tags include a line break. Must be used in conjunction with `collapseWhitespace=true` | `false` |
37
38
  | `collapseBooleanAttributes` | [Omit attribute values from boolean attributes](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_boolean_attributes) | `false` |
38
39
  | `removeAttributeQuotes` | [Remove quotes around attributes when possible.](http://perfectionkills.com/experimenting-with-html-minifier/#remove_attribute_quotes) | `false` |
39
40
  | `removeRedundantAttributes` | [Remove attributes when value matches default.](http://perfectionkills.com/experimenting-with-html-minifier/#remove_redundant_attributes) | `false` |
40
41
  | `useShortDoctype` | [Replaces the doctype with the short (HTML5) doctype](http://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype) | `false` |
41
42
  | `removeEmptyAttributes` | [Remove all attributes with whitespace-only values](http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_or_blank_attributes) | `false` |
43
+ | `removeScriptTypeAttributes` | Remove `type="text/javascript"` from `script` tags. Other `type` attribute values are left intact. | `false` |
44
+ | `removeStyleLinkTypeAttributes`| Remove `type="text/css"` from `style` and `link` tags. Other `type` attribute values are left intact. | `false` |
42
45
  | `removeOptionalTags` | [Remove unrequired tags](http://perfectionkills.com/experimenting-with-html-minifier/#remove_optional_tags) | `false` |
46
+ | `removeIgnored` | Remove all tags starting and ending with `<%`, `%>`, `<?`, `?>` | `false` |
43
47
  | `removeEmptyElements` | [Remove all elements with empty contents](http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_elements) | `false` |
44
48
  | `lint` | [Toggle linting](http://perfectionkills.com/experimenting-with-html-minifier/#validate_input_through_html_lint) | `false` |
45
49
  | `keepClosingSlash` | Keep the trailing slash on singleton elements | `false` |
46
50
  | `caseSensitive` | Treat attributes in case sensitive manner (useful for SVG; e.g. viewBox) | `false` |
47
51
  | `minifyJS` | Minify Javascript in script elements and on* attributes (uses [UglifyJS](https://github.com/mishoo/UglifyJS2)) | `false` (could be `true`, `false`, `Object` (options)) |
48
- | `minifyCSS` | Minify CSS in style elements and style attributes (uses [clean-css](https://github.com/GoalSmashers/clean-css)) | `false` (could be `true`, `false`, `Object` (options)) |
49
- | `ignoreCustomComments` | Array of regex'es that allow to ignore certain comments, when matched | `[ ]` |
50
- | `processScripts` | Array of strings corresponding to types of script elements to process through minifier (e.g. "text/ng-template", "text/x-handlebars-template", etc.) | `[ ]` |
52
+ | `minifyCSS` | Minify CSS in style elements and style attributes (uses [clean-css](https://github.com/GoalSmashers/clean-css)) | `false` (could be `true`, `false`, `Object` (options)) |
53
+ | `minifyURLs` | Minify URLs in various attributes (uses [relateurl](https://github.com/stevenvachon/relateurl)) | `false` (could be `Object` (options)) |
54
+ | `ignoreCustomComments` | Array of regex'es that allow to ignore certain comments, when matched | `[ ]` |
55
+ | `processScripts` | Array of strings corresponding to types of script elements to process through minifier (e.g. `text/ng-template`, `text/x-handlebars-template`, etc.) | `[ ]` |
56
+ | `maxLineLength` | Specify a maximum line length. Compressed output will be split by newlines at valid HTML split-points. |
57
+ | `customAttrAssign` | Arrays of regex'es that allow to support custom attribute assign expressions (e.g. `'<div flex?="{{mode != cover}}"></div>'`) | `[ ]` |
58
+ | `customAttrSurround` | Arrays of regex'es that allow to support custom attribute surround expressions (e.g. `<input {{#if value}}checked="checked"{{/if}}>`) | `[ ]` |
59
+ | `customAttrCollapse` | Regex that specifies custom attribute to strip newlines from (e.g. `/ng\-class/`) | |
60
+
51
61
 
52
62
  Chunks of markup can be ignored by wrapping them with `<!-- htmlmin:ignore -->`.
53
63
 
54
- Installation Instructions
55
- -------------------------
64
+ ## Installation Instructions
56
65
 
57
66
  From NPM for use as a command line app:
58
- ```
67
+ ```bash
59
68
  npm install html-minifier -g
60
69
  ```
61
70
 
62
71
  From NPM for programmatic use:
63
- ```
72
+ ```bash
64
73
  npm install html-minifier
65
74
  ```
66
75
 
67
76
  From Git:
68
- ```
77
+ ```bash
69
78
  git clone git://github.com/kangax/html-minifier.git
70
79
  cd html-minifier
71
80
  npm link .
72
81
  ```
73
82
 
74
- Usage
75
- --------
83
+ ## Usage
84
+
76
85
  For command line usage please see `html-minifier --help`
77
86
 
78
- Node.js
79
- ========
80
- ```
81
- require('html-minifier')
82
- ```
87
+ ## Node.js
83
88
 
89
+ ```js
90
+ var minify = require('html-minifier').minify;
91
+ var result = minify('<p title="blah" id="moo">foo</p>', {
92
+ removeAttributeQuotes: true
93
+ });
94
+ result; // '<p title=blah id=moo>foo</p>'
95
+ ```
84
96
 
85
- [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/kangax/html-minifier/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
97
+ Running benchmarks
98
+ ------------------
99
+ Benchmarks for minified HTML:
100
+ ```
101
+ node benchmark.js
102
+ ```
package/cli.js CHANGED
@@ -53,25 +53,28 @@ usage += ' on the command line you must escape those such as --ignore-custom-
53
53
  cli.setUsage(usage);
54
54
 
55
55
  var mainOptions = {
56
- removeComments: [[false, 'Strip HTML comments'], false],
57
- removeCommentsFromCDATA: [[false, 'Strip HTML comments from scripts and styles'], false],
58
- removeCDATASectionsFromCDATA: [[false, 'Remove CDATA sections from script and style elements'], false],
59
- collapseWhitespace: [[false, 'Collapse white space that contributes to text nodes in a document tree.'], false],
60
- conservativeCollapse: [[false, 'Always collapse to 1 space (never remove it entirely)'], false],
61
- collapseBooleanAttributes: [[false, 'Omit attribute values from boolean attributes'], false],
62
- removeAttributeQuotes: [[false, 'Remove quotes around attributes when possible.'], false],
63
- removeRedundantAttributes: [[false, 'Remove attributes when value matches default.'], false],
64
- useShortDoctype: [[false, 'Replaces the doctype with the short (HTML5) doctype'], false],
65
- removeEmptyAttributes: [[false, 'Remove all attributes with whitespace-only values'], false],
66
- removeOptionalTags: [[false, 'Remove unrequired tags'], false],
67
- removeEmptyElements: [[false, 'Remove all elements with empty contents'], false],
68
- lint: [[false, 'Toggle linting'], false],
69
- keepClosingSlash: [[false, 'Keep the trailing slash on singleton elements'], false],
70
- caseSensitive: [[false, 'Treat attributes in case sensitive manner (useful for SVG; e.g. viewBox)'], false],
71
- minifyJS: [[false, 'Minify Javascript in script elements and on* attributes (uses UglifyJS)'], false],
72
- minifyCSS: [[false, 'Minify CSS in style elements and style attributes (uses clean-css)'], false],
73
- ignoreCustomComments: [[false, 'Array of regex\'es that allow to ignore certain comments, when matched', 'string'], true],
74
- processScripts: [[false, 'Array of strings corresponding to types of script elements to process through minifier (e.g. "text/ng-template", "text/x-handlebars-template", etc.)', 'string'], true]
56
+ removeComments: [[false, 'Strip HTML comments']],
57
+ removeCommentsFromCDATA: [[false, 'Strip HTML comments from scripts and styles']],
58
+ removeCDATASectionsFromCDATA: [[false, 'Remove CDATA sections from script and style elements']],
59
+ collapseWhitespace: [[false, 'Collapse white space that contributes to text nodes in a document tree.']],
60
+ conservativeCollapse: [[false, 'Always collapse to 1 space (never remove it entirely)']],
61
+ preserveLineBreaks: [[false, 'Always collapse to 1 line break (never remove it entirely) when whitespace between tags include a line break.']],
62
+ collapseBooleanAttributes: [[false, 'Omit attribute values from boolean attributes']],
63
+ removeAttributeQuotes: [[false, 'Remove quotes around attributes when possible.']],
64
+ removeRedundantAttributes: [[false, 'Remove attributes when value matches default.']],
65
+ useShortDoctype: [[false, 'Replaces the doctype with the short (HTML5) doctype']],
66
+ removeEmptyAttributes: [[false, 'Remove all attributes with whitespace-only values']],
67
+ removeOptionalTags: [[false, 'Remove unrequired tags']],
68
+ removeEmptyElements: [[false, 'Remove all elements with empty contents']],
69
+ lint: [[false, 'Toggle linting']],
70
+ keepClosingSlash: [[false, 'Keep the trailing slash on singleton elements']],
71
+ caseSensitive: [[false, 'Treat attributes in case sensitive manner (useful for SVG; e.g. viewBox)']],
72
+ minifyJS: [[false, 'Minify Javascript in script elements and on* attributes (uses UglifyJS)']],
73
+ minifyCSS: [[false, 'Minify CSS in style elements and style attributes (uses clean-css)']],
74
+ minifyURLs: [[false, 'Minify URLs in various attributes (uses relateurl)']],
75
+ ignoreCustomComments: [[false, 'Array of regex\'es that allow to ignore certain comments, when matched', 'string'], 'json'],
76
+ processScripts: [[false, 'Array of strings corresponding to types of script elements to process through minifier (e.g. "text/ng-template", "text/x-handlebars-template", etc.)', 'string'], 'json'],
77
+ maxLineLength: [[false, 'Max line length', 'number'], true]
75
78
  };
76
79
 
77
80
  var cliOptions = {
@@ -109,25 +112,17 @@ cli.main(function(args, options) {
109
112
 
110
113
  mainOptionKeys.forEach(function(key) {
111
114
  var paramKey = changeCase.paramCase(key);
115
+ var value = options[paramKey];
112
116
  if (options[paramKey] !== null) {
113
- if (mainOptions[key][1]) {
114
- var value = options[paramKey];
115
- if (value !== null) {
116
- var jsonArray;
117
- try {
118
- jsonArray = JSON.parse(value);
119
- }
120
- catch (e) {}
121
- if (jsonArray instanceof Array) {
122
- minifyOptions[key] = jsonArray;
123
- }
124
- else {
125
- minifyOptions[key] = [value];
126
- }
127
- }
128
- }
129
- else {
130
- minifyOptions[key] = true;
117
+ switch (mainOptions[key][1]) {
118
+ case 'json':
119
+ minifyOptions[key] = parseJSONOption(value);
120
+ break;
121
+ case true:
122
+ minifyOptions[key] = value;
123
+ break;
124
+ default:
125
+ minifyOptions[key] = true;
131
126
  }
132
127
  }
133
128
  });
@@ -196,6 +191,22 @@ cli.main(function(args, options) {
196
191
 
197
192
  }
198
193
 
194
+ function parseJSONOption(value) {
195
+ if (value !== null) {
196
+ var jsonArray;
197
+ try {
198
+ jsonArray = JSON.parse(value);
199
+ }
200
+ catch (e) {}
201
+ if (jsonArray instanceof Array) {
202
+ return jsonArray;
203
+ }
204
+ else {
205
+ return [value];
206
+ }
207
+ }
208
+ }
209
+
199
210
  // Run minify
200
211
  var minified = null;
201
212
  try {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * HTMLMinifier v0.6.5 (http://kangax.github.io/html-minifier/)
2
+ * HTMLMinifier v0.6.9 (http://kangax.github.io/html-minifier/)
3
3
  * Copyright 2010-2014 Juriy "kangax" Zaytsev
4
4
  * Licensed under MIT (https://github.com/kangax/html-minifier/blob/gh-pages/LICENSE)
5
5
  */
@@ -37,13 +37,24 @@
37
37
  'use strict';
38
38
 
39
39
  // Regular Expressions for parsing tags and attributes
40
- var startTag = /^<([\w:-]+)((?:\s*[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
40
+ var singleAttrIdentifier = /([\w:.-]+)/,
41
+ singleAttrAssign = /=/,
42
+ singleAttrAssigns = [ singleAttrAssign ],
43
+ singleAttrValues = [
44
+ /"((?:\\.|[^"])*)"/.source, // attr value double quotes
45
+ /'((?:\\.|[^'])*)'/.source, // attr value, single quotes
46
+ /([^>\s]+)/.source // attr value, no quotes
47
+ ],
48
+ startTagOpen = /^<([\w:-]+)/,
49
+ startTagClose = /\s*(\/?)>/,
41
50
  endTag = /^<\/([\w:-]+)[^>]*>/,
42
51
  endingSlash = /\/>$/,
43
- attr = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g,
44
- doctype = /^<!DOCTYPE [^>]+>/i,
45
- startIgnore = /<(%|\?)/,
46
- endIgnore = /(%|\?)>/;
52
+ doctype = /^<!DOCTYPE [^>]+>/i;
53
+
54
+ var IS_REGEX_CAPTURING_BROKEN = false;
55
+ 'x'.replace(/x(.)?/g, function(m, g) {
56
+ IS_REGEX_CAPTURING_BROKEN = g === '';
57
+ });
47
58
 
48
59
  // Empty Elements - HTML 4.01
49
60
  var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,wbr');
@@ -66,12 +77,91 @@
66
77
 
67
78
  var reCache = {}, stackedTag, reStackedTag, tagMatch;
68
79
 
80
+ function startTagForHandler( handler ) {
81
+ var customStartTagAttrs;
82
+
83
+ var startTagAttrs = new RegExp(
84
+ '(?:\\s*[\\w:.-]+'
85
+ + '(?:\\s*'
86
+ + '(?:' + joinSingleAttrAssigns(handler) + ')'
87
+ + '\\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\\s]+)'
88
+ + ')?'
89
+ + ')*'
90
+ );
91
+
92
+ if ( handler.customAttrSurround ) {
93
+ var attrClauses = [];
94
+
95
+ for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
96
+ // Capture the custom attribute opening and closing markup surrounding the standard attribute rules
97
+ attrClauses[i] = '(?:\\s*'
98
+ + handler.customAttrSurround[i][0].source
99
+ + startTagAttrs.source
100
+ + handler.customAttrSurround[i][1].source
101
+ + ')';
102
+ }
103
+ attrClauses.unshift(startTagAttrs.source);
104
+
105
+ customStartTagAttrs = new RegExp(
106
+ '((?:' + attrClauses.join('|') + ')*)'
107
+ );
108
+ }
109
+ else {
110
+ // No custom attribute wrappers specified, so just capture the standard attribute rules
111
+ customStartTagAttrs = new RegExp('(' + startTagAttrs.source + ')');
112
+ }
113
+
114
+ return new RegExp(startTagOpen.source + customStartTagAttrs.source + startTagClose.source);
115
+ }
116
+
117
+ function attrForHandler( handler ) {
118
+ var singleAttr = new RegExp(
119
+ singleAttrIdentifier.source
120
+ + '(?:\\s*'
121
+ + '(' + joinSingleAttrAssigns( handler ) + ')'
122
+ + '\\s*'
123
+ + '(?:'
124
+ + singleAttrValues.join('|')
125
+ + ')'
126
+ + ')?'
127
+ );
128
+
129
+ if ( handler.customAttrSurround ) {
130
+ var attrClauses = [];
131
+ for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
132
+ attrClauses[i] = '(?:'
133
+ + '(' + handler.customAttrSurround[i][0].source + ')'
134
+ + singleAttr.source
135
+ + '(' + handler.customAttrSurround[i][1].source + ')'
136
+ + ')';
137
+ }
138
+ attrClauses.unshift('(?:' + singleAttr.source + ')');
139
+
140
+ return new RegExp(attrClauses.join('|'), 'g');
141
+ }
142
+ else {
143
+ return new RegExp(singleAttr.source, 'g');
144
+ }
145
+ }
146
+
147
+ function joinSingleAttrAssigns( handler ) {
148
+ return singleAttrAssigns.concat(
149
+ handler.customAttrAssign || []
150
+ ).map(function (assign) {
151
+ return '(?:' + assign.source + ')';
152
+ }).join('|');
153
+ }
154
+
69
155
  var HTMLParser = global.HTMLParser = function( html, handler ) {
70
156
  var index, chars, match, stack = [], last = html, prevTag, nextTag;
71
157
  stack.last = function() {
72
- return this[ this.length - 1 ];
158
+ var last = this[ this.length - 1 ];
159
+ return last && last.tag;
73
160
  };
74
161
 
162
+ var startTag = startTagForHandler(handler);
163
+ var attr = attrForHandler(handler);
164
+
75
165
  while ( html ) {
76
166
  chars = true;
77
167
 
@@ -79,7 +169,7 @@
79
169
  if ( !stack.last() || !special[ stack.last() ] ) {
80
170
 
81
171
  // Comment:
82
- if ( html.indexOf('<!--') === 0 ) {
172
+ if ( /^<!--/.test( html ) ) {
83
173
  index = html.indexOf('-->');
84
174
 
85
175
  if ( index >= 0 ) {
@@ -92,7 +182,7 @@
92
182
  }
93
183
 
94
184
  // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
95
- if ( html.indexOf('<![') === 0 ) {
185
+ if ( /^<!\[/.test( html ) ) {
96
186
  index = html.indexOf(']>');
97
187
 
98
188
  if (index >= 0) {
@@ -105,13 +195,23 @@
105
195
  }
106
196
 
107
197
  // Ignored elements?
108
- else if (html.search(startIgnore) === 0) {
109
- index = html.search(endIgnore); // Find closing tag.
110
- if (index >= 0) { // Found?
111
- // @TODO: Pass matched open/close tags back to handler.
112
- handler.ignore && handler.ignore(html.substring(0, index + 2)); // Return ignored string if callback exists.
113
- html = html.substring(index + 2); // Next starting point for parser.
114
- chars = false; // Chars flag.
198
+ else if ( /^<\?/.test( html ) ) {
199
+ index = html.indexOf( '?>', 2 );
200
+ if ( index >= 0 ) {
201
+ if ( handler.chars ) {
202
+ handler.chars( html.substring( 0, index + 2 ) );
203
+ }
204
+ html = html.substring( index + 2 );
205
+ }
206
+ }
207
+
208
+ else if ( /^<%/.test( html ) ) {
209
+ index = html.indexOf( '%>', 2 );
210
+ if ( index >= 0 ) {
211
+ if ( handler.chars ) {
212
+ handler.chars(html.substring( 0, index + 2) );
213
+ }
214
+ html = html.substring( index + 2 );
115
215
  }
116
216
  }
117
217
 
@@ -125,7 +225,7 @@
125
225
  }
126
226
 
127
227
  // End tag:
128
- else if ( html.indexOf('</') === 0 ) {
228
+ else if ( /^<\//.test( html ) ) {
129
229
  match = html.match( endTag );
130
230
 
131
231
  if ( match ) {
@@ -137,9 +237,8 @@
137
237
 
138
238
  // Start tag:
139
239
  }
140
- else if ( html.indexOf('<') === 0 ) {
240
+ else if ( /^</.test( html ) ) {
141
241
  match = html.match( startTag );
142
-
143
242
  if ( match ) {
144
243
  html = html.substring( match[0].length );
145
244
  match[0].replace( startTag, parseStartTag );
@@ -220,31 +319,67 @@
220
319
 
221
320
  unary = empty[ tagName ] || !!unary;
222
321
 
322
+ var attrs = [];
323
+
324
+ rest.replace(attr, function () {
325
+ var name, value, fallbackValue, customOpen, customClose, customAssign;
326
+ var ncp = 7; // number of captured parts, scalar
327
+
328
+ // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
329
+ if (IS_REGEX_CAPTURING_BROKEN && arguments[0].indexOf('""') === -1) {
330
+ if (arguments[3] === '') { arguments[3] = undefined; }
331
+ if (arguments[4] === '') { arguments[4] = undefined; }
332
+ if (arguments[5] === '') { arguments[5] = undefined; }
333
+ }
334
+
335
+ name = arguments[1];
336
+ if ( name ) {
337
+ customAssign = arguments[2];
338
+ fallbackValue = arguments[3];
339
+ value = fallbackValue || arguments[4] || arguments[5];
340
+ }
341
+ else if ( handler.customAttrSurround ) {
342
+ for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) {
343
+ name = arguments[i * ncp + 7];
344
+ customAssign = arguments[i * ncp + 8];
345
+ if ( name ) {
346
+ fallbackValue = arguments[i * ncp + 9];
347
+ value = fallbackValue
348
+ || arguments[i * ncp + 10]
349
+ || arguments[i * ncp + 11];
350
+ customOpen = arguments[i * ncp + 6];
351
+ customClose = arguments[i * ncp + 12];
352
+ break;
353
+ }
354
+ }
355
+ }
356
+
357
+ if ( value === undefined ) {
358
+ value = fillAttrs[name] ? name : fallbackValue;
359
+ }
360
+
361
+ attrs.push({
362
+ name: name,
363
+ value: value,
364
+ escaped: value && value.replace(/(^|.)("+)/g, function(match) {
365
+ return match.replace(/"/g, '&quot;');
366
+ }),
367
+ customAssign: customAssign || '=',
368
+ customOpen: customOpen || '',
369
+ customClose: customClose || ''
370
+ });
371
+ });
372
+
223
373
  if ( !unary ) {
224
- stack.push( tagName );
374
+ stack.push( { tag: tagName, attrs: attrs } );
225
375
  }
226
376
  else {
227
377
  unarySlash = tag.match( endingSlash );
228
378
  }
229
379
 
230
- if ( handler.start ) {
231
- var attrs = [];
232
-
233
- rest.replace(attr, function(match, name) {
234
- var value = arguments[2] ? arguments[2] :
235
- arguments[3] ? arguments[3] :
236
- arguments[4] ? arguments[4] :
237
- fillAttrs[name] ? name : arguments[2];
238
- attrs.push({
239
- name: name,
240
- value: value,
241
- escaped: value && value.replace(/(^|[^\\])"/g, '$1&quot;')
242
- });
243
- });
244
380
 
245
- if ( handler.start ) {
246
- handler.start( tagName, attrs, unary, unarySlash );
247
- }
381
+ if ( handler.start ) {
382
+ handler.start( tagName, attrs, unary, unarySlash );
248
383
  }
249
384
  }
250
385
 
@@ -255,9 +390,11 @@
255
390
  if ( !tagName ) {
256
391
  pos = 0;
257
392
  }
258
- else { // Find the closest opened tag of the same type
393
+ else {
394
+ // Find the closest opened tag of the same type
395
+ var needle = tagName.toLowerCase();
259
396
  for ( pos = stack.length - 1; pos >= 0; pos-- ) {
260
- if ( stack[ pos ].toLowerCase() === tagName ) {
397
+ if ( stack[ pos ].tag.toLowerCase() === needle ) {
261
398
  break;
262
399
  }
263
400
  }
@@ -267,7 +404,7 @@
267
404
  // Close all the open elements, up the stack
268
405
  for ( var i = stack.length - 1; i >= pos; i-- ) {
269
406
  if ( handler.end ) {
270
- handler.end( stack[ i ] );
407
+ handler.end( stack[ i ].tag, stack[ i ].attrs );
271
408
  }
272
409
  }
273
410
 
@@ -470,21 +607,34 @@
470
607
  'code', 'del', 'dfn', 'em', 'font', 'i', 'ins', 'kbd', 'mark', 'q',
471
608
  'rt', 'rp', 's', 'samp', 'small', 'span', 'strike', 'strong',
472
609
  'sub', 'sup', 'svg', 'time', 'tt', 'u', 'var'
473
- ];
610
+ ],
611
+ lineBreakBefore = /^[\t ]*[\n\r]+[\t\n\r ]*/,
612
+ lineBreakAfter = /[\t\n\r ]*[\n\r]+[\t ]*$/,
613
+ preserveBefore = lineBreakBefore.test(str) ? '\n' : ' ',
614
+ preserveAfter = lineBreakAfter.test(str) ? '\n' : ' ',
615
+ lineBreakStamp = '[{htmlmin-lb}]';
474
616
 
475
617
  if (prevTag && prevTag !== 'img' && prevTag !== 'input' && (prevTag.substr(0,1) !== '/'
476
618
  || ( prevTag.substr(0,1) === '/' && tags.indexOf(prevTag.substr(1)) === -1))) {
477
- str = str.replace(/^\s+/, options.conservativeCollapse ? ' ' : '');
619
+ str = str.replace(/^\s+/, options.conservativeCollapse ? ' ' : options.preserveLineBreaks ? preserveBefore : '');
478
620
  }
479
621
 
480
622
  if (nextTag && nextTag !== 'img' && nextTag !== 'input' && (nextTag.substr(0,1) === '/'
481
623
  || ( nextTag.substr(0,1) !== '/' && tags.indexOf(nextTag) === -1))) {
482
- str = str.replace(/\s+$/, options.conservativeCollapse ? ' ' : '');
624
+ str = str.replace(/\s+$/, options.conservativeCollapse ? ' ' : options.preserveLineBreaks ? preserveAfter : '');
483
625
  }
484
626
 
485
627
  if (prevTag && nextTag) {
628
+
629
+ if (options.preserveLineBreaks) {
630
+ str = str
631
+ .replace(lineBreakBefore, lineBreakStamp)
632
+ .replace(lineBreakAfter, lineBreakStamp);
633
+ }
486
634
  // strip non space whitespace then compress spaces to one
487
- return str.replace(/[\t\n\r]+/g, ' ').replace(/[ ]+/g, ' ');
635
+ return str
636
+ .replace(/[\t\n\r]+/g, ' ').replace(/[ ]+/g, ' ')
637
+ .replace(lineBreakStamp, '\n');
488
638
  }
489
639
 
490
640
  return str;
@@ -576,8 +726,23 @@
576
726
  );
577
727
  }
578
728
 
579
- function isBooleanAttribute(attrName) {
580
- return (/^(?:allowfullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultchecked|defaultmuted|defaultselected|defer|disabled|draggable|enabled|formnovalidate|hidden|indeterminate|inert|ismap|itemscope|loop|multiple|muted|nohref|noresize|noshade|novalidate|nowrap|open|pauseonexit|readonly|required|reversed|scoped|seamless|selected|sortable|spellcheck|truespeed|typemustmatch|visible)$/).test(attrName);
729
+ var enumeratedAttributeValues = {
730
+ draggable: ['true', 'false'] // defaults to 'auto'
731
+ };
732
+
733
+ function isBooleanAttribute(attrName, attrValue) {
734
+ var isSimpleBoolean = (/^(?:allowfullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultchecked|defaultmuted|defaultselected|defer|disabled|enabled|formnovalidate|hidden|indeterminate|inert|ismap|itemscope|loop|multiple|muted|nohref|noresize|noshade|novalidate|nowrap|open|pauseonexit|readonly|required|reversed|scoped|seamless|selected|sortable|spellcheck|truespeed|typemustmatch|visible)$/i).test(attrName);
735
+ if (isSimpleBoolean) {
736
+ return true;
737
+ }
738
+
739
+ var attrValueEnumeration = enumeratedAttributeValues[attrName.toLowerCase()];
740
+ if (!attrValueEnumeration) {
741
+ return false;
742
+ }
743
+ else {
744
+ return (-1 === attrValueEnumeration.indexOf(attrValue.toLowerCase()));
745
+ }
581
746
  }
582
747
 
583
748
  function isUriTypeAttribute(attrName, tag) {
@@ -608,7 +773,7 @@
608
773
  }
609
774
 
610
775
  function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
611
- if (isEventAttribute(attrName)) {
776
+ if (attrValue && isEventAttribute(attrName)) {
612
777
  attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '').replace(/\s*;$/, '');
613
778
  if (options.minifyJS) {
614
779
  var wrappedCode = '(function(){' + attrValue + '})()';
@@ -620,11 +785,21 @@
620
785
  else if (attrName === 'class') {
621
786
  return collapseWhitespace(trimWhitespace(attrValue));
622
787
  }
623
- else if (isUriTypeAttribute(attrName, tag) || isNumberTypeAttribute(attrName, tag)) {
788
+ else if (isUriTypeAttribute(attrName, tag)) {
789
+ attrValue = trimWhitespace(attrValue);
790
+ if (options.minifyURLs) {
791
+ return minifyURLs(attrValue, options.minifyURLs);
792
+ }
793
+ return attrValue;
794
+ }
795
+ else if (isNumberTypeAttribute(attrName, tag)) {
624
796
  return trimWhitespace(attrValue);
625
797
  }
626
798
  else if (attrName === 'style') {
627
- attrValue = trimWhitespace(attrValue).replace(/\s*;\s*$/, '');
799
+ attrValue = trimWhitespace(attrValue);
800
+ if (attrValue) {
801
+ attrValue = attrValue.replace(/\s*;\s*$/, '');
802
+ }
628
803
  if (options.minifyCSS) {
629
804
  return minifyCSS(attrValue, options.minifyCSS);
630
805
  }
@@ -633,6 +808,9 @@
633
808
  else if (isMetaViewport(tag, attrs) && attrName === 'content') {
634
809
  attrValue = attrValue.replace(/1\.0/g, '1').replace(/\s+/g, '');
635
810
  }
811
+ else if (attrValue && options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
812
+ attrValue = attrValue.replace(/\n+/g, '');
813
+ }
636
814
  return attrValue;
637
815
  }
638
816
 
@@ -693,7 +871,7 @@
693
871
  '?:down|up|over|move|out)|key(?:press|down|up)))$');
694
872
 
695
873
  function canDeleteEmptyAttribute(tag, attrName, attrValue) {
696
- var isValueEmpty = /^(["'])?\s*\1$/.test(attrValue);
874
+ var isValueEmpty = !attrValue || (/^\s*$/).test(attrValue);
697
875
  if (isValueEmpty) {
698
876
  return (
699
877
  (tag === 'input' && attrName === 'value') ||
@@ -702,8 +880,20 @@
702
880
  return false;
703
881
  }
704
882
 
705
- function canRemoveElement(tag) {
706
- return tag !== 'textarea';
883
+ function canRemoveElement(tag, attrs) {
884
+ if (tag === 'textarea') {
885
+ return false;
886
+ }
887
+
888
+ if (tag === 'script') {
889
+ for (var i = attrs.length - 1; i >= 0; i--) {
890
+ if (attrs[i].name === 'src') {
891
+ return false;
892
+ }
893
+ }
894
+ }
895
+
896
+ return true;
707
897
  }
708
898
 
709
899
  function canCollapseWhitespace(tag) {
@@ -722,11 +912,13 @@
722
912
  return markup;
723
913
  }
724
914
 
725
- function normalizeAttribute(attr, attrs, tag, options) {
915
+ function normalizeAttribute(attr, attrs, tag, unarySlash, index, options) {
726
916
 
727
917
  var attrName = options.caseSensitive ? attr.name : attr.name.toLowerCase(),
728
918
  attrValue = attr.escaped,
729
- attrFragment;
919
+ attrFragment,
920
+ emittedAttrValue,
921
+ isTerminalOfUnarySlash = unarySlash && index === attrs.length - 1;
730
922
 
731
923
  if ((options.removeRedundantAttributes &&
732
924
  isAttributeRedundant(tag, attrName, attrValue, attrs))
@@ -742,8 +934,11 @@
742
934
  attrValue = cleanAttributeValue(tag, attrName, attrValue, options, attrs);
743
935
 
744
936
  if (attrValue !== undefined && !options.removeAttributeQuotes ||
745
- !canRemoveAttributeQuotes(attrValue)) {
746
- attrValue = '"' + attrValue + '"';
937
+ !canRemoveAttributeQuotes(attrValue) || isTerminalOfUnarySlash) {
938
+ emittedAttrValue = '"' + attrValue + '"';
939
+ }
940
+ else {
941
+ emittedAttrValue = attrValue;
747
942
  }
748
943
 
749
944
  if (options.removeEmptyAttributes &&
@@ -752,14 +947,14 @@
752
947
  }
753
948
 
754
949
  if (attrValue === undefined || (options.collapseBooleanAttributes &&
755
- isBooleanAttribute(attrName))) {
950
+ isBooleanAttribute(attrName, attrValue))) {
756
951
  attrFragment = attrName;
757
952
  }
758
953
  else {
759
- attrFragment = attrName + '=' + attrValue;
954
+ attrFragment = attrName + attr.customAssign + emittedAttrValue;
760
955
  }
761
956
 
762
- return (' ' + attrFragment);
957
+ return (' ' + attr.customOpen + attrFragment + attr.customClose);
763
958
  }
764
959
 
765
960
  function setDefaultTesters(options) {
@@ -775,6 +970,37 @@
775
970
  }
776
971
  }
777
972
 
973
+ function minifyURLs(text, options) {
974
+ if (typeof options !== 'object') {
975
+ options = { };
976
+ }
977
+
978
+ try {
979
+ // try to get global reference first
980
+ var __RelateUrl = global.RelateUrl;
981
+
982
+ if (typeof __RelateUrl === 'undefined' && typeof require === 'function') {
983
+ __RelateUrl = require('relateurl');
984
+ }
985
+
986
+ // noop
987
+ if (!__RelateUrl) {
988
+ return text;
989
+ }
990
+
991
+ if (__RelateUrl.relate) {
992
+ return __RelateUrl.relate(text, options);
993
+ }
994
+ else {
995
+ return text;
996
+ }
997
+ }
998
+ catch (err) {
999
+ log(err);
1000
+ }
1001
+ return text;
1002
+ }
1003
+
778
1004
  function minifyJS(text, options) {
779
1005
  if (typeof options !== 'object') {
780
1006
  options = { };
@@ -867,6 +1093,12 @@
867
1093
  isIgnoring = false,
868
1094
  t = new Date();
869
1095
 
1096
+ if (options.removeIgnored) {
1097
+ value = value
1098
+ .replace(/<\?[^\?]+\?>/g, '')
1099
+ .replace(/<%[^%]+%>/g, '');
1100
+ }
1101
+
870
1102
  function _canCollapseWhitespace(tag, attrs) {
871
1103
  return canCollapseWhitespace(tag) || options.canCollapseWhitespace(tag, attrs);
872
1104
  }
@@ -881,7 +1113,7 @@
881
1113
  start: function( tag, attrs, unary, unarySlash ) {
882
1114
 
883
1115
  if (isIgnoring) {
884
- buffer.push('<', tag, attrsToMarkup(attrs), unarySlash ? '/' : '', '>');
1116
+ buffer.push('<' + tag, attrsToMarkup(attrs), unarySlash ? '/' : '', '>');
885
1117
  return;
886
1118
  }
887
1119
 
@@ -900,20 +1132,30 @@
900
1132
  }
901
1133
  }
902
1134
 
903
- buffer.push('<', tag);
1135
+ var openTag = '<' + tag;
1136
+ var closeTag = ((unarySlash && options.keepClosingSlash) ? '/' : '') + '>';
1137
+ if ( attrs.length === 0) {
1138
+ openTag += closeTag;
1139
+ }
1140
+
1141
+ buffer.push(openTag);
904
1142
 
905
1143
  lint && lint.testElement(tag);
906
1144
 
1145
+ var token;
907
1146
  for ( var i = 0, len = attrs.length; i < len; i++ ) {
908
1147
  lint && lint.testAttribute(tag, attrs[i].name.toLowerCase(), attrs[i].escaped);
909
- buffer.push(normalizeAttribute(attrs[i], attrs, tag, options));
1148
+ token = normalizeAttribute(attrs[i], attrs, tag, unarySlash, i, options);
1149
+ if ( i === len - 1 ) {
1150
+ token += closeTag;
1151
+ }
1152
+ buffer.push(token);
910
1153
  }
911
- buffer.push(((unarySlash && options.keepClosingSlash) ? '/' : '') + '>');
912
1154
  },
913
- end: function( tag ) {
1155
+ end: function( tag, attrs ) {
914
1156
 
915
1157
  if (isIgnoring) {
916
- buffer.push('</', tag, '>');
1158
+ buffer.push('</' + tag + '>');
917
1159
  return;
918
1160
  }
919
1161
 
@@ -930,9 +1172,14 @@
930
1172
  }
931
1173
 
932
1174
  var isElementEmpty = currentChars === '' && tag === currentTag;
933
- if ((options.removeEmptyElements && isElementEmpty && canRemoveElement(tag))) {
1175
+ if ((options.removeEmptyElements && isElementEmpty && canRemoveElement(tag, attrs))) {
934
1176
  // remove last "element" from buffer, return
935
- buffer.splice(buffer.lastIndexOf('<'));
1177
+ for ( var i = buffer.length - 1; i >= 0; i-- ) {
1178
+ if ( /^<[^\/!]/.test(buffer[i]) ) {
1179
+ buffer.splice(i);
1180
+ break;
1181
+ }
1182
+ }
936
1183
  return;
937
1184
  }
938
1185
  else if (options.removeOptionalTags && isOptionalTag(tag)) {
@@ -941,7 +1188,7 @@
941
1188
  }
942
1189
  else {
943
1190
  // push end tag to buffer
944
- buffer.push('</', options.caseSensitive ? tag : tag.toLowerCase(), '>');
1191
+ buffer.push('</' + (options.caseSensitive ? tag : tag.toLowerCase()) + '>');
945
1192
  results.push.apply(results, buffer);
946
1193
  }
947
1194
  // flush buffer
@@ -949,6 +1196,9 @@
949
1196
  currentChars = '';
950
1197
  },
951
1198
  chars: function( text, prevTag, nextTag ) {
1199
+ prevTag = prevTag === '' ? 'comment' : prevTag;
1200
+ nextTag = nextTag === '' ? 'comment' : nextTag;
1201
+
952
1202
  if (isIgnoring) {
953
1203
  buffer.push(text);
954
1204
  return;
@@ -973,12 +1223,12 @@
973
1223
  }
974
1224
  if (options.collapseWhitespace) {
975
1225
  if (!stackNoTrimWhitespace.length) {
976
- text = (prevTag || nextTag) ?
1226
+ text = ((prevTag && prevTag !== 'comment') || (nextTag && nextTag !== 'comment')) ?
977
1227
  collapseWhitespaceSmart(text, prevTag, nextTag, options)
978
1228
  : trimWhitespace(text);
979
1229
  }
980
1230
  if (!stackNoCollapseWhitespace.length) {
981
- text = collapseWhitespace(text);
1231
+ text = !(prevTag && nextTag || nextTag === 'html') ? collapseWhitespace(text) : text;
982
1232
  }
983
1233
  }
984
1234
  currentChars = text;
@@ -992,7 +1242,9 @@
992
1242
 
993
1243
  if (/^\s*htmlmin:ignore/.test(text)) {
994
1244
  isIgnoring = !isIgnoring;
995
- buffer.push('<!--' + text + '-->');
1245
+ if (!options.removeComments) {
1246
+ buffer.push('<!--' + text + '-->');
1247
+ }
996
1248
  return;
997
1249
  }
998
1250
  if (options.removeComments) {
@@ -1011,21 +1263,47 @@
1011
1263
  }
1012
1264
  buffer.push(text);
1013
1265
  },
1014
- ignore: function(text) {
1015
- // `text` === strings that start with `<?` or `<%` and end with `?>` or `%>`.
1016
- buffer.push(options.removeIgnored ? '' : text); // `text` allowed by default.
1017
- },
1018
1266
  doctype: function(doctype) {
1019
1267
  buffer.push(options.useShortDoctype ? '<!DOCTYPE html>' : collapseWhitespace(doctype));
1020
- }
1268
+ },
1269
+ customAttrAssign: options.customAttrAssign,
1270
+ customAttrSurround: options.customAttrSurround
1021
1271
  });
1022
1272
 
1023
1273
  results.push.apply(results, buffer);
1024
- var str = results.join('');
1274
+ var str = joinResultSegments(results, options);
1025
1275
  log('minified in: ' + (new Date() - t) + 'ms');
1026
1276
  return str;
1027
1277
  }
1028
1278
 
1279
+ function joinResultSegments( results, options ) {
1280
+ var str;
1281
+ var maxLineLength = options.maxLineLength;
1282
+ if ( maxLineLength ) {
1283
+ var token;
1284
+ var lines = [];
1285
+ var line = '';
1286
+ for ( var i = 0, len = results.length; i < len; i++ ) {
1287
+ token = results[i];
1288
+ if ( line.length + token.length < maxLineLength ) {
1289
+ line += token;
1290
+ }
1291
+ else {
1292
+ lines.push(line.replace(/^\n/, ''));
1293
+ line = token;
1294
+ }
1295
+ }
1296
+ lines.push(line);
1297
+
1298
+ str = lines.join('\n');
1299
+ }
1300
+ else {
1301
+ str = results.join('');
1302
+ }
1303
+
1304
+ return str;
1305
+ }
1306
+
1029
1307
  // for CommonJS enviroments, export everything
1030
1308
  if ( typeof exports !== 'undefined' ) {
1031
1309
  exports.minify = minify;
@@ -1048,10 +1326,10 @@
1048
1326
  'use strict';
1049
1327
 
1050
1328
  function isPresentationalElement(tag) {
1051
- return (/^(?:b|i|big|small|hr|blink|marquee)$/).test(tag);
1329
+ return (/^(?:big|small|hr|blink|marquee)$/).test(tag);
1052
1330
  }
1053
1331
  function isDeprecatedElement(tag) {
1054
- return (/^(?:applet|basefont|center|dir|font|isindex|s|strike|u)$/).test(tag);
1332
+ return (/^(?:applet|basefont|center|dir|font|isindex|strike)$/).test(tag);
1055
1333
  }
1056
1334
  function isEventAttribute(attrName) {
1057
1335
  return (/^on[a-z]+/).test(attrName);
@@ -1,6 +1,6 @@
1
1
  /*!
2
- * HTMLMinifier v0.6.5 (http://kangax.github.io/html-minifier/)
2
+ * HTMLMinifier v0.6.9 (http://kangax.github.io/html-minifier/)
3
3
  * Copyright 2010-2014 Juriy "kangax" Zaytsev
4
4
  * Licensed under MIT (https://github.com/kangax/html-minifier/blob/gh-pages/LICENSE)
5
5
  */
6
- !function(a){"use strict";function b(a){for(var b={},c=a.split(","),d=0;d<c.length;d++)b[c[d]]=!0,b[c[d].toUpperCase()]=!0;return b}var c,d,e,f=/^<([\w:-]+)((?:\s*[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,g=/^<\/([\w:-]+)[^>]*>/,h=/\/>$/,i=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g,j=/^<!DOCTYPE [^>]+>/i,k=/<(%|\?)/,l=/(%|\?)>/,m=b("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,wbr"),n=b("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,noscript,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,svg,textarea,tt,u,var"),o=b("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source"),p=b("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"),q=b("script,style,noscript"),r={},s=a.HTMLParser=function(a,b){function s(a,c,d,e){for(var f=!1;!b.html5&&z.last()&&n[z.last()];)t("",z.last());if(o[c]&&z.last()===c&&t("",c),e=m[c]||!!e,e?f=a.match(h):z.push(c),b.start){var g=[];d.replace(i,function(a,b){var c=arguments[2]?arguments[2]:arguments[3]?arguments[3]:arguments[4]?arguments[4]:p[b]?b:arguments[2];g.push({name:b,value:c,escaped:c&&c.replace(/(^|[^\\])"/g,"$1&quot;")})}),b.start&&b.start(c,g,e,f)}}function t(a,c){var d;if(c)for(d=z.length-1;d>=0&&z[d].toLowerCase()!==c;d--);else d=0;if(d>=0){for(var e=z.length-1;e>=d;e--)b.end&&b.end(z[e]);z.length=d}}var u,v,w,x,y,z=[],A=a;for(z.last=function(){return this[this.length-1]};a;){if(v=!0,z.last()&&q[z.last()])c=z.last().toLowerCase(),d=r[c]||(r[c]=new RegExp("([\\s\\S]*?)</"+c+"[^>]*>","i")),a=a.replace(d,function(a,d){return"script"!==c&&"style"!==c&&"noscript"!==c&&(d=d.replace(/<!--([\s\S]*?)-->/g,"$1").replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g,"$1")),b.chars&&b.chars(d),""}),t("",c);else if(0===a.indexOf("<!--")&&(u=a.indexOf("-->"),u>=0&&(b.comment&&b.comment(a.substring(4,u)),a=a.substring(u+3),v=!1)),0===a.indexOf("<![")?(u=a.indexOf("]>"),u>=0&&(b.comment&&b.comment(a.substring(2,u+1),!0),a=a.substring(u+2),v=!1)):0===a.search(k)?(u=a.search(l),u>=0&&(b.ignore&&b.ignore(a.substring(0,u+2)),a=a.substring(u+2),v=!1)):(w=j.exec(a))?(b.doctype&&b.doctype(w[0]),a=a.substring(w[0].length),v=!1):0===a.indexOf("</")?(w=a.match(g),w&&(a=a.substring(w[0].length),w[0].replace(g,t),x="/"+w[1].toLowerCase(),v=!1)):0===a.indexOf("<")&&(w=a.match(f),w&&(a=a.substring(w[0].length),w[0].replace(f,s),x=w[1].toLowerCase(),v=!1)),v){u=a.indexOf("<");var B=0>u?a:a.substring(0,u);a=0>u?"":a.substring(u),e=a.match(f),e?y=e[1]:(e=a.match(g),y=e?"/"+e[1]:""),b.chars&&b.chars(B,x,y)}if(a===A)throw"Parse Error: "+a;A=a}t()};a.HTMLtoXML=function(a){var b="";return new s(a,{start:function(a,c,d){b+="<"+a;for(var e=0;e<c.length;e++)b+=" "+c[e].name+'="'+c[e].escaped+'"';b+=(d?"/":"")+">"},end:function(a){b+="</"+a+">"},chars:function(a){b+=a},comment:function(a){b+="<!--"+a+"-->"},ignore:function(a){b+=a}}),b},a.HTMLtoDOM=function(a,c){var d=b("html,head,body,title"),e={link:"head",base:"head"};c?c=c.ownerDocument||c.getOwnerDocument&&c.getOwnerDocument()||c:"undefined"!=typeof DOMDocument?c=new DOMDocument:"undefined"!=typeof document&&document.implementation&&document.implementation.createDocument?c=document.implementation.createDocument("","",null):"undefined"!=typeof ActiveX&&(c=new ActiveXObject("Msxml.DOMDocument"));var f=[],g=c.documentElement||c.getDocumentElement&&c.getDocumentElement();if(!g&&c.createElement&&!function(){var a=c.createElement("html"),b=c.createElement("head");b.appendChild(c.createElement("title")),a.appendChild(b),a.appendChild(c.createElement("body")),c.appendChild(a)}(),c.getElementsByTagName)for(var h in d)d[h]=c.getElementsByTagName(h)[0];var i=d.body;return new s(a,{start:function(a,b,g){if(d[a])return void(i=d[a]);var h=c.createElement(a);for(var j in b)h.setAttribute(b[j].name,b[j].value);e[a]&&"boolean"!=typeof d[e[a]]?d[e[a]].appendChild(h):i&&i.appendChild&&i.appendChild(h),g||(f.push(h),i=h)},end:function(){f.length-=1,i=f[f.length-1]},chars:function(a){i.appendChild(c.createTextNode(a))},comment:function(){},ignore:function(){}}),c}}("undefined"==typeof exports?this:exports),function(a){"use strict";function b(a){return a?a.replace(/[\t\n\r ]+/g," "):a}function c(a,b,c,d){var e=["a","abbr","acronym","b","bdi","bdo","big","button","cite","code","del","dfn","em","font","i","ins","kbd","mark","q","rt","rp","s","samp","small","span","strike","strong","sub","sup","svg","time","tt","u","var"];return b&&"img"!==b&&"input"!==b&&("/"!==b.substr(0,1)||"/"===b.substr(0,1)&&-1===e.indexOf(b.substr(1)))&&(a=a.replace(/^\s+/,d.conservativeCollapse?" ":"")),c&&"img"!==c&&"input"!==c&&("/"===c.substr(0,1)||"/"!==c.substr(0,1)&&-1===e.indexOf(c))&&(a=a.replace(/\s+$/,d.conservativeCollapse?" ":"")),b&&c?a.replace(/[\t\n\r]+/g," ").replace(/[ ]+/g," "):a}function d(a){return/\[if[^\]]+\]/.test(a)||/\s*((?:<!)?\[endif\])$/.test(a)}function e(a,b){if(/^!/.test(a))return!0;if(b.ignoreCustomComments)for(var c=0,d=b.ignoreCustomComments.length;d>c;c++)if(b.ignoreCustomComments[c].test(a))return!0;return!1}function f(a){return/^on[a-z]+/.test(a)}function g(a){return/^[^\x20\t\n\f\r"'`=<>]+$/.test(a)&&!/\/$/.test(a)&&!/\/$/.test(a)}function h(a,b){for(var c=a.length;c--;)if(a[c].name.toLowerCase()===b)return!0;return!1}function i(a,b,c,d){return c=c?H(c.toLowerCase()):"","script"===a&&"language"===b&&"javascript"===c||"form"===a&&"method"===b&&"get"===c||"input"===a&&"type"===b&&"text"===c||"script"===a&&"charset"===b&&!h(d,"src")||"a"===a&&"name"===b&&h(d,"id")||"area"===a&&"shape"===b&&"rect"===c}function j(a,b,c){return"script"===a&&"type"===b&&"text/javascript"===H(c.toLowerCase())}function k(a,b,c){return("style"===a||"link"===a)&&"type"===b&&"text/css"===H(c.toLowerCase())}function l(a){return/^(?:allowfullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultchecked|defaultmuted|defaultselected|defer|disabled|draggable|enabled|formnovalidate|hidden|indeterminate|inert|ismap|itemscope|loop|multiple|muted|nohref|noresize|noshade|novalidate|nowrap|open|pauseonexit|readonly|required|reversed|scoped|seamless|selected|sortable|spellcheck|truespeed|typemustmatch|visible)$/.test(a)}function m(a,b){return/^(?:a|area|link|base)$/.test(b)&&"href"===a||"img"===b&&/^(?:src|longdesc|usemap)$/.test(a)||"object"===b&&/^(?:classid|codebase|data|usemap)$/.test(a)||"q"===b&&"cite"===a||"blockquote"===b&&"cite"===a||("ins"===b||"del"===b)&&"cite"===a||"form"===b&&"action"===a||"input"===b&&("src"===a||"usemap"===a)||"head"===b&&"profile"===a||"script"===b&&("src"===a||"for"===a)}function n(a,b){return/^(?:a|area|object|button)$/.test(b)&&"tabindex"===a||"input"===b&&("maxlength"===a||"tabindex"===a)||"select"===b&&("size"===a||"tabindex"===a)||"textarea"===b&&/^(?:rows|cols|tabindex)$/.test(a)||"colgroup"===b&&"span"===a||"col"===b&&"span"===a||("th"===b||"td"===b)&&("rowspan"===a||"colspan"===a)}function o(a,c,d,e,g){if(f(c)){if(d=H(d).replace(/^javascript:\s*/i,"").replace(/\s*;$/,""),e.minifyJS){var h="(function(){"+d+"})()",i=C(h,e.minifyJS);return i.slice(12,i.length-4).replace(/"/g,"&quot;")}return d}return"class"===c?b(H(d)):m(c,a)||n(c,a)?H(d):"style"===c?(d=H(d).replace(/\s*;\s*$/,""),e.minifyCSS?D(d,e.minifyCSS):d):(p(a,g)&&"content"===c&&(d=d.replace(/1\.0/g,"1").replace(/\s+/g,"")),d)}function p(a,b){if("meta"!==a)return!1;for(var c=0,d=b.length;d>c;c++)if("name"===b[c].name&&"viewport"===b[c].value)return!0}function q(a){return a.replace(/^(\[[^\]]+\]>)\s*/,"$1").replace(/\s*(<!\[endif\])$/,"$1")}function r(a){return a.replace(/^(?:\s*\/\*\s*<!\[CDATA\[\s*\*\/|\s*\/\/\s*<!\[CDATA\[.*)/,"").replace(/(?:\/\*\s*\]\]>\s*\*\/|\/\/\s*\]\]>)\s*$/,"")}function s(a,b,c){for(var d=0,e=c.length;e>d;d++)if("type"===c[d].name.toLowerCase()&&b.processScripts.indexOf(c[d].value)>-1)return E(a,b);return a}function t(a,b){return a.replace(I[b],"").replace(J[b],"")}function u(a){return/^(?:html|t?body|t?head|tfoot|tr|td|th|dt|dd|option|colgroup|source)$/.test(a)}function v(a,b,c){var d=/^(["'])?\s*\1$/.test(c);return d?"input"===a&&"value"===b||K.test(b):!1}function w(a){return"textarea"!==a}function x(a){return!/^(?:script|style|pre|textarea)$/.test(a)}function y(a){return!/^(?:pre|textarea)$/.test(a)}function z(a){for(var b="",c=0,d=a.length;d>c;c++)b+=" "+a[c].name+(l(a[c].value)?"":'="'+a[c].value+'"');return b}function A(a,b,c,d){var e,f=d.caseSensitive?a.name:a.name.toLowerCase(),h=a.escaped;return d.removeRedundantAttributes&&i(c,f,h,b)||d.removeScriptTypeAttributes&&j(c,f,h)||d.removeStyleLinkTypeAttributes&&k(c,f,h)?"":(h=o(c,f,h,d,b),(void 0!==h&&!d.removeAttributeQuotes||!g(h))&&(h='"'+h+'"'),d.removeEmptyAttributes&&v(c,f,h)?"":(e=void 0===h||d.collapseBooleanAttributes&&l(f)?f:f+"="+h," "+e))}function B(a){for(var b=["canCollapseWhitespace","canTrimWhitespace"],c=0,d=b.length;d>c;c++)a[b[c]]||(a[b[c]]=function(){return!1})}function C(b,c){"object"!=typeof c&&(c={}),c.fromString=!0,c.output={inline_script:!0};try{var d=a.UglifyJS;if("undefined"==typeof d&&"function"==typeof require&&(d=require("uglify-js")),!d)return b;if(d.minify)return d.minify(b,c).code;if(d.parse){var e=d.parse(b);e.figure_out_scope();var f=d.Compressor(),g=e.transform(f);g.figure_out_scope(),g.compute_char_frequency(),c.mangle!==!1&&g.mangle_names();var h=d.OutputStream(c.output);return g.print(h),h.toString()}return b}catch(i){F(i)}return b}function D(a,b){"object"!=typeof b&&(b={}),"undefined"==typeof b.noAdvanced&&(b.noAdvanced=!0);try{if("undefined"!=typeof CleanCSS)return new CleanCSS(b).minify(a);if("function"==typeof require){var c=require("clean-css");return new c(b).minify(a)}}catch(d){F(d)}return a}function E(a,f){function g(a,b){return x(a)||f.canCollapseWhitespace(a,b)}function h(a,b){return y(a)||f.canTrimWhitespace(a,b)}f=f||{},a=H(a),B(f);var i=[],j=[],k="",l="",m=[],n=[],o=[],p=f.lint,v=!1,E=new Date;new G(a,{html5:"undefined"!=typeof f.html5?f.html5:!0,start:function(a,b,c,d){if(v)return void j.push("<",a,z(b),d?"/":"",">");a=f.caseSensitive?a:a.toLowerCase(),l=a,k="",m=b,f.collapseWhitespace&&(h(a,b)||n.push(a),g(a,b)||o.push(a)),j.push("<",a),p&&p.testElement(a);for(var e=0,i=b.length;i>e;e++)p&&p.testAttribute(a,b[e].name.toLowerCase(),b[e].escaped),j.push(A(b[e],b,a,f));j.push((d&&f.keepClosingSlash?"/":"")+">")},end:function(a){if(v)return void j.push("</",a,">");f.collapseWhitespace&&(n.length&&a===n[n.length-1]&&n.pop(),o.length&&a===o[o.length-1]&&o.pop());var b=""===k&&a===l;return f.removeEmptyElements&&b&&w(a)?void j.splice(j.lastIndexOf("<")):void(f.removeOptionalTags&&u(a)||(j.push("</",f.caseSensitive?a:a.toLowerCase(),">"),i.push.apply(i,j),j.length=0,k=""))},chars:function(a,d,e){return v?void j.push(a):(("script"===l||"style"===l)&&(f.removeCommentsFromCDATA&&(a=t(a,l)),f.removeCDATASectionsFromCDATA&&(a=r(a)),f.processScripts&&(a=s(a,f,m))),"script"===l&&f.minifyJS&&(a=C(a,f.minifyJS)),"style"===l&&f.minifyCSS&&(a=D(a,f.minifyCSS)),f.collapseWhitespace&&(n.length||(a=d||e?c(a,d,e,f):H(a)),o.length||(a=b(a))),k=a,p&&p.testChars(a),void j.push(a))},comment:function(a,b){var c=b?"<!":"<!--",g=b?">":"-->";return/^\s*htmlmin:ignore/.test(a)?(v=!v,void j.push("<!--"+a+"-->")):(a=f.removeComments?d(a)?c+q(a)+g:e(a,f)?"<!--"+a+"-->":"":c+a+g,void j.push(a))},ignore:function(a){j.push(f.removeIgnored?"":a)},doctype:function(a){j.push(f.useShortDoctype?"<!DOCTYPE html>":b(a))}}),i.push.apply(i,j);var I=i.join("");return F("minified in: "+(new Date-E)+"ms"),I}var F,G;F=a.console&&a.console.log?function(b){a.console.log(b)}:function(){},a.HTMLParser?G=a.HTMLParser:"function"==typeof require&&(G=require("./htmlparser").HTMLParser);var H=function(a){return"string"!=typeof a?a:a.replace(/^\s+/,"").replace(/\s+$/,"")};String.prototype.trim&&(H=function(a){return"string"!=typeof a?a:a.trim()});var I={script:/^\s*(?:\/\/)?\s*<!--.*\n?/,style:/^\s*<!--\s*/},J={script:/\s*(?:\/\/)?\s*-->\s*$/,style:/\s*-->\s*$/},K=new RegExp("^(?:class|id|style|title|lang|dir|on(?:focus|blur|change|click|dblclick|mouse(?:down|up|over|move|out)|key(?:press|down|up)))$");"undefined"!=typeof exports?exports.minify=E:a.minify=E}(this),function(a){"use strict";function b(a){return/^(?:b|i|big|small|hr|blink|marquee)$/.test(a)}function c(a){return/^(?:applet|basefont|center|dir|font|isindex|s|strike|u)$/.test(a)}function d(a){return/^on[a-z]+/.test(a)}function e(a){return"style"===a.toLowerCase()}function f(a,b){return"align"===b&&/^(?:caption|applet|iframe|img|imput|object|legend|table|hr|div|h[1-6]|p)$/.test(a)||"alink"===b&&"body"===a||"alt"===b&&"applet"===a||"archive"===b&&"applet"===a||"background"===b&&"body"===a||"bgcolor"===b&&/^(?:table|t[rdh]|body)$/.test(a)||"border"===b&&/^(?:img|object)$/.test(a)||"clear"===b&&"br"===a||"code"===b&&"applet"===a||"codebase"===b&&"applet"===a||"color"===b&&/^(?:base(?:font)?)$/.test(a)||"compact"===b&&/^(?:dir|[dou]l|menu)$/.test(a)||"face"===b&&/^base(?:font)?$/.test(a)||"height"===b&&/^(?:t[dh]|applet)$/.test(a)||"hspace"===b&&/^(?:applet|img|object)$/.test(a)||"language"===b&&"script"===a||"link"===b&&"body"===a||"name"===b&&"applet"===a||"noshade"===b&&"hr"===a||"nowrap"===b&&/^t[dh]$/.test(a)||"object"===b&&"applet"===a||"prompt"===b&&"isindex"===a||"size"===b&&/^(?:hr|font|basefont)$/.test(a)||"start"===b&&"ol"===a||"text"===b&&"body"===a||"type"===b&&/^(?:li|ol|ul)$/.test(a)||"value"===b&&"li"===a||"version"===b&&"html"===a||"vlink"===b&&"body"===a||"vspace"===b&&/^(?:applet|img|object)$/.test(a)||"width"===b&&/^(?:hr|td|th|applet|pre)$/.test(a)}function g(a,b){return"href"===a&&/^\s*javascript\s*:\s*void\s*(\s+0|\(\s*0\s*\))\s*$/i.test(b)}function h(){this.log=[],this._lastElement=null,this._isElementRepeated=!1}h.prototype.testElement=function(a){c(a)?this.log.push('Found <span class="deprecated-element">deprecated</span> <strong><code>&lt;'+a+"&gt;</code></strong> element"):b(a)?this.log.push('Found <span class="presentational-element">presentational</span> <strong><code>&lt;'+a+"&gt;</code></strong> element"):this.checkRepeatingElement(a)},h.prototype.checkRepeatingElement=function(a){"br"===a&&"br"===this._lastElement?this._isElementRepeated=!0:this._isElementRepeated&&(this._reportRepeatingElement(),this._isElementRepeated=!1),this._lastElement=a},h.prototype._reportRepeatingElement=function(){this.log.push("Found <code>&lt;br></code> sequence. Try replacing it with styling.")},h.prototype.testAttribute=function(a,b,c){d(b)?this.log.push('Found <span class="event-attribute">event attribute</span> (<strong>'+b+"</strong>) on <strong><code>&lt;"+a+"&gt;</code></strong> element."):f(a,b)?this.log.push('Found <span class="deprecated-attribute">deprecated</span> <strong>'+b+"</strong> attribute on <strong><code>&lt;"+a+"&gt;</code></strong> element."):e(b)?this.log.push('Found <span class="style-attribute">style attribute</span> on <strong><code>&lt;'+a+"&gt;</code></strong> element."):g(b,c)&&this.log.push('Found <span class="inaccessible-attribute">inaccessible attribute</span> (on <strong><code>&lt;'+a+"&gt;</code></strong> element).")},h.prototype.testChars=function(a){this._lastElement="",/(&nbsp;\s*){2,}/.test(a)&&this.log.push("Found repeating <strong><code>&amp;nbsp;</code></strong> sequence. Try replacing it with styling.")},h.prototype.test=function(a,b,c){this.testElement(a),this.testAttribute(a,b,c)},h.prototype.populate=function(a){if(this._isElementRepeated&&this._reportRepeatingElement(),this.log.length)if(a)a.innerHTML="<ol><li>"+this.log.join("<li>")+"</ol>";else{var b=" - "+this.log.join("\n - ").replace(/(<([^>]+)>)/gi,"").replace(/&lt;/g,"<").replace(/&gt;/g,">");console.log(b)}},a.HTMLLint=h}("undefined"==typeof exports?this:exports);
6
+ !function(a){"use strict";function b(a){var b,c=new RegExp("(?:\\s*[\\w:.-]+(?:\\s*(?:"+d(a)+")\\s*(?:(?:\"[^\"]*\")|(?:'[^']*')|[^>\\s]+))?)*");if(a.customAttrSurround){for(var e=[],f=a.customAttrSurround.length-1;f>=0;f--)e[f]="(?:\\s*"+a.customAttrSurround[f][0].source+c.source+a.customAttrSurround[f][1].source+")";e.unshift(c.source),b=new RegExp("((?:"+e.join("|")+")*)")}else b=new RegExp("("+c.source+")");return new RegExp(j.source+b.source+k.source)}function c(a){var b=new RegExp(f.source+"(?:\\s*("+d(a)+")\\s*(?:"+i.join("|")+"))?");if(a.customAttrSurround){for(var c=[],e=a.customAttrSurround.length-1;e>=0;e--)c[e]="(?:("+a.customAttrSurround[e][0].source+")"+b.source+"("+a.customAttrSurround[e][1].source+"))";return c.unshift("(?:"+b.source+")"),new RegExp(c.join("|"),"g")}return new RegExp(b.source,"g")}function d(a){return h.concat(a.customAttrAssign||[]).map(function(a){return"(?:"+a.source+")"}).join("|")}function e(a){for(var b={},c=a.split(","),d=0;d<c.length;d++)b[c[d]]=!0,b[c[d].toUpperCase()]=!0;return b}var f=/([\w:.-]+)/,g=/=/,h=[g],i=[/"((?:\\.|[^"])*)"/.source,/'((?:\\.|[^'])*)'/.source,/([^>\s]+)/.source],j=/^<([\w:-]+)/,k=/\s*(\/?)>/,l=/^<\/([\w:-]+)[^>]*>/,m=/\/>$/,n=/^<!DOCTYPE [^>]+>/i,o=!1;"x".replace(/x(.)?/g,function(a,b){o=""===b});var p,q,r,s=e("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,wbr"),t=e("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,noscript,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,svg,textarea,tt,u,var"),u=e("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source"),v=e("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"),w=e("script,style,noscript"),x={},y=a.HTMLParser=function(a,d){function e(a,b,c,e){for(var g=!1;!d.html5&&y.last()&&t[y.last()];)f("",y.last());u[b]&&y.last()===b&&f("",b),e=s[b]||!!e;var h=[];c.replace(B,function(){var a,b,c,e,f,g,i=7;if(o&&-1===arguments[0].indexOf('""')&&(""===arguments[3]&&(arguments[3]=void 0),""===arguments[4]&&(arguments[4]=void 0),""===arguments[5]&&(arguments[5]=void 0)),a=arguments[1])g=arguments[2],c=arguments[3],b=c||arguments[4]||arguments[5];else if(d.customAttrSurround)for(var j=d.customAttrSurround.length-1;j>=0;j--)if(a=arguments[j*i+7],g=arguments[j*i+8],a){c=arguments[j*i+9],b=c||arguments[j*i+10]||arguments[j*i+11],e=arguments[j*i+6],f=arguments[j*i+12];break}void 0===b&&(b=v[a]?a:c),h.push({name:a,value:b,escaped:b&&b.replace(/(^|.)("+)/g,function(a){return a.replace(/"/g,"&quot;")}),customAssign:g||"=",customOpen:e||"",customClose:f||""})}),e?g=a.match(m):y.push({tag:b,attrs:h}),d.start&&d.start(b,h,e,g)}function f(a,b){var c;if(b){var e=b.toLowerCase();for(c=y.length-1;c>=0&&y[c].tag.toLowerCase()!==e;c--);}else c=0;if(c>=0){for(var f=y.length-1;f>=c;f--)d.end&&d.end(y[f].tag,y[f].attrs);y.length=c}}var g,h,i,j,k,y=[],z=a;y.last=function(){var a=this[this.length-1];return a&&a.tag};for(var A=b(d),B=c(d);a;){if(h=!0,y.last()&&w[y.last()])p=y.last().toLowerCase(),q=x[p]||(x[p]=new RegExp("([\\s\\S]*?)</"+p+"[^>]*>","i")),a=a.replace(q,function(a,b){return"script"!==p&&"style"!==p&&"noscript"!==p&&(b=b.replace(/<!--([\s\S]*?)-->/g,"$1").replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g,"$1")),d.chars&&d.chars(b),""}),f("",p);else if(/^<!--/.test(a)&&(g=a.indexOf("-->"),g>=0&&(d.comment&&d.comment(a.substring(4,g)),a=a.substring(g+3),h=!1)),/^<!\[/.test(a)?(g=a.indexOf("]>"),g>=0&&(d.comment&&d.comment(a.substring(2,g+1),!0),a=a.substring(g+2),h=!1)):/^<\?/.test(a)?(g=a.indexOf("?>",2),g>=0&&(d.chars&&d.chars(a.substring(0,g+2)),a=a.substring(g+2))):/^<%/.test(a)?(g=a.indexOf("%>",2),g>=0&&(d.chars&&d.chars(a.substring(0,g+2)),a=a.substring(g+2))):(i=n.exec(a))?(d.doctype&&d.doctype(i[0]),a=a.substring(i[0].length),h=!1):/^<\//.test(a)?(i=a.match(l),i&&(a=a.substring(i[0].length),i[0].replace(l,f),j="/"+i[1].toLowerCase(),h=!1)):/^</.test(a)&&(i=a.match(A),i&&(a=a.substring(i[0].length),i[0].replace(A,e),j=i[1].toLowerCase(),h=!1)),h){g=a.indexOf("<");var C=0>g?a:a.substring(0,g);a=0>g?"":a.substring(g),r=a.match(A),r?k=r[1]:(r=a.match(l),k=r?"/"+r[1]:""),d.chars&&d.chars(C,j,k)}if(a===z)throw"Parse Error: "+a;z=a}f()};a.HTMLtoXML=function(a){var b="";return new y(a,{start:function(a,c,d){b+="<"+a;for(var e=0;e<c.length;e++)b+=" "+c[e].name+'="'+c[e].escaped+'"';b+=(d?"/":"")+">"},end:function(a){b+="</"+a+">"},chars:function(a){b+=a},comment:function(a){b+="<!--"+a+"-->"},ignore:function(a){b+=a}}),b},a.HTMLtoDOM=function(a,b){var c=e("html,head,body,title"),d={link:"head",base:"head"};b?b=b.ownerDocument||b.getOwnerDocument&&b.getOwnerDocument()||b:"undefined"!=typeof DOMDocument?b=new DOMDocument:"undefined"!=typeof document&&document.implementation&&document.implementation.createDocument?b=document.implementation.createDocument("","",null):"undefined"!=typeof ActiveX&&(b=new ActiveXObject("Msxml.DOMDocument"));var f=[],g=b.documentElement||b.getDocumentElement&&b.getDocumentElement();if(!g&&b.createElement&&!function(){var a=b.createElement("html"),c=b.createElement("head");c.appendChild(b.createElement("title")),a.appendChild(c),a.appendChild(b.createElement("body")),b.appendChild(a)}(),b.getElementsByTagName)for(var h in c)c[h]=b.getElementsByTagName(h)[0];var i=c.body;return new y(a,{start:function(a,e,g){if(c[a])return void(i=c[a]);var h=b.createElement(a);for(var j in e)h.setAttribute(e[j].name,e[j].value);d[a]&&"boolean"!=typeof c[d[a]]?c[d[a]].appendChild(h):i&&i.appendChild&&i.appendChild(h),g||(f.push(h),i=h)},end:function(){f.length-=1,i=f[f.length-1]},chars:function(a){i.appendChild(b.createTextNode(a))},comment:function(){},ignore:function(){}}),b}}("undefined"==typeof exports?this:exports),function(a){"use strict";function b(a){return a?a.replace(/[\t\n\r ]+/g," "):a}function c(a,b,c,d){var e=["a","abbr","acronym","b","bdi","bdo","big","button","cite","code","del","dfn","em","font","i","ins","kbd","mark","q","rt","rp","s","samp","small","span","strike","strong","sub","sup","svg","time","tt","u","var"],f=/^[\t ]*[\n\r]+[\t\n\r ]*/,g=/[\t\n\r ]*[\n\r]+[\t ]*$/,h=f.test(a)?"\n":" ",i=g.test(a)?"\n":" ",j="[{htmlmin-lb}]";return b&&"img"!==b&&"input"!==b&&("/"!==b.substr(0,1)||"/"===b.substr(0,1)&&-1===e.indexOf(b.substr(1)))&&(a=a.replace(/^\s+/,d.conservativeCollapse?" ":d.preserveLineBreaks?h:"")),c&&"img"!==c&&"input"!==c&&("/"===c.substr(0,1)||"/"!==c.substr(0,1)&&-1===e.indexOf(c))&&(a=a.replace(/\s+$/,d.conservativeCollapse?" ":d.preserveLineBreaks?i:"")),b&&c?(d.preserveLineBreaks&&(a=a.replace(f,j).replace(g,j)),a.replace(/[\t\n\r]+/g," ").replace(/[ ]+/g," ").replace(j,"\n")):a}function d(a){return/\[if[^\]]+\]/.test(a)||/\s*((?:<!)?\[endif\])$/.test(a)}function e(a,b){if(/^!/.test(a))return!0;if(b.ignoreCustomComments)for(var c=0,d=b.ignoreCustomComments.length;d>c;c++)if(b.ignoreCustomComments[c].test(a))return!0;return!1}function f(a){return/^on[a-z]+/.test(a)}function g(a){return/^[^\x20\t\n\f\r"'`=<>]+$/.test(a)&&!/\/$/.test(a)&&!/\/$/.test(a)}function h(a,b){for(var c=a.length;c--;)if(a[c].name.toLowerCase()===b)return!0;return!1}function i(a,b,c,d){return c=c?J(c.toLowerCase()):"","script"===a&&"language"===b&&"javascript"===c||"form"===a&&"method"===b&&"get"===c||"input"===a&&"type"===b&&"text"===c||"script"===a&&"charset"===b&&!h(d,"src")||"a"===a&&"name"===b&&h(d,"id")||"area"===a&&"shape"===b&&"rect"===c}function j(a,b,c){return"script"===a&&"type"===b&&"text/javascript"===J(c.toLowerCase())}function k(a,b,c){return("style"===a||"link"===a)&&"type"===b&&"text/css"===J(c.toLowerCase())}function l(a,b){var c=/^(?:allowfullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultchecked|defaultmuted|defaultselected|defer|disabled|enabled|formnovalidate|hidden|indeterminate|inert|ismap|itemscope|loop|multiple|muted|nohref|noresize|noshade|novalidate|nowrap|open|pauseonexit|readonly|required|reversed|scoped|seamless|selected|sortable|spellcheck|truespeed|typemustmatch|visible)$/i.test(a);if(c)return!0;var d=K[a.toLowerCase()];return d?-1===d.indexOf(b.toLowerCase()):!1}function m(a,b){return/^(?:a|area|link|base)$/.test(b)&&"href"===a||"img"===b&&/^(?:src|longdesc|usemap)$/.test(a)||"object"===b&&/^(?:classid|codebase|data|usemap)$/.test(a)||"q"===b&&"cite"===a||"blockquote"===b&&"cite"===a||("ins"===b||"del"===b)&&"cite"===a||"form"===b&&"action"===a||"input"===b&&("src"===a||"usemap"===a)||"head"===b&&"profile"===a||"script"===b&&("src"===a||"for"===a)}function n(a,b){return/^(?:a|area|object|button)$/.test(b)&&"tabindex"===a||"input"===b&&("maxlength"===a||"tabindex"===a)||"select"===b&&("size"===a||"tabindex"===a)||"textarea"===b&&/^(?:rows|cols|tabindex)$/.test(a)||"colgroup"===b&&"span"===a||"col"===b&&"span"===a||("th"===b||"td"===b)&&("rowspan"===a||"colspan"===a)}function o(a,c,d,e,g){if(d&&f(c)){if(d=J(d).replace(/^javascript:\s*/i,"").replace(/\s*;$/,""),e.minifyJS){var h="(function(){"+d+"})()",i=D(h,e.minifyJS);return i.slice(12,i.length-4).replace(/"/g,"&quot;")}return d}return"class"===c?b(J(d)):m(c,a)?(d=J(d),e.minifyURLs?C(d,e.minifyURLs):d):n(c,a)?J(d):"style"===c?(d=J(d),d&&(d=d.replace(/\s*;\s*$/,"")),e.minifyCSS?E(d,e.minifyCSS):d):(p(a,g)&&"content"===c?d=d.replace(/1\.0/g,"1").replace(/\s+/g,""):d&&e.customAttrCollapse&&e.customAttrCollapse.test(c)&&(d=d.replace(/\n+/g,"")),d)}function p(a,b){if("meta"!==a)return!1;for(var c=0,d=b.length;d>c;c++)if("name"===b[c].name&&"viewport"===b[c].value)return!0}function q(a){return a.replace(/^(\[[^\]]+\]>)\s*/,"$1").replace(/\s*(<!\[endif\])$/,"$1")}function r(a){return a.replace(/^(?:\s*\/\*\s*<!\[CDATA\[\s*\*\/|\s*\/\/\s*<!\[CDATA\[.*)/,"").replace(/(?:\/\*\s*\]\]>\s*\*\/|\/\/\s*\]\]>)\s*$/,"")}function s(a,b,c){for(var d=0,e=c.length;e>d;d++)if("type"===c[d].name.toLowerCase()&&b.processScripts.indexOf(c[d].value)>-1)return F(a,b);return a}function t(a,b){return a.replace(L[b],"").replace(M[b],"")}function u(a){return/^(?:html|t?body|t?head|tfoot|tr|td|th|dt|dd|option|colgroup|source)$/.test(a)}function v(a,b,c){var d=!c||/^\s*$/.test(c);return d?"input"===a&&"value"===b||N.test(b):!1}function w(a,b){if("textarea"===a)return!1;if("script"===a)for(var c=b.length-1;c>=0;c--)if("src"===b[c].name)return!1;return!0}function x(a){return!/^(?:script|style|pre|textarea)$/.test(a)}function y(a){return!/^(?:pre|textarea)$/.test(a)}function z(a){for(var b="",c=0,d=a.length;d>c;c++)b+=" "+a[c].name+(l(a[c].value)?"":'="'+a[c].value+'"');return b}function A(a,b,c,d,e,f){var h,m,n=f.caseSensitive?a.name:a.name.toLowerCase(),p=a.escaped,q=d&&e===b.length-1;return f.removeRedundantAttributes&&i(c,n,p,b)||f.removeScriptTypeAttributes&&j(c,n,p)||f.removeStyleLinkTypeAttributes&&k(c,n,p)?"":(p=o(c,n,p,f,b),m=void 0!==p&&!f.removeAttributeQuotes||!g(p)||q?'"'+p+'"':p,f.removeEmptyAttributes&&v(c,n,p)?"":(h=void 0===p||f.collapseBooleanAttributes&&l(n,p)?n:n+a.customAssign+m," "+a.customOpen+h+a.customClose))}function B(a){for(var b=["canCollapseWhitespace","canTrimWhitespace"],c=0,d=b.length;d>c;c++)a[b[c]]||(a[b[c]]=function(){return!1})}function C(b,c){"object"!=typeof c&&(c={});try{var d=a.RelateUrl;return"undefined"==typeof d&&"function"==typeof require&&(d=require("relateurl")),d&&d.relate?d.relate(b,c):b}catch(e){H(e)}return b}function D(b,c){"object"!=typeof c&&(c={}),c.fromString=!0,c.output={inline_script:!0};try{var d=a.UglifyJS;if("undefined"==typeof d&&"function"==typeof require&&(d=require("uglify-js")),!d)return b;if(d.minify)return d.minify(b,c).code;if(d.parse){var e=d.parse(b);e.figure_out_scope();var f=d.Compressor(),g=e.transform(f);g.figure_out_scope(),g.compute_char_frequency(),c.mangle!==!1&&g.mangle_names();var h=d.OutputStream(c.output);return g.print(h),h.toString()}return b}catch(i){H(i)}return b}function E(a,b){"object"!=typeof b&&(b={}),"undefined"==typeof b.noAdvanced&&(b.noAdvanced=!0);try{if("undefined"!=typeof CleanCSS)return new CleanCSS(b).minify(a);if("function"==typeof require){var c=require("clean-css");return new c(b).minify(a)}}catch(d){H(d)}return a}function F(a,f){function g(a,b){return x(a)||f.canCollapseWhitespace(a,b)}function h(a,b){return y(a)||f.canTrimWhitespace(a,b)}f=f||{},a=J(a),B(f);var i=[],j=[],k="",l="",m=[],n=[],o=[],p=f.lint,v=!1,C=new Date;f.removeIgnored&&(a=a.replace(/<\?[^\?]+\?>/g,"").replace(/<%[^%]+%>/g,"")),new I(a,{html5:"undefined"!=typeof f.html5?f.html5:!0,start:function(a,b,c,d){if(v)return void j.push("<"+a,z(b),d?"/":"",">");a=f.caseSensitive?a:a.toLowerCase(),l=a,k="",m=b,f.collapseWhitespace&&(h(a,b)||n.push(a),g(a,b)||o.push(a));var e="<"+a,i=(d&&f.keepClosingSlash?"/":"")+">";0===b.length&&(e+=i),j.push(e),p&&p.testElement(a);for(var q,r=0,s=b.length;s>r;r++)p&&p.testAttribute(a,b[r].name.toLowerCase(),b[r].escaped),q=A(b[r],b,a,d,r,f),r===s-1&&(q+=i),j.push(q)},end:function(a,b){if(v)return void j.push("</"+a+">");f.collapseWhitespace&&(n.length&&a===n[n.length-1]&&n.pop(),o.length&&a===o[o.length-1]&&o.pop());var c=""===k&&a===l;if(f.removeEmptyElements&&c&&w(a,b)){for(var d=j.length-1;d>=0;d--)if(/^<[^\/!]/.test(j[d])){j.splice(d);break}}else f.removeOptionalTags&&u(a)||(j.push("</"+(f.caseSensitive?a:a.toLowerCase())+">"),i.push.apply(i,j),j.length=0,k="")},chars:function(a,d,e){return d=""===d?"comment":d,e=""===e?"comment":e,v?void j.push(a):(("script"===l||"style"===l)&&(f.removeCommentsFromCDATA&&(a=t(a,l)),f.removeCDATASectionsFromCDATA&&(a=r(a)),f.processScripts&&(a=s(a,f,m))),"script"===l&&f.minifyJS&&(a=D(a,f.minifyJS)),"style"===l&&f.minifyCSS&&(a=E(a,f.minifyCSS)),f.collapseWhitespace&&(n.length||(a=d&&"comment"!==d||e&&"comment"!==e?c(a,d,e,f):J(a)),o.length||(a=d&&e||"html"===e?a:b(a))),k=a,p&&p.testChars(a),void j.push(a))},comment:function(a,b){var c=b?"<!":"<!--",g=b?">":"-->";return/^\s*htmlmin:ignore/.test(a)?(v=!v,void(f.removeComments||j.push("<!--"+a+"-->"))):(a=f.removeComments?d(a)?c+q(a)+g:e(a,f)?"<!--"+a+"-->":"":c+a+g,void j.push(a))},doctype:function(a){j.push(f.useShortDoctype?"<!DOCTYPE html>":b(a))},customAttrAssign:f.customAttrAssign,customAttrSurround:f.customAttrSurround}),i.push.apply(i,j);var F=G(i,f);return H("minified in: "+(new Date-C)+"ms"),F}function G(a,b){var c,d=b.maxLineLength;if(d){for(var e,f=[],g="",h=0,i=a.length;i>h;h++)e=a[h],g.length+e.length<d?g+=e:(f.push(g.replace(/^\n/,"")),g=e);f.push(g),c=f.join("\n")}else c=a.join("");return c}var H,I;H=a.console&&a.console.log?function(b){a.console.log(b)}:function(){},a.HTMLParser?I=a.HTMLParser:"function"==typeof require&&(I=require("./htmlparser").HTMLParser);var J=function(a){return"string"!=typeof a?a:a.replace(/^\s+/,"").replace(/\s+$/,"")};String.prototype.trim&&(J=function(a){return"string"!=typeof a?a:a.trim()});var K={draggable:["true","false"]},L={script:/^\s*(?:\/\/)?\s*<!--.*\n?/,style:/^\s*<!--\s*/},M={script:/\s*(?:\/\/)?\s*-->\s*$/,style:/\s*-->\s*$/},N=new RegExp("^(?:class|id|style|title|lang|dir|on(?:focus|blur|change|click|dblclick|mouse(?:down|up|over|move|out)|key(?:press|down|up)))$");"undefined"!=typeof exports?exports.minify=F:a.minify=F}(this),function(a){"use strict";function b(a){return/^(?:big|small|hr|blink|marquee)$/.test(a)}function c(a){return/^(?:applet|basefont|center|dir|font|isindex|strike)$/.test(a)}function d(a){return/^on[a-z]+/.test(a)}function e(a){return"style"===a.toLowerCase()}function f(a,b){return"align"===b&&/^(?:caption|applet|iframe|img|imput|object|legend|table|hr|div|h[1-6]|p)$/.test(a)||"alink"===b&&"body"===a||"alt"===b&&"applet"===a||"archive"===b&&"applet"===a||"background"===b&&"body"===a||"bgcolor"===b&&/^(?:table|t[rdh]|body)$/.test(a)||"border"===b&&/^(?:img|object)$/.test(a)||"clear"===b&&"br"===a||"code"===b&&"applet"===a||"codebase"===b&&"applet"===a||"color"===b&&/^(?:base(?:font)?)$/.test(a)||"compact"===b&&/^(?:dir|[dou]l|menu)$/.test(a)||"face"===b&&/^base(?:font)?$/.test(a)||"height"===b&&/^(?:t[dh]|applet)$/.test(a)||"hspace"===b&&/^(?:applet|img|object)$/.test(a)||"language"===b&&"script"===a||"link"===b&&"body"===a||"name"===b&&"applet"===a||"noshade"===b&&"hr"===a||"nowrap"===b&&/^t[dh]$/.test(a)||"object"===b&&"applet"===a||"prompt"===b&&"isindex"===a||"size"===b&&/^(?:hr|font|basefont)$/.test(a)||"start"===b&&"ol"===a||"text"===b&&"body"===a||"type"===b&&/^(?:li|ol|ul)$/.test(a)||"value"===b&&"li"===a||"version"===b&&"html"===a||"vlink"===b&&"body"===a||"vspace"===b&&/^(?:applet|img|object)$/.test(a)||"width"===b&&/^(?:hr|td|th|applet|pre)$/.test(a)}function g(a,b){return"href"===a&&/^\s*javascript\s*:\s*void\s*(\s+0|\(\s*0\s*\))\s*$/i.test(b)}function h(){this.log=[],this._lastElement=null,this._isElementRepeated=!1}h.prototype.testElement=function(a){c(a)?this.log.push('Found <span class="deprecated-element">deprecated</span> <strong><code>&lt;'+a+"&gt;</code></strong> element"):b(a)?this.log.push('Found <span class="presentational-element">presentational</span> <strong><code>&lt;'+a+"&gt;</code></strong> element"):this.checkRepeatingElement(a)},h.prototype.checkRepeatingElement=function(a){"br"===a&&"br"===this._lastElement?this._isElementRepeated=!0:this._isElementRepeated&&(this._reportRepeatingElement(),this._isElementRepeated=!1),this._lastElement=a},h.prototype._reportRepeatingElement=function(){this.log.push("Found <code>&lt;br></code> sequence. Try replacing it with styling.")},h.prototype.testAttribute=function(a,b,c){d(b)?this.log.push('Found <span class="event-attribute">event attribute</span> (<strong>'+b+"</strong>) on <strong><code>&lt;"+a+"&gt;</code></strong> element."):f(a,b)?this.log.push('Found <span class="deprecated-attribute">deprecated</span> <strong>'+b+"</strong> attribute on <strong><code>&lt;"+a+"&gt;</code></strong> element."):e(b)?this.log.push('Found <span class="style-attribute">style attribute</span> on <strong><code>&lt;'+a+"&gt;</code></strong> element."):g(b,c)&&this.log.push('Found <span class="inaccessible-attribute">inaccessible attribute</span> (on <strong><code>&lt;'+a+"&gt;</code></strong> element).")},h.prototype.testChars=function(a){this._lastElement="",/(&nbsp;\s*){2,}/.test(a)&&this.log.push("Found repeating <strong><code>&amp;nbsp;</code></strong> sequence. Try replacing it with styling.")},h.prototype.test=function(a,b,c){this.testElement(a),this.testAttribute(a,b,c)},h.prototype.populate=function(a){if(this._isElementRepeated&&this._reportRepeatingElement(),this.log.length)if(a)a.innerHTML="<ol><li>"+this.log.join("<li>")+"</ol>";else{var b=" - "+this.log.join("\n - ").replace(/(<([^>]+)>)/gi,"").replace(/&lt;/g,"<").replace(/&gt;/g,">");console.log(b)}},a.HTMLLint=h}("undefined"==typeof exports?this:exports);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "html-minifier",
3
3
  "description": "HTML minifier with lint-like capabilities.",
4
- "version": "0.6.5",
4
+ "version": "0.6.9",
5
5
  "keywords": [
6
6
  "html",
7
7
  "minifier",
@@ -42,21 +42,21 @@
42
42
  "change-case": "2.1.x",
43
43
  "clean-css": "2.2.x",
44
44
  "cli": "0.6.x",
45
- "uglify-js": "2.4.x"
45
+ "uglify-js": "2.4.x",
46
+ "relateurl": "0.2.x"
46
47
  },
47
48
  "devDependencies": {
48
49
  "cli-table": "0.3.x",
49
50
  "grunt": "0.4.x",
50
- "grunt-contrib-concat": "0.4.x",
51
+ "grunt-contrib-concat": "0.5.x",
51
52
  "grunt-contrib-jshint": "0.10.x",
52
53
  "grunt-contrib-qunit": "0.5.x",
53
- "grunt-contrib-uglify": "0.5.x",
54
+ "grunt-contrib-uglify": "0.6.x",
54
55
  "grunt-exec": "0.4.x",
55
- "grunt-jscs": "0.6.x",
56
+ "grunt-jscs": "0.7.x",
56
57
  "load-grunt-tasks": "0.6.x",
57
- "qunit": "0.6.x",
58
- "time-grunt": "0.3.x",
59
- "underscore": "1.6.x"
58
+ "qunit": "0.7.x",
59
+ "time-grunt": "1.0.x"
60
60
  },
61
61
  "files": [
62
62
  "dist",