• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

node-opcua / node-opcua / 25848855345

14 May 2026 07:56AM UTC coverage: 92.029% (-0.1%) from 92.174%
25848855345

push

github

erossignon
fix(pnpm): make cross-workspace deps resolve correctly in pnpm 11

pnpm 11 no longer honors `link-workspace-packages=true` from .npmrc, so
plain-version cross-workspace deps (e.g. "2.171.0") fall through to the
registry and 404 on workspace-only packages like @sterfive/fix-tsconfigs
or any unpublished version bump.

- Set `linkWorkspacePackages: true` in pnpm-workspace.yaml (pnpm 11's
  preferred location for workspace-wide config).
- Switch the drift-check CI job to `--frozen-lockfile`. Strict-lockfile
  is the right mode for a verification job; `--no-frozen-lockfile` in
  non-recursive install was bypassing the existing `link:` resolution
  in the lockfile and was its own version of this same bug.

Includes the lockfile refresh that was blocked by this issue —
node-opcua-client-browser had 6 new specifiers (@types/ws, ws, plus
internal workspace deps) that couldn't be resolved.

18409 of 21711 branches covered (84.79%)

163854 of 178047 relevant lines covered (92.03%)

434321.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.26
/packages/node-opcua-xml2json/source/xml2json.ts
1
/**
2✔
2
 * @module node-opcua-xml2json
2✔
3
 * node -> see if https://github.com/isaacs/sax-js could be used instead
2✔
4
 */
2✔
5

2✔
6

2✔
7
import { assert } from "node-opcua-assert";
2✔
8
import { SaxLtx } from "./thirdparties/parser/lts";
2✔
9

2✔
10
export type SimpleCallback = (err?: Error) => void;
2✔
11
export type Callback<T> = (err?: Error | null, result?: T) => void;
2✔
12

2✔
13

2✔
14
export interface Parser {
2✔
15
    [key: string]: ReaderState;
2✔
16
}
2✔
17

2✔
18
/**
2✔
19
 * @static
2✔
20
 * @private
2✔
21

2✔
22
 * @param parser {map<ReaderState|options>}
2✔
23
 * @return {map}
2✔
24
 */
2✔
25
function _coerceParser(parser: ParserLike): Parser {
149,132✔
26
    for (const name of Object.keys(parser)) {
149,132✔
27
        if (parser[name] && !(parser[name] instanceof ReaderState)) {
186,529✔
28
            // this is to prevent recursion
146,527✔
29
            const tmp = parser[name];
146,527✔
30
            delete parser[name];
146,527✔
31
            parser[name] = new ReaderState(tmp);
146,527✔
32
        }
146,527✔
33
    }
186,529✔
34
    return parser as Parser;
149,132✔
35
}
149,132✔
36

2✔
37
export interface XmlAttributes {
2✔
38
    [key: string]: string;
2✔
39
}
2✔
40

2✔
41
export interface ReaderStateParser {
2✔
42
    parser?: ParserLike;
2✔
43
    init?: (this: IReaderState, name: string, attrs: XmlAttributes, parent: IReaderState, engine: Xml2Json) => void;
2✔
44
    finish?: (this: IReaderState) => void;
2✔
45
    startElement?: (this: IReaderState, name: string, attrs: XmlAttributes) => void;
2✔
46
    endElement?: (this: IReaderState, name: string) => void;
2✔
47
}
2✔
48

2✔
49
export interface ParserLike {
2✔
50
    [key: string]: ReaderStateParserLike;
2✔
51
}
2✔
52

2✔
53
export interface ReaderStateParserLike {
2✔
54
    parser?: ParserLike;
2✔
55
    init?: (this: any, name: string, attrs: XmlAttributes, parent: IReaderState, engine: Xml2Json) => void;
2✔
56
    finish?: (this: any) => void;
2✔
57
    startElement?: (this: any, name: string, attrs: XmlAttributes) => void;
2✔
58
    endElement?: (this: any, name: string) => void;
2✔
59
}
2✔
60

