node-opcua-xml2json 2.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.yml +13 -0
- package/LICENSE +20 -0
- package/dist/source/definition_parser.d.ts +3 -0
- package/dist/source/definition_parser.js +84 -0
- package/dist/source/definition_parser.js.map +1 -0
- package/dist/source/extension_object_parser.d.ts +17 -0
- package/dist/source/extension_object_parser.js +234 -0
- package/dist/source/extension_object_parser.js.map +1 -0
- package/dist/source/fragment_cloner.d.ts +13 -0
- package/dist/source/fragment_cloner.js +41 -0
- package/dist/source/fragment_cloner.js.map +1 -0
- package/dist/source/fragment_cloner_parser.d.ts +8 -0
- package/dist/source/fragment_cloner_parser.js +17 -0
- package/dist/source/fragment_cloner_parser.js.map +1 -0
- package/dist/source/index.d.ts +6 -0
- package/dist/source/index.js +19 -0
- package/dist/source/index.js.map +1 -0
- package/dist/source/nodejs/xml2json_fs.d.ts +10 -0
- package/dist/source/nodejs/xml2json_fs.js +39 -0
- package/dist/source/nodejs/xml2json_fs.js.map +1 -0
- package/dist/source/xml2Json_pojo_tools.d.ts +19 -0
- package/dist/source/xml2Json_pojo_tools.js +89 -0
- package/dist/source/xml2Json_pojo_tools.js.map +1 -0
- package/dist/source/xml2json.d.ts +177 -0
- package/dist/source/xml2json.js +296 -0
- package/dist/source/xml2json.js.map +1 -0
- package/dist/source/xml2json_pojo.d.ts +7 -0
- package/dist/source/xml2json_pojo.js +29 -0
- package/dist/source/xml2json_pojo.js.map +1 -0
- package/package.json +40 -0
- package/pnpm-lock.yaml +77 -0
- package/source/definition_parser.ts +84 -0
- package/source/extension_object_parser.ts +276 -0
- package/source/fragment_cloner.ts +51 -0
- package/source/fragment_cloner_parser.ts +17 -0
- package/source/index.ts +6 -0
- package/source/nodejs/xml2json_fs.ts +43 -0
- package/source/xml2Json_pojo_tools.ts +98 -0
- package/source/xml2json.ts +394 -0
- package/source/xml2json_pojo.ts +27 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-xml2json
|
|
3
|
+
* node -> see if https://github.com/isaacs/sax-js could be used instead
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// tslint:disable:max-classes-per-file
|
|
7
|
+
// tslint:disable:no-var-requires
|
|
8
|
+
// tslint:disable:unified-signatures
|
|
9
|
+
|
|
10
|
+
import { assert } from "node-opcua-assert";
|
|
11
|
+
const LtxParser = require("ltx/lib/parsers/ltx.js");
|
|
12
|
+
|
|
13
|
+
export type SimpleCallback = (err?: Error) => void;
|
|
14
|
+
export type Callback<T> = (err?: Error | null, result?: T) => void;
|
|
15
|
+
|
|
16
|
+
declare interface LtxParser {
|
|
17
|
+
write(str: string): void;
|
|
18
|
+
|
|
19
|
+
end(): void;
|
|
20
|
+
|
|
21
|
+
on(eventName: "startElement", eventHandler: (name: string, attrs: XmlAttributes) => void): void;
|
|
22
|
+
|
|
23
|
+
on(eventName: "endElement", eventHandler: (name: string) => void): void;
|
|
24
|
+
|
|
25
|
+
on(eventName: "text", eventHandler: (name: string) => void): void;
|
|
26
|
+
|
|
27
|
+
on(eventName: "close", eventHandler: () => void): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface Parser {
|
|
31
|
+
[key: string]: ReaderState;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @static
|
|
36
|
+
* @private
|
|
37
|
+
* @method _coerceParser
|
|
38
|
+
* @param parser {map<ReaderState|options>}
|
|
39
|
+
* @return {map}
|
|
40
|
+
*/
|
|
41
|
+
function _coerceParser(parser: ParserLike): Parser {
|
|
42
|
+
for (const name of Object.keys(parser)) {
|
|
43
|
+
if (parser[name] && !(parser[name] instanceof ReaderState)) {
|
|
44
|
+
// this is to prevent recursion
|
|
45
|
+
const tmp = parser[name];
|
|
46
|
+
delete parser[name];
|
|
47
|
+
parser[name] = new ReaderState(tmp);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return parser as Parser;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface XmlAttributes {
|
|
54
|
+
[key: string]: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ReaderStateParser {
|
|
58
|
+
parser?: ParserLike;
|
|
59
|
+
init?: (this: IReaderState, name: string, attrs: XmlAttributes, parent: IReaderState, engine: Xml2Json) => void;
|
|
60
|
+
finish?: (this: IReaderState) => void;
|
|
61
|
+
startElement?: (this: IReaderState, name: string, attrs: XmlAttributes) => void;
|
|
62
|
+
endElement?: (this: IReaderState, name: string) => void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ParserLike {
|
|
66
|
+
[key: string]: ReaderStateParserLike;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ReaderStateParserLike {
|
|
70
|
+
parser?: ParserLike;
|
|
71
|
+
init?: (this: any, name: string, attrs: XmlAttributes, parent: IReaderState, engine: Xml2Json) => void;
|
|
72
|
+
finish?: (this: any) => void;
|
|
73
|
+
startElement?: (this: any, name: string, attrs: XmlAttributes) => void;
|
|
74
|
+
endElement?: (this: any, name: string) => void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface IReaderState {
|
|
78
|
+
_on_init(elementName: string, attrs: XmlAttributes, parent: IReaderState, level: number, engine: Xml2Json): void;
|
|
79
|
+
|
|
80
|
+
_on_finish(): void;
|
|
81
|
+
|
|
82
|
+
_on_startElement(level: number, elementName: string, attrs: XmlAttributes): void;
|
|
83
|
+
|
|
84
|
+
_on_endElement(level: number, elementName: string): void;
|
|
85
|
+
|
|
86
|
+
_on_endElement2(level: number, elementName: string): void;
|
|
87
|
+
|
|
88
|
+
_on_text(text: string): void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class ReaderStateBase {}
|
|
92
|
+
export interface ReaderStateBase extends IReaderState {}
|
|
93
|
+
/**
|
|
94
|
+
* @class ReaderState
|
|
95
|
+
* @private
|
|
96
|
+
* @param options
|
|
97
|
+
* @param [options.parser=null] {map<ReaderState|options}}
|
|
98
|
+
* @param [options.init|null]
|
|
99
|
+
* @param [options.finish]
|
|
100
|
+
* @param [options.startElement]
|
|
101
|
+
* @param [options.endElement]
|
|
102
|
+
*/
|
|
103
|
+
export class ReaderState extends ReaderStateBase {
|
|
104
|
+
public _init?: (name: string, attrs: XmlAttributes, parent: IReaderState, engine: Xml2Json) => void;
|
|
105
|
+
public _finish?: () => void;
|
|
106
|
+
public _startElement?: (name: string, attrs: XmlAttributes) => void;
|
|
107
|
+
public _endElement?: (name: string) => void;
|
|
108
|
+
|
|
109
|
+
public parser: any;
|
|
110
|
+
public attrs?: XmlAttributes;
|
|
111
|
+
public chunks: any[] = [];
|
|
112
|
+
public text = "";
|
|
113
|
+
public name? = "";
|
|
114
|
+
public level = -1;
|
|
115
|
+
public currentLevel = -1;
|
|
116
|
+
|
|
117
|
+
public engine?: Xml2Json;
|
|
118
|
+
|
|
119
|
+
public parent?: IReaderState;
|
|
120
|
+
public root?: Xml2Json;
|
|
121
|
+
public data?: any;
|
|
122
|
+
|
|
123
|
+
constructor(options: ReaderStateParser | ReaderState) {
|
|
124
|
+
super();
|
|
125
|
+
// ensure options object has only expected properties
|
|
126
|
+
options.parser = options.parser || {};
|
|
127
|
+
|
|
128
|
+
if (!(options instanceof ReaderStateBase)) {
|
|
129
|
+
this._init = options.init;
|
|
130
|
+
this._finish = options.finish;
|
|
131
|
+
this._startElement = options.startElement;
|
|
132
|
+
this._endElement = options.endElement;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.parser = _coerceParser(options.parser);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @method _on_init
|
|
140
|
+
* @param elementName - the name of the element
|
|
141
|
+
* @param attrs
|
|
142
|
+
* @param parent
|
|
143
|
+
* @param level
|
|
144
|
+
* @param engine
|
|
145
|
+
* @protected
|
|
146
|
+
*/
|
|
147
|
+
public _on_init(elementName: string, attrs: XmlAttributes, parent: IReaderState, level: number, engine: Xml2Json): void {
|
|
148
|
+
this.name = elementName;
|
|
149
|
+
this.parent = parent;
|
|
150
|
+
this.engine = engine;
|
|
151
|
+
this.data = {};
|
|
152
|
+
this.level = level;
|
|
153
|
+
this.currentLevel = this.level;
|
|
154
|
+
this.attrs = attrs;
|
|
155
|
+
assert(this.attrs);
|
|
156
|
+
if (this._init) {
|
|
157
|
+
this._init(elementName, attrs, parent, engine);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public _on_finish(): void {
|
|
162
|
+
if (this._finish) {
|
|
163
|
+
this._finish();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param level
|
|
169
|
+
* @param elementName - the name of the element
|
|
170
|
+
* @param attrs
|
|
171
|
+
* @protected
|
|
172
|
+
*/
|
|
173
|
+
public _on_startElement(level: number, elementName: string, attrs: XmlAttributes): void {
|
|
174
|
+
this.currentLevel = level;
|
|
175
|
+
// console.log("wxxxx _on_startElement#" + this.name, elementName, this.currentLevel);
|
|
176
|
+
|
|
177
|
+
this.chunks = [];
|
|
178
|
+
this.text = "";
|
|
179
|
+
|
|
180
|
+
if (this._startElement) {
|
|
181
|
+
this._startElement(elementName, attrs);
|
|
182
|
+
}
|
|
183
|
+
if (this.engine && Object.prototype.hasOwnProperty.call(this.parser, elementName)) {
|
|
184
|
+
// console.log("promoting ", elementName, this.level);
|
|
185
|
+
this.engine._promote(this.parser[elementName], level, elementName, attrs);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public _on_endElement2(level: number, elementName: string): void {
|
|
190
|
+
if (this._endElement) {
|
|
191
|
+
this._endElement(elementName);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @method _on_endElement
|
|
197
|
+
* @protected
|
|
198
|
+
*/
|
|
199
|
+
public _on_endElement(level: number, elementName: string): void {
|
|
200
|
+
// console.log("wxxxx _on_endElement#" + this.name, elementName, level, this.currentLevel);
|
|
201
|
+
assert(this.attrs);
|
|
202
|
+
this.chunks = this.chunks || [];
|
|
203
|
+
|
|
204
|
+
if (this.level > level) {
|
|
205
|
+
// we end a child element of this node
|
|
206
|
+
this._on_endElement2(level, elementName);
|
|
207
|
+
} else if (this.level === level) {
|
|
208
|
+
// we received the end event of this node
|
|
209
|
+
// we need to finish
|
|
210
|
+
this.text = this.chunks.join("");
|
|
211
|
+
this.chunks = [];
|
|
212
|
+
// this is the end
|
|
213
|
+
this._on_finish();
|
|
214
|
+
if (
|
|
215
|
+
this.parent &&
|
|
216
|
+
(this.parent as any).parser &&
|
|
217
|
+
Object.prototype.hasOwnProperty.call((this.parent as any).parser, elementName)
|
|
218
|
+
) {
|
|
219
|
+
// console.log("xxx demoting#" + this.name, elementName, this.level);
|
|
220
|
+
this.engine!._demote(this, level, elementName);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @method _on_text
|
|
227
|
+
* @param text {String} the text found inside the element
|
|
228
|
+
* @protected
|
|
229
|
+
*/
|
|
230
|
+
public _on_text(text: string): void {
|
|
231
|
+
this.chunks = this.chunks || [];
|
|
232
|
+
text = text.trim();
|
|
233
|
+
if (text.length === 0) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this.chunks.push(text);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const regexp = /(([^:]+):)?(.*)/;
|
|
241
|
+
|
|
242
|
+
function resolve_namespace(name: string) {
|
|
243
|
+
const m = name.match(regexp);
|
|
244
|
+
if (!m) {
|
|
245
|
+
throw new Error("Invalid match");
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
ns: m[2],
|
|
249
|
+
tag: m[3]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @class Xml2Json
|
|
255
|
+
* @param options - the state machine as a ReaderState node.
|
|
256
|
+
* @param [options.parser=null] {ReaderState}
|
|
257
|
+
* @param [options.init|null]
|
|
258
|
+
* @param [options.finish]
|
|
259
|
+
* @param [options.startElement]
|
|
260
|
+
* @param [options.endElement]
|
|
261
|
+
* @constructor
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* var parser = new Xml2Json({
|
|
265
|
+
* parser: {
|
|
266
|
+
* 'person': {
|
|
267
|
+
* init: function(name,attrs) {
|
|
268
|
+
* this.parent.root.obj = {};
|
|
269
|
+
* this.obj = this.parent.root.obj;
|
|
270
|
+
* this.obj['name'] = attrs['name'];
|
|
271
|
+
* },
|
|
272
|
+
* parser: {
|
|
273
|
+
* 'address': {
|
|
274
|
+
* finish: function(){
|
|
275
|
+
* this.parent.obj['address'] = this.text;
|
|
276
|
+
* }
|
|
277
|
+
* }
|
|
278
|
+
* }
|
|
279
|
+
* }
|
|
280
|
+
* }
|
|
281
|
+
* });
|
|
282
|
+
*
|
|
283
|
+
* var xml_string = "<employees>" +
|
|
284
|
+
* " <person name='John'>" +
|
|
285
|
+
* " <address>Paris</address>" +
|
|
286
|
+
* " </person>" +
|
|
287
|
+
* "</employees>";
|
|
288
|
+
*
|
|
289
|
+
* parser.parseString(xml_string, function() {
|
|
290
|
+
* parser.obj.should.eql({name: 'John',address: 'Paris'});
|
|
291
|
+
* done();
|
|
292
|
+
* });
|
|
293
|
+
*/
|
|
294
|
+
export class Xml2Json {
|
|
295
|
+
public currentLevel = 0;
|
|
296
|
+
private state_stack: any[] = [];
|
|
297
|
+
private current_state: IReaderState | null = null;
|
|
298
|
+
|
|
299
|
+
constructor(options: ReaderStateParser) {
|
|
300
|
+
const state = options instanceof ReaderStateBase ? (options as ReaderState) : new ReaderState(options);
|
|
301
|
+
state.root = this;
|
|
302
|
+
|
|
303
|
+
this.state_stack = [];
|
|
304
|
+
this.current_state = null;
|
|
305
|
+
this._promote(state, 0);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* @method parseString
|
|
310
|
+
* @async
|
|
311
|
+
*/
|
|
312
|
+
public parseString(xml_text: string): Promise<any>;
|
|
313
|
+
public parseString(xml_text: string, callback: Callback<any> | SimpleCallback): void;
|
|
314
|
+
public parseString(xml_text: string, callback?: Callback<any> | SimpleCallback): any {
|
|
315
|
+
const parser = this._prepareParser(callback!);
|
|
316
|
+
parser.write(xml_text);
|
|
317
|
+
parser.end();
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* @param new_state
|
|
321
|
+
* @param name
|
|
322
|
+
* @param attr
|
|
323
|
+
* @private
|
|
324
|
+
* @internal
|
|
325
|
+
*/
|
|
326
|
+
public _promote(new_state: IReaderState, level: number, name?: string, attr?: XmlAttributes): void {
|
|
327
|
+
attr = attr || {};
|
|
328
|
+
this.state_stack.push({
|
|
329
|
+
backup: {},
|
|
330
|
+
state: this.current_state
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const parent = this.current_state;
|
|
334
|
+
this.current_state = new_state;
|
|
335
|
+
this.current_state._on_init(name || "???", attr, parent!, level, this);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
*
|
|
340
|
+
* @private
|
|
341
|
+
* @internal
|
|
342
|
+
*/
|
|
343
|
+
public _demote(cur_state: IReaderState, level: number, elementName: string): void {
|
|
344
|
+
/// assert(this.current_state === cur_state);
|
|
345
|
+
const { state, backup } = this.state_stack.pop();
|
|
346
|
+
this.current_state = state;
|
|
347
|
+
if (this.current_state) {
|
|
348
|
+
this.current_state._on_endElement2(level, elementName);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
protected _prepareParser(callback: Callback<any> | SimpleCallback): LtxParser {
|
|
353
|
+
assert(callback instanceof Function);
|
|
354
|
+
const parser = new LtxParser();
|
|
355
|
+
this.currentLevel = 0;
|
|
356
|
+
parser.on("startElement", (name: string, attrs: XmlAttributes) => {
|
|
357
|
+
const tag_ns = resolve_namespace(name);
|
|
358
|
+
this.currentLevel += 1;
|
|
359
|
+
if (this.current_state) {
|
|
360
|
+
this.current_state._on_startElement(this.currentLevel, tag_ns.tag, attrs);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
parser.on("endElement", (name: string) => {
|
|
364
|
+
const tag_ns = resolve_namespace(name);
|
|
365
|
+
if (this.current_state) {
|
|
366
|
+
this.current_state._on_endElement(this.currentLevel, tag_ns.tag);
|
|
367
|
+
}
|
|
368
|
+
this.currentLevel -= 1;
|
|
369
|
+
if (this.currentLevel === 0) {
|
|
370
|
+
parser.emit("close");
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
parser.on("text", (text: string) => {
|
|
374
|
+
text = text.trim();
|
|
375
|
+
if (text.length === 0) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (this.current_state) {
|
|
379
|
+
this.current_state._on_text(text);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
parser.once("close", () => {
|
|
383
|
+
if (callback) {
|
|
384
|
+
(callback as any)(null, (this.current_state! as any)._pojo);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
return parser;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// tslint:disable:no-var-requires
|
|
392
|
+
const thenify = require("thenify");
|
|
393
|
+
const opts = { multiArgs: false };
|
|
394
|
+
Xml2Json.prototype.parseString = thenify.withCallback(Xml2Json.prototype.parseString, opts);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { IReaderState, ReaderState, ReaderStateParser, Xml2Json, XmlAttributes } from "./xml2json";
|
|
2
|
+
import { ReaderState2, withPojoLambda } from "./xml2Json_pojo_tools";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const json_extractor: ReaderState2 = new ReaderState2();
|
|
6
|
+
export const json_parser: ReaderStateParser = {
|
|
7
|
+
init(this: IReaderState, elementName: string, attrs: XmlAttributes, parent: IReaderState, engine: Xml2Json) {
|
|
8
|
+
json_extractor._on_init(elementName, attrs, parent, 0, engine);
|
|
9
|
+
},
|
|
10
|
+
finish(this: any) {
|
|
11
|
+
this.parent._pojo = json_extractor._pojo;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function startPojo(pThis: ReaderState, elementName: string, attrs: XmlAttributes, withPojo: withPojoLambda) {
|
|
16
|
+
pThis.engine!._promote(json_extractor, pThis.engine!.currentLevel, elementName, attrs);
|
|
17
|
+
json_extractor._withPojo = (name: string, pojo: any) => {
|
|
18
|
+
withPojo(name, pojo);
|
|
19
|
+
pThis.engine!._demote(json_extractor, pThis.engine!.currentLevel, elementName);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Xml2JsonPojo extends Xml2Json {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(json_extractor as ReaderStateParser);
|
|
26
|
+
}
|
|
27
|
+
}
|