node-opcua-xml2json 2.142.0 → 2.143.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/dist/source/nodejs/xml2json_fs.d.ts +1 -2
- package/dist/source/nodejs/xml2json_fs.js +10 -28
- package/dist/source/nodejs/xml2json_fs.js.map +1 -1
- package/dist/source/thirdparties/escape.d.ts +4 -0
- package/dist/source/thirdparties/escape.js +77 -0
- package/dist/source/thirdparties/escape.js.map +1 -0
- package/dist/source/thirdparties/parser/lts.d.ts +7 -0
- package/dist/source/thirdparties/parser/lts.js +257 -0
- package/dist/source/thirdparties/parser/lts.js.map +1 -0
- package/dist/source/xml2json.d.ts +2 -17
- package/dist/source/xml2json.js +16 -28
- package/dist/source/xml2json.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -4
- package/source/nodejs/xml2json_fs.ts +7 -31
- package/source/thirdparties/escape.ts +87 -0
- package/source/thirdparties/parser/lts.ts +259 -0
- package/source/xml2json.ts +18 -49
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { EventEmitter } from "events";
|
|
4
|
+
import { unescapeXML } from "../escape";
|
|
5
|
+
|
|
6
|
+
const STATE_TEXT = 0;
|
|
7
|
+
const STATE_IGNORE_COMMENT = 1;
|
|
8
|
+
const STATE_IGNORE_INSTRUCTION = 2;
|
|
9
|
+
const STATE_TAG_NAME = 3;
|
|
10
|
+
const STATE_TAG = 4;
|
|
11
|
+
const STATE_ATTR_NAME = 5;
|
|
12
|
+
const STATE_ATTR_EQ = 6;
|
|
13
|
+
const STATE_ATTR_QUOT = 7;
|
|
14
|
+
const STATE_ATTR_VALUE = 8;
|
|
15
|
+
const STATE_CDATA = 9;
|
|
16
|
+
const STATE_IGNORE_CDATA = 10;
|
|
17
|
+
|
|
18
|
+
export class SaxLtx extends EventEmitter {
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
#write: (data: string) => void;
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
|
|
25
|
+
function _handleTagOpening(
|
|
26
|
+
this: SaxLtx,
|
|
27
|
+
endTag: boolean| undefined,
|
|
28
|
+
tagName: string|undefined,
|
|
29
|
+
attrs: string
|
|
30
|
+
) {
|
|
31
|
+
if (!endTag) {
|
|
32
|
+
this.emit("startElement", tagName, attrs);
|
|
33
|
+
if (selfClosing) {
|
|
34
|
+
this.emit("endElement", tagName, true);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
this.emit("endElement", tagName, false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
let state = STATE_TEXT;
|
|
41
|
+
let remainder: string | null = null;
|
|
42
|
+
let parseRemainder: boolean = false;
|
|
43
|
+
let tagName: string | undefined;
|
|
44
|
+
let attrs: string | {} | undefined;
|
|
45
|
+
let endTag: boolean|undefined;
|
|
46
|
+
let selfClosing: boolean | undefined;
|
|
47
|
+
let attrQuote: number;
|
|
48
|
+
let attrQuoteChar: string;
|
|
49
|
+
let recordStart: number | undefined = 0;
|
|
50
|
+
let attrName: string|undefined;
|
|
51
|
+
|
|
52
|
+
this.#write = function write(data: string ) {
|
|
53
|
+
let pos = 0;
|
|
54
|
+
|
|
55
|
+
/* Anything from previous write()? */
|
|
56
|
+
if (remainder) {
|
|
57
|
+
data = remainder + data;
|
|
58
|
+
pos += !parseRemainder ? remainder.length : 0;
|
|
59
|
+
parseRemainder = false;
|
|
60
|
+
remainder = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function endRecording(): undefined | string {
|
|
64
|
+
if (typeof recordStart === "number") {
|
|
65
|
+
const recorded = data.slice(recordStart, pos);
|
|
66
|
+
recordStart = undefined;
|
|
67
|
+
return recorded;
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (; pos < data.length; pos++) {
|
|
73
|
+
switch (state) {
|
|
74
|
+
case STATE_TEXT: {
|
|
75
|
+
// if we're looping through text, fast-forward using indexOf to
|
|
76
|
+
// the next '<' character
|
|
77
|
+
const lt = data.indexOf("<", pos);
|
|
78
|
+
if (lt !== -1 && pos !== lt) {
|
|
79
|
+
pos = lt;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case STATE_ATTR_VALUE: {
|
|
85
|
+
// if we're looping through an attribute, fast-forward using
|
|
86
|
+
// indexOf to the next end quote character
|
|
87
|
+
const quot = data.indexOf(attrQuoteChar, pos);
|
|
88
|
+
if (quot !== -1) {
|
|
89
|
+
pos = quot;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case STATE_IGNORE_COMMENT: {
|
|
95
|
+
// if we're looping through a comment, fast-forward using
|
|
96
|
+
// indexOf to the first end-comment character
|
|
97
|
+
const endcomment = data.indexOf("-->", pos);
|
|
98
|
+
if (endcomment !== -1) {
|
|
99
|
+
pos = endcomment + 2; // target the '>' character
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case STATE_IGNORE_CDATA: {
|
|
105
|
+
// if we're looping through a CDATA, fast-forward using
|
|
106
|
+
// indexOf to the first end-CDATA character ]]>
|
|
107
|
+
const endCDATA = data.indexOf("]]>", pos);
|
|
108
|
+
if (endCDATA !== -1) {
|
|
109
|
+
pos = endCDATA + 2; // target the '>' character
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
// No default
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const c = data.charCodeAt(pos);
|
|
118
|
+
switch (state) {
|
|
119
|
+
case STATE_TEXT:
|
|
120
|
+
if (c === 60 /* < */) {
|
|
121
|
+
const text = endRecording();
|
|
122
|
+
if (text) {
|
|
123
|
+
this.emit("text", unescapeXML(text));
|
|
124
|
+
}
|
|
125
|
+
state = STATE_TAG_NAME;
|
|
126
|
+
recordStart = pos + 1;
|
|
127
|
+
attrs = {};
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
case STATE_CDATA:
|
|
131
|
+
if (c === 93 /* ] */) {
|
|
132
|
+
if (data.substring(pos + 1, 2) === "]>") {
|
|
133
|
+
const cData = endRecording();
|
|
134
|
+
if (cData) {
|
|
135
|
+
this.emit("text", cData);
|
|
136
|
+
}
|
|
137
|
+
state = STATE_TEXT;
|
|
138
|
+
} else if (data.length < pos + 2) {
|
|
139
|
+
parseRemainder = true;
|
|
140
|
+
pos = data.length;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
case STATE_TAG_NAME:
|
|
145
|
+
if (c === 47 /* / */ && recordStart === pos) {
|
|
146
|
+
recordStart = pos + 1;
|
|
147
|
+
endTag = true;
|
|
148
|
+
} else if (c === 33 /* ! */) {
|
|
149
|
+
if (data.substring(pos + 1, 7) === "[CDATA[") {
|
|
150
|
+
recordStart = pos + 8;
|
|
151
|
+
state = STATE_CDATA;
|
|
152
|
+
} else if (
|
|
153
|
+
data.length < pos + 8 &&
|
|
154
|
+
"[CDATA[".startsWith(data.slice(pos + 1))
|
|
155
|
+
) {
|
|
156
|
+
// We potentially have CDATA, but the chunk is ending; stop here and let the next write() decide
|
|
157
|
+
parseRemainder = true;
|
|
158
|
+
pos = data.length;
|
|
159
|
+
} else {
|
|
160
|
+
recordStart = undefined;
|
|
161
|
+
state = STATE_IGNORE_COMMENT;
|
|
162
|
+
}
|
|
163
|
+
} else if (c === 63 /* ? */) {
|
|
164
|
+
recordStart = undefined;
|
|
165
|
+
state = STATE_IGNORE_INSTRUCTION;
|
|
166
|
+
} else if (c <= 32 || c === 47 /* / */ || c === 62 /* > */) {
|
|
167
|
+
tagName = endRecording();
|
|
168
|
+
pos--;
|
|
169
|
+
state = STATE_TAG;
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
case STATE_IGNORE_COMMENT:
|
|
173
|
+
if (c === 62 /* > */) {
|
|
174
|
+
const prevFirst = data.charCodeAt(pos - 1);
|
|
175
|
+
const prevSecond = data.charCodeAt(pos - 2);
|
|
176
|
+
if (
|
|
177
|
+
(prevFirst === 45 /* - */ && prevSecond === 45) /* - */ ||
|
|
178
|
+
(prevFirst === 93 /* ] */ && prevSecond === 93) /* ] */
|
|
179
|
+
) {
|
|
180
|
+
state = STATE_TEXT;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
case STATE_IGNORE_INSTRUCTION:
|
|
185
|
+
if (c === 62 /* > */) {
|
|
186
|
+
const prev = data.charCodeAt(pos - 1);
|
|
187
|
+
if (prev === 63 /* ? */) {
|
|
188
|
+
state = STATE_TEXT;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
case STATE_TAG:
|
|
193
|
+
if (c === 62 /* > */) {
|
|
194
|
+
_handleTagOpening.call(this, endTag, tagName, attrs as any);
|
|
195
|
+
tagName = undefined;
|
|
196
|
+
attrs = undefined;
|
|
197
|
+
endTag = undefined;
|
|
198
|
+
selfClosing = undefined;
|
|
199
|
+
state = STATE_TEXT;
|
|
200
|
+
recordStart = pos + 1;
|
|
201
|
+
} else if (c === 47 /* / */) {
|
|
202
|
+
selfClosing = true;
|
|
203
|
+
} else if (c > 32) {
|
|
204
|
+
recordStart = pos;
|
|
205
|
+
state = STATE_ATTR_NAME;
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
case STATE_ATTR_NAME:
|
|
209
|
+
if (c <= 32 || c === 61 /* = */) {
|
|
210
|
+
attrName = endRecording();
|
|
211
|
+
pos--;
|
|
212
|
+
state = STATE_ATTR_EQ;
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
case STATE_ATTR_EQ:
|
|
216
|
+
if (c === 61 /* = */) {
|
|
217
|
+
state = STATE_ATTR_QUOT;
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
case STATE_ATTR_QUOT:
|
|
221
|
+
if (c === 34 /* " */ || c === 39 /* ' */) {
|
|
222
|
+
attrQuote = c;
|
|
223
|
+
attrQuoteChar = c === 34 ? '"' : "'";
|
|
224
|
+
state = STATE_ATTR_VALUE;
|
|
225
|
+
recordStart = pos + 1;
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
case STATE_ATTR_VALUE:
|
|
229
|
+
if (c === attrQuote) {
|
|
230
|
+
const value = unescapeXML(endRecording()!);
|
|
231
|
+
(attrs as any)[attrName!] = value;
|
|
232
|
+
attrName = undefined;
|
|
233
|
+
state = STATE_TAG;
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (typeof recordStart === "number" && recordStart <= data.length) {
|
|
240
|
+
remainder = data.slice(recordStart);
|
|
241
|
+
recordStart = 0;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
public write(data: string | Buffer) {
|
|
248
|
+
this.#write(data.toString());
|
|
249
|
+
}
|
|
250
|
+
public end(data?: string | undefined) {
|
|
251
|
+
if (data) {
|
|
252
|
+
this.write(data);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Uh, yeah */
|
|
256
|
+
this.write = function write() { };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
package/source/xml2json.ts
CHANGED
|
@@ -3,29 +3,13 @@
|
|
|
3
3
|
* node -> see if https://github.com/isaacs/sax-js could be used instead
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
// tslint:disable:max-classes-per-file
|
|
7
|
-
// tslint:disable:no-var-requires
|
|
8
|
-
// tslint:disable:unified-signatures
|
|
9
6
|
|
|
10
7
|
import { assert } from "node-opcua-assert";
|
|
11
|
-
|
|
8
|
+
import { SaxLtx } from "./thirdparties/parser/lts";
|
|
12
9
|
|
|
13
10
|
export type SimpleCallback = (err?: Error) => void;
|
|
14
11
|
export type Callback<T> = (err?: Error | null, result?: T) => void;
|
|
15
12
|
|
|
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
13
|
|
|
30
14
|
export interface Parser {
|
|
31
15
|
[key: string]: ReaderState;
|
|
@@ -88,8 +72,8 @@ export interface IReaderState {
|
|
|
88
72
|
_on_text(text: string): void;
|
|
89
73
|
}
|
|
90
74
|
|
|
91
|
-
export class ReaderStateBase {}
|
|
92
|
-
export interface ReaderStateBase extends IReaderState {}
|
|
75
|
+
export class ReaderStateBase { }
|
|
76
|
+
export interface ReaderStateBase extends IReaderState { }
|
|
93
77
|
/**
|
|
94
78
|
* @private
|
|
95
79
|
*/
|
|
@@ -281,22 +265,8 @@ export class Xml2Json {
|
|
|
281
265
|
this._promote(state, 0);
|
|
282
266
|
}
|
|
283
267
|
|
|
284
|
-
public
|
|
285
|
-
|
|
286
|
-
const parser = this._prepareParser((err: Error | null | undefined, r: Record<string, unknown>) => (retValue = r));
|
|
287
|
-
parser.write(xml_text);
|
|
288
|
-
parser.end();
|
|
289
|
-
return retValue;
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* @deprecated
|
|
293
|
-
*/
|
|
294
|
-
public parseString(xml_text: string): Promise<any>;
|
|
295
|
-
public parseString(xml_text: string, callback: Callback<any> | SimpleCallback): void;
|
|
296
|
-
public parseString(xml_text: string, callback?: Callback<any> | SimpleCallback): any {
|
|
297
|
-
const parser = this._prepareParser(callback!);
|
|
298
|
-
parser.write(xml_text);
|
|
299
|
-
parser.end();
|
|
268
|
+
public parseString(xml_text: string): Record<string, unknown> {
|
|
269
|
+
return this.__parseInternal(xml_text);
|
|
300
270
|
}
|
|
301
271
|
/**
|
|
302
272
|
* @private
|
|
@@ -331,9 +301,8 @@ export class Xml2Json {
|
|
|
331
301
|
* @private
|
|
332
302
|
* @internal
|
|
333
303
|
*/
|
|
334
|
-
protected
|
|
335
|
-
|
|
336
|
-
const parser = new LtxParser();
|
|
304
|
+
protected __parseInternal(data: string): Record<string, unknown> {
|
|
305
|
+
const parser = new SaxLtx();
|
|
337
306
|
this.currentLevel = 0;
|
|
338
307
|
parser.on("startElement", (name: string, attrs: XmlAttributes) => {
|
|
339
308
|
const tag_ns = resolve_namespace(name);
|
|
@@ -361,16 +330,16 @@ export class Xml2Json {
|
|
|
361
330
|
this.current_state._on_text(text);
|
|
362
331
|
}
|
|
363
332
|
});
|
|
364
|
-
parser.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
333
|
+
parser.write(data);
|
|
334
|
+
parser.end("");
|
|
335
|
+
return (this.current_state! as any)._pojo;
|
|
336
|
+
/*
|
|
337
|
+
return await new Promise((resolve) => {
|
|
338
|
+
parser.once("close", () => {
|
|
339
|
+
resolve((this.current_state! as any)._pojo);
|
|
340
|
+
});
|
|
341
|
+
//parser.write(data);
|
|
342
|
+
parser.end(data);
|
|
343
|
+
})*/
|
|
370
344
|
}
|
|
371
345
|
}
|
|
372
|
-
|
|
373
|
-
// tslint:disable:no-var-requires
|
|
374
|
-
import { withCallback } from "thenify-ex";
|
|
375
|
-
const opts = { multiArgs: false };
|
|
376
|
-
Xml2Json.prototype.parseString = withCallback(Xml2Json.prototype.parseString, opts);
|