@ubercode/dcmtk 0.4.0 → 0.6.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/{DicomInstance-CGBr3a-C.d.ts → DicomInstance-BjrSIEGN.d.ts} +50 -2
- package/dist/{DicomInstance-DWOjhccQ.d.cts → DicomInstance-zsmyd7fs.d.cts} +50 -2
- package/dist/{dcmodify-BvaIeyJg.d.ts → dcmodify-CHvwChFu.d.ts} +1 -1
- package/dist/{dcmodify-B9js5K1f.d.cts → dcmodify-Cf-RPHF3.d.cts} +1 -1
- package/dist/dicom.cjs +72 -7
- package/dist/dicom.cjs.map +1 -1
- package/dist/dicom.d.cts +3 -3
- package/dist/dicom.d.ts +3 -3
- package/dist/dicom.js +72 -7
- package/dist/dicom.js.map +1 -1
- package/dist/index-BuGhfFlQ.d.cts +1735 -0
- package/dist/index-DylL4aB-.d.ts +1735 -0
- package/dist/index.cjs +203 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +203 -19
- package/dist/index.js.map +1 -1
- package/dist/servers.cjs +202 -18
- package/dist/servers.cjs.map +1 -1
- package/dist/servers.d.cts +6 -1654
- package/dist/servers.d.ts +6 -1654
- package/dist/servers.js +202 -18
- package/dist/servers.js.map +1 -1
- package/dist/tools.cjs +24 -7
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +2 -2
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +24 -7
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30480,6 +30480,45 @@ function segmentsToModifyPath(segments) {
|
|
|
30480
30480
|
function segmentsToString(segments) {
|
|
30481
30481
|
return segmentsToModifyPath(segments);
|
|
30482
30482
|
}
|
|
30483
|
+
|
|
30484
|
+
// src/dicom/walkTags.ts
|
|
30485
|
+
var DEFAULT_MAX_DEPTH = 16;
|
|
30486
|
+
function walkTags(data, options) {
|
|
30487
|
+
const ctx = {
|
|
30488
|
+
maxDepth: options?.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
30489
|
+
vrSet: options?.vrFilter !== void 0 ? new Set(options.vrFilter) : void 0,
|
|
30490
|
+
results: []
|
|
30491
|
+
};
|
|
30492
|
+
walkLevel(data, 0, "", ctx);
|
|
30493
|
+
return ctx.results;
|
|
30494
|
+
}
|
|
30495
|
+
function walkLevel(data, depth, pathPrefix, ctx) {
|
|
30496
|
+
const keys = Object.keys(data);
|
|
30497
|
+
for (let i = 0; i < keys.length; i++) {
|
|
30498
|
+
const tag2 = keys[i];
|
|
30499
|
+
if (tag2 === void 0) continue;
|
|
30500
|
+
const element = data[tag2];
|
|
30501
|
+
if (element === void 0) continue;
|
|
30502
|
+
const vr = element.vr;
|
|
30503
|
+
const path2 = pathPrefix.length > 0 ? `${pathPrefix}.${tag2}` : tag2;
|
|
30504
|
+
if (ctx.vrSet === void 0 || ctx.vrSet.has(vr)) {
|
|
30505
|
+
ctx.results.push({ tag: tag2, element, vr, depth, path: path2 });
|
|
30506
|
+
}
|
|
30507
|
+
if (vr === "SQ" && element.Value !== void 0 && depth < ctx.maxDepth) {
|
|
30508
|
+
walkSequenceItems(element.Value, depth, path2, ctx);
|
|
30509
|
+
}
|
|
30510
|
+
}
|
|
30511
|
+
}
|
|
30512
|
+
function walkSequenceItems(items, depth, parentPath, ctx) {
|
|
30513
|
+
for (let idx = 0; idx < items.length; idx++) {
|
|
30514
|
+
const item = items[idx];
|
|
30515
|
+
if (typeof item !== "object" || item === null) continue;
|
|
30516
|
+
const itemPath = `${parentPath}[${idx}]`;
|
|
30517
|
+
walkLevel(item, depth + 1, itemPath, ctx);
|
|
30518
|
+
}
|
|
30519
|
+
}
|
|
30520
|
+
|
|
30521
|
+
// src/dicom/DicomDataset.ts
|
|
30483
30522
|
var HEX_TAG_KEY = /^[0-9A-Fa-f]{8}$/;
|
|
30484
30523
|
function isDicomJsonElement(value) {
|
|
30485
30524
|
return typeof value === "object" && value !== null && "vr" in value && typeof value["vr"] === "string";
|
|
@@ -30808,6 +30847,15 @@ var DicomDataset = class _DicomDataset {
|
|
|
30808
30847
|
const result = collectWildcard(this.data, segments);
|
|
30809
30848
|
return result.values;
|
|
30810
30849
|
}
|
|
30850
|
+
/**
|
|
30851
|
+
* Walks all tags in this dataset, optionally filtering by VR and recursing into sequences.
|
|
30852
|
+
*
|
|
30853
|
+
* @param options - Optional VR filter and max depth
|
|
30854
|
+
* @returns A readonly array of all matching tag entries
|
|
30855
|
+
*/
|
|
30856
|
+
walkTags(options) {
|
|
30857
|
+
return walkTags(this.data, options);
|
|
30858
|
+
}
|
|
30811
30859
|
// -----------------------------------------------------------------------
|
|
30812
30860
|
// Convenience readonly getters
|
|
30813
30861
|
// -----------------------------------------------------------------------
|
|
@@ -31286,15 +31334,32 @@ function convertSequence(attr, element) {
|
|
|
31286
31334
|
}
|
|
31287
31335
|
if (values.length > 0) element.Value = values;
|
|
31288
31336
|
}
|
|
31289
|
-
|
|
31337
|
+
var NUMERIC_JSON_VRS = /* @__PURE__ */ new Set(["DS", "FL", "FD", "IS", "SL", "SS", "SV", "UL", "US", "UV"]);
|
|
31338
|
+
function unwrapValue(v) {
|
|
31339
|
+
if (typeof v !== "object" || v === null) return v;
|
|
31340
|
+
const obj = v;
|
|
31341
|
+
if ("#text" in obj) return obj["#text"];
|
|
31342
|
+
const keys = Object.keys(obj);
|
|
31343
|
+
if (keys.length === 1 && keys[0] !== void 0 && keys[0].startsWith("@_")) {
|
|
31344
|
+
return obj[keys[0]];
|
|
31345
|
+
}
|
|
31346
|
+
return v;
|
|
31347
|
+
}
|
|
31348
|
+
function coerceNumeric(value) {
|
|
31349
|
+
if (typeof value === "number") return value;
|
|
31350
|
+
if (typeof value !== "string") return value;
|
|
31351
|
+
const trimmed = value.trim();
|
|
31352
|
+
if (trimmed.length === 0) return value;
|
|
31353
|
+
const num = Number(trimmed);
|
|
31354
|
+
return Number.isNaN(num) ? value : num;
|
|
31355
|
+
}
|
|
31356
|
+
function convertRegularValue(attr, element, vr) {
|
|
31290
31357
|
const valArray = toArray(attr.Value);
|
|
31291
31358
|
const values = [];
|
|
31359
|
+
const isNumeric = NUMERIC_JSON_VRS.has(vr);
|
|
31292
31360
|
for (const v of valArray) {
|
|
31293
|
-
|
|
31294
|
-
|
|
31295
|
-
} else {
|
|
31296
|
-
values.push(v);
|
|
31297
|
-
}
|
|
31361
|
+
const unwrapped = unwrapValue(v);
|
|
31362
|
+
values.push(isNumeric ? coerceNumeric(unwrapped) : unwrapped);
|
|
31298
31363
|
}
|
|
31299
31364
|
if (values.length > 0) element.Value = values;
|
|
31300
31365
|
}
|
|
@@ -31311,7 +31376,7 @@ function convertElement(attr) {
|
|
|
31311
31376
|
} else if (element.vr === "SQ" && attr.Item !== void 0) {
|
|
31312
31377
|
convertSequence(attr, element);
|
|
31313
31378
|
} else if (attr.Value !== void 0) {
|
|
31314
|
-
convertRegularValue(attr, element);
|
|
31379
|
+
convertRegularValue(attr, element, vr);
|
|
31315
31380
|
}
|
|
31316
31381
|
return Object.freeze(element);
|
|
31317
31382
|
}
|
|
@@ -36119,8 +36184,9 @@ var DEFAULT_MAX_POOL_SIZE = 10;
|
|
|
36119
36184
|
var DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
36120
36185
|
var CONNECTION_RETRY_INTERVAL_MS = 500;
|
|
36121
36186
|
var MAX_CONNECTION_RETRIES = 200;
|
|
36187
|
+
var MAX_OUTPUT_LINES_PER_ASSOCIATION = 500;
|
|
36122
36188
|
var DicomReceiverOptionsSchema = zod.z.object({
|
|
36123
|
-
port: zod.z.number().int().min(
|
|
36189
|
+
port: zod.z.number().int().min(0).max(65535),
|
|
36124
36190
|
storageDir: zod.z.string().min(1).refine(isSafePath, { message: "path traversal detected in storageDir" }),
|
|
36125
36191
|
aeTitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
36126
36192
|
minPoolSize: zod.z.number().int().min(1).max(100).optional(),
|
|
@@ -36128,6 +36194,9 @@ var DicomReceiverOptionsSchema = zod.z.object({
|
|
|
36128
36194
|
connectionTimeoutMs: zod.z.number().int().positive().optional(),
|
|
36129
36195
|
configFile: zod.z.string().min(1).refine(isSafePath, { message: "path traversal detected in configFile" }).optional(),
|
|
36130
36196
|
configProfile: zod.z.string().min(1).optional(),
|
|
36197
|
+
acseTimeout: zod.z.number().int().positive().optional(),
|
|
36198
|
+
dimseTimeout: zod.z.number().int().positive().optional(),
|
|
36199
|
+
maxPdu: zod.z.number().int().min(4096).max(131072).optional(),
|
|
36131
36200
|
signal: zod.z.instanceof(AbortSignal).optional()
|
|
36132
36201
|
}).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
|
|
36133
36202
|
message: "minPoolSize must be <= maxPoolSize"
|
|
@@ -36256,6 +36325,57 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36256
36325
|
onAssociationComplete(listener) {
|
|
36257
36326
|
return this.on("ASSOCIATION_COMPLETE", listener);
|
|
36258
36327
|
}
|
|
36328
|
+
/**
|
|
36329
|
+
* Registers a listener for new associations (bubbled from workers).
|
|
36330
|
+
*
|
|
36331
|
+
* @param listener - Callback receiving association received data
|
|
36332
|
+
* @returns this for chaining
|
|
36333
|
+
*/
|
|
36334
|
+
onAssociationReceived(listener) {
|
|
36335
|
+
return this.on("ASSOCIATION_RECEIVED", listener);
|
|
36336
|
+
}
|
|
36337
|
+
/**
|
|
36338
|
+
* Registers a listener for C-STORE requests (bubbled from workers).
|
|
36339
|
+
*
|
|
36340
|
+
* @param listener - Callback receiving C-STORE request data
|
|
36341
|
+
* @returns this for chaining
|
|
36342
|
+
*/
|
|
36343
|
+
onCStoreRequest(listener) {
|
|
36344
|
+
return this.on("C_STORE_REQUEST", listener);
|
|
36345
|
+
}
|
|
36346
|
+
/**
|
|
36347
|
+
* Registers a listener for C-ECHO requests (bubbled from workers).
|
|
36348
|
+
*
|
|
36349
|
+
* @param listener - Callback receiving echo request data
|
|
36350
|
+
* @returns this for chaining
|
|
36351
|
+
*/
|
|
36352
|
+
onEchoRequest(listener) {
|
|
36353
|
+
return this.on("ECHO_REQUEST", listener);
|
|
36354
|
+
}
|
|
36355
|
+
/**
|
|
36356
|
+
* Registers a listener for refused associations (bubbled from workers).
|
|
36357
|
+
*
|
|
36358
|
+
* @param listener - Callback receiving refusing association data
|
|
36359
|
+
* @returns this for chaining
|
|
36360
|
+
*/
|
|
36361
|
+
onRefusingAssociation(listener) {
|
|
36362
|
+
return this.on("REFUSING_ASSOCIATION", listener);
|
|
36363
|
+
}
|
|
36364
|
+
/**
|
|
36365
|
+
* Routes an external socket to an idle worker.
|
|
36366
|
+
*
|
|
36367
|
+
* The pool must be started via `start()` before calling this method.
|
|
36368
|
+
* Use this when managing your own TCP listener (e.g., protocol router).
|
|
36369
|
+
*
|
|
36370
|
+
* @param socket - An incoming net.Socket to route to a worker
|
|
36371
|
+
*/
|
|
36372
|
+
handleSocket(socket) {
|
|
36373
|
+
if (!this.started) {
|
|
36374
|
+
socket.destroy(new Error("DicomReceiver: not started"));
|
|
36375
|
+
return;
|
|
36376
|
+
}
|
|
36377
|
+
void this.handleConnection(socket);
|
|
36378
|
+
}
|
|
36259
36379
|
/** Current pool status. */
|
|
36260
36380
|
get poolStatus() {
|
|
36261
36381
|
let idle = 0;
|
|
@@ -36269,8 +36389,11 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36269
36389
|
// -----------------------------------------------------------------------
|
|
36270
36390
|
// TCP proxy
|
|
36271
36391
|
// -----------------------------------------------------------------------
|
|
36272
|
-
/** Starts the TCP proxy on the configured port. */
|
|
36392
|
+
/** Starts the TCP proxy on the configured port. Skips if port is 0. */
|
|
36273
36393
|
startTcpProxy() {
|
|
36394
|
+
if (this.options.port === 0) {
|
|
36395
|
+
return Promise.resolve(ok(void 0));
|
|
36396
|
+
}
|
|
36274
36397
|
return new Promise((resolve) => {
|
|
36275
36398
|
this.tcpServer = net__namespace.createServer((socket) => {
|
|
36276
36399
|
void this.handleConnection(socket);
|
|
@@ -36323,6 +36446,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36323
36446
|
worker.associationDir = associationDir;
|
|
36324
36447
|
worker.files = [];
|
|
36325
36448
|
worker.fileSizes = [];
|
|
36449
|
+
worker.outputLines = [];
|
|
36326
36450
|
worker.startAt = Date.now();
|
|
36327
36451
|
this.pipeConnection(worker, remoteSocket);
|
|
36328
36452
|
void this.replenishPool();
|
|
@@ -36381,6 +36505,19 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36381
36505
|
}
|
|
36382
36506
|
return ok(void 0);
|
|
36383
36507
|
}
|
|
36508
|
+
/** Creates a Dcmrecv instance with the pool's shared options. */
|
|
36509
|
+
createDcmrecv(port, tempDir) {
|
|
36510
|
+
return Dcmrecv.create({
|
|
36511
|
+
port,
|
|
36512
|
+
aeTitle: this.options.aeTitle ?? "DCMRECV",
|
|
36513
|
+
outputDirectory: tempDir,
|
|
36514
|
+
configFile: this.options.configFile,
|
|
36515
|
+
configProfile: this.options.configProfile,
|
|
36516
|
+
acseTimeout: this.options.acseTimeout,
|
|
36517
|
+
dimseTimeout: this.options.dimseTimeout,
|
|
36518
|
+
maxPdu: this.options.maxPdu
|
|
36519
|
+
});
|
|
36520
|
+
}
|
|
36384
36521
|
/** Spawns a single Dcmrecv worker with an ephemeral port. */
|
|
36385
36522
|
async spawnWorker() {
|
|
36386
36523
|
const portResult = await allocatePort();
|
|
@@ -36389,13 +36526,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36389
36526
|
const tempDir = path__namespace.join(os__namespace.tmpdir(), `dcmrecv-pool-${String(port)}-${String(Date.now())}`);
|
|
36390
36527
|
const mkdirResult = await ensureDirectory(tempDir);
|
|
36391
36528
|
if (!mkdirResult.ok) return mkdirResult;
|
|
36392
|
-
const createResult =
|
|
36393
|
-
port,
|
|
36394
|
-
aeTitle: this.options.aeTitle ?? "DCMRECV",
|
|
36395
|
-
outputDirectory: tempDir,
|
|
36396
|
-
configFile: this.options.configFile,
|
|
36397
|
-
configProfile: this.options.configProfile
|
|
36398
|
-
});
|
|
36529
|
+
const createResult = this.createDcmrecv(port, tempDir);
|
|
36399
36530
|
if (!createResult.ok) return createResult;
|
|
36400
36531
|
const dcmrecv = createResult.value;
|
|
36401
36532
|
const worker = {
|
|
@@ -36407,6 +36538,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36407
36538
|
associationDir: void 0,
|
|
36408
36539
|
files: [],
|
|
36409
36540
|
fileSizes: [],
|
|
36541
|
+
outputLines: [],
|
|
36410
36542
|
startAt: void 0,
|
|
36411
36543
|
remoteSocket: void 0,
|
|
36412
36544
|
workerSocket: void 0
|
|
@@ -36468,10 +36600,15 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36468
36600
|
// -----------------------------------------------------------------------
|
|
36469
36601
|
// Worker event wiring
|
|
36470
36602
|
// -----------------------------------------------------------------------
|
|
36471
|
-
/** Wires
|
|
36603
|
+
/** Wires all events on a worker: file handling, association lifecycle, output capture. */
|
|
36472
36604
|
wireWorkerEvents(worker) {
|
|
36473
36605
|
this.wireFileReceived(worker);
|
|
36474
36606
|
this.wireAssociationComplete(worker);
|
|
36607
|
+
this.wireAssociationReceived(worker);
|
|
36608
|
+
this.wireCStoreRequest(worker);
|
|
36609
|
+
this.wireEchoRequest(worker);
|
|
36610
|
+
this.wireRefusingAssociation(worker);
|
|
36611
|
+
this.wireOutputCapture(worker);
|
|
36475
36612
|
}
|
|
36476
36613
|
/** Wires FILE_RECEIVED from dcmrecv worker to handleFileReceived. */
|
|
36477
36614
|
wireFileReceived(worker) {
|
|
@@ -36519,6 +36656,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36519
36656
|
const assocId = worker.associationId ?? data.associationId;
|
|
36520
36657
|
const assocDir = worker.associationDir ?? "";
|
|
36521
36658
|
const files = [...worker.files];
|
|
36659
|
+
const output = [...worker.outputLines];
|
|
36522
36660
|
const endAt = Date.now();
|
|
36523
36661
|
const startAt = worker.startAt ?? endAt;
|
|
36524
36662
|
const totalBytes = sumArray(worker.fileSizes);
|
|
@@ -36536,19 +36674,65 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
36536
36674
|
totalBytes,
|
|
36537
36675
|
bytesPerSecond,
|
|
36538
36676
|
startAt,
|
|
36539
|
-
endAt
|
|
36677
|
+
endAt,
|
|
36678
|
+
output
|
|
36540
36679
|
});
|
|
36541
36680
|
worker.state = "idle";
|
|
36542
36681
|
worker.associationId = void 0;
|
|
36543
36682
|
worker.associationDir = void 0;
|
|
36544
36683
|
worker.files = [];
|
|
36545
36684
|
worker.fileSizes = [];
|
|
36685
|
+
worker.outputLines = [];
|
|
36546
36686
|
worker.startAt = void 0;
|
|
36547
36687
|
worker.remoteSocket = void 0;
|
|
36548
36688
|
worker.workerSocket = void 0;
|
|
36549
36689
|
void this.scaleDown();
|
|
36550
36690
|
});
|
|
36551
36691
|
}
|
|
36692
|
+
/** Bubbles ASSOCIATION_RECEIVED from dcmrecv worker. */
|
|
36693
|
+
wireAssociationReceived(worker) {
|
|
36694
|
+
worker.dcmrecv.onEvent("ASSOCIATION_RECEIVED", (data) => {
|
|
36695
|
+
this.emit("ASSOCIATION_RECEIVED", {
|
|
36696
|
+
associationId: worker.associationId ?? "",
|
|
36697
|
+
callingAE: data.callingAE,
|
|
36698
|
+
calledAE: data.calledAE,
|
|
36699
|
+
source: data.source
|
|
36700
|
+
});
|
|
36701
|
+
});
|
|
36702
|
+
}
|
|
36703
|
+
/** Bubbles C_STORE_REQUEST from dcmrecv worker. */
|
|
36704
|
+
wireCStoreRequest(worker) {
|
|
36705
|
+
worker.dcmrecv.onEvent("C_STORE_REQUEST", (data) => {
|
|
36706
|
+
this.emit("C_STORE_REQUEST", {
|
|
36707
|
+
associationId: worker.associationId ?? "",
|
|
36708
|
+
raw: data.raw
|
|
36709
|
+
});
|
|
36710
|
+
});
|
|
36711
|
+
}
|
|
36712
|
+
/** Bubbles ECHO_REQUEST from dcmrecv worker. */
|
|
36713
|
+
wireEchoRequest(worker) {
|
|
36714
|
+
worker.dcmrecv.onEvent("ECHO_REQUEST", () => {
|
|
36715
|
+
this.emit("ECHO_REQUEST", {
|
|
36716
|
+
associationId: worker.associationId ?? ""
|
|
36717
|
+
});
|
|
36718
|
+
});
|
|
36719
|
+
}
|
|
36720
|
+
/** Bubbles REFUSING_ASSOCIATION from dcmrecv worker. */
|
|
36721
|
+
wireRefusingAssociation(worker) {
|
|
36722
|
+
worker.dcmrecv.onEvent("REFUSING_ASSOCIATION", (data) => {
|
|
36723
|
+
this.emit("REFUSING_ASSOCIATION", {
|
|
36724
|
+
reason: data.reason
|
|
36725
|
+
});
|
|
36726
|
+
});
|
|
36727
|
+
}
|
|
36728
|
+
/** Captures worker output lines during busy associations. */
|
|
36729
|
+
wireOutputCapture(worker) {
|
|
36730
|
+
worker.dcmrecv.on("line", ({ text }) => {
|
|
36731
|
+
if (worker.state === "busy" && worker.outputLines.length < MAX_OUTPUT_LINES_PER_ASSOCIATION) {
|
|
36732
|
+
worker.outputLines.push(text);
|
|
36733
|
+
}
|
|
36734
|
+
});
|
|
36735
|
+
}
|
|
36552
36736
|
// -----------------------------------------------------------------------
|
|
36553
36737
|
// Abort signal
|
|
36554
36738
|
// -----------------------------------------------------------------------
|
|
@@ -37913,6 +38097,7 @@ exports.storescu = storescu;
|
|
|
37913
38097
|
exports.tag = tag;
|
|
37914
38098
|
exports.tagPathToSegments = tagPathToSegments;
|
|
37915
38099
|
exports.termscu = termscu;
|
|
38100
|
+
exports.walkTags = walkTags;
|
|
37916
38101
|
exports.xml2dcm = xml2dcm;
|
|
37917
38102
|
exports.xml2dsr = xml2dsr;
|
|
37918
38103
|
exports.xmlToJson = xmlToJson;
|