[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | const Mixin = require('../../utils/mixin');
|
---|
| 4 | const Tokenizer = require('../../tokenizer');
|
---|
| 5 | const LocationInfoTokenizerMixin = require('./tokenizer-mixin');
|
---|
| 6 | const LocationInfoOpenElementStackMixin = require('./open-element-stack-mixin');
|
---|
| 7 | const HTML = require('../../common/html');
|
---|
| 8 |
|
---|
| 9 | //Aliases
|
---|
| 10 | const $ = HTML.TAG_NAMES;
|
---|
| 11 |
|
---|
| 12 | class LocationInfoParserMixin extends Mixin {
|
---|
| 13 | constructor(parser) {
|
---|
| 14 | super(parser);
|
---|
| 15 |
|
---|
| 16 | this.parser = parser;
|
---|
| 17 | this.treeAdapter = this.parser.treeAdapter;
|
---|
| 18 | this.posTracker = null;
|
---|
| 19 | this.lastStartTagToken = null;
|
---|
| 20 | this.lastFosterParentingLocation = null;
|
---|
| 21 | this.currentToken = null;
|
---|
| 22 | }
|
---|
| 23 |
|
---|
| 24 | _setStartLocation(element) {
|
---|
| 25 | let loc = null;
|
---|
| 26 |
|
---|
| 27 | if (this.lastStartTagToken) {
|
---|
| 28 | loc = Object.assign({}, this.lastStartTagToken.location);
|
---|
| 29 | loc.startTag = this.lastStartTagToken.location;
|
---|
| 30 | }
|
---|
| 31 |
|
---|
| 32 | this.treeAdapter.setNodeSourceCodeLocation(element, loc);
|
---|
| 33 | }
|
---|
| 34 |
|
---|
| 35 | _setEndLocation(element, closingToken) {
|
---|
| 36 | const loc = this.treeAdapter.getNodeSourceCodeLocation(element);
|
---|
| 37 |
|
---|
| 38 | if (loc) {
|
---|
| 39 | if (closingToken.location) {
|
---|
| 40 | const ctLoc = closingToken.location;
|
---|
| 41 | const tn = this.treeAdapter.getTagName(element);
|
---|
| 42 |
|
---|
| 43 | // NOTE: For cases like <p> <p> </p> - First 'p' closes without a closing
|
---|
| 44 | // tag and for cases like <td> <p> </td> - 'p' closes without a closing tag.
|
---|
| 45 | const isClosingEndTag = closingToken.type === Tokenizer.END_TAG_TOKEN && tn === closingToken.tagName;
|
---|
| 46 | const endLoc = {};
|
---|
| 47 | if (isClosingEndTag) {
|
---|
| 48 | endLoc.endTag = Object.assign({}, ctLoc);
|
---|
| 49 | endLoc.endLine = ctLoc.endLine;
|
---|
| 50 | endLoc.endCol = ctLoc.endCol;
|
---|
| 51 | endLoc.endOffset = ctLoc.endOffset;
|
---|
| 52 | } else {
|
---|
| 53 | endLoc.endLine = ctLoc.startLine;
|
---|
| 54 | endLoc.endCol = ctLoc.startCol;
|
---|
| 55 | endLoc.endOffset = ctLoc.startOffset;
|
---|
| 56 | }
|
---|
| 57 |
|
---|
| 58 | this.treeAdapter.updateNodeSourceCodeLocation(element, endLoc);
|
---|
| 59 | }
|
---|
| 60 | }
|
---|
| 61 | }
|
---|
| 62 |
|
---|
| 63 | _getOverriddenMethods(mxn, orig) {
|
---|
| 64 | return {
|
---|
| 65 | _bootstrap(document, fragmentContext) {
|
---|
| 66 | orig._bootstrap.call(this, document, fragmentContext);
|
---|
| 67 |
|
---|
| 68 | mxn.lastStartTagToken = null;
|
---|
| 69 | mxn.lastFosterParentingLocation = null;
|
---|
| 70 | mxn.currentToken = null;
|
---|
| 71 |
|
---|
| 72 | const tokenizerMixin = Mixin.install(this.tokenizer, LocationInfoTokenizerMixin);
|
---|
| 73 |
|
---|
| 74 | mxn.posTracker = tokenizerMixin.posTracker;
|
---|
| 75 |
|
---|
| 76 | Mixin.install(this.openElements, LocationInfoOpenElementStackMixin, {
|
---|
| 77 | onItemPop: function(element) {
|
---|
| 78 | mxn._setEndLocation(element, mxn.currentToken);
|
---|
| 79 | }
|
---|
| 80 | });
|
---|
| 81 | },
|
---|
| 82 |
|
---|
| 83 | _runParsingLoop(scriptHandler) {
|
---|
| 84 | orig._runParsingLoop.call(this, scriptHandler);
|
---|
| 85 |
|
---|
| 86 | // NOTE: generate location info for elements
|
---|
| 87 | // that remains on open element stack
|
---|
| 88 | for (let i = this.openElements.stackTop; i >= 0; i--) {
|
---|
| 89 | mxn._setEndLocation(this.openElements.items[i], mxn.currentToken);
|
---|
| 90 | }
|
---|
| 91 | },
|
---|
| 92 |
|
---|
| 93 | //Token processing
|
---|
| 94 | _processTokenInForeignContent(token) {
|
---|
| 95 | mxn.currentToken = token;
|
---|
| 96 | orig._processTokenInForeignContent.call(this, token);
|
---|
| 97 | },
|
---|
| 98 |
|
---|
| 99 | _processToken(token) {
|
---|
| 100 | mxn.currentToken = token;
|
---|
| 101 | orig._processToken.call(this, token);
|
---|
| 102 |
|
---|
| 103 | //NOTE: <body> and <html> are never popped from the stack, so we need to updated
|
---|
| 104 | //their end location explicitly.
|
---|
| 105 | const requireExplicitUpdate =
|
---|
| 106 | token.type === Tokenizer.END_TAG_TOKEN &&
|
---|
| 107 | (token.tagName === $.HTML || (token.tagName === $.BODY && this.openElements.hasInScope($.BODY)));
|
---|
| 108 |
|
---|
| 109 | if (requireExplicitUpdate) {
|
---|
| 110 | for (let i = this.openElements.stackTop; i >= 0; i--) {
|
---|
| 111 | const element = this.openElements.items[i];
|
---|
| 112 |
|
---|
| 113 | if (this.treeAdapter.getTagName(element) === token.tagName) {
|
---|
| 114 | mxn._setEndLocation(element, token);
|
---|
| 115 | break;
|
---|
| 116 | }
|
---|
| 117 | }
|
---|
| 118 | }
|
---|
| 119 | },
|
---|
| 120 |
|
---|
| 121 | //Doctype
|
---|
| 122 | _setDocumentType(token) {
|
---|
| 123 | orig._setDocumentType.call(this, token);
|
---|
| 124 |
|
---|
| 125 | const documentChildren = this.treeAdapter.getChildNodes(this.document);
|
---|
| 126 | const cnLength = documentChildren.length;
|
---|
| 127 |
|
---|
| 128 | for (let i = 0; i < cnLength; i++) {
|
---|
| 129 | const node = documentChildren[i];
|
---|
| 130 |
|
---|
| 131 | if (this.treeAdapter.isDocumentTypeNode(node)) {
|
---|
| 132 | this.treeAdapter.setNodeSourceCodeLocation(node, token.location);
|
---|
| 133 | break;
|
---|
| 134 | }
|
---|
| 135 | }
|
---|
| 136 | },
|
---|
| 137 |
|
---|
| 138 | //Elements
|
---|
| 139 | _attachElementToTree(element) {
|
---|
| 140 | //NOTE: _attachElementToTree is called from _appendElement, _insertElement and _insertTemplate methods.
|
---|
| 141 | //So we will use token location stored in this methods for the element.
|
---|
| 142 | mxn._setStartLocation(element);
|
---|
| 143 | mxn.lastStartTagToken = null;
|
---|
| 144 | orig._attachElementToTree.call(this, element);
|
---|
| 145 | },
|
---|
| 146 |
|
---|
| 147 | _appendElement(token, namespaceURI) {
|
---|
| 148 | mxn.lastStartTagToken = token;
|
---|
| 149 | orig._appendElement.call(this, token, namespaceURI);
|
---|
| 150 | },
|
---|
| 151 |
|
---|
| 152 | _insertElement(token, namespaceURI) {
|
---|
| 153 | mxn.lastStartTagToken = token;
|
---|
| 154 | orig._insertElement.call(this, token, namespaceURI);
|
---|
| 155 | },
|
---|
| 156 |
|
---|
| 157 | _insertTemplate(token) {
|
---|
| 158 | mxn.lastStartTagToken = token;
|
---|
| 159 | orig._insertTemplate.call(this, token);
|
---|
| 160 |
|
---|
| 161 | const tmplContent = this.treeAdapter.getTemplateContent(this.openElements.current);
|
---|
| 162 |
|
---|
| 163 | this.treeAdapter.setNodeSourceCodeLocation(tmplContent, null);
|
---|
| 164 | },
|
---|
| 165 |
|
---|
| 166 | _insertFakeRootElement() {
|
---|
| 167 | orig._insertFakeRootElement.call(this);
|
---|
| 168 | this.treeAdapter.setNodeSourceCodeLocation(this.openElements.current, null);
|
---|
| 169 | },
|
---|
| 170 |
|
---|
| 171 | //Comments
|
---|
| 172 | _appendCommentNode(token, parent) {
|
---|
| 173 | orig._appendCommentNode.call(this, token, parent);
|
---|
| 174 |
|
---|
| 175 | const children = this.treeAdapter.getChildNodes(parent);
|
---|
| 176 | const commentNode = children[children.length - 1];
|
---|
| 177 |
|
---|
| 178 | this.treeAdapter.setNodeSourceCodeLocation(commentNode, token.location);
|
---|
| 179 | },
|
---|
| 180 |
|
---|
| 181 | //Text
|
---|
| 182 | _findFosterParentingLocation() {
|
---|
| 183 | //NOTE: store last foster parenting location, so we will be able to find inserted text
|
---|
| 184 | //in case of foster parenting
|
---|
| 185 | mxn.lastFosterParentingLocation = orig._findFosterParentingLocation.call(this);
|
---|
| 186 |
|
---|
| 187 | return mxn.lastFosterParentingLocation;
|
---|
| 188 | },
|
---|
| 189 |
|
---|
| 190 | _insertCharacters(token) {
|
---|
| 191 | orig._insertCharacters.call(this, token);
|
---|
| 192 |
|
---|
| 193 | const hasFosterParent = this._shouldFosterParentOnInsertion();
|
---|
| 194 |
|
---|
| 195 | const parent =
|
---|
| 196 | (hasFosterParent && mxn.lastFosterParentingLocation.parent) ||
|
---|
| 197 | this.openElements.currentTmplContent ||
|
---|
| 198 | this.openElements.current;
|
---|
| 199 |
|
---|
| 200 | const siblings = this.treeAdapter.getChildNodes(parent);
|
---|
| 201 |
|
---|
| 202 | const textNodeIdx =
|
---|
| 203 | hasFosterParent && mxn.lastFosterParentingLocation.beforeElement
|
---|
| 204 | ? siblings.indexOf(mxn.lastFosterParentingLocation.beforeElement) - 1
|
---|
| 205 | : siblings.length - 1;
|
---|
| 206 |
|
---|
| 207 | const textNode = siblings[textNodeIdx];
|
---|
| 208 |
|
---|
| 209 | //NOTE: if we have location assigned by another token, then just update end position
|
---|
| 210 | const tnLoc = this.treeAdapter.getNodeSourceCodeLocation(textNode);
|
---|
| 211 |
|
---|
| 212 | if (tnLoc) {
|
---|
| 213 | const { endLine, endCol, endOffset } = token.location;
|
---|
| 214 | this.treeAdapter.updateNodeSourceCodeLocation(textNode, { endLine, endCol, endOffset });
|
---|
| 215 | } else {
|
---|
| 216 | this.treeAdapter.setNodeSourceCodeLocation(textNode, token.location);
|
---|
| 217 | }
|
---|
| 218 | }
|
---|
| 219 | };
|
---|
| 220 | }
|
---|
| 221 | }
|
---|
| 222 |
|
---|
| 223 | module.exports = LocationInfoParserMixin;
|
---|