Skip to content

Commit

Permalink
Replaced _tagStack with _contentFlags, tweaked DefaultHandler
Browse files Browse the repository at this point in the history
That fixed tautologistics#29.
  • Loading branch information
fb55 committed Nov 5, 2011
1 parent 0e78ab5 commit bc12cd8
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 88 deletions.
30 changes: 26 additions & 4 deletions lib/DefaultHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var ElementType = require("./ElementType.js");
function DefaultHandler(callback, options){
this.dom = [];
this._done = false;
this._inSpecialTag = false;
this._tagStack = [];
if(options){
this._options = options;
Expand Down Expand Up @@ -30,6 +31,7 @@ var emptyTags={area:true,base:true,basefont:true,br:true,col:true,frame:true,hr:
DefaultHandler.prototype.reset = function() {
this.dom = [];
this._done = false;
this._inSpecialTag = false;
this._tagStack = [];
};
//Signals the handler that parsing is done
Expand All @@ -56,17 +58,33 @@ DefaultHandler.prototype._closeTag = function(name){

var pos = this._tagStack.length - 1;
while (pos !== -1 && this._tagStack[pos--].name !== name) { }
if ( ++pos !== 0 || this._tagStack[0].name === name)
this._tagStack.splice(pos, this._tagStack.length);
if ( pos !== -1 || this._tagStack[0].name === name)
this._tagStack.splice(pos+1);
};

DefaultHandler.prototype._addDomElement = function(element){
if(!this._options.verbose) delete element.raw;

var lastTag = this._tagStack[this._tagStack.length-1];
var lastTag = this._tagStack[this._tagStack.length-1], tmp;
if(!lastTag) this.dom.push(element);
else{ //There are parent elements
if(!lastTag.children) lastTag.children = [element];
if(!lastTag.children){
lastTag.children = [element];
return;
}
tmp = lastTag.children[lastTag.children.length-1];
if(element.type === ElementType.Comment && tmp.type === ElementType.Comment){
tmp.data += element.data;
if(this._options.verbose) tmp.raw = tmp.data;
}
else if(this._inSpecialTag && element.type === ElementType.Text){
if(tmp.type !== ElementType.Text) lastTag.children.push(element);
else {
tmp.data += element.data;
if(this._options.verbose)
tmp.raw = tmp.data;
}
}
else lastTag.children.push(element);
}
}
Expand All @@ -76,6 +94,10 @@ DefaultHandler.prototype._openTag = function(element){

this._addDomElement(element);

if(element.type === ElementType.Script || element.type === ElementType.Style){
this._inSpecialTag = true;
}

//Don't add tags to the tag stack that can't have children
if(!this._isEmptyTag(element.name)) this._tagStack.push(element);
}
Expand Down
159 changes: 78 additions & 81 deletions lib/Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ function Parser(handler, options){

this._buffer = "";
this._prevTagSep = "";
this._contentFlags = 0;
this._done = false;
this._tagStack = [];
this._elements = [];
this._current = 0;
this._location = {
Expand Down Expand Up @@ -64,10 +64,10 @@ Parser.prototype.done = function(){
var rawData = this._buffer;
this._buffer = "";
var element = {
raw: rawData
, data: this._parseState === ElementType.Text ? rawData : rawData.trim()
, type: this._parseState
};
raw: rawData,
data: this._parseState === ElementType.Text ? rawData : rawData.trim(),
type: this._parseState
};
if(tagTypes[this._parseState]){
element.name = parseTagName(element.data);
var attrs = parseAttributes(element.data);
Expand All @@ -76,7 +76,7 @@ Parser.prototype.done = function(){
this._elements.push(element);
}

this.writeHandler(true);
this.writeHandler();
this._handler.done();
};

Expand All @@ -86,14 +86,14 @@ Parser.prototype.reset = function(){
this._prevTagSep = "";
this._done = false;
this._current = 0;
this._contentFlags = 0;
this._location = {
row: 0
, col: 0
, charOffset: 0
, inBuffer: 0
};
this._parseState = ElementType.Text;
this._tagStack = [];
this._elements = [];
this._handler.reset();
};
Expand Down Expand Up @@ -125,11 +125,18 @@ var parseTagName = function(data){
return match[1] + match[2];
};

//Special tags that are threated differently
var SpecialTags = {};
SpecialTags[ElementType.Style] = 1; //2^0
SpecialTags[ElementType.Script] = 2; //2^1
SpecialTags["w"] = 4; //2^2 - if set, append prev tag sep to data
SpecialTags[ElementType.Comment] = 8; //2^8

//Parses through HTML text and returns an array of found elements
Parser.prototype.parseTags = function(){
var buffer = this._buffer, stack = this._tagStack;
var buffer = this._buffer;

var next, type, tagSep, rawData, element, elementName, prevElement, elementType, elementData, attributes, includeName = false;
var next, tagSep, rawData, element, elementName, prevElement, elementType, elementData, attributes, includeName = false;

var opening = buffer.indexOf("<"), closing = buffer.indexOf(">");

Expand All @@ -155,55 +162,44 @@ Parser.prototype.parseTags = function(){
elementData = rawData;
elementName = "";
}

type = stack[stack.length-1];


//This section inspects the current tag stack and modifies the current
//element if we're actually parsing a special area (script/comment/style tag)
if(type === ElementType.Comment){ //We're currently in a comment tag

prevElement = this._elements[this._elements.length - 1];
if(this._contentFlags === 0){ /*do nothing*/ }
else if(this._contentFlags >= SpecialTags[ElementType.Comment]){ //We're currently in a comment tag
elementType = ElementType.Comment; //Change the current element's type to a comment

if(tagSep === ">" && rawData.substr(-2) === "--"){ //comment ends
stack.pop();
rawData = rawData.slice(0, -2);
//If the previous element is a comment, append the current text to it
if(prevElement && prevElement.type === ElementType.Comment){ //Previous element was a comment
prevElement.data = prevElement.raw += rawData;
//This causes the current element to not be added to the element list
rawData = elementData = "";
elementType = ElementType.Text;
}
else elementType = ElementType.Comment; //Change the current element's type to a comment
}
else { //Still in a comment tag
elementType = ElementType.Comment;
//If the previous element is a comment, append the current text to it
if(prevElement && prevElement.type === ElementType.Comment){
prevElement.data = prevElement.raw += rawData + tagSep;
//This causes the current element to not be added to the element list
rawData = elementData = "";
elementType = ElementType.Text;
}
else elementData = rawData += tagSep;
this._contentFlags -= SpecialTags[ElementType.Comment];
elementData = rawData = rawData.slice(0, -2);
}
else elementData = rawData += tagSep;
this._prevTagSep = tagSep;
}
else if(type === ElementType.Script && elementName === "/script") stack.pop();
else if(type === ElementType.Style && elementName === "/style") stack.pop();
else if(!this._options.xmlMode && (type === ElementType.Script || type === ElementType.Style)){
//special behaviour for script & style tags
if(rawData.substring(0, 3) !== "!--"){ //Make sure we're not in a comment
//All data from here to style close is now a text element
elementType = ElementType.Text;
//If the previous element is text, append the current text to it
prevElement = this._elements[this._elements.length - 1];
if(prevElement && prevElement.type === ElementType.Text){
prevElement.data = prevElement.raw += this._prevTagSep + rawData;
//This causes the current element to not be added to the element list
rawData = elementData = "";
} else elementData = rawData; //The previous element was not text
//if it's a closing tag, remove the flag
else if(this._contentFlags >= SpecialTags[ElementType.Script] && elementName === "/script"){
this._contentFlags %= SpecialTags["w"]; //remove the written flag
this._contentFlags -= SpecialTags[ElementType.Script];
}
else if(this._contentFlags >= SpecialTags[ElementType.Style] && elementName === "/style"){
this._contentFlags %= SpecialTags["w"]; //remove the written flag
this._contentFlags -= SpecialTags[ElementType.Style];
}
//special behaviour for script & style tags
//Make sure we're not in a comment
else if(!this._options.xmlMode && rawData.substring(0, 3) !== "!--"){
//All data from here to style close is now a text element
elementType = ElementType.Text;
//If the previous element is text, append the last tag sep to element
if(this._contentFlags >= SpecialTags["w"]){
elementData = rawData = this._prevTagSep + rawData;
}
else{ //The previous element was not text
this._contentFlags += SpecialTags["w"];
elementData = rawData;
}
this._prevTagSep = tagSep;
}


Expand All @@ -212,13 +208,14 @@ Parser.prototype.parseTags = function(){
if(elementType === ElementType.Tag){
if(rawData.substring(0, 3) === "!--"){ //This tag is really comment
elementType = ElementType.Comment;
elementData = rawData = rawData.substr(3);
this._contentFlags %= SpecialTags["w"]; //remove the written flag
//Check if the comment is terminated in the current element
if(tagSep === ">" && rawData.substr(-2) === "--")
elementData = rawData = rawData.slice(0, -2);
elementData = rawData = rawData.slice(3, -2);
else { //It's not so push the comment onto the tag stack
rawData += tagSep;
stack.push(ElementType.Comment);
elementData = rawData = rawData.substr(3) + tagSep;
this._contentFlags += SpecialTags[ElementType.Comment];
this._prevTagSep = tagSep;
}
}
else {
Expand All @@ -236,12 +233,18 @@ Parser.prototype.parseTags = function(){
else if(elementName === "script"){
elementType = ElementType.Script;
//Special tag, push onto the tag stack if not terminated
if(elementData.substr(-1) !== "/") stack.push(ElementType.Script);
if(elementData.substr(-1) !== "/"){
this._contentFlags += SpecialTags[ElementType.Script];
this._prevTagSep = tagSep;
}
}
else if(elementName === "style"){
elementType = ElementType.Style;
//Special tag, push onto the tag stack if not terminated
if(elementData.substr(-1) !== "/") stack.push(ElementType.Style);
if(elementData.substr(-1) !== "/"){
this._contentFlags += SpecialTags[ElementType.Style];
this._prevTagSep = tagSep;
}
}
}
}
Expand Down Expand Up @@ -271,42 +274,35 @@ Parser.prototype.parseTags = function(){
/*
switch(elementType){
case ElementType.Text:
this._handler.ontext(rawData);
break;
case ElementType.Tag:
case ElementType.Style:
case ElementType.Script:
if(elementName[0] === "/") this._handler.onclosetag(elementName.substr(1));
else this._handler.onopentag(elementName, parseAttributes(elementData));
this._handler.writeText(element);
break;
case ElementType.Comment:
this._handler.oncomment(rawData);
this._handler.writeComment(element);
break;
case ElementType.Directive:
this._handler.onprocessinginstruction(rawData);
this._handler.writeDirective(element);
break;
default: throw Error("Unsupported type: " + elementType);
//case ElementType.Tag:
//case ElementType.Style:
//case ElementType.Script:
default:
if(elementName[0] === "/") this._handler._closeTag(elementName.substr(1));
else this._handler._openTag(elementName, parseAttributes(elementData));
}
*/

//If tag self-terminates, add an explicit, separate closing tag
if( elementType !== ElementType.Text
&& elementType !== ElementType.Comment
&& elementType !== ElementType.Directive
&& elementData.substr(-1) === "/"
){
//this._handler.onclosetag(elementName);
if(tagTypes[elementType] && elementData.substr(-1) === "/"){
//this._handler._closeTag(elementName);
this._elements.push({
raw: elementName = "/" + elementName
, data: elementName
, name: elementName
, type: elementType
raw: elementName = "/" + elementName,
data: elementName, name: elementName,
type: elementType
});
}
}
this._parseState = (tagSep === "<") ? ElementType.Tag : ElementType.Text;
this._current = next + 1;
this._prevTagSep = tagSep;
}

if(this._options.includeLocation){
Expand All @@ -332,15 +328,18 @@ Parser.prototype.getLocation = function(startTag){
chunk = false;
}

var rows = this._buffer.substring(l.charOffset, l.charOffset = end).split("\n"),
var rows = this._buffer.substring(l.charOffset, end).split("\n"),
rowNum = rows.length - 1;

l.charOffset = end;
l.inBuffer += rowNum;

var num = rows[rowNum].replace(_reRow,"").length;
if(rowNum == 0) l.col += num;
if(rowNum === 0) l.col += num;
else l.col = num;

if(arguments.length === 0) return;

return {
line: l.row + l.inBuffer + 1,
col: l.col + (chunk ? 0: 1)
Expand All @@ -358,9 +357,7 @@ var validateHandler = function(handler){
};

//Writes parsed elements out to the handler
Parser.prototype.writeHandler = function(forceFlush){
if(this._tagStack.length && !forceFlush)
return;
Parser.prototype.writeHandler = function(){
while (this._elements.length){
var element = this._elements.shift();
switch (element.type){
Expand Down
6 changes: 3 additions & 3 deletions tests/23-template_script_tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ exports.options = {
handler: {}
, parser: {}
};
exports.html = "<script type=\"text/template\"> <h1>Heading1</h1></script>";
exports.html = "<script type=\"text/template\"><h1>Heading1</h1></script>";
exports.expected = [ { raw: 'script type="text/template"',
data: 'script type="text/template"',
type: 'script',
name: 'script',
attribs: { type: 'text/template' },
children:
[ { raw: ' <h1>Heading1</h1>',
data: ' <h1>Heading1</h1>',
[ { raw: '<h1>Heading1</h1>',
data: '<h1>Heading1</h1>',
type: 'text' } ] } ];

0 comments on commit bc12cd8

Please sign in to comment.