• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3var Doctype = require('../common/doctype');
4
5//Conversion tables for DOM Level1 structure emulation
6var nodeTypes = {
7    element: 1,
8    text: 3,
9    cdata: 4,
10    comment: 8
11};
12
13var nodePropertyShorthands = {
14    tagName: 'name',
15    childNodes: 'children',
16    parentNode: 'parent',
17    previousSibling: 'prev',
18    nextSibling: 'next',
19    nodeValue: 'data'
20};
21
22//Node
23var Node = function (props) {
24    for (var key in props) {
25        if (props.hasOwnProperty(key))
26            this[key] = props[key];
27    }
28};
29
30Node.prototype = {
31    get firstChild() {
32        var children = this.children;
33        return children && children[0] || null;
34    },
35
36    get lastChild() {
37        var children = this.children;
38        return children && children[children.length - 1] || null;
39    },
40
41    get nodeType() {
42        return nodeTypes[this.type] || nodeTypes.element;
43    }
44};
45
46Object.keys(nodePropertyShorthands).forEach(function (key) {
47    var shorthand = nodePropertyShorthands[key];
48
49    Object.defineProperty(Node.prototype, key, {
50        get: function () {
51            return this[shorthand] || null;
52        },
53        set: function (val) {
54            this[shorthand] = val;
55            return val;
56        }
57    });
58});
59
60
61//Node construction
62exports.createDocument =
63exports.createDocumentFragment = function () {
64    return new Node({
65        type: 'root',
66        name: 'root',
67        parent: null,
68        prev: null,
69        next: null,
70        children: []
71    });
72};
73
74exports.createElement = function (tagName, namespaceURI, attrs) {
75    var attribs = {},
76        attribsNamespace = {},
77        attribsPrefix = {};
78
79    for (var i = 0; i < attrs.length; i++) {
80        var attrName = attrs[i].name;
81
82        attribs[attrName] = attrs[i].value;
83        attribsNamespace[attrName] = attrs[i].namespace;
84        attribsPrefix[attrName] = attrs[i].prefix;
85    }
86
87    return new Node({
88        type: tagName === 'script' || tagName === 'style' ? tagName : 'tag',
89        name: tagName,
90        namespace: namespaceURI,
91        attribs: attribs,
92        'x-attribsNamespace': attribsNamespace,
93        'x-attribsPrefix': attribsPrefix,
94        children: [],
95        parent: null,
96        prev: null,
97        next: null
98    });
99};
100
101exports.createCommentNode = function (data) {
102    return new Node({
103        type: 'comment',
104        data: data,
105        parent: null,
106        prev: null,
107        next: null
108    });
109};
110
111var createTextNode = function (value) {
112    return new Node({
113        type: 'text',
114        data: value,
115        parent: null,
116        prev: null,
117        next: null
118    });
119};
120
121
122//Tree mutation
123exports.setDocumentType = function (document, name, publicId, systemId) {
124    var data = Doctype.serializeContent(name, publicId, systemId),
125        doctypeNode = null;
126
127    for (var i = 0; i < document.children.length; i++) {
128        if (document.children[i].type === 'directive' && document.children[i].name === '!doctype') {
129            doctypeNode = document.children[i];
130            break;
131        }
132    }
133
134    if (doctypeNode) {
135        doctypeNode.data = data;
136        doctypeNode['x-name'] = name;
137        doctypeNode['x-publicId'] = publicId;
138        doctypeNode['x-systemId'] = systemId;
139    }
140
141    else {
142        appendChild(document, new Node({
143            type: 'directive',
144            name: '!doctype',
145            data: data,
146            'x-name': name,
147            'x-publicId': publicId,
148            'x-systemId': systemId
149        }));
150    }
151
152};
153
154exports.setQuirksMode = function (document) {
155    document.quirksMode = true;
156};
157
158exports.isQuirksMode = function (document) {
159    return document.quirksMode;
160};
161
162var appendChild = exports.appendChild = function (parentNode, newNode) {
163    var prev = parentNode.children[parentNode.children.length - 1];
164
165    if (prev) {
166        prev.next = newNode;
167        newNode.prev = prev;
168    }
169
170    parentNode.children.push(newNode);
171    newNode.parent = parentNode;
172};
173
174var insertBefore = exports.insertBefore = function (parentNode, newNode, referenceNode) {
175    var insertionIdx = parentNode.children.indexOf(referenceNode),
176        prev = referenceNode.prev;
177
178    if (prev) {
179        prev.next = newNode;
180        newNode.prev = prev;
181    }
182
183    referenceNode.prev = newNode;
184    newNode.next = referenceNode;
185
186    parentNode.children.splice(insertionIdx, 0, newNode);
187    newNode.parent = parentNode;
188};
189
190exports.detachNode = function (node) {
191    if (node.parent) {
192        var idx = node.parent.children.indexOf(node),
193            prev = node.prev,
194            next = node.next;
195
196        node.prev = null;
197        node.next = null;
198
199        if (prev)
200            prev.next = next;
201
202        if (next)
203            next.prev = prev;
204
205        node.parent.children.splice(idx, 1);
206        node.parent = null;
207    }
208};
209
210exports.insertText = function (parentNode, text) {
211    var lastChild = parentNode.children[parentNode.children.length - 1];
212
213    if (lastChild && lastChild.type === 'text')
214        lastChild.data += text;
215    else
216        appendChild(parentNode, createTextNode(text));
217};
218
219exports.insertTextBefore = function (parentNode, text, referenceNode) {
220    var prevNode = parentNode.children[parentNode.children.indexOf(referenceNode) - 1];
221
222    if (prevNode && prevNode.type === 'text')
223        prevNode.data += text;
224    else
225        insertBefore(parentNode, createTextNode(text), referenceNode);
226};
227
228exports.adoptAttributes = function (recipientNode, attrs) {
229    for (var i = 0; i < attrs.length; i++) {
230        var attrName = attrs[i].name;
231
232        if (typeof recipientNode.attribs[attrName] === 'undefined') {
233            recipientNode.attribs[attrName] = attrs[i].value;
234            recipientNode['x-attribsNamespace'][attrName] = attrs[i].namespace;
235            recipientNode['x-attribsPrefix'][attrName] = attrs[i].prefix;
236        }
237    }
238};
239
240
241//Tree traversing
242exports.getFirstChild = function (node) {
243    return node.children[0];
244};
245
246exports.getChildNodes = function (node) {
247    return node.children;
248};
249
250exports.getParentNode = function (node) {
251    return node.parent;
252};
253
254exports.getAttrList = function (node) {
255    var attrList = [];
256
257    for (var name in node.attribs) {
258        if (node.attribs.hasOwnProperty(name)) {
259            attrList.push({
260                name: name,
261                value: node.attribs[name],
262                namespace: node['x-attribsNamespace'][name],
263                prefix: node['x-attribsPrefix'][name]
264            });
265        }
266    }
267
268    return attrList;
269};
270
271
272//Node data
273exports.getTagName = function (element) {
274    return element.name;
275};
276
277exports.getNamespaceURI = function (element) {
278    return element.namespace;
279};
280
281exports.getTextNodeContent = function (textNode) {
282    return textNode.data;
283};
284
285exports.getCommentNodeContent = function (commentNode) {
286    return commentNode.data;
287};
288
289exports.getDocumentTypeNodeName = function (doctypeNode) {
290    return doctypeNode['x-name'];
291};
292
293exports.getDocumentTypeNodePublicId = function (doctypeNode) {
294    return doctypeNode['x-publicId'];
295};
296
297exports.getDocumentTypeNodeSystemId = function (doctypeNode) {
298    return doctypeNode['x-systemId'];
299};
300
301
302//Node types
303exports.isTextNode = function (node) {
304    return node.type === 'text';
305};
306
307exports.isCommentNode = function (node) {
308    return node.type === 'comment';
309};
310
311exports.isDocumentTypeNode = function (node) {
312    return node.type === 'directive' && node.name === '!doctype';
313};
314
315exports.isElementNode = function (node) {
316    return !!node.attribs;
317};
318