'use strict'; | |
var OpenElementStack = require('./open_element_stack'), | |
Tokenizer = require('../tokenization/tokenizer'), | |
HTML = require('../common/html'); | |
//Aliases | |
var $ = HTML.TAG_NAMES; | |
function setEndLocation(element, closingToken, treeAdapter) { | |
var loc = element.__location; | |
if (!loc) | |
return; | |
if (!loc.startTag) { | |
loc.startTag = { | |
start: loc.start, | |
end: loc.end | |
}; | |
} | |
if (closingToken.location) { | |
var tn = treeAdapter.getTagName(element), | |
// NOTE: For cases like <p> <p> </p> - First 'p' closes without a closing tag and | |
// for cases like <td> <p> </td> - 'p' closes without a closing tag | |
isClosingEndTag = closingToken.type === Tokenizer.END_TAG_TOKEN && | |
tn === closingToken.tagName; | |
if (isClosingEndTag) { | |
loc.endTag = { | |
start: closingToken.location.start, | |
end: closingToken.location.end | |
}; | |
} | |
loc.end = closingToken.location.end; | |
} | |
} | |
//NOTE: patch open elements stack, so we can assign end location for the elements | |
function patchOpenElementsStack(stack, parser) { | |
var treeAdapter = parser.treeAdapter; | |
stack.pop = function () { | |
setEndLocation(this.current, parser.currentToken, treeAdapter); | |
OpenElementStack.prototype.pop.call(this); | |
}; | |
stack.popAllUpToHtmlElement = function () { | |
for (var i = this.stackTop; i > 0; i--) | |
setEndLocation(this.items[i], parser.currentToken, treeAdapter); | |
OpenElementStack.prototype.popAllUpToHtmlElement.call(this); | |
}; | |
stack.remove = function (element) { | |
setEndLocation(element, parser.currentToken, treeAdapter); | |
OpenElementStack.prototype.remove.call(this, element); | |
}; | |
} | |
exports.assign = function (parser) { | |
//NOTE: obtain Parser proto this way to avoid module circular references | |
var parserProto = Object.getPrototypeOf(parser), | |
treeAdapter = parser.treeAdapter; | |
//NOTE: patch _reset method | |
parser._reset = function (html, document, fragmentContext) { | |
parserProto._reset.call(this, html, document, fragmentContext); | |
this.attachableElementLocation = null; | |
this.lastFosterParentingLocation = null; | |
this.currentToken = null; | |
patchOpenElementsStack(this.openElements, parser); | |
}; | |
parser._processTokenInForeignContent = function (token) { | |
this.currentToken = token; | |
parserProto._processTokenInForeignContent.call(this, token); | |
}; | |
parser._processToken = function (token) { | |
this.currentToken = token; | |
parserProto._processToken.call(this, token); | |
//NOTE: <body> and <html> are never popped from the stack, so we need to updated | |
//their end location explicitly. | |
if (token.type === Tokenizer.END_TAG_TOKEN && | |
(token.tagName === $.HTML || | |
(token.tagName === $.BODY && this.openElements.hasInScope($.BODY)))) { | |
for (var i = this.openElements.stackTop; i >= 0; i--) { | |
var element = this.openElements.items[i]; | |
if (this.treeAdapter.getTagName(element) === token.tagName) { | |
setEndLocation(element, token, treeAdapter); | |
break; | |
} | |
} | |
} | |
}; | |
//Doctype | |
parser._setDocumentType = function (token) { | |
parserProto._setDocumentType.call(this, token); | |
var documentChildren = this.treeAdapter.getChildNodes(this.document), | |
cnLength = documentChildren.length; | |
for (var i = 0; i < cnLength; i++) { | |
var node = documentChildren[i]; | |
if (this.treeAdapter.isDocumentTypeNode(node)) { | |
node.__location = token.location; | |
break; | |
} | |
} | |
}; | |
//Elements | |
parser._attachElementToTree = function (element) { | |
//NOTE: _attachElementToTree is called from _appendElement, _insertElement and _insertTemplate methods. | |
//So we will use token location stored in this methods for the element. | |
element.__location = this.attachableElementLocation || null; | |
this.attachableElementLocation = null; | |
parserProto._attachElementToTree.call(this, element); | |
}; | |
parser._appendElement = function (token, namespaceURI) { | |
this.attachableElementLocation = token.location; | |
parserProto._appendElement.call(this, token, namespaceURI); | |
}; | |
parser._insertElement = function (token, namespaceURI) { | |
this.attachableElementLocation = token.location; | |
parserProto._insertElement.call(this, token, namespaceURI); | |
}; | |
parser._insertTemplate = function (token) { | |
this.attachableElementLocation = token.location; | |
parserProto._insertTemplate.call(this, token); | |
var tmplContent = this.treeAdapter.getChildNodes(this.openElements.current)[0]; | |
tmplContent.__location = null; | |
}; | |
parser._insertFakeRootElement = function () { | |
parserProto._insertFakeRootElement.call(this); | |
this.openElements.current.__location = null; | |
}; | |
//Comments | |
parser._appendCommentNode = function (token, parent) { | |
parserProto._appendCommentNode.call(this, token, parent); | |
var children = this.treeAdapter.getChildNodes(parent), | |
commentNode = children[children.length - 1]; | |
commentNode.__location = token.location; | |
}; | |
//Text | |
parser._findFosterParentingLocation = function () { | |
//NOTE: store last foster parenting location, so we will be able to find inserted text | |
//in case of foster parenting | |
this.lastFosterParentingLocation = parserProto._findFosterParentingLocation.call(this); | |
return this.lastFosterParentingLocation; | |
}; | |
parser._insertCharacters = function (token) { | |
parserProto._insertCharacters.call(this, token); | |
var hasFosterParent = this._shouldFosterParentOnInsertion(), | |
parentingLocation = this.lastFosterParentingLocation, | |
parent = (hasFosterParent && parentingLocation.parent) || | |
this.openElements.currentTmplContent || | |
this.openElements.current, | |
siblings = this.treeAdapter.getChildNodes(parent), | |
textNodeIdx = hasFosterParent && parentingLocation.beforeElement ? | |
siblings.indexOf(parentingLocation.beforeElement) - 1 : | |
siblings.length - 1, | |
textNode = siblings[textNodeIdx]; | |
//NOTE: if we have location assigned by another token, then just update end position | |
if (textNode.__location) | |
textNode.__location.end = token.location.end; | |
else | |
textNode.__location = token.location; | |
}; | |
}; | |