From 3edd364c3b40f5fd9e2b339eaea522c9493bf2f8 Mon Sep 17 00:00:00 2001 From: Yanzhen Yu Date: Sun, 30 Sep 2018 15:26:00 +0800 Subject: [PATCH] basic rebuild implementation --- src/index.ts | 140 ++---------------------------------------------- src/rebuild.ts | 46 ++++++++++++++++ src/snapshot.ts | 89 ++++++++++++++++++++++++++++++ src/types.ts | 55 +++++++++++++++++++ 4 files changed, 193 insertions(+), 137 deletions(-) create mode 100644 src/rebuild.ts create mode 100644 src/snapshot.ts create mode 100644 src/types.ts diff --git a/src/index.ts b/src/index.ts index 4a003d2b..758f2df9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,138 +1,4 @@ -let _id = 1; +import snapshot from './snapshot'; +import rebuild from './rebuild'; -function genId(): number { - return _id++; -} - -enum NodeType { - Document, - DocumentType, - Element, - Text, - CDATA, - Comment, -} - -type serializedNode = - | documentNode - | documentTypeNode - | elementNode - | textNode - | cdataNode - | commentNode; - -type documentNode = { - type: NodeType.Document; - childNodes: serializedNode[]; -}; - -type documentTypeNode = { - type: NodeType.DocumentType; - name: string; - publicId: string; - systemId: string; -}; - -type attributes = { - [key: string]: string; -}; -type elementNode = { - type: NodeType.Element; - tagName: string; - attributes: attributes; - childNodes: serializedNode[]; -}; - -type textNode = { - type: NodeType.Text; - textContent: string; -}; - -type cdataNode = { - type: NodeType.CDATA; - textContent: ''; -}; - -type commentNode = { - type: NodeType.Comment; - textContent: string; -}; - -function serializeNode(n: Node): serializedNode | false { - switch (n.nodeType) { - case n.DOCUMENT_NODE: - return { - type: NodeType.Document, - childNodes: [], - }; - case n.DOCUMENT_TYPE_NODE: - return { - type: NodeType.DocumentType, - name: (n as DocumentType).name, - publicId: (n as DocumentType).publicId, - systemId: (n as DocumentType).systemId, - }; - case n.ELEMENT_NODE: - const tagName = (n as HTMLElement).tagName.toLowerCase(); - const attributes: attributes = {}; - for (const { name, value } of Array.from((n as HTMLElement).attributes)) { - attributes[name] = value; - } - return { - type: NodeType.Element, - tagName, - attributes, - childNodes: [], - }; - case n.TEXT_NODE: - // The parent node may not be a html element which has a tagName attribute. - // So just let it be undefined which is ok in this use case. - const parentTagName = - n.parentNode && (n.parentNode as HTMLElement).tagName; - let textContent = (n as Text).textContent; - if (parentTagName === 'SCRIPT') { - textContent = ''; - } - return { - type: NodeType.Text, - textContent, - }; - case n.CDATA_SECTION_NODE: - return { - type: NodeType.CDATA, - textContent: '', - }; - case n.COMMENT_NODE: - return { - type: NodeType.Comment, - textContent: (n as Comment).textContent, - }; - default: - return false; - } -} - -type serializedNodeWithId = serializedNode & { id: number }; - -function snapshot(n: Node): serializedNodeWithId | null { - const _serializedNode = serializeNode(n); - if (!_serializedNode) { - // TODO: dev only - console.warn(n, 'not serialized'); - return null; - } - const serializedNode: serializedNodeWithId = Object.assign(_serializedNode, { - id: genId(), - }); - if ( - serializedNode.type === NodeType.Document || - serializedNode.type === NodeType.Element - ) { - for (const childN of Array.from(n.childNodes)) { - serializedNode.childNodes.push(snapshot(childN)); - } - } - return serializedNode; -} - -export default snapshot; +export { snapshot, rebuild }; diff --git a/src/rebuild.ts b/src/rebuild.ts new file mode 100644 index 00000000..1d581e45 --- /dev/null +++ b/src/rebuild.ts @@ -0,0 +1,46 @@ +import { serializedNodeWithId, NodeType } from './types'; + +function buildNode(n: serializedNodeWithId): Node | null { + switch (n.type) { + case NodeType.Document: + return document.implementation.createDocument(null, '', null); + case NodeType.DocumentType: + return document.implementation.createDocumentType( + n.name, + n.publicId, + n.systemId, + ); + case NodeType.Element: + const node = document.createElement(n.tagName); + for (const name in n.attributes) { + if (n.attributes.hasOwnProperty(name)) { + node.setAttribute(name, n.attributes[name]); + } + } + return node; + case NodeType.Text: + return document.createTextNode(n.textContent); + case NodeType.CDATA: + return document.createCDATASection(n.textContent); + case NodeType.Comment: + return document.createComment(n.textContent); + default: + return null; + } +} + +function rebuild(n: serializedNodeWithId): Node | null { + const root = buildNode(n); + if (!root) { + return null; + } + if (n.type === NodeType.Document || n.type === NodeType.Element) { + for (const childN of n.childNodes) { + const childNode = rebuild(childN); + root.appendChild(childNode); + } + } + return root; +} + +export default rebuild; diff --git a/src/snapshot.ts b/src/snapshot.ts new file mode 100644 index 00000000..ce805da5 --- /dev/null +++ b/src/snapshot.ts @@ -0,0 +1,89 @@ +import { + serializedNode, + serializedNodeWithId, + NodeType, + attributes, +} from './types'; + +let _id = 1; + +function genId(): number { + return _id++; +} + +function serializeNode(n: Node): serializedNode | false { + switch (n.nodeType) { + case n.DOCUMENT_NODE: + return { + type: NodeType.Document, + childNodes: [], + }; + case n.DOCUMENT_TYPE_NODE: + return { + type: NodeType.DocumentType, + name: (n as DocumentType).name, + publicId: (n as DocumentType).publicId, + systemId: (n as DocumentType).systemId, + }; + case n.ELEMENT_NODE: + const tagName = (n as HTMLElement).tagName.toLowerCase(); + const attributes: attributes = {}; + for (const { name, value } of Array.from((n as HTMLElement).attributes)) { + attributes[name] = value; + } + return { + type: NodeType.Element, + tagName, + attributes, + childNodes: [], + }; + case n.TEXT_NODE: + // The parent node may not be a html element which has a tagName attribute. + // So just let it be undefined which is ok in this use case. + const parentTagName = + n.parentNode && (n.parentNode as HTMLElement).tagName; + let textContent = (n as Text).textContent; + if (parentTagName === 'SCRIPT') { + textContent = ''; + } + return { + type: NodeType.Text, + textContent, + }; + case n.CDATA_SECTION_NODE: + return { + type: NodeType.CDATA, + textContent: '', + }; + case n.COMMENT_NODE: + return { + type: NodeType.Comment, + textContent: (n as Comment).textContent, + }; + default: + return false; + } +} + +function snapshot(n: Node): serializedNodeWithId | null { + const _serializedNode = serializeNode(n); + if (!_serializedNode) { + // TODO: dev only + console.warn(n, 'not serialized'); + return null; + } + const serializedNode: serializedNodeWithId = Object.assign(_serializedNode, { + id: genId(), + }); + if ( + serializedNode.type === NodeType.Document || + serializedNode.type === NodeType.Element + ) { + for (const childN of Array.from(n.childNodes)) { + serializedNode.childNodes.push(snapshot(childN)); + } + } + return serializedNode; +} + +export default snapshot; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..25fc9962 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,55 @@ +export enum NodeType { + Document, + DocumentType, + Element, + Text, + CDATA, + Comment, +} + +export type documentNode = { + type: NodeType.Document; + childNodes: serializedNodeWithId[]; +}; + +export type documentTypeNode = { + type: NodeType.DocumentType; + name: string; + publicId: string; + systemId: string; +}; + +export type attributes = { + [key: string]: string; +}; +export type elementNode = { + type: NodeType.Element; + tagName: string; + attributes: attributes; + childNodes: serializedNodeWithId[]; +}; + +export type textNode = { + type: NodeType.Text; + textContent: string; +}; + +export type cdataNode = { + type: NodeType.CDATA; + textContent: ''; +}; + +export type commentNode = { + type: NodeType.Comment; + textContent: string; +}; + +export type serializedNode = + | documentNode + | documentTypeNode + | elementNode + | textNode + | cdataNode + | commentNode; + +export type serializedNodeWithId = serializedNode & { id: number };