@ubercode/dcmtk 0.12.0 → 0.13.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/tools.d.cts CHANGED
@@ -78,6 +78,8 @@ interface Dcm2jsonOptions extends ToolBaseOptions {
78
78
  readonly directOnly?: boolean | undefined;
79
79
  /** Assume the specified character set when SpecificCharacterSet (0008,0005) is absent. Passed to dcm2xml as `+Ca`. Only effective on the XML path (dcm2json binary does not support this flag). */
80
80
  readonly charsetAssume?: string | undefined;
81
+ /** Fallback charset to retry with when UTF-8 conversion fails. On charset error, the XML path retries with `+Ca <fallback>`. `'Latin1'` recommended — maps every byte to a valid character. */
82
+ readonly charsetFallback?: string | undefined;
81
83
  /** Verbosity level for diagnostic output. `'verbose'` maps to `-v`, `'debug'` maps to `-d`. */
82
84
  readonly verbosity?: 'verbose' | 'debug' | undefined;
83
85
  }
package/dist/tools.d.ts CHANGED
@@ -78,6 +78,8 @@ interface Dcm2jsonOptions extends ToolBaseOptions {
78
78
  readonly directOnly?: boolean | undefined;
79
79
  /** Assume the specified character set when SpecificCharacterSet (0008,0005) is absent. Passed to dcm2xml as `+Ca`. Only effective on the XML path (dcm2json binary does not support this flag). */
80
80
  readonly charsetAssume?: string | undefined;
81
+ /** Fallback charset to retry with when UTF-8 conversion fails. On charset error, the XML path retries with `+Ca <fallback>`. `'Latin1'` recommended — maps every byte to a valid character. */
82
+ readonly charsetFallback?: string | undefined;
81
83
  /** Verbosity level for diagnostic output. `'verbose'` maps to `-v`, `'debug'` maps to `-d`. */
82
84
  readonly verbosity?: 'verbose' | 'debug' | undefined;
83
85
  }
package/dist/tools.js CHANGED
@@ -470,7 +470,12 @@ var parser = new XMLParser({
470
470
  ignoreAttributes: false,
471
471
  attributeNamePrefix: "@_",
472
472
  parseTagValue: false,
473
- isArray: (name) => ARRAY_TAG_NAMES.has(name)
473
+ isArray: (name) => ARRAY_TAG_NAMES.has(name),
474
+ // DICOM XML does not use XML entities. Disable entity processing to avoid
475
+ // the default expansion limit (1000) which rejects files with >1000 tags.
476
+ // Without this, large studies fall through to dcm2json which hangs on
477
+ // compressed pixel data (DCMTK bug).
478
+ processEntities: false
474
479
  });
475
480
  function xmlToJson(xml) {
476
481
  try {
@@ -531,6 +536,7 @@ var Dcm2jsonOptionsSchema = z.object({
531
536
  signal: z.instanceof(AbortSignal).optional(),
532
537
  directOnly: z.boolean().optional(),
533
538
  charsetAssume: z.string().min(1).optional(),
539
+ charsetFallback: z.string().min(1).optional(),
534
540
  verbosity: z.enum(["verbose", "debug"]).optional()
535
541
  }).strict().optional();
536
542
  var VERBOSITY_FLAGS2 = { verbose: "-v", debug: "-d" };
@@ -544,27 +550,34 @@ function buildXmlOpts(options) {
544
550
  const result = {};
545
551
  if (options?.verbosity !== void 0) result["verbosity"] = options.verbosity;
546
552
  if (options?.charsetAssume !== void 0) result["charsetAssume"] = options.charsetAssume;
553
+ if (options?.charsetFallback !== void 0) result["charsetFallback"] = options.charsetFallback;
547
554
  return result;
548
555
  }
556
+ function isCharsetError(stderrOutput) {
557
+ return stderrOutput.includes("convert character encoding") || stderrOutput.includes("Illegal byte sequence");
558
+ }
559
+ async function runXmlAndParse(binary, args, execOpts) {
560
+ const xmlResult = await execCommand(binary, args, execOpts);
561
+ if (!xmlResult.ok) return err(xmlResult.error);
562
+ if (xmlResult.value.exitCode !== 0) {
563
+ return err(createToolError("dcm2xml", args, xmlResult.value.exitCode, xmlResult.value.stderr));
564
+ }
565
+ const jsonResult = xmlToJson(xmlResult.value.stdout);
566
+ if (!jsonResult.ok) return err(jsonResult.error);
567
+ return ok({ data: jsonResult.value, source: "xml" });
568
+ }
549
569
  async function tryXmlPath(inputPath, timeoutMs, signal, opts) {
550
570
  const xmlBinary = resolveBinary("dcm2xml");
551
- if (!xmlBinary.ok) {
552
- return err(xmlBinary.error);
553
- }
571
+ if (!xmlBinary.ok) return err(xmlBinary.error);
554
572
  const charsetArgs = opts?.charsetAssume !== void 0 ? ["+Ca", opts.charsetAssume] : [];
555
573
  const xmlArgs = [...buildVerbosityArgs(opts?.verbosity), ...charsetArgs, "-nat", inputPath];
556
- const xmlResult = await execCommand(xmlBinary.value, xmlArgs, { timeoutMs, signal });
557
- if (!xmlResult.ok) {
558
- return err(xmlResult.error);
574
+ const execOpts = signal !== void 0 ? { timeoutMs, signal } : { timeoutMs };
575
+ const result = await runXmlAndParse(xmlBinary.value, xmlArgs, execOpts);
576
+ if (!result.ok && opts?.charsetFallback !== void 0 && isCharsetError(result.error.message)) {
577
+ const fallbackArgs = [...buildVerbosityArgs(opts.verbosity), "+Ca", opts.charsetFallback, "-nat", inputPath];
578
+ return runXmlAndParse(xmlBinary.value, fallbackArgs, execOpts);
559
579
  }
560
- if (xmlResult.value.exitCode !== 0) {
561
- return err(createToolError("dcm2xml", xmlArgs, xmlResult.value.exitCode, xmlResult.value.stderr));
562
- }
563
- const jsonResult = xmlToJson(xmlResult.value.stdout);
564
- if (!jsonResult.ok) {
565
- return err(jsonResult.error);
566
- }
567
- return ok({ data: jsonResult.value, source: "xml" });
580
+ return result;
568
581
  }
569
582
  async function tryDirectPath(inputPath, timeoutMs, signal, verbosity) {
570
583
  const jsonBinary = resolveBinary("dcm2json");