fhirsmith 0.4.2 → 0.5.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/library/cron-utilities.js +136 -0
  4. package/library/html-server.js +13 -29
  5. package/library/html.js +3 -8
  6. package/library/languages.js +160 -37
  7. package/library/package-manager.js +48 -1
  8. package/library/utilities.js +100 -19
  9. package/package.json +2 -2
  10. package/packages/package-crawler.js +6 -1
  11. package/packages/packages.js +38 -54
  12. package/publisher/publisher.js +19 -27
  13. package/registry/api.js +11 -10
  14. package/registry/crawler.js +31 -29
  15. package/registry/model.js +5 -26
  16. package/registry/registry.js +32 -41
  17. package/server.js +53 -5
  18. package/shl/shl.js +0 -18
  19. package/static/assets/js/statuspage.js +1 -9
  20. package/stats.js +39 -1
  21. package/token/token.js +14 -9
  22. package/translations/Messages.properties +2 -1
  23. package/tx/README.md +17 -6
  24. package/tx/cs/cs-api.js +19 -1
  25. package/tx/cs/cs-base.js +77 -0
  26. package/tx/cs/cs-country.js +46 -0
  27. package/tx/cs/cs-cpt.js +9 -5
  28. package/tx/cs/cs-cs.js +27 -13
  29. package/tx/cs/cs-lang.js +60 -22
  30. package/tx/cs/cs-loinc.js +69 -98
  31. package/tx/cs/cs-mimetypes.js +4 -0
  32. package/tx/cs/cs-ndc.js +6 -0
  33. package/tx/cs/cs-omop.js +16 -15
  34. package/tx/cs/cs-rxnorm.js +23 -1
  35. package/tx/cs/cs-snomed.js +283 -40
  36. package/tx/cs/cs-ucum.js +90 -70
  37. package/tx/importers/import-sct.module.js +371 -35
  38. package/tx/importers/readme.md +117 -7
  39. package/tx/library/bundle.js +5 -0
  40. package/tx/library/capabilitystatement.js +3 -142
  41. package/tx/library/codesystem.js +19 -173
  42. package/tx/library/conceptmap.js +4 -218
  43. package/tx/library/designations.js +14 -1
  44. package/tx/library/extensions.js +7 -0
  45. package/tx/library/namingsystem.js +3 -89
  46. package/tx/library/operation-outcome.js +8 -3
  47. package/tx/library/parameters.js +3 -2
  48. package/tx/library/renderer.js +10 -6
  49. package/tx/library/terminologycapabilities.js +3 -243
  50. package/tx/library/valueset.js +3 -235
  51. package/tx/library.js +100 -13
  52. package/tx/operation-context.js +23 -4
  53. package/tx/params.js +35 -38
  54. package/tx/provider.js +6 -5
  55. package/tx/sct/expressions.js +12 -3
  56. package/tx/tx-html.js +80 -89
  57. package/tx/tx.fhir.org.yml +6 -5
  58. package/tx/tx.js +163 -13
  59. package/tx/vs/vs-database.js +56 -39
  60. package/tx/vs/vs-package.js +21 -2
  61. package/tx/vs/vs-vsac.js +175 -39
  62. package/tx/workers/batch-validate.js +2 -0
  63. package/tx/workers/batch.js +2 -0
  64. package/tx/workers/expand.js +132 -112
  65. package/tx/workers/lookup.js +33 -14
  66. package/tx/workers/metadata.js +2 -2
  67. package/tx/workers/read.js +3 -2
  68. package/tx/workers/related.js +574 -0
  69. package/tx/workers/search.js +46 -9
  70. package/tx/workers/subsumes.js +13 -3
  71. package/tx/workers/translate.js +7 -3
  72. package/tx/workers/validate.js +258 -285
  73. package/tx/workers/worker.js +43 -39
  74. package/tx/xml/bundle-xml.js +237 -0
  75. package/tx/xml/xml-base.js +215 -64
  76. package/tx/xversion/xv-bundle.js +71 -0
  77. package/tx/xversion/xv-capabiliityStatement.js +137 -0
  78. package/tx/xversion/xv-codesystem.js +169 -0
  79. package/tx/xversion/xv-conceptmap.js +224 -0
  80. package/tx/xversion/xv-namingsystem.js +88 -0
  81. package/tx/xversion/xv-operationoutcome.js +27 -0
  82. package/tx/xversion/xv-parameters.js +87 -0
  83. package/tx/xversion/xv-resource.js +45 -0
  84. package/tx/xversion/xv-terminologyCapabilities.js +214 -0
  85. package/tx/xversion/xv-valueset.js +234 -0
  86. package/utilities/dev-proxy-server.js +126 -0
  87. package/utilities/explode-results.js +58 -0
  88. package/utilities/split-by-system.js +198 -0
  89. package/utilities/vsac-cs-fetcher.js +0 -0
  90. package/{windows-install.js → utilities/windows-install.js} +2 -0
  91. package/vcl/vcl.js +0 -18
  92. package/xig/xig.js +108 -99
