1'use strict'; 2 3var OpenElementStack = require('./open_element_stack'), 4 Tokenizer = require('../tokenization/tokenizer'), 5 HTML = require('../common/html'); 6 7 8//Aliases 9var $ = HTML.TAG_NAMES; 10 11 12function setEndLocation(element, closingToken, treeAdapter) { 13 var loc = element.__location; 14 15 if (!loc) 16 return; 17 18 if (!loc.startTag) { 19 loc.startTag = { 20 start: loc.start, 21 end: loc.end 22 }; 23 } 24 25 if (closingToken.location) { 26 var tn = treeAdapter.getTagName(element), 27 // NOTE: For cases like <p> <p> </p> - First 'p' closes without a closing tag and 28 // for cases like <td> <p> </td> - 'p' closes without a closing tag 29 isClosingEndTag = closingToken.type === Tokenizer.END_TAG_TOKEN && 30 tn === closingToken.tagName; 31 32 if (isClosingEndTag) { 33 loc.endTag = { 34 start: closingToken.location.start, 35 end: closingToken.location.end 36 }; 37 } 38 39 loc.end = closingToken.location.end; 40 } 41} 42 43//NOTE: patch open elements stack, so we can assign end location for the elements 44function patchOpenElementsStack(stack, parser) { 45 var treeAdapter = parser.treeAdapter; 46 47 stack.pop = function () { 48 setEndLocation(this.current, parser.currentToken, treeAdapter); 49 OpenElementStack.prototype.pop.call(this); 50 }; 51 52 stack.popAllUpToHtmlElement = function () { 53 for (var i = this.stackTop; i > 0; i--) 54 setEndLocation(this.items[i], parser.currentToken, treeAdapter); 55 56 OpenElementStack.prototype.popAllUpToHtmlElement.call(this); 57 }; 58 59 stack.remove = function (element) { 60 setEndLocation(element, parser.currentToken, treeAdapter); 61 OpenElementStack.prototype.remove.call(this, element); 62 }; 63} 64 65exports.assign = function (parser) { 66 //NOTE: obtain Parser proto this way to avoid module circular references 67 var parserProto = Object.getPrototypeOf(parser), 68 treeAdapter = parser.treeAdapter; 69 70 71 //NOTE: patch _reset method 72 parser._reset = function (html, document, fragmentContext) { 73 parserProto._reset.call(this, html, document, fragmentContext); 74 75 this.attachableElementLocation = null; 76 this.lastFosterParentingLocation = null; 77 this.currentToken = null; 78 79 patchOpenElementsStack(this.openElements, parser); 80 }; 81 82 parser._processTokenInForeignContent = function (token) { 83 this.currentToken = token; 84 parserProto._processTokenInForeignContent.call(this, token); 85 }; 86 87 parser._processToken = function (token) { 88 this.currentToken = token; 89 parserProto._processToken.call(this, token); 90 91 //NOTE: <body> and <html> are never popped from the stack, so we need to updated 92 //their end location explicitly. 93 if (token.type === Tokenizer.END_TAG_TOKEN && 94 (token.tagName === $.HTML || 95 (token.tagName === $.BODY && this.openElements.hasInScope($.BODY)))) { 96 for (var i = this.openElements.stackTop; i >= 0; i--) { 97 var element = this.openElements.items[i]; 98 99 if (this.treeAdapter.getTagName(element) === token.tagName) { 100 setEndLocation(element, token, treeAdapter); 101 break; 102 } 103 } 104 } 105 }; 106 107 //Doctype 108 parser._setDocumentType = function (token) { 109 parserProto._setDocumentType.call(this, token); 110 111 var documentChildren = this.treeAdapter.getChildNodes(this.document), 112 cnLength = documentChildren.length; 113 114 for (var i = 0; i < cnLength; i++) { 115 var node = documentChildren[i]; 116 117 if (this.treeAdapter.isDocumentTypeNode(node)) { 118 node.__location = token.location; 119 break; 120 } 121 } 122 }; 123 124 //Elements 125 parser._attachElementToTree = function (element) { 126 //NOTE: _attachElementToTree is called from _appendElement, _insertElement and _insertTemplate methods. 127 //So we will use token location stored in this methods for the element. 128 element.__location = this.attachableElementLocation || null; 129 this.attachableElementLocation = null; 130 parserProto._attachElementToTree.call(this, element); 131 }; 132 133 parser._appendElement = function (token, namespaceURI) { 134 this.attachableElementLocation = token.location; 135 parserProto._appendElement.call(this, token, namespaceURI); 136 }; 137 138 parser._insertElement = function (token, namespaceURI) { 139 this.attachableElementLocation = token.location; 140 parserProto._insertElement.call(this, token, namespaceURI); 141 }; 142 143 parser._insertTemplate = function (token) { 144 this.attachableElementLocation = token.location; 145 parserProto._insertTemplate.call(this, token); 146 147 var tmplContent = this.treeAdapter.getChildNodes(this.openElements.current)[0]; 148 149 tmplContent.__location = null; 150 }; 151 152 parser._insertFakeRootElement = function () { 153 parserProto._insertFakeRootElement.call(this); 154 this.openElements.current.__location = null; 155 }; 156 157 //Comments 158 parser._appendCommentNode = function (token, parent) { 159 parserProto._appendCommentNode.call(this, token, parent); 160 161 var children = this.treeAdapter.getChildNodes(parent), 162 commentNode = children[children.length - 1]; 163 164 commentNode.__location = token.location; 165 }; 166 167 //Text 168 parser._findFosterParentingLocation = function () { 169 //NOTE: store last foster parenting location, so we will be able to find inserted text 170 //in case of foster parenting 171 this.lastFosterParentingLocation = parserProto._findFosterParentingLocation.call(this); 172 return this.lastFosterParentingLocation; 173 }; 174 175 parser._insertCharacters = function (token) { 176 parserProto._insertCharacters.call(this, token); 177 178 var hasFosterParent = this._shouldFosterParentOnInsertion(), 179 parentingLocation = this.lastFosterParentingLocation, 180 parent = (hasFosterParent && parentingLocation.parent) || 181 this.openElements.currentTmplContent || 182 this.openElements.current, 183 siblings = this.treeAdapter.getChildNodes(parent), 184 textNodeIdx = hasFosterParent && parentingLocation.beforeElement ? 185 siblings.indexOf(parentingLocation.beforeElement) - 1 : 186 siblings.length - 1, 187 textNode = siblings[textNodeIdx]; 188 189 //NOTE: if we have location assigned by another token, then just update end position 190 if (textNode.__location) 191 textNode.__location.end = token.location.end; 192 193 else 194 textNode.__location = token.location; 195 }; 196}; 197 198