| 'use strict'; |
| |
| const Mixin = require('../../utils/mixin'); |
| const Tokenizer = require('../../tokenizer'); |
| const LocationInfoTokenizerMixin = require('./tokenizer-mixin'); |
| const LocationInfoOpenElementStackMixin = require('./open-element-stack-mixin'); |
| const HTML = require('../../common/html'); |
| |
| //Aliases |
| const $ = HTML.TAG_NAMES; |
| |
| class LocationInfoParserMixin extends Mixin { |
| constructor(parser) { |
| super(parser); |
| |
| this.parser = parser; |
| this.treeAdapter = this.parser.treeAdapter; |
| this.posTracker = null; |
| this.lastStartTagToken = null; |
| this.lastFosterParentingLocation = null; |
| this.currentToken = null; |
| } |
| |
| _setStartLocation(element) { |
| let loc = null; |
| |
| if (this.lastStartTagToken) { |
| loc = Object.assign({}, this.lastStartTagToken.location); |
| loc.startTag = this.lastStartTagToken.location; |
| } |
| |
| this.treeAdapter.setNodeSourceCodeLocation(element, loc); |
| } |
| |
| _setEndLocation(element, closingToken) { |
| const loc = this.treeAdapter.getNodeSourceCodeLocation(element); |
| |
| if (loc) { |
| if (closingToken.location) { |
| const ctLoc = closingToken.location; |
| const tn = this.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. |
| const isClosingEndTag = closingToken.type === Tokenizer.END_TAG_TOKEN && tn === closingToken.tagName; |
| |
| if (isClosingEndTag) { |
| loc.endTag = Object.assign({}, ctLoc); |
| loc.endLine = ctLoc.endLine; |
| loc.endCol = ctLoc.endCol; |
| loc.endOffset = ctLoc.endOffset; |
| } else { |
| loc.endLine = ctLoc.startLine; |
| loc.endCol = ctLoc.startCol; |
| loc.endOffset = ctLoc.startOffset; |
| } |
| } |
| } |
| } |
| |
| _getOverriddenMethods(mxn, orig) { |
| return { |
| _bootstrap(document, fragmentContext) { |
| orig._bootstrap.call(this, document, fragmentContext); |
| |
| mxn.lastStartTagToken = null; |
| mxn.lastFosterParentingLocation = null; |
| mxn.currentToken = null; |
| |
| const tokenizerMixin = Mixin.install(this.tokenizer, LocationInfoTokenizerMixin); |
| |
| mxn.posTracker = tokenizerMixin.posTracker; |
| |
| Mixin.install(this.openElements, LocationInfoOpenElementStackMixin, { |
| onItemPop: function(element) { |
| mxn._setEndLocation(element, mxn.currentToken); |
| } |
| }); |
| }, |
| |
| _runParsingLoop(scriptHandler) { |
| orig._runParsingLoop.call(this, scriptHandler); |
| |
| // NOTE: generate location info for elements |
| // that remains on open element stack |
| for (let i = this.openElements.stackTop; i >= 0; i--) { |
| mxn._setEndLocation(this.openElements.items[i], mxn.currentToken); |
| } |
| }, |
| |
| //Token processing |
| _processTokenInForeignContent(token) { |
| mxn.currentToken = token; |
| orig._processTokenInForeignContent.call(this, token); |
| }, |
| |
| _processToken(token) { |
| mxn.currentToken = token; |
| orig._processToken.call(this, token); |
| |
| //NOTE: <body> and <html> are never popped from the stack, so we need to updated |
| //their end location explicitly. |
| const requireExplicitUpdate = |
| token.type === Tokenizer.END_TAG_TOKEN && |
| (token.tagName === $.HTML || (token.tagName === $.BODY && this.openElements.hasInScope($.BODY))); |
| |
| if (requireExplicitUpdate) { |
| for (let i = this.openElements.stackTop; i >= 0; i--) { |
| const element = this.openElements.items[i]; |
| |
| if (this.treeAdapter.getTagName(element) === token.tagName) { |
| mxn._setEndLocation(element, token); |
| break; |
| } |
| } |
| } |
| }, |
| |
| //Doctype |
| _setDocumentType(token) { |
| orig._setDocumentType.call(this, token); |
| |
| const documentChildren = this.treeAdapter.getChildNodes(this.document); |
| const cnLength = documentChildren.length; |
| |
| for (let i = 0; i < cnLength; i++) { |
| const node = documentChildren[i]; |
| |
| if (this.treeAdapter.isDocumentTypeNode(node)) { |
| this.treeAdapter.setNodeSourceCodeLocation(node, token.location); |
| break; |
| } |
| } |
| }, |
| |
| //Elements |
| _attachElementToTree(element) { |
| //NOTE: _attachElementToTree is called from _appendElement, _insertElement and _insertTemplate methods. |
| //So we will use token location stored in this methods for the element. |
| mxn._setStartLocation(element); |
| mxn.lastStartTagToken = null; |
| orig._attachElementToTree.call(this, element); |
| }, |
| |
| _appendElement(token, namespaceURI) { |
| mxn.lastStartTagToken = token; |
| orig._appendElement.call(this, token, namespaceURI); |
| }, |
| |
| _insertElement(token, namespaceURI) { |
| mxn.lastStartTagToken = token; |
| orig._insertElement.call(this, token, namespaceURI); |
| }, |
| |
| _insertTemplate(token) { |
| mxn.lastStartTagToken = token; |
| orig._insertTemplate.call(this, token); |
| |
| const tmplContent = this.treeAdapter.getTemplateContent(this.openElements.current); |
| |
| this.treeAdapter.setNodeSourceCodeLocation(tmplContent, null); |
| }, |
| |
| _insertFakeRootElement() { |
| orig._insertFakeRootElement.call(this); |
| this.treeAdapter.setNodeSourceCodeLocation(this.openElements.current, null); |
| }, |
| |
| //Comments |
| _appendCommentNode(token, parent) { |
| orig._appendCommentNode.call(this, token, parent); |
| |
| const children = this.treeAdapter.getChildNodes(parent); |
| const commentNode = children[children.length - 1]; |
| |
| this.treeAdapter.setNodeSourceCodeLocation(commentNode, token.location); |
| }, |
| |
| //Text |
| _findFosterParentingLocation() { |
| //NOTE: store last foster parenting location, so we will be able to find inserted text |
| //in case of foster parenting |
| mxn.lastFosterParentingLocation = orig._findFosterParentingLocation.call(this); |
| |
| return mxn.lastFosterParentingLocation; |
| }, |
| |
| _insertCharacters(token) { |
| orig._insertCharacters.call(this, token); |
| |
| const hasFosterParent = this._shouldFosterParentOnInsertion(); |
| |
| const parent = |
| (hasFosterParent && mxn.lastFosterParentingLocation.parent) || |
| this.openElements.currentTmplContent || |
| this.openElements.current; |
| |
| const siblings = this.treeAdapter.getChildNodes(parent); |
| |
| const textNodeIdx = |
| hasFosterParent && mxn.lastFosterParentingLocation.beforeElement |
| ? siblings.indexOf(mxn.lastFosterParentingLocation.beforeElement) - 1 |
| : siblings.length - 1; |
| |
| const textNode = siblings[textNodeIdx]; |
| |
| //NOTE: if we have location assigned by another token, then just update end position |
| const tnLoc = this.treeAdapter.getNodeSourceCodeLocation(textNode); |
| |
| if (tnLoc) { |
| tnLoc.endLine = token.location.endLine; |
| tnLoc.endCol = token.location.endCol; |
| tnLoc.endOffset = token.location.endOffset; |
| } else { |
| this.treeAdapter.setNodeSourceCodeLocation(textNode, token.location); |
| } |
| } |
| }; |
| } |
| } |
| |
| module.exports = LocationInfoParserMixin; |