123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- //node types
- const NODE = 1;
- const TEXT = 2;
- const CDATA = 3;
- const COMMENT = 4;
- const name2type = {
- 'NODE': NODE,
- 'TEXT': TEXT,
- 'CDATA': CDATA,
- 'COMMENT': COMMENT,
- };
- const type2name = {
- [NODE]: 'NODE',
- [TEXT]: 'TEXT',
- [CDATA]: 'CDATA',
- [COMMENT]: 'COMMENT',
- };
- class NodeBase {
- makeSelectorObj(selectorString) {
- const result = {all: false, before: false, type: 0, name: ''};
- if (selectorString === '') {
- result.before = true;
- } else if (selectorString === '*') {
- result.all = true;
- } else if (selectorString[0] === '*') {
- const typeName = selectorString.substring(1);
- result.type = name2type[typeName];
- if (!result.type)
- throw new Error(`Unknown selector type: ${typeName}`);
- } else {
- result.name = selectorString;
- }
- return result;
- }
- checkNode(rawNode, selectorObj) {
- return selectorObj.all || selectorObj.before
- || (selectorObj.type && rawNode[0] === selectorObj.type)
- || (rawNode[0] === NODE && rawNode[1] === selectorObj.name);
- }
- findNodeIndex(nodes, selectorObj) {
- for (let i = 0; i < nodes.length; i++)
- if (this.checkNode(nodes[i], selectorObj))
- return i;
- }
- rawAdd(nodes, rawNode, selectorObj) {
- if (selectorObj.all) {
- nodes.push(rawNode);
- } else if (selectorObj.before) {
- nodes.unshift(rawNode);
- } else {
- const index = this.findNodeIndex(nodes, selectorObj);
- if (index >= 0)
- nodes.splice(index, 0, rawNode);
- else
- nodes.push(rawNode);
- }
- }
- rawRemove(nodes, selectorObj) {
- if (selectorObj.before)
- return;
- for (let i = nodes.length - 1; i >= 0; i--) {
- if (this.checkNode(nodes[i], selectorObj))
- nodes.splice(i, 1);
- }
- }
- }
- class NodeObject extends NodeBase {
- constructor(rawNode) {
- super();
- if (rawNode)
- this.raw = rawNode;
- else
- this.raw = [];
- }
- get type() {
- return this.raw[0] || null;
- }
- get name() {
- if (this.type === NODE)
- return this.raw[1] || null;
- return null;
- }
- set name(value) {
- if (this.type === NODE)
- this.raw[1] = value;
- }
- get attrs() {
- if (this.type === NODE && Array.isArray(this.raw[2]))
- return new Map(this.raw[2]);
- return null;
- }
- set attrs(value) {
- if (this.type === NODE)
- if (value && value.size)
- this.raw[2] = Array.from(value);
- else
- this.raw[2] = null;
- }
- get value() {
- switch (this.type) {
- case NODE:
- return this.raw[3] || null;
- case TEXT:
- case CDATA:
- case COMMENT:
- return this.raw[1] || null;
- }
- return null;
- }
- add(node, after = '*') {
- if (this.type !== NODE)
- return;
- const selectorObj = this.makeSelectorObj(after);
- if (!Array.isArray(this.raw[3]))
- this.raw[3] = [];
- this.rawAdd(this.raw[3], node.raw, selectorObj);
- }
- remove(selector = '') {
- if (this.type !== NODE || !this.raw[3])
- return;
- const selectorObj = this.makeSelectorObj(selector);
- this.rawRemove(this.raw[3], selectorObj);
- if (!this.raw[3].length)
- this.raw[3] = null;
- }
- each(callback) {
- if (this.type !== NODE || !this.raw[3])
- return;
- for (const n of this.raw[3]) {
- callback(new NodeObject(n));
- }
- }
- }
- class XmlParser extends NodeBase {
- constructor(rawNodes = []) {
- super();
- this.NODE = NODE;
- this.TEXT = TEXT;
- this.CDATA = CDATA;
- this.COMMENT = COMMENT;
- this.rawNodes = rawNodes;
- }
- get count() {
- return this.rawNodes.length;
- }
- toObject(node) {
- return new NodeObject(node);
- }
- newParser(nodes) {
- return new XmlParser(nodes);
- }
- checkType(type) {
- if (!type2name[type])
- throw new Error(`Invalid type: ${type}`);
- }
- createTypedNode(type, nameOrValue, attrs = null, value = null) {
- this.checkType(type);
- switch (type) {
- case NODE:
- if (!nameOrValue || typeof(nameOrValue) !== 'string')
- throw new Error('Node name must be non-empty string');
- return new NodeObject([type, nameOrValue, attrs, value]);
- case TEXT:
- case CDATA:
- case COMMENT:
- if (typeof(nameOrValue) !== 'string')
- throw new Error('Node value must be of type string');
- return new NodeObject([type, nameOrValue]);
- }
- }
- createNode(name, attrs = null, value = null) {
- return this.createTypedNode(NODE, name, attrs, value);
- }
- createText(value = null) {
- return this.createTypedNode(TEXT, value);
- }
- createCdata(value = null) {
- return this.createTypedNode(CDATA, value);
- }
- createComment(value = null) {
- return this.createTypedNode(COMMENT, value);
- }
- add(node, after = '*') {
- const selectorObj = this.makeSelectorObj(after);
- for (const n of this.rawNodes) {
- if (n && n[0] === NODE) {
- if (!Array.isArray(n[3]))
- n[3] = [];
- this.rawAdd(n[3], node.raw, selectorObj);
- }
- }
- }
- addRoot(node, after = '*') {
- const selectorObj = this.makeSelectorObj(after);
- this.rawAdd(this.rawNodes, node.raw, selectorObj);
- }
- remove(selector = '') {
- const selectorObj = this.makeSelectorObj(selector);
- for (const n of this.rawNodes) {
- if (n && n[0] === NODE && Array.isArray(n[3])) {
- this.rawRemove(n[3], selectorObj);
- if (!n[3].length)
- n[3] = null;
- }
- }
- }
- removeRoot(selector = '') {
- const selectorObj = this.makeSelectorObj(selector);
- this.rawRemove(this.rawNodes, selectorObj);
- }
- each(callback) {
- for (const n of this.rawNodes) {
- callback(new NodeObject(n));
- }
- }
- rawSelect(nodes, selectorObj, callback) {
- for (const n of nodes)
- if (this.checkNode(n, selectorObj))
- callback(n);
- }
- select(selector = '', self = false) {
- let newRawNodes = [];
- if (selector.indexOf('/') >= 0) {
- const selectors = selector.split('/');
- let res = this;
- for (const sel of selectors) {
- res = res.select(sel, self);
- self = false;
- }
- newRawNodes = res.rawNodes;
- } else {
- const selectorObj = this.makeSelectorObj(selector);
- if (self) {
- this.rawSelect(this.rawNodes, selectorObj, (node) => {
- newRawNodes.push(node);
- })
- } else {
- for (const n of this.rawNodes) {
- if (n && n[0] === NODE && Array.isArray(n[3])) {
- this.rawSelect(n[3], selectorObj, (node) => {
- newRawNodes.push(node);
- })
- }
- }
- }
- }
- return new XmlParser(newRawNodes);
- }
- s(selector, self) {
- return this.select(selector, self);
- }
- selectFirst(selector, self) {
- const result = this.select(selector, self);
- const node = (result.count ? result.rawNodes[0] : null);
- return this.toObject(node);
- }
- sf(selector, self) {
- return this.selectFirst(selector, self);
- }
- toJson(format = false) {
- if (format)
- return JSON.stringify(this.rawNodes, null, 2);
- else
- return JSON.stringify(this.rawNodes);
- }
- fromJson(jsonString) {
- const parsed = JSON.parse(jsonString);
- if (!Array.isArray(parsed))
- throw new Error('JSON parse error: root element must be array');
- this.rawNodes = parsed;
- }
- toString() {
- }
- fromSrtring() {
- }
- }
- module.exports = XmlParser;
|