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;
|
---|