basic rebuild implementation
This commit is contained in:
140
src/index.ts
140
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 };
|
||||
|
||||
46
src/rebuild.ts
Normal file
46
src/rebuild.ts
Normal file
@@ -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;
|
||||
89
src/snapshot.ts
Normal file
89
src/snapshot.ts
Normal file
@@ -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;
|
||||
55
src/types.ts
Normal file
55
src/types.ts
Normal file
@@ -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 };
|
||||
Reference in New Issue
Block a user