package/tx/tx-html.js CHANGED
@@ -8,6 +8,7 @@ const path = require('path');
8
8
  const htmlServer = require('../library/html-server');
9
9
  const Logger = require('../library/logger');
10
10
  const packageJson = require("../package.json");
11
+ const escape = require('escape-html');
11
12
 
12
13
  const txHtmlLog = Logger.getInstance().child({ module: 'tx-html' });
13
14
 
@@ -62,28 +63,6 @@ class TxHtmlRenderer {
62
63
  this.liquid = liquid;
63
64
  }
64
65
 
65
- /**
66
- * Escape HTML special characters
67
- */
68
- escapeHtml(text) {
69
- if (text === null || text === undefined) {
70
- return '';
71
- }
72
- if (typeof text !== 'string') {
73
- return String(text);
74
- }
75
-
76
- const map = {
77
- '&': '&',
78
- '<': '&lt;',
79
- '>': '&gt;',
80
- '"': '&quot;',
81
- "'": '&#x27;'
82
- };
83
-
84
- return text.replace(/[&<>"']/g, m => map[m]);
85
- }
86
-
87
66
  /**
88
67
  * Render a page with the TX template
89
68
  */
@@ -102,8 +81,20 @@ class TxHtmlRenderer {
102
81
  * Check if request accepts HTML
103
82
  */
104
83
  acceptsHtml(req) {
105
- const accept = req.headers.accept || '';
106
- return accept.includes('text/html');
84
+ let _fmt = req.query._format || req.query.format || req.body?._format;
85
+ if (_fmt && typeof _fmt !== 'string') {
86
+ _fmt = null;
87
+ }
88
+ if (_fmt && _fmt == 'html') {
89
+ return true;
90
+ }
91
+ if (!_fmt) {
92
+ _fmt = req.headers.accept || '';
93
+ }
94
+ if (typeof _fmt !== 'string') {
95
+ return false;
96
+ }
97
+ return _fmt.includes('text/html');
107
98
  }
108
99
 
109
100
  /**
@@ -146,7 +137,7 @@ class TxHtmlRenderer {
146
137
 
147
138
  // eslint-disable-next-line no-unused-vars
148
139
  async buildSearchForm(req, mode, params) {
149
- const html = await this.liquid.renderFile('search-form', { baseUrl: this.escapeHtml(req.baseUrl) });
140
+ const html = await this.liquid.renderFile('search-form', { baseUrl: escape(req.baseUrl) });
150
141
  return html;
151
142
  }
152
143
 
@@ -178,8 +169,8 @@ class TxHtmlRenderer {
178
169
 
179
170
  html += '<table class="grid">';
180
171
  html += '<tr>';
181
- html += `<td><strong>FHIR Version:</strong> ${this.escapeHtml(provider.getFhirVersion())}</td>`;
182
- html += `<td><strong>Uptime:</strong> ${this.escapeHtml(uptimeStr)}</td>`;
172
+ html += `<td><strong>FHIR Version:</strong> ${escape(provider.getFhirVersion())}</td>`;
173
+ html += `<td><strong>Uptime:</strong> ${escape(uptimeStr)}</td>`;
183
174
  html += `<td><strong>Request Count:</strong> ${provider.requestCount}</td>`;
184
175
  html += '</tr>';
185
176
  html += '<tr>';
@@ -227,7 +218,7 @@ class TxHtmlRenderer {
227
218
  const sorted = [...provider.contentSources].sort();
228
219
  html += '<ul>';
229
220
  for (const source of sorted) {
230
- html += `<li>${this.escapeHtml(source)}</li>`;
221
+ html += `<li>${escape(source)}</li>`;
231
222
  }
232
223
  html += '</ul>';
233
224
  } else {
@@ -255,9 +246,9 @@ class TxHtmlRenderer {
255
246
 
256
247
  for (const factory of uniqueFactories) {
257
248
  html += '<tr>';
258
- html += `<td>${this.escapeHtml(factory.name())}</td>`;
259
- html += `<td>${this.escapeHtml(factory.system())}</td>`;
260
- html += `<td>${this.escapeHtml(factory.version() || '-')}</td>`;
249
+ html += `<td>${escape(factory.name())}</td>`;
250
+ html += `<td>${escape(factory.system())}</td>`;
251
+ html += `<td>${escape(factory.version() || '-')}</td>`;
261
252
  html += `<td>${factory.useCount ? factory.useCount() : '-'}</td>`;
262
253
  html += '</tr>';
263
254
  }
@@ -329,7 +320,7 @@ class TxHtmlRenderer {
329
320
  html += `<button type="button" class="btn btn-sm btn-outline-secondary" onclick="toggleJsonSource('${resourceId}')">`;
330
321
  html += 'Show JSON Source</button>';
331
322
  html += `<div id="${resourceId}" class="json-content" style="display: none; margin-top: 10px;">`;
332
- html += `<pre>${this.escapeHtml(JSON.stringify(json, null, 2))}</pre>`;
323
+ html += `<pre>${escape(JSON.stringify(json, null, 2))}</pre>`;
333
324
  html += '</div>';
334
325
  html += '</div>';
335
326
 
@@ -341,7 +332,7 @@ class TxHtmlRenderer {
341
332
  */
342
333
  async renderParameter(param) {
343
334
  let html = '<tr>';
344
- html += `<td>${this.escapeHtml(param.name || '')}</td>`;
335
+ html += `<td>${escape(param.name || '')}</td>`;
345
336
  html += '<td>';
346
337
  html += await this.renderParameterValue(param);
347
338
  html += '</td>';
@@ -358,7 +349,7 @@ class TxHtmlRenderer {
358
349
  let html = '<ul>';
359
350
  for (const part of param.part) {
360
351
  html += '<li>';
361
- html += `<strong>${this.escapeHtml(part.name || '')}:</strong> `;
352
+ html += `<strong>${escape(part.name || '')}:</strong> `;
362
353
  html += await this.renderParameterValue(part);
363
354
  html += '</li>';
364
355
  }
@@ -393,40 +384,40 @@ class TxHtmlRenderer {
393
384
 
394
385
  // Primitive types
395
386
  if (param.valueString !== undefined) {
396
- return this.escapeHtml(param.valueString);
387
+ return escape(param.valueString);
397
388
  }
398
389
  if (param.valueBoolean !== undefined) {
399
390
  return param.valueBoolean ? 'true' : 'false';
400
391
  }
401
392
  if (param.valueInteger !== undefined) {
402
- return this.escapeHtml(String(param.valueInteger));
393
+ return escape(String(param.valueInteger));
403
394
  }
404
395
  if (param.valueDecimal !== undefined) {
405
- return this.escapeHtml(String(param.valueDecimal));
396
+ return escape(String(param.valueDecimal));
406
397
  }
407
398
  if (param.valueUri !== undefined) {
408
- return this.escapeHtml(param.valueUri);
399
+ return escape(param.valueUri);
409
400
  }
410
401
  if (param.valueUrl !== undefined) {
411
- return this.escapeHtml(param.valueUrl);
402
+ return escape(param.valueUrl);
412
403
  }
413
404
  if (param.valueCanonical !== undefined) {
414
- return this.escapeHtml(param.valueCanonical);
405
+ return escape(param.valueCanonical);
415
406
  }
416
407
  if (param.valueCode !== undefined) {
417
- return `<code>${this.escapeHtml(param.valueCode)}</code>`;
408
+ return `<code>${escape(param.valueCode)}</code>`;
418
409
  }
419
410
  if (param.valueDate !== undefined) {
420
- return this.escapeHtml(param.valueDate);
411
+ return escape(param.valueDate);
421
412
  }
422
413
  if (param.valueDateTime !== undefined) {
423
- return this.escapeHtml(param.valueDateTime);
414
+ return escape(param.valueDateTime);
424
415
  }
425
416
  if (param.valueTime !== undefined) {
426
- return this.escapeHtml(param.valueTime);
417
+ return escape(param.valueTime);
427
418
  }
428
419
  if (param.valueInstant !== undefined) {
429
- return this.escapeHtml(param.valueInstant);
420
+ return escape(param.valueInstant);
430
421
  }
431
422
 
432
423
  return '<em>(empty)</em>';
@@ -440,16 +431,16 @@ class TxHtmlRenderer {
440
431
 
441
432
  let parts = [];
442
433
  if (coding.system) {
443
- parts.push(this.escapeHtml(coding.system));
434
+ parts.push(escape(coding.system));
444
435
  }
445
436
  if (coding.code) {
446
- parts.push(`<code>${this.escapeHtml(coding.code)}</code>`);
437
+ parts.push(`<code>${escape(coding.code)}</code>`);
447
438
  }
448
439
  if (coding.display) {
449
- parts.push(`"${this.escapeHtml(coding.display)}"`);
440
+ parts.push(`"${escape(coding.display)}"`);
450
441
  }
451
442
  if (coding.version) {
452
- parts.push(`(version: ${this.escapeHtml(coding.version)})`);
443
+ parts.push(`(version: ${escape(coding.version)})`);
453
444
  }
454
445
 
455
446
  return parts.join(' | ') || '<em>(empty coding)</em>';
@@ -464,7 +455,7 @@ class TxHtmlRenderer {
464
455
  let html = '';
465
456
 
466
457
  if (cc.text) {
467
- html += `<strong>${this.escapeHtml(cc.text)}</strong>`;
458
+ html += `<strong>${escape(cc.text)}</strong>`;
468
459
  }
469
460
 
470
461
  if (cc.coding && Array.isArray(cc.coding) && cc.coding.length > 0) {
@@ -488,18 +479,18 @@ class TxHtmlRenderer {
488
479
  let html = '';
489
480
 
490
481
  if (qty.comparator) {
491
- html += this.escapeHtml(qty.comparator) + ' ';
482
+ html += escape(qty.comparator) + ' ';
492
483
  }
493
484
  if (qty.value !== undefined) {
494
- html += this.escapeHtml(String(qty.value));
485
+ html += escape(String(qty.value));
495
486
  }
496
487
  if (qty.unit) {
497
- html += ' ' + this.escapeHtml(qty.unit);
488
+ html += ' ' + escape(qty.unit);
498
489
  } else if (qty.code) {
499
- html += ' ' + this.escapeHtml(qty.code);
490
+ html += ' ' + escape(qty.code);
500
491
  }
501
492
  if (qty.system) {
502
- html += ` <small>(${this.escapeHtml(qty.system)})</small>`;
493
+ html += ` <small>(${escape(qty.system)})</small>`;
503
494
  }
504
495
 
505
496
  return html || '<em>(empty Quantity)</em>';
@@ -514,19 +505,19 @@ class TxHtmlRenderer {
514
505
  let html = '';
515
506
 
516
507
  if (att.title) {
517
- html += `<strong>${this.escapeHtml(att.title)}</strong><br/>`;
508
+ html += `<strong>${escape(att.title)}</strong><br/>`;
518
509
  }
519
510
  if (att.contentType) {
520
- html += `Content-Type: ${this.escapeHtml(att.contentType)}<br/>`;
511
+ html += `Content-Type: ${escape(att.contentType)}<br/>`;
521
512
  }
522
513
  if (att.url) {
523
- html += `URL: <a href="${this.escapeHtml(att.url)}">${this.escapeHtml(att.url)}</a><br/>`;
514
+ html += `URL: <a href="${escape(att.url)}">${escape(att.url)}</a><br/>`;
524
515
  }
525
516
  if (att.size !== undefined) {
526
- html += `Size: ${this.escapeHtml(String(att.size))} bytes<br/>`;
517
+ html += `Size: ${escape(String(att.size))} bytes<br/>`;
527
518
  }
528
519
  if (att.language) {
529
- html += `Language: ${this.escapeHtml(att.language)}<br/>`;
520
+ html += `Language: ${escape(att.language)}<br/>`;
530
521
  }
531
522
  if (att.data) {
532
523
  html += `<small>(base64 data present, ${att.data.length} chars)</small>`;
@@ -544,16 +535,16 @@ class TxHtmlRenderer {
544
535
  let parts = [];
545
536
 
546
537
  if (id.use) {
547
- parts.push(`[${this.escapeHtml(id.use)}]`);
538
+ parts.push(`[${escape(id.use)}]`);
548
539
  }
549
540
  if (id.type && id.type.text) {
550
- parts.push(this.escapeHtml(id.type.text));
541
+ parts.push(escape(id.type.text));
551
542
  }
552
543
  if (id.system) {
553
- parts.push(this.escapeHtml(id.system));
544
+ parts.push(escape(id.system));
554
545
  }
555
546
  if (id.value) {
556
- parts.push(`<strong>${this.escapeHtml(id.value)}</strong>`);
547
+ parts.push(`<strong>${escape(id.value)}</strong>`);
557
548
  }
558
549
  if (id.period) {
559
550
  parts.push(this.renderPeriod(id.period));
@@ -571,11 +562,11 @@ class TxHtmlRenderer {
571
562
  let html = '';
572
563
 
573
564
  if (period.start && period.end) {
574
- html = `${this.escapeHtml(period.start)} to ${this.escapeHtml(period.end)}`;
565
+ html = `${escape(period.start)} to ${escape(period.end)}`;
575
566
  } else if (period.start) {
576
- html = `from ${this.escapeHtml(period.start)}`;
567
+ html = `from ${escape(period.start)}`;
577
568
  } else if (period.end) {
578
- html = `until ${this.escapeHtml(period.end)}`;
569
+ html = `until ${escape(period.end)}`;
579
570
  }
580
571
 
581
572
  return html || '<em>(empty Period)</em>';
@@ -590,7 +581,7 @@ class TxHtmlRenderer {
590
581
  if (!inBundle) {
591
582
  html += await this.liquid.renderFile('codesystem-operations', {
592
583
  opsId: this.generateResourceId(),
593
- url: this.escapeHtml(json.url || '')
584
+ url: escape(json.url || '')
594
585
  });
595
586
  }
596
587
 
@@ -608,7 +599,7 @@ class TxHtmlRenderer {
608
599
  opsId: this.generateResourceId(),
609
600
  vcSystemId: this.generateResourceId(),
610
601
  inferSystemId: this.generateResourceId(),
611
- url: this.escapeHtml(json.url || '')
602
+ url: escape(json.url || '')
612
603
  });
613
604
  }
614
605
 
@@ -665,9 +656,9 @@ class TxHtmlRenderer {
665
656
  }
666
657
 
667
658
  html += '">';
668
- html += `<strong>${this.escapeHtml(issue.severity || 'unknown')}:</strong> `;
669
- html += `[${this.escapeHtml(issue.code || 'unknown')}] `;
670
- html += this.escapeHtml(issue.diagnostics || issue.details?.text || 'No details');
659
+ html += `<strong>${escape(issue.severity || 'unknown')}:</strong> `;
660
+ html += `[${escape(issue.code || 'unknown')}] `;
661
+ html += escape(issue.diagnostics || issue.details?.text || 'No details');
671
662
  html += '</div>';
672
663
  }
673
664
  }
@@ -741,18 +732,18 @@ class TxHtmlRenderer {
741
732
  const params = resourceType === 'CodeSystem' ? CODESYSTEM_PARAMS : SEARCH_PARAMS;
742
733
 
743
734
  let html = '<div class="alert alert-info">Enter search criteria:</div>';
744
- html += `<form method="get" action="${this.escapeHtml(req.baseUrl)}/${this.escapeHtml(resourceType)}">`;
735
+ html += `<form method="get" action="${escape(req.baseUrl)}/${escape(resourceType)}">`;
745
736
  html += '<div class="row">';
746
737
 
747
738
  // Build form fields
748
739
  for (const param of params) {
749
740
  html += '<div class="col-md-4 mb-3">';
750
- html += `<label for="${param.name}" class="form-label">${this.escapeHtml(param.label)}</label>`;
741
+ html += `<label for="${param.name}" class="form-label">${escape(param.label)}</label>`;
751
742
 
752
743
  if (param.type === 'select') {
753
744
  html += `<select name="${param.name}" id="${param.name}" class="form-select">`;
754
745
  for (const opt of param.options) {
755
- html += `<option value="${this.escapeHtml(opt)}">${this.escapeHtml(opt || '(any)')}</option>`;
746
+ html += `<option value="${escape(opt)}">${escape(opt || '(any)')}</option>`;
756
747
  }
757
748
  html += '</select>';
758
749
  } else {
@@ -770,7 +761,7 @@ class TxHtmlRenderer {
770
761
  html += '<label for="_sort" class="form-label">Sort By</label>';
771
762
  html += '<select name="_sort" id="_sort" class="form-select">';
772
763
  for (const opt of SORT_OPTIONS) {
773
- html += `<option value="${this.escapeHtml(opt)}">${this.escapeHtml(opt || '(default)')}</option>`;
764
+ html += `<option value="${escape(opt)}">${escape(opt || '(default)')}</option>`;
774
765
  }
775
766
  html += '</select>';
776
767
  html += '</div>';
@@ -781,8 +772,8 @@ class TxHtmlRenderer {
781
772
  html += '<label class="form-label">Elements to include:</label><br/>';
782
773
  for (const elem of ELEMENT_OPTIONS) {
783
774
  html += `<div class="form-check form-check-inline">`;
784
- html += `<input type="checkbox" name="_elements" value="${this.escapeHtml(elem)}" id="elem_${elem}" class="form-check-input"/>`;
785
- html += `<label for="elem_${elem}" class="form-check-label">${this.escapeHtml(elem)}</label>`;
775
+ html += `<input type="checkbox" name="_elements" value="${escape(elem)}" id="elem_${elem}" class="form-check-input"/>`;
776
+ html += `<label for="elem_${elem}" class="form-check-label">${escape(elem)}</label>`;
786
777
  html += '</div>';
787
778
  }
788
779
  html += '</div>';
@@ -827,7 +818,7 @@ class TxHtmlRenderer {
827
818
  html += '<th>ID</th>';
828
819
  for (const elem of elements) {
829
820
  if (elem !== 'id') {
830
- html += `<th>${this.escapeHtml(elem)}</th>`;
821
+ html += `<th>${escape(elem)}</th>`;
831
822
  }
832
823
  }
833
824
  html += '</tr></thead>';
@@ -842,13 +833,13 @@ class TxHtmlRenderer {
842
833
  // ID column with link
843
834
  const id = resource.id || '';
844
835
  const resourceType = resource.resourceType || '';
845
- html += `<td><a href="${this.escapeHtml(req.baseUrl)}/${this.escapeHtml(resourceType)}/${this.escapeHtml(id)}">${this.escapeHtml(id)}</a></td>`;
836
+ html += `<td><a href="${escape(req.baseUrl)}/${escape(resourceType)}/${escape(id)}">${escape(id)}</a></td>`;
846
837
 
847
838
  // Other element columns
848
839
  for (const elem of elements) {
849
840
  if (elem !== 'id') {
850
841
  const value = resource[elem];
851
- html += `<td>${this.escapeHtml(this.formatValue(value))}</td>`;
842
+ html += `<td>${escape(this.formatValue(value))}</td>`;
852
843
  }
853
844
  }
854
845
 
@@ -879,7 +870,7 @@ class TxHtmlRenderer {
879
870
  html += '<div class="card mb-3">';
880
871
  html += '<div class="card-header">Bundle Summary</div>';
881
872
  html += '<div class="card-body">';
882
- html += `<p><strong>Type:</strong> ${this.escapeHtml(json.type)}</p>`;
873
+ html += `<p><strong>Type:</strong> ${escape(json.type)}</p>`;
883
874
  html += `<p><strong>Total:</strong> ${total}</p>`;
884
875
  html += '</div>';
885
876
  html += '</div>';
@@ -890,10 +881,10 @@ class TxHtmlRenderer {
890
881
 
891
882
  if (entry.resource) {
892
883
  const resource = entry.resource;
893
- html += `<h4>${this.escapeHtml(resource.resourceType)}/${this.escapeHtml(resource.id || 'unknown')}</h4>`;
884
+ html += `<h4>${escape(resource.resourceType)}/${escape(resource.id || 'unknown')}</h4>`;
894
885
 
895
886
  if (entry.fullUrl) {
896
- html += `<p><small><a href="${this.escapeHtml(entry.fullUrl)}">${this.escapeHtml(entry.fullUrl)}</a></small></p>`;
887
+ html += `<p><small><a href="${escape(entry.fullUrl)}">${escape(entry.fullUrl)}</a></small></p>`;
897
888
  }
898
889
 
899
890
  // Render the resource
@@ -925,9 +916,9 @@ class TxHtmlRenderer {
925
916
  const label = rel.charAt(0).toUpperCase() + rel.slice(1);
926
917
 
927
918
  if (isDisabled) {
928
- html += `<li class="page-item active"><span class="page-link">${this.escapeHtml(label)}</span></li>`;
919
+ html += `<li class="page-item active"><span class="page-link">${escape(label)}</span></li>`;
929
920
  } else {
930
- html += `<li class="page-item"><a class="page-link" href="${this.escapeHtml(link.url)}">${this.escapeHtml(label)}</a></li>`;
921
+ html += `<li class="page-item"><a class="page-link" href="${escape(link.url)}">${escape(label)}</a></li>`;
931
922
  }
932
923
  }
933
924
  }
@@ -943,7 +934,7 @@ class TxHtmlRenderer {
943
934
  let html = '<div class="card mb-3">';
944
935
  html += '<div class="card-header">Bundle</div>';
945
936
  html += '<div class="card-body">';
946
- html += `<p><strong>Type:</strong> ${this.escapeHtml(json.type)}</p>`;
937
+ html += `<p><strong>Type:</strong> ${escape(json.type)}</p>`;
947
938
  html += `<p><strong>Total:</strong> ${json.total || 'N/A'}</p>`;
948
939
  html += '</div>';
949
940
  html += '</div>';
@@ -953,7 +944,7 @@ class TxHtmlRenderer {
953
944
  html += '<h4>Links</h4>';
954
945
  html += '<ul>';
955
946
  for (const link of json.link) {
956
- html += `<li><strong>${this.escapeHtml(link.relation)}:</strong> <a href="${this.escapeHtml(link.url)}">${this.escapeHtml(link.url)}</a></li>`;
947
+ html += `<li><strong>${escape(link.relation)}:</strong> <a href="${escape(link.url)}">${escape(link.url)}</a></li>`;
957
948
  }
958
949
  html += '</ul>';
959
950
  }
@@ -1038,7 +1029,7 @@ class TxHtmlRenderer {
1038
1029
  html += `<button type="button" class="btn btn-sm btn-outline-secondary" onclick="toggleJsonSource('${resourceId}')">`;
1039
1030
  html += 'Show JSON Source</button>';
1040
1031
  html += `<div id="${resourceId}" class="json-content" style="display: none; margin-top: 10px;">`;
1041
- html += `<pre>${this.escapeHtml(JSON.stringify(json, null, 2))}</pre>`;
1032
+ html += `<pre>${escape(JSON.stringify(json, null, 2))}</pre>`;
1042
1033
  html += '</div>';
1043
1034
  html += '</div>';
1044
1035
 
@@ -10,12 +10,13 @@ sources:
10
10
  - internal:usstates
11
11
  - internal:hgvs
12
12
  - ucum:tx/data/ucum-essence.xml
13
- - loinc:loinc-2.81-b.db
13
+ - loinc:loinc-2.77-a.db
14
+ - loinc!:loinc-2.81-b.db
14
15
  - rxnorm:rxnorm_02032025-a.db
15
16
  - ndc:ndc-20211101.db
16
17
  - unii:unii_20240622.db
17
- - snomed!:sct_intl_20250201.cache
18
18
  - snomed:sct_intl_20240201.cache
19
+ - snomed!:sct_intl_20250201.cache
19
20
  - snomed:sct_se_20231130.cache
20
21
  - snomed:sct_au_20230731.cache
21
22
  - snomed:sct_be_20231115.cache
@@ -24,16 +25,16 @@ sources:
24
25
  - snomed:sct_ips_20241216.cache
25
26
  - snomed:sct_nl_20240930.cache
26
27
  - snomed:sct_uk_20230412.cache
27
- - snomed:sct_us_20250901.cache
28
28
  - snomed:sct_us_20230301.cache
29
+ - snomed:sct_us_20250901.cache
29
30
  - snomed:sct_test_20250814.cache
30
- - cpt:cpt-2023-fragment-0.1.db
31
+ - cpt:CodeSystem-cpt.db|cpt-2023-fragment-0.1.db
31
32
  - omop:omop_v20250227.db
32
33
  - npm:hl7.terminology
33
34
  - npm:fhir.tx.support.r4
34
35
  - npm:ihe.formatcode.fhir
35
36
  - npm:fhir.dicom
36
37
  - npm:hl7.fhir.us.core
37
- - npm:us.nlm.vsac
38
38
  - npm:us.cdc.phinvads
39
39
  - npm:hl7.fhir.uv.sdc
40
+ - internal:vsac