diff --git a/lib/DefaultHandler.js b/lib/DefaultHandler.js index 2201731..6f3b663 100644 --- a/lib/DefaultHandler.js +++ b/lib/DefaultHandler.js @@ -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; @@ -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 @@ -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); } } @@ -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); } diff --git a/lib/Parser.js b/lib/Parser.js index a464cc7..7566fca 100644 --- a/lib/Parser.js +++ b/lib/Parser.js @@ -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 = { @@ -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); @@ -76,7 +76,7 @@ Parser.prototype.done = function(){ this._elements.push(element); } - this.writeHandler(true); + this.writeHandler(); this._handler.done(); }; @@ -86,6 +86,7 @@ Parser.prototype.reset = function(){ this._prevTagSep = ""; this._done = false; this._current = 0; + this._contentFlags = 0; this._location = { row: 0 , col: 0 @@ -93,7 +94,6 @@ Parser.prototype.reset = function(){ , inBuffer: 0 }; this._parseState = ElementType.Text; - this._tagStack = []; this._elements = []; this._handler.reset(); }; @@ -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(">"); @@ -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; } @@ -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 { @@ -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; + } } } } @@ -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){ @@ -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) @@ -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){ diff --git a/tests/23-template_script_tags.js b/tests/23-template_script_tags.js index 6e256d2..24864fd 100644 --- a/tests/23-template_script_tags.js +++ b/tests/23-template_script_tags.js @@ -3,13 +3,13 @@ exports.options = { handler: {} , parser: {} }; -exports.html = ""; +exports.html = ""; exports.expected = [ { raw: 'script type="text/template"', data: 'script type="text/template"', type: 'script', name: 'script', attribs: { type: 'text/template' }, children: - [ { raw: '

Heading1

', - data: '

Heading1

', + [ { raw: '

Heading1

', + data: '

Heading1

', type: 'text' } ] } ]; \ No newline at end of file