htmllint
Version:
An unofficial html5 linter.
118 lines (101 loc) • 3.92 kB
JavaScript
var htmlparser2 = require('htmlparser2'),
util = require('util'),
knife = require('../knife');
var DomHandler = htmlparser2.DomHandler,
DomBuilder = function () {
this.parser = null;
this.attributes = {};
this.attribArr = [];
this.dupes = [];
DomHandler.apply(this, Array.prototype.slice.call(arguments));
};
module.exports = DomBuilder;
util.inherits(DomBuilder, DomHandler);
// NOTE: this must be called before parsing begins
DomBuilder.prototype.initialize = function (parser) {
this.parser = parser;
};
DomBuilder.prototype.start = function (htmlText) {
this.htmlText = htmlText;
this.lineColFunc = knife.getLineColFunc(htmlText);
// When a tag has no close, startIndex is too large by 3 for the
// next calls to onopentag and _addDomElement. Keep track of this.
this.wasClosed = true;
};
DomBuilder.prototype.onerror = function (error) {
// TODO: actually bubble this up or queue errors
throw error;
};
DomBuilder.prototype.onattribute = function (rawName, value) {
var name = rawName.toLowerCase();
if (!this.attributes[name]) {
this.attributes[name] = {
rawName: rawName,
value: value
};
this.attribArr.push(name);
} else {
this.dupes.push(name);
}
};
/*eslint-disable no-underscore-dangle*/
DomBuilder.prototype.onopentag = function (name, attribs) {
DomHandler.prototype.onopentag.call(this, name, attribs);
var ele = this._tagStack[this._tagStack.length - 1];
ele.openIndex = this.parser.startIndex;
if (!this.wasClosed) { ele.openIndex -= 3; }
this.wasClosed = true;
ele.open = this.htmlText.slice(ele.openIndex + 1, this.parser.endIndex);
ele.openLineCol = this.lineColFunc(ele.openIndex);
// remove duplicate data
delete ele.lineCol;
ele.attribs = this.attributes;
//ele.attribsArr = this.attribArr;
knife.inputIndices(ele.attribs, ele.open, ele.openIndex);
this.attribArr
.sort(function (a, b) {
return ele.attribs[a].nameIndex - ele.attribs[b].nameIndex;
})
.forEach(function (attrib) {
var a = ele.attribs[attrib];
a.nameLineCol = this.lineColFunc(a.nameIndex);
a.valueLineCol = this.lineColFunc(a.valueIndex);
}, this);
this.attribArr = [];
this.attributes = {};
ele.dupes = this.dupes;
this.dupes = [];
};
DomBuilder.prototype.onclosetag = function () {
var ele = this._tagStack[this._tagStack.length - 1];
if (ele && !knife.isVoidElement(ele.name)) {
// Mercifully, no whitespace is allowed between < and /
this.wasClosed = this.htmlText[this.parser.startIndex + 1] === '/';
ele.close = this.wasClosed
? this.htmlText.slice(this.parser.startIndex + 2, this.parser.endIndex)
: '';
ele.closeIndex = this.parser.startIndex;
if (!this.wasClosed && ele.closeIndex == ele.openIndex) {
ele.closeIndex += ele.open.length + 1;
}
ele.closeLineCol = this.lineColFunc(ele.closeIndex);
}
DomHandler.prototype.onclosetag.call(this);
};
DomBuilder.prototype.onprocessinginstruction = function (name, data) {
// htmlparser2 doesn't normally update the position when processing
// declarations or processing directives (<!doctype ...> or <?...> elements)
this.parser._updatePosition(2);
DomHandler.prototype.onprocessinginstruction.call(this, name, data);
};
DomBuilder.prototype._addDomElement = function (ele) {
if (!this.parser) {
// TODO: rewrite error msg
throw new Error('stop being a bone head >.<');
}
ele.index = this.parser.startIndex;
if (!this.wasClosed) { ele.index -= 3; }
ele.lineCol = this.lineColFunc(ele.index);
DomHandler.prototype._addDomElement.call(this, ele);
};
/*eslint-enable no-underscore-dangle*/