2✔
61
export interface IReaderState {
2✔
62
    _on_init(elementName: string, attrs: XmlAttributes, parent: IReaderState, level: number, engine: Xml2Json): void;
2✔
63

2✔
64
    _on_finish(): void;
2✔
65

2✔
66
    _on_startElement(level: number, elementName: string, attrs: XmlAttributes): void;
2✔
67

2✔
68
    _on_endElement(level: number, elementName: string): void;
2✔
69

2✔
70
    _on_endElement2(level: number, elementName: string): void;
2✔
71

2✔
72
    _on_text(text: string): void;
2✔
73
}
2✔
74

2✔
75
export class ReaderStateBase { }
2✔
76
export interface ReaderStateBase extends IReaderState { }
2✔
77
/**
2✔
78
 * @private
2✔
79
 */
2✔
80
export class ReaderState extends ReaderStateBase {
2✔
81
    public _init?: (name: string, attrs: XmlAttributes, parent: IReaderState, engine: Xml2Json) => void;
149,130✔
82
    public _finish?: () => void;
149,130✔
83
    public _startElement?: (name: string, attrs: XmlAttributes) => void;
149,130✔
84
    public _endElement?: (name: string) => void;
149,130✔
85

149,130✔
86
    public parser: any;
149,130✔
87
    public attrs?: XmlAttributes;
149,130✔
88
    public chunks: any[] = [];
149,130✔
89
    public text = "";
149,130✔
90
    public name? = "";
149,130✔
91
    public level = -1;
149,130✔
92
    public currentLevel = -1;
149,130✔
93

149,130✔
94
    public engine?: Xml2Json;
149,130✔
95

149,130✔
96
    public parent?: IReaderState;
149,130✔
97
    public root?: Xml2Json;
149,130✔
98
    public data?: any;
149,130✔
99

149,130✔
100
    constructor(options: ReaderStateParser | ReaderState) {
149,130✔
101
        super();
149,132✔
102
        // ensure options object has only expected properties
149,132✔
103
        options.parser = options.parser || {};
149,132✔
104

149,132✔
105
        if (!(options instanceof ReaderStateBase)) {
149,132✔
106
            this._init = options.init;
149,132✔
107
            this._finish = options.finish;
149,132✔
108
            this._startElement = options.startElement;
149,132✔
109
            this._endElement = options.endElement;
149,132✔
110
        }
149,132✔
111

149,132✔
112
        this.parser = _coerceParser(options.parser);
149,132✔
113
    }
149,132✔
114

149,130✔
115
    /**
149,130✔
116
     * @protected
149,130✔
117
     */
149,130✔
118
    public _on_init(elementName: string, attrs: XmlAttributes, parent: IReaderState, level: number, engine: Xml2Json): void {
149,130✔
119
        this.name = elementName;
23,639,151✔
120
        this.parent = parent;
23,639,151✔
121
        this.engine = engine;
23,639,151✔
122
        this.data = {};
23,639,151✔
123
        this.level = level;
23,639,151✔
124
        this.currentLevel = this.level;
23,639,151✔
125
        this.attrs = attrs;
23,639,151✔
126
        assert(this.attrs);
23,639,151✔
127
        if (this._init) {
23,639,151✔
128
            this._init(elementName, attrs, parent, engine);
8,103,875✔
129
        }
8,103,875✔
130
    }
23,639,151✔
131
    /**
149,130✔
132
     * @protected
149,130✔
133
     */
149,130✔
134
    public _on_finish(): void {
149,130✔
135
        if (this._finish) {
23,636,270✔
136
            this._finish();
19,346,744✔
137
        }
19,346,744✔
138
    }
23,636,270✔
139

149,130✔
140
    /**
149,130✔
141
     * @protected
149,130✔
142
     */
149,130✔
143
    public _on_startElement(level: number, elementName: string, attrs: XmlAttributes): void {
149,130✔
144
        this.currentLevel = level;
25,142,561✔
145

25,142,561✔
146
        this.chunks = [];
25,142,561✔
147
        this.text = "";
25,142,561✔
148

25,142,561✔
149
        if (this._startElement) {
25,142,561✔
150
            this._startElement(elementName, attrs);
523,241✔
151
        }
523,241✔
152
        if (this.engine && Object.prototype.hasOwnProperty.call(this.parser, elementName)) {
25,142,561✔
153
            this.engine._promote(this.parser[elementName], level, elementName, attrs);
23,637,196✔
154
        }
23,637,196✔
155
    }
25,142,561✔
156

149,130✔
157
    /**
149,130✔
158
     * @protected
149,130✔
159
     */
149,130✔
160
    public _on_endElement2(level: number, elementName: string): void {
149,130✔
161
        if (this._endElement) {
23,636,560✔
162
            this._endElement(elementName);
143,710✔
163
        }
143,710✔
164
    }
23,636,560✔
165

149,130✔
166
    /**
149,130✔
167

149,130✔
168
     * @protected
149,130✔
169
     */
149,130✔
170
    public _on_endElement(level: number, elementName: string): void {
149,130✔
171
        assert(this.attrs);
25,141,345✔
172
        this.chunks = this.chunks || [];
25,141,345!
173

25,141,345✔
174
        if (this.level > level) {
25,141,345!
175
            // we end a child element of this node
×
176
            this._on_endElement2(level, elementName);
×
177
        } else if (this.level === level) {
25,141,345✔
178
            // we received the end event of this node
23,636,270✔
179
            // we need to finish
23,636,270✔
180
            this.text = this.chunks.join("");
23,636,270✔
181
            this.chunks = [];
23,636,270✔
182
            // this is the end
23,636,270✔
183
            this._on_finish();
23,636,270✔
184
            if (
23,636,270✔
185
                this.parent &&
23,636,270✔
186
                (this.parent as any).parser &&
23,636,270✔
187
                Object.prototype.hasOwnProperty.call((this.parent as any).parser, elementName)
23,636,270✔
188
            ) {
23,636,270✔
189
                this.engine!._demote(this, level, elementName);
23,636,270✔
190
            }
23,636,270✔
191
        }
23,636,270✔
192
    }
25,141,345✔
193

149,130✔
194
    /**
149,130✔
195
     * @param text {String} the text found inside the element
149,130✔
196
     * @protected
149,130✔
197
     */
149,130✔
198
    public _on_text(text: string): void {
149,130✔
199
        this.chunks = this.chunks || [];
14,844,858!
200
        text = text.trim();
14,844,858✔
201
        if (text.length === 0) {
14,844,858!
202
            return;
×
203
        }
×
204
        this.chunks.push(text);
14,844,858✔
205
    }
14,844,858✔
206
}
149,130✔
207

2✔
208
const regexp = /(([^:]+):)?(.*)/;
2✔
209

2✔
210
function resolve_namespace(name: string) {
50,288,858✔
211
    const m = name.match(regexp);
50,288,858✔
212
    if (!m) {
50,288,858!
213
        throw new Error("Invalid match");
×
214
    }
×
215
    return {
50,288,858✔
216
        ns: m[2],
50,288,858✔
217
        tag: m[3]
50,288,858✔
218
    };
50,288,858✔
219
}
50,288,858✔
220

2✔
221
/**
2✔
222
 *
2✔
223
 * @example
2✔
224
 *  var parser = new Xml2Json({
2✔
225
 *       parser: {
2✔
226
 *           'person': {
2✔
227
 *               init: function(name,attrs) {
2✔
228
 *                   this.parent.root.obj = {};
2✔
229
 *                   this.obj =  this.parent.root.obj;
2✔
230
 *                   this.obj['name'] = attrs['name'];
2✔
231
 *               },
2✔
232
 *               parser: {
2✔
233
 *                   'address': {
2✔
234
 *                       finish: function(){
2✔
235
 *                           this.parent.obj['address'] = this.text;
2✔
236
 *                       }
2✔
237
 *                   }
2✔
238
 *               }
2✔
239
 *           }
2✔
240
 *       }
2✔
241
 *   });
2✔
242
 *
2✔
243
 * var xml_string =  "<employees>" +
2✔
244
 * "  <person name='John'>" +
2✔
245
 * "     <address>Paris</address>" +
2✔
246
 * "   </person>" +
2✔
247
 * "</employees>";
2✔
248
 *
2✔
249
 * parser.parseString(xml_string, function() {
2✔
250
 *       parser.obj.should.eql({name: 'John',address: 'Paris'});
2✔
251
 *       done();
2✔
252
 *   });
2✔
253
 */
2✔
254
export class Xml2Json {
2✔
255
    public currentLevel = 0;
1,957✔
256
    private state_stack: any[] = [];
1,957✔
257
    private current_state: IReaderState | null = null;
1,957✔
258

1,957✔
259
    constructor(options: ReaderStateParser) {
1,957✔
260
        const state = options instanceof ReaderStateBase ? (options as ReaderState) : new ReaderState(options);
1,957✔
261
        state.root = this;
1,957✔
262

1,957✔
263
        this.state_stack = [];
1,957✔
264
        this.current_state = null;
1,957✔
265
        this._promote(state, 0);
1,957✔
266
    }
1,957✔
267

1,957✔
268
    public parseString(xml_text: string): Record<string, unknown> {
1,957✔
269
        return this.__parseInternal(xml_text);
2,221✔
270
    }
2,221✔
271
    /**
1,957✔
272
     * @private
1,957✔
273
     * @internal
1,957✔
274
     */
1,957✔
275
    public _promote(new_state: IReaderState, level: number, name?: string, attr?: XmlAttributes): void {
1,957✔
276
        attr = attr || {};
23,639,443✔
277
        this.state_stack.push({
23,639,443✔
278
            backup: {},
23,639,443✔
279
            state: this.current_state
23,639,443✔
280
        });
23,639,443✔
281

23,639,443✔
282
        const parent = this.current_state;
23,639,443✔
283
        this.current_state = new_state;
23,639,443✔
284
        this.current_state._on_init(name || "???", attr, parent!, level, this);
23,639,443✔
285
    }
23,639,443✔
286

1,957✔
287
    /**
1,957✔
288
     * @private
1,957✔
289
     * @internal
1,957✔
290
     */
1,957✔
291
    public _demote(cur_state: IReaderState, level: number, elementName: string): void {
1,957✔
292
        ///  assert(this.current_state === cur_state);
23,636,560✔
293
        const { state, backup } = this.state_stack.pop();
23,636,560✔
294
        this.current_state = state;
23,636,560✔
295
        if (this.current_state) {
23,636,560✔
296
            this.current_state._on_endElement2(level, elementName);
23,636,560✔
297
        }
23,636,560✔
298
    }
23,636,560✔
299

1,957✔
300
    /**
1,957✔
301
     * @private
1,957✔
302
     * @internal
1,957✔
303
     */
1,957✔
304
    protected __parseInternal(data: string): Record<string, unknown> {
1,957✔
305
        const parser = new SaxLtx();
2,222✔
306
        this.currentLevel = 0;
2,222✔
307
        parser.on("startElement", (name: string, attrs: XmlAttributes) => {
2,222✔
308
            const tag_ns = resolve_namespace(name);
25,144,892✔
309
            this.currentLevel += 1;
25,144,892✔
310
            if (this.current_state) {
25,144,892✔
311
                this.current_state._on_startElement(this.currentLevel, tag_ns.tag, attrs);
25,144,892✔
312
            }
25,144,892✔
313
        });
2,222✔
314
        parser.on("endElement", (name: string) => {
2,222✔
315
            const tag_ns = resolve_namespace(name);
25,143,966✔
316
            if (this.current_state) {
25,143,966✔
317
                this.current_state._on_endElement(this.currentLevel, tag_ns.tag);
25,143,966✔
318
            }
25,143,966✔
319
            this.currentLevel -= 1;
25,143,966✔
320
            if (this.currentLevel === 0) {
25,143,966✔
321
                parser.emit("close");
1,294✔
322
            }
1,294✔
323
        });
2,222✔
324
        parser.on("text", (text: string) => {
2,222✔
325
            text = text.trim();
49,397,829✔
326
            if (text.length === 0) {
49,397,829✔
327
                return;
34,551,522✔
328
            }
34,551,522✔
329
            if (this.current_state) {
14,846,309✔
330
                this.current_state._on_text(text);
14,846,307✔
331
            }
14,846,307✔
332
        });
2,222✔
333
        parser.write(data);
2,222✔
334
        parser.end("");
2,222✔
335
        return (this.current_state! as any)._pojo;
2,222✔
336
        /*
2,222✔
337
        return await new Promise((resolve) => {
2,222✔
338
            parser.once("close", () => {
2,222✔
339
                resolve((this.current_state! as any)._pojo);
2,222✔
340
            });
2,222✔
341
            //parser.write(data);
2,222✔
342
            parser.end(data);
2,222✔
343
        })*/
2,222✔
344
    }
2,222✔
345
}
1,957✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc