| 'use strict'; | 
|   | 
| const doctype = require('parse5/lib/common/doctype'); | 
| const { DOCUMENT_MODE } = require('parse5/lib/common/html'); | 
|   | 
| //Conversion tables for DOM Level1 structure emulation | 
| const nodeTypes = { | 
|     element: 1, | 
|     text: 3, | 
|     cdata: 4, | 
|     comment: 8 | 
| }; | 
|   | 
| const nodePropertyShorthands = { | 
|     tagName: 'name', | 
|     childNodes: 'children', | 
|     parentNode: 'parent', | 
|     previousSibling: 'prev', | 
|     nextSibling: 'next', | 
|     nodeValue: 'data' | 
| }; | 
|   | 
| //Node | 
| class Node { | 
|     constructor(props) { | 
|         for (const key of Object.keys(props)) { | 
|             this[key] = props[key]; | 
|         } | 
|     } | 
|   | 
|     get firstChild() { | 
|         const children = this.children; | 
|   | 
|         return (children && children[0]) || null; | 
|     } | 
|   | 
|     get lastChild() { | 
|         const children = this.children; | 
|   | 
|         return (children && children[children.length - 1]) || null; | 
|     } | 
|   | 
|     get nodeType() { | 
|         return nodeTypes[this.type] || nodeTypes.element; | 
|     } | 
| } | 
|   | 
| Object.keys(nodePropertyShorthands).forEach(key => { | 
|     const shorthand = nodePropertyShorthands[key]; | 
|   | 
|     Object.defineProperty(Node.prototype, key, { | 
|         get: function() { | 
|             return this[shorthand] || null; | 
|         }, | 
|         set: function(val) { | 
|             this[shorthand] = val; | 
|             return val; | 
|         } | 
|     }); | 
| }); | 
|   | 
| //Node construction | 
| exports.createDocument = function() { | 
|     return new Node({ | 
|         type: 'root', | 
|         name: 'root', | 
|         parent: null, | 
|         prev: null, | 
|         next: null, | 
|         children: [], | 
|         'x-mode': DOCUMENT_MODE.NO_QUIRKS | 
|     }); | 
| }; | 
|   | 
| exports.createDocumentFragment = function() { | 
|     return new Node({ | 
|         type: 'root', | 
|         name: 'root', | 
|         parent: null, | 
|         prev: null, | 
|         next: null, | 
|         children: [] | 
|     }); | 
| }; | 
|   | 
| exports.createElement = function(tagName, namespaceURI, attrs) { | 
|     const attribs = Object.create(null); | 
|     const attribsNamespace = Object.create(null); | 
|     const attribsPrefix = Object.create(null); | 
|   | 
|     for (let i = 0; i < attrs.length; i++) { | 
|         const attrName = attrs[i].name; | 
|   | 
|         attribs[attrName] = attrs[i].value; | 
|         attribsNamespace[attrName] = attrs[i].namespace; | 
|         attribsPrefix[attrName] = attrs[i].prefix; | 
|     } | 
|   | 
|     return new Node({ | 
|         type: tagName === 'script' || tagName === 'style' ? tagName : 'tag', | 
|         name: tagName, | 
|         namespace: namespaceURI, | 
|         attribs: attribs, | 
|         'x-attribsNamespace': attribsNamespace, | 
|         'x-attribsPrefix': attribsPrefix, | 
|         children: [], | 
|         parent: null, | 
|         prev: null, | 
|         next: null | 
|     }); | 
| }; | 
|   | 
| exports.createCommentNode = function(data) { | 
|     return new Node({ | 
|         type: 'comment', | 
|         data: data, | 
|         parent: null, | 
|         prev: null, | 
|         next: null | 
|     }); | 
| }; | 
|   | 
| const createTextNode = function(value) { | 
|     return new Node({ | 
|         type: 'text', | 
|         data: value, | 
|         parent: null, | 
|         prev: null, | 
|         next: null | 
|     }); | 
| }; | 
|   | 
| //Tree mutation | 
| const appendChild = (exports.appendChild = function(parentNode, newNode) { | 
|     const prev = parentNode.children[parentNode.children.length - 1]; | 
|   | 
|     if (prev) { | 
|         prev.next = newNode; | 
|         newNode.prev = prev; | 
|     } | 
|   | 
|     parentNode.children.push(newNode); | 
|     newNode.parent = parentNode; | 
| }); | 
|   | 
| const insertBefore = (exports.insertBefore = function(parentNode, newNode, referenceNode) { | 
|     const insertionIdx = parentNode.children.indexOf(referenceNode); | 
|     const prev = referenceNode.prev; | 
|   | 
|     if (prev) { | 
|         prev.next = newNode; | 
|         newNode.prev = prev; | 
|     } | 
|   | 
|     referenceNode.prev = newNode; | 
|     newNode.next = referenceNode; | 
|   | 
|     parentNode.children.splice(insertionIdx, 0, newNode); | 
|     newNode.parent = parentNode; | 
| }); | 
|   | 
| exports.setTemplateContent = function(templateElement, contentElement) { | 
|     appendChild(templateElement, contentElement); | 
| }; | 
|   | 
| exports.getTemplateContent = function(templateElement) { | 
|     return templateElement.children[0]; | 
| }; | 
|   | 
| exports.setDocumentType = function(document, name, publicId, systemId) { | 
|     const data = doctype.serializeContent(name, publicId, systemId); | 
|     let doctypeNode = null; | 
|   | 
|     for (let i = 0; i < document.children.length; i++) { | 
|         if (document.children[i].type === 'directive' && document.children[i].name === '!doctype') { | 
|             doctypeNode = document.children[i]; | 
|             break; | 
|         } | 
|     } | 
|   | 
|     if (doctypeNode) { | 
|         doctypeNode.data = data; | 
|         doctypeNode['x-name'] = name; | 
|         doctypeNode['x-publicId'] = publicId; | 
|         doctypeNode['x-systemId'] = systemId; | 
|     } else { | 
|         appendChild( | 
|             document, | 
|             new Node({ | 
|                 type: 'directive', | 
|                 name: '!doctype', | 
|                 data: data, | 
|                 'x-name': name, | 
|                 'x-publicId': publicId, | 
|                 'x-systemId': systemId | 
|             }) | 
|         ); | 
|     } | 
| }; | 
|   | 
| exports.setDocumentMode = function(document, mode) { | 
|     document['x-mode'] = mode; | 
| }; | 
|   | 
| exports.getDocumentMode = function(document) { | 
|     return document['x-mode']; | 
| }; | 
|   | 
| exports.detachNode = function(node) { | 
|     if (node.parent) { | 
|         const idx = node.parent.children.indexOf(node); | 
|         const prev = node.prev; | 
|         const next = node.next; | 
|   | 
|         node.prev = null; | 
|         node.next = null; | 
|   | 
|         if (prev) { | 
|             prev.next = next; | 
|         } | 
|   | 
|         if (next) { | 
|             next.prev = prev; | 
|         } | 
|   | 
|         node.parent.children.splice(idx, 1); | 
|         node.parent = null; | 
|     } | 
| }; | 
|   | 
| exports.insertText = function(parentNode, text) { | 
|     const lastChild = parentNode.children[parentNode.children.length - 1]; | 
|   | 
|     if (lastChild && lastChild.type === 'text') { | 
|         lastChild.data += text; | 
|     } else { | 
|         appendChild(parentNode, createTextNode(text)); | 
|     } | 
| }; | 
|   | 
| exports.insertTextBefore = function(parentNode, text, referenceNode) { | 
|     const prevNode = parentNode.children[parentNode.children.indexOf(referenceNode) - 1]; | 
|   | 
|     if (prevNode && prevNode.type === 'text') { | 
|         prevNode.data += text; | 
|     } else { | 
|         insertBefore(parentNode, createTextNode(text), referenceNode); | 
|     } | 
| }; | 
|   | 
| exports.adoptAttributes = function(recipient, attrs) { | 
|     for (let i = 0; i < attrs.length; i++) { | 
|         const attrName = attrs[i].name; | 
|   | 
|         if (typeof recipient.attribs[attrName] === 'undefined') { | 
|             recipient.attribs[attrName] = attrs[i].value; | 
|             recipient['x-attribsNamespace'][attrName] = attrs[i].namespace; | 
|             recipient['x-attribsPrefix'][attrName] = attrs[i].prefix; | 
|         } | 
|     } | 
| }; | 
|   | 
| //Tree traversing | 
| exports.getFirstChild = function(node) { | 
|     return node.children[0]; | 
| }; | 
|   | 
| exports.getChildNodes = function(node) { | 
|     return node.children; | 
| }; | 
|   | 
| exports.getParentNode = function(node) { | 
|     return node.parent; | 
| }; | 
|   | 
| exports.getAttrList = function(element) { | 
|     const attrList = []; | 
|   | 
|     for (const name in element.attribs) { | 
|         attrList.push({ | 
|             name: name, | 
|             value: element.attribs[name], | 
|             namespace: element['x-attribsNamespace'][name], | 
|             prefix: element['x-attribsPrefix'][name] | 
|         }); | 
|     } | 
|   | 
|     return attrList; | 
| }; | 
|   | 
| //Node data | 
| exports.getTagName = function(element) { | 
|     return element.name; | 
| }; | 
|   | 
| exports.getNamespaceURI = function(element) { | 
|     return element.namespace; | 
| }; | 
|   | 
| exports.getTextNodeContent = function(textNode) { | 
|     return textNode.data; | 
| }; | 
|   | 
| exports.getCommentNodeContent = function(commentNode) { | 
|     return commentNode.data; | 
| }; | 
|   | 
| exports.getDocumentTypeNodeName = function(doctypeNode) { | 
|     return doctypeNode['x-name']; | 
| }; | 
|   | 
| exports.getDocumentTypeNodePublicId = function(doctypeNode) { | 
|     return doctypeNode['x-publicId']; | 
| }; | 
|   | 
| exports.getDocumentTypeNodeSystemId = function(doctypeNode) { | 
|     return doctypeNode['x-systemId']; | 
| }; | 
|   | 
| //Node types | 
| exports.isTextNode = function(node) { | 
|     return node.type === 'text'; | 
| }; | 
|   | 
| exports.isCommentNode = function(node) { | 
|     return node.type === 'comment'; | 
| }; | 
|   | 
| exports.isDocumentTypeNode = function(node) { | 
|     return node.type === 'directive' && node.name === '!doctype'; | 
| }; | 
|   | 
| exports.isElementNode = function(node) { | 
|     return !!node.attribs; | 
| }; | 
|   | 
| // Source code location | 
| exports.setNodeSourceCodeLocation = function(node, location) { | 
|     node.sourceCodeLocation = location; | 
| }; | 
|   | 
| exports.getNodeSourceCodeLocation = function(node) { | 
|     return node.sourceCodeLocation; | 
| }; | 
|   | 
| exports.updateNodeSourceCodeLocation = function(node, endLocation) { | 
|     node.sourceCodeLocation = Object.assign(node.sourceCodeLocation, endLocation); | 
| }; |