XmlParser.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. //node types
  2. const NODE = 1;
  3. const TEXT = 2;
  4. const CDATA = 3;
  5. const COMMENT = 4;
  6. const name2type = {
  7. 'NODE': NODE,
  8. 'TEXT': TEXT,
  9. 'CDATA': CDATA,
  10. 'COMMENT': COMMENT,
  11. };
  12. const type2name = {
  13. [NODE]: 'NODE',
  14. [TEXT]: 'TEXT',
  15. [CDATA]: 'CDATA',
  16. [COMMENT]: 'COMMENT',
  17. };
  18. class NodeBase {
  19. makeSelectorObj(selectorString) {
  20. const result = {all: false, before: false, type: 0, name: ''};
  21. if (selectorString === '') {
  22. result.before = true;
  23. } else if (selectorString === '*') {
  24. result.all = true;
  25. } else if (selectorString[0] === '*') {
  26. const typeName = selectorString.substring(1);
  27. result.type = name2type[typeName];
  28. if (!result.type)
  29. throw new Error(`Unknown selector type: ${typeName}`);
  30. } else {
  31. result.name = selectorString;
  32. }
  33. return result;
  34. }
  35. checkNode(rawNode, selectorObj) {
  36. return selectorObj.all || selectorObj.before
  37. || (selectorObj.type && rawNode[0] === selectorObj.type)
  38. || (rawNode[0] === NODE && rawNode[1] === selectorObj.name);
  39. }
  40. findNodeIndex(nodes, selectorObj) {
  41. for (let i = 0; i < nodes.length; i++)
  42. if (this.checkNode(nodes[i], selectorObj))
  43. return i;
  44. }
  45. rawAdd(nodes, rawNode, selectorObj) {
  46. if (selectorObj.all) {
  47. nodes.push(rawNode);
  48. } else if (selectorObj.before) {
  49. nodes.unshift(rawNode);
  50. } else {
  51. const index = this.findNodeIndex(nodes, selectorObj);
  52. if (index >= 0)
  53. nodes.splice(index, 0, rawNode);
  54. else
  55. nodes.push(rawNode);
  56. }
  57. }
  58. rawRemove(nodes, selectorObj) {
  59. if (selectorObj.before)
  60. return;
  61. for (let i = nodes.length - 1; i >= 0; i--) {
  62. if (this.checkNode(nodes[i], selectorObj))
  63. nodes.splice(i, 1);
  64. }
  65. }
  66. }
  67. class NodeObject extends NodeBase {
  68. constructor(rawNode) {
  69. super();
  70. if (rawNode)
  71. this.raw = rawNode;
  72. else
  73. this.raw = [];
  74. }
  75. get type() {
  76. return this.raw[0] || null;
  77. }
  78. get name() {
  79. if (this.type === NODE)
  80. return this.raw[1] || null;
  81. return null;
  82. }
  83. set name(value) {
  84. if (this.type === NODE)
  85. this.raw[1] = value;
  86. }
  87. get attrs() {
  88. if (this.type === NODE && Array.isArray(this.raw[2]))
  89. return new Map(this.raw[2]);
  90. return null;
  91. }
  92. set attrs(value) {
  93. if (this.type === NODE)
  94. if (value && value.size)
  95. this.raw[2] = Array.from(value);
  96. else
  97. this.raw[2] = null;
  98. }
  99. get value() {
  100. switch (this.type) {
  101. case NODE:
  102. return this.raw[3] || null;
  103. case TEXT:
  104. case CDATA:
  105. case COMMENT:
  106. return this.raw[1] || null;
  107. }
  108. return null;
  109. }
  110. add(node, after = '*') {
  111. if (this.type !== NODE)
  112. return;
  113. const selectorObj = this.makeSelectorObj(after);
  114. if (!Array.isArray(this.raw[3]))
  115. this.raw[3] = [];
  116. this.rawAdd(this.raw[3], node.raw, selectorObj);
  117. }
  118. remove(selector = '') {
  119. if (this.type !== NODE || !this.raw[3])
  120. return;
  121. const selectorObj = this.makeSelectorObj(selector);
  122. this.rawRemove(this.raw[3], selectorObj);
  123. if (!this.raw[3].length)
  124. this.raw[3] = null;
  125. }
  126. each(callback) {
  127. if (this.type !== NODE || !this.raw[3])
  128. return;
  129. for (const n of this.raw[3]) {
  130. callback(new NodeObject(n));
  131. }
  132. }
  133. }
  134. class XmlParser extends NodeBase {
  135. constructor(rawNodes = []) {
  136. super();
  137. this.NODE = NODE;
  138. this.TEXT = TEXT;
  139. this.CDATA = CDATA;
  140. this.COMMENT = COMMENT;
  141. this.rawNodes = rawNodes;
  142. }
  143. get count() {
  144. return this.rawNodes.length;
  145. }
  146. toObject(node) {
  147. return new NodeObject(node);
  148. }
  149. newParser(nodes) {
  150. return new XmlParser(nodes);
  151. }
  152. checkType(type) {
  153. if (!type2name[type])
  154. throw new Error(`Invalid type: ${type}`);
  155. }
  156. createTypedNode(type, nameOrValue, attrs = null, value = null) {
  157. this.checkType(type);
  158. switch (type) {
  159. case NODE:
  160. if (!nameOrValue || typeof(nameOrValue) !== 'string')
  161. throw new Error('Node name must be non-empty string');
  162. return new NodeObject([type, nameOrValue, attrs, value]);
  163. case TEXT:
  164. case CDATA:
  165. case COMMENT:
  166. if (typeof(nameOrValue) !== 'string')
  167. throw new Error('Node value must be of type string');
  168. return new NodeObject([type, nameOrValue]);
  169. }
  170. }
  171. createNode(name, attrs = null, value = null) {
  172. return this.createTypedNode(NODE, name, attrs, value);
  173. }
  174. createText(value = null) {
  175. return this.createTypedNode(TEXT, value);
  176. }
  177. createCdata(value = null) {
  178. return this.createTypedNode(CDATA, value);
  179. }
  180. createComment(value = null) {
  181. return this.createTypedNode(COMMENT, value);
  182. }
  183. add(node, after = '*') {
  184. const selectorObj = this.makeSelectorObj(after);
  185. for (const n of this.rawNodes) {
  186. if (n && n[0] === NODE) {
  187. if (!Array.isArray(n[3]))
  188. n[3] = [];
  189. this.rawAdd(n[3], node.raw, selectorObj);
  190. }
  191. }
  192. }
  193. addRoot(node, after = '*') {
  194. const selectorObj = this.makeSelectorObj(after);
  195. this.rawAdd(this.rawNodes, node.raw, selectorObj);
  196. }
  197. remove(selector = '') {
  198. const selectorObj = this.makeSelectorObj(selector);
  199. for (const n of this.rawNodes) {
  200. if (n && n[0] === NODE && Array.isArray(n[3])) {
  201. this.rawRemove(n[3], selectorObj);
  202. if (!n[3].length)
  203. n[3] = null;
  204. }
  205. }
  206. }
  207. removeRoot(selector = '') {
  208. const selectorObj = this.makeSelectorObj(selector);
  209. this.rawRemove(this.rawNodes, selectorObj);
  210. }
  211. each(callback) {
  212. for (const n of this.rawNodes) {
  213. callback(new NodeObject(n));
  214. }
  215. }
  216. rawSelect(nodes, selectorObj, callback) {
  217. for (const n of nodes)
  218. if (this.checkNode(n, selectorObj))
  219. callback(n);
  220. }
  221. select(selector = '', self = false) {
  222. let newRawNodes = [];
  223. if (selector.indexOf('/') >= 0) {
  224. const selectors = selector.split('/');
  225. let res = this;
  226. for (const sel of selectors) {
  227. res = res.select(sel, self);
  228. self = false;
  229. }
  230. newRawNodes = res.rawNodes;
  231. } else {
  232. const selectorObj = this.makeSelectorObj(selector);
  233. if (self) {
  234. this.rawSelect(this.rawNodes, selectorObj, (node) => {
  235. newRawNodes.push(node);
  236. })
  237. } else {
  238. for (const n of this.rawNodes) {
  239. if (n && n[0] === NODE && Array.isArray(n[3])) {
  240. this.rawSelect(n[3], selectorObj, (node) => {
  241. newRawNodes.push(node);
  242. })
  243. }
  244. }
  245. }
  246. }
  247. return new XmlParser(newRawNodes);
  248. }
  249. s(selector, self) {
  250. return this.select(selector, self);
  251. }
  252. selectFirst(selector, self) {
  253. const result = this.select(selector, self);
  254. const node = (result.count ? result.rawNodes[0] : null);
  255. return this.toObject(node);
  256. }
  257. sf(selector, self) {
  258. return this.selectFirst(selector, self);
  259. }
  260. toJson(format = false) {
  261. if (format)
  262. return JSON.stringify(this.rawNodes, null, 2);
  263. else
  264. return JSON.stringify(this.rawNodes);
  265. }
  266. fromJson(jsonString) {
  267. const parsed = JSON.parse(jsonString);
  268. if (!Array.isArray(parsed))
  269. throw new Error('JSON parse error: root element must be array');
  270. this.rawNodes = parsed;
  271. }
  272. toString() {
  273. }
  274. fromSrtring() {
  275. }
  276. }
  277. module.exports = XmlParser;