• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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