From: Juriy Zaytsev Date: Fri, 12 Feb 2010 21:28:20 +0000 (-0500) Subject: Extract LINT logic into a separate file -- htmllint.js, now also under a standalone... X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=1cc3f849b922889afcee9861a5b555ee0974f59a;p=html-minifier.git Extract LINT logic into a separate file -- htmllint.js, now also under a standalone constructor -- HTMLLint (still needs tests) --- diff --git a/index.html b/index.html index c25676b..0569e5d 100644 --- a/index.html +++ b/index.html @@ -4,120 +4,133 @@ HTML minifier + + +
-
-

HTML Minifier (ver. 0.3)

- -

- -

- -
-
-
    -
  • - - - - - - Conditional comments are left intact. - -
  • -
  • - - -
  • -
  • - - -
  • -
  • - - -
  • -
  • - - -
  • -
  • - - -
    - <script language="Javascript" ...>
    - <form method="get" ...>
    - <input type="text" ...>
    - <script src="..." charset="...">
    - <a id="..." name="...">
    - <... onclick="javascript:..." ...> -
    -
  • -
  • - - -
  • -
  • - - -
  • -
  • - -
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
    + <script language="Javascript" ...>
    + <form method="get" ...>
    + <input type="text" ...>
    + <script src="..." charset="...">
    + <a id="..." name="...">
    + <... onclick="javascript:..." ...> +
    +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
-

-

- Minifier is very draft and is not yet thoroughly tested. Use at your own risk. -

+
+ LINT REPORT: +
+
TODO:

- Minifier is made by kangax, + HTMLMinifier is made by kangax, using tweaked version of HTML parser by John Resig (which, in its turn, is based on work of Erik Arvidsson).

diff --git a/master.css b/master.css index 09f79f8..e7e6ee1 100644 --- a/master.css +++ b/master.css @@ -1,22 +1,29 @@ -body { font-family: "Cambria", Georgia, Times, "Times New Roman", serif; } +body { font-family: "Cambria", Georgia, Times, "Times New Roman", serif; margin-top: 0; padding-top: 0; } textarea { height: 30em; } h1 { margin-top: 0.5em; font-size: 1.25em; } button { font-weight: bold; width: 100px; } -#wrapper { overflow: hidden; width: 65%; float: left; } +#outer-wrapper { overflow: hidden; } +#wrapper { width: 65%; float: left; } #input { width: 99%; height: 18em; } #output { width: 99%; height: 18em; margin-bottom: 2em; } -#options { float: right; width: 33%; padding-left: 1em; margin-top: 2.5em; min-height: 50em; } +#options { float: right; width: 33%; padding-left: 1em; margin-top: 3em; } #options ul { list-style: none; padding: 0.5em; overflow: hidden; background: #ffe; margin-top: 0; } #options ul li { float: left; clear: both; padding-bottom: 0.5em; } #options ul li div { margin-left: 1.75em; } #options label, #options input { float: left; } #options input { margin-right: 0.5em; } -#stats { margin-bottom: 2em; } +#stats { margin-bottom: 2em; overflow: hidden; margin-top: 0; } #todo { font-family: monospace; margin-bottom: 2em; } -#warning { background: #fcc; padding: 0.25em; display: inline-block; } +#warning { background: #fcc; padding: 0.25em; display: inline-block; margin-top: 0; font-size: 0.85em; } +#lint-report { font-family: monospace; } +#report { margin-bottom: 5em; } +#report ul { margin: 0.5em; padding-left: 1em; list-style: none; } .success { color: green; } .failure { color: red; } .quiet { font-size: 0.85em; color: #888; } -.short { display: inline-block; width: 20em; margin-top: 0.25em; } \ No newline at end of file +.short { display: inline-block; width: 20em; margin-top: 0.25em; } + +.deprecated-element, .deprecated-attribute { color: red; } +.presentational-element, .presentational-attribute { color: #FF8C00; } \ No newline at end of file diff --git a/master.js b/master.js index e020834..30dcde6 100644 --- a/master.js +++ b/master.js @@ -21,6 +21,7 @@ removeEmptyAttributes: byId('remove-empty-attributes').checked, removeEmptyElements: byId('remove-empty-elements').checked, removeOptionalTags: byId('remove-optional-tags').checked, + lint: byId('use-htmllint').checked ? new HTMLLint() : null }; } @@ -33,8 +34,10 @@ byId('convert-btn').onclick = function() { try { - var originalValue = byId('input').value, - minifiedValue = minify(originalValue, getOptions()), + var options = getOptions(), + lint = options.lint, + originalValue = byId('input').value, + minifiedValue = minify(originalValue, options), diff = originalValue.length - minifiedValue.length, savings = originalValue.length ? ((100 * diff) / originalValue.length).toFixed(2) : 0; @@ -46,6 +49,10 @@ '. Minified size: ' + commify(minifiedValue.length) + '' + '. Savings: ' + commify(diff) + ' (' + savings + '%).' + ''; + + if (lint) { + lint.populate(byId('report')); + } } catch(err) { byId('output').value = ''; diff --git a/src/htmllint.js b/src/htmllint.js new file mode 100644 index 0000000..35416fe --- /dev/null +++ b/src/htmllint.js @@ -0,0 +1,102 @@ +/*! + * HTMLLint (to be used in conjunction with HTMLMinifier) + * + * Copyright (c) 2010 Juriy "kangax" Zaytsev + * Licensed under the MIT license. + * + */ + +(function(global){ + + function isPresentationalElement(tag) { + return (/^(?:b|i|big|small|hr|blink|marquee)$/).test(tag); + } + function isDeprecatedElement(tag) { + return (/^(?:applet|basefont|center|dir|font|isindex|menu|s|strike|u)$/).test(tag); + } + function isEventAttribute(attrName) { + return (/^on[a-z]+/).test(attrName); + } + function isDeprecatedAttribute(tag, attrName) { + return ( + (attrName === 'align' && + (/^(?:caption|applet|iframe|img|imput|object|legend|table|hr|div|h[1-6]|p)$/).test(tag)) || + (attrName === 'alink' && tag === 'body') || + (attrName === 'alt' && tag === 'applet') || + (attrName === 'archive' && tag === 'applet') || + (attrName === 'background' && tag === 'body') || + (attrName === 'bgcolor' && (/^(?:table|t[rdh]|body)$/).test(tag)) || + (attrName === 'border' && (/^(?:img|object)$/).test(tag)) || + (attrName === 'clear' && tag === 'br') || + (attrName === 'code' && tag === 'applet') || + (attrName === 'codebase' && tag === 'applet') || + (attrName === 'color' && (/^(?:base(?:font)?)$/).test(tag)) || + (attrName === 'compact' && (/^(?:dir|[dou]l|menu)$/).test(tag)) || + (attrName === 'face' && (/^base(?:font)?$/).test(tag)) || + (attrName === 'height' && (/^(?:t[dh]|applet)$/).test(tag)) || + (attrName === 'hspace' && (/^(?:applet|img|object)$/).test(tag)) || + (attrName === 'language' && tag === 'script') || + (attrName === 'link' && tag === 'body') || + (attrName === 'name' && tag === 'applet') || + (attrName === 'noshade' && tag === 'hr') || + (attrName === 'nowrap' && (/^t[dh]$/).test(tag)) || + (attrName === 'object' && tag === 'applet') || + (attrName === 'prompt' && tag === 'isindex') || + (attrName === 'size' && (/^(?:hr|font|basefont)$/).test(tag)) || + (attrName === 'start' && tag === 'ol') || + (attrName === 'text' && tag === 'body') || + (attrName === 'type' && (/^(?:li|ol|ul)$/).test(tag)) || + (attrName === 'value' && tag === 'li') || + (attrName === 'version' && tag === 'html') || + (attrName === 'vlink' && tag === 'body') || + (attrName === 'vspace' && (/^(?:applet|img|object)$/).test(tag)) || + (attrName === 'width' && (/^(?:hr|td|th|applet|pre)$/).test(tag)) + ); + } + + function Lint() { + this.log = [ ]; + } + + Lint.prototype._testElement = function(tag, attrName) { + if (isDeprecatedElement(tag)) { + this.log.push( + '
  • Warning: found deprecated element (', + tag, ')
  • '); + } + else if (isPresentationalElement(tag)) { + this.log.push( + '
  • Warning: found presentational element (', + tag, ')
  • '); + } + }; + + Lint.prototype._testAttribute = function(tag, attrName) { + if (isEventAttribute(attrName)) { + this.log.push( + '
  • Warning: found event attribute (', + attrName, ')
  • '); + } + else if (isDeprecatedAttribute(tag, attrName)) { + this.log.push( + '
  • Warning: found deprecated attribute (', + attrName, ' on ', tag, ' element)
  • '); + } + }; + + Lint.prototype.test = function(tag, attrName) { + this._testElement(tag, attrName); + this._testAttribute(tag, attrName); + }; + + Lint.prototype.populate = function(writeToElement) { + var report; + if (this.log.length && writeToElement) { + report = ''; + writeToElement.innerHTML = report; + } + }; + + global.HTMLLint = Lint; + +})(this); \ No newline at end of file diff --git a/src/htmlminifier.js b/src/htmlminifier.js index ce57761..5efeec4 100644 --- a/src/htmlminifier.js +++ b/src/htmlminifier.js @@ -1,5 +1,5 @@ /*! - * HTML Minifier v0.3 + * HTMLMinifier v0.3 * http://kangax.github.com/html-minifier/ * * Copyright (c) 2010 Juriy "kangax" Zaytsev @@ -32,6 +32,10 @@ return (/\[if[^\]]+\]/).test(text); } + function isEventAttribute(attrName) { + return (/^on[a-z]+/).test(attrName); + } + function canRemoveAttributeQuotes(value) { // http://www.w3.org/TR/html4/intro/sgmltut.html#attributes // avoid \w, which could match unicode in some implementations @@ -77,11 +81,10 @@ } function cleanAttributeValue(tag, attrName, attrValue) { - if (/^on[a-z]+/.test(attrName)) { + if (isEventAttribute(attrName)) { return trimWhitespace(attrValue.replace(/^\s*javascript:\s*/i, '')); } if (attrName === 'class') { - // trim and collapse whitesapce return collapseWhitespace(trimWhitespace(attrValue)); } return attrValue; @@ -172,7 +175,8 @@ buffer = [ ], currentChars = '', currentTag = '', - t = new Date(); + lint = options.lint, + t = new Date() HTMLParser(value, { start: function( tag, attrs, unary ) { @@ -183,6 +187,7 @@ buffer.push('<', tag); for ( var i = 0, len = attrs.length; i < len; i++ ) { + lint && lint.test(tag, attrs[i].name.toLowerCase()); buffer.push(normalizeAttribute(attrs[i], attrs, tag, options)); } @@ -235,12 +240,9 @@ } }); - results.push.apply(results, buffer); - + results.push.apply(results, buffer) var str = results.join(''); - log('minified in: ' + (new Date() - t) + 'ms'); - return str; }