import { NODE_END, ELEMENT_NODE, ATTRIBUTE_NODE, TEXT_NODE, CDATA_SECTION_NODE, COMMENT_NODE, DOCUMENT_NODE, DOCUMENT_TYPE_NODE, DOCUMENT_FRAGMENT_NODE } from './constants.js'; import {END, PREV} from './symbols.js'; import {htmlClasses} from './register-html-class.js'; import {knownBoundaries, knownSiblings} from './utils.js'; import {Attr} from '../interface/attr.js'; import {CDATASection} from '../interface/cdata-section.js'; import {Comment} from '../interface/comment.js'; import {DocumentType} from '../interface/document-type.js'; import {Text} from '../interface/text.js'; import {HTMLDocument} from '../html/document.js'; import {HTMLElement} from '../html/element.js'; import {SVGElement} from '../svg/element.js'; const {parse} = JSON; const append = (parentNode, node, end) => { node.parentNode = parentNode; knownSiblings(end[PREV], node, end); }; const createHTMLElement = (ownerDocument, localName) => { if (htmlClasses.has(localName)) { const Class = htmlClasses.get(localName); return new Class(ownerDocument, localName); } return new HTMLElement(ownerDocument, localName); }; /** * @typedef {number|string} jsdonValue - either a node type or its content */ /** * Given a stringified, or arrayfied DOM element, returns an HTMLDocument * that represent the content of such string, or array. * @param {string|jsdonValue[]} value * @returns {HTMLDocument} */ export const parseJSON = value => { const array = typeof value === 'string' ? parse(value) : value; const {length} = array; const document = new HTMLDocument; let parentNode = document, end = parentNode[END], svg = false, i = 0; while (i < length) { let nodeType = array[i++]; switch (nodeType) { case ELEMENT_NODE: { const localName = array[i++]; const isSVG = svg || localName === 'svg' || localName === 'SVG'; const element = isSVG ? new SVGElement(document, localName, parentNode.ownerSVGElement || null) : createHTMLElement(document, localName); knownBoundaries(end[PREV], element, end); element.parentNode = parentNode; parentNode = element; end = parentNode[END]; svg = isSVG; break; } case ATTRIBUTE_NODE: { const name = array[i++]; const value = typeof array[i] === 'string' ? array[i++] : ''; const attr = new Attr(document, name, value); attr.ownerElement = parentNode; knownSiblings(end[PREV], attr, end); break; } case TEXT_NODE: append(parentNode, new Text(document, array[i++]), end); break; case COMMENT_NODE: append(parentNode, new Comment(document, array[i++]), end); break; case CDATA_SECTION_NODE: append(parentNode, new CDATASection(document, array[i++]), end); break; case DOCUMENT_TYPE_NODE: { const args = [document]; while (typeof array[i] === 'string') args.push(array[i++]); if (args.length === 3 && /\.dtd$/i.test(args[2])) args.splice(2, 0, ''); append(parentNode, new DocumentType(...args), end); break; } case DOCUMENT_FRAGMENT_NODE: parentNode = document.createDocumentFragment(); end = parentNode[END]; /* eslint no-fallthrough:0 */ case DOCUMENT_NODE: break; default: do { nodeType -= NODE_END; if (svg && !parentNode.ownerSVGElement) svg = false; parentNode = parentNode.parentNode || parentNode; } while (nodeType < 0); end = parentNode[END]; break; } } switch (i && array[0]) { case ELEMENT_NODE: return document.firstElementChild; case DOCUMENT_FRAGMENT_NODE: return parentNode; } return document; }; /** * * @param {Document|Element} node the Document or Element to serialize * @returns {jsdonValue[]} the linear jsdon serialized array */ export const toJSON = node => node.toJSON();