You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
8.5 KiB
223 lines
8.5 KiB
'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; |
|
const endLoc = {}; |
|
if (isClosingEndTag) { |
|
endLoc.endTag = Object.assign({}, ctLoc); |
|
endLoc.endLine = ctLoc.endLine; |
|
endLoc.endCol = ctLoc.endCol; |
|
endLoc.endOffset = ctLoc.endOffset; |
|
} else { |
|
endLoc.endLine = ctLoc.startLine; |
|
endLoc.endCol = ctLoc.startCol; |
|
endLoc.endOffset = ctLoc.startOffset; |
|
} |
|
|
|
this.treeAdapter.updateNodeSourceCodeLocation(element, endLoc); |
|
} |
|
} |
|
} |
|
|
|
_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) { |
|
const { endLine, endCol, endOffset } = token.location; |
|
this.treeAdapter.updateNodeSourceCodeLocation(textNode, { endLine, endCol, endOffset }); |
|
} else { |
|
this.treeAdapter.setNodeSourceCodeLocation(textNode, token.location); |
|
} |
|
} |
|
}; |
|
} |
|
} |
|
|
|
module.exports = LocationInfoParserMixin;
|
|
|