fhirsmith 0.4.2 → 0.5.1

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 +24 -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 +89 -12
  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 +241 -230
@@ -15,105 +15,203 @@ class FhirXmlBase {
15
15
  * @type {Set<string>}
16
16
  */
17
17
  static _arrayElements = new Set([
18
- // Common resource elements
19
- 'identifier',
18
+ // Common resource / DomainResource elements
19
+ 'contained',
20
+ // Note: 'identifier' is context-dependent - see _isArrayElement
21
+ // (0..1 on ConceptMap in R4, 0..* on most other resources and ConceptMap in R5)
20
22
  'contact',
21
23
  'useContext',
22
24
  'jurisdiction',
23
25
  'extension',
24
26
  'modifierExtension',
25
27
 
26
- // CodeSystem elements (at resource level)
27
- 'concept', // CodeSystem.concept is array
28
- 'filter', // CodeSystem.filter is array (but ValueSet.compose.include.filter is also array)
29
- 'operator', // CodeSystem.filter.operator is array
30
- 'designation', // concept.designation is array
28
+ // Meta elements
29
+ // Note: Meta.profile, Meta.security, Meta.tag are all context-dependent
30
+ // (array in meta, but singular elsewhere) - handled in _isArrayElement
31
+
32
+ // CodeSystem elements
33
+ 'concept', // CodeSystem.concept 0..* (recursive)
34
+ 'filter', // CodeSystem.filter 0..* (also ValueSet.compose.include.filter)
35
+ 'operator', // CodeSystem.filter.operator 0..*
36
+ 'designation', // concept.designation 0..*
31
37
 
32
38
  // ValueSet elements
33
- 'include',
34
- 'exclude',
35
- 'contains',
36
- 'parameter',
37
- 'valueSet',
39
+ 'include', // ValueSet.compose.include 0..*
40
+ 'exclude', // ValueSet.compose.exclude 0..*
41
+ 'contains', // ValueSet.expansion.contains 0..* (recursive)
42
+ 'parameter', // ValueSet.expansion.parameter 0..*, Parameters.parameter 0..*
43
+ 'valueSet', // ValueSet.compose.include.valueSet 0..*
38
44
 
39
45
  // ConceptMap elements
40
- 'group',
41
- 'element',
42
- 'target',
43
- 'dependsOn',
44
- 'product',
45
- 'additionalAttribute',
46
+ 'group', // ConceptMap.group 0..*
47
+ 'element', // ConceptMap.group.element 0..*
48
+ // Note: 'target' is context-dependent - see _isArrayElement
49
+ // (array in ConceptMap.group.element.target, but single uri in ConceptMap.group.target R4)
50
+ 'dependsOn', // ConceptMap.group.element.target.dependsOn 0..*
51
+ 'product', // ConceptMap.group.element.target.product 0..*
52
+ 'additionalAttribute', // R5 ConceptMap.additionalAttribute 0..*
46
53
 
47
54
  // OperationOutcome elements
48
- 'issue',
49
- 'location',
50
- 'expression',
55
+ 'issue', // OperationOutcome.issue 0..*
56
+ 'location', // OperationOutcome.issue.location 0..*
57
+ 'expression', // OperationOutcome.issue.expression 0..*
51
58
 
52
59
  // Parameters elements
53
- 'part',
60
+ 'part', // Parameters.parameter.part 0..*
54
61
 
55
62
  // Common data type elements
56
- 'coding',
57
- 'telecom',
58
- 'address',
59
- 'given',
60
- 'prefix',
61
- 'suffix',
62
- 'line',
63
- 'link',
64
- 'entry',
63
+ 'coding', // CodeableConcept.coding 0..*
64
+ 'telecom', // ContactDetail.telecom, ContactPoint uses
65
+ // Note: 'address' is NOT in this set - it is 0..1 in endpoint.address
66
+ // and none of our terminology resources use Address arrays directly
67
+ 'given', // HumanName.given 0..*
68
+ 'prefix', // HumanName.prefix 0..*
69
+ 'suffix', // HumanName.suffix 0..*
70
+ 'line', // Address.line 0..*
71
+ 'link', // Bundle.link 0..*
72
+ 'entry', // Bundle.entry 0..*
65
73
 
66
74
  // NamingSystem elements
67
- 'uniqueId',
75
+ 'uniqueId', // NamingSystem.uniqueId 0..*
76
+
77
+ // CapabilityStatement elements
78
+ 'instantiates', // CS.instantiates 0..*
79
+ 'imports', // CS.imports 0..* (R4+)
80
+ 'format', // CS.format 1..*
81
+ 'patchFormat', // CS.patchFormat 0..*
82
+ 'acceptLanguage', // CS.acceptLanguage 0..* (R5)
83
+ 'implementationGuide', // CS.implementationGuide 0..*
84
+ 'rest', // CS.rest 0..*
85
+ 'resource', // CS.rest.resource 0..*
86
+ 'interaction', // CS.rest.resource.interaction 0..*, CS.rest.interaction 0..*
87
+ 'searchInclude', // CS.rest.resource.searchInclude 0..*
88
+ 'searchRevInclude', // CS.rest.resource.searchRevInclude 0..*
89
+ 'searchParam', // CS.rest.resource.searchParam 0..*
90
+ 'operation', // CS.rest.resource.operation 0..*, CS.rest.operation 0..*
91
+ 'supportedProfile', // CS.rest.resource.supportedProfile 0..* (R4+)
92
+ 'compartment', // CS.rest.compartment 0..*
93
+ 'messaging', // CS.messaging 0..*
94
+ 'endpoint', // CS.messaging.endpoint 0..*
95
+ 'supportedMessage', // CS.messaging.supportedMessage 0..*
96
+ 'document', // CS.document 0..*
97
+ 'service', // CS.rest.security.service 0..*
98
+
99
+ // TerminologyCapabilities elements
100
+ 'codeSystem', // TC.codeSystem 0..*
68
101
  ]);
69
102
 
70
103
  /**
71
- * Elements that are arrays ONLY at resource/backbone level, not inside filters/other contexts
72
- * 'property' is an array in CodeSystem.property and CodeSystem.concept.property
73
- * but NOT in ValueSet.compose.include.filter.property (which is a single code)
104
+ * Elements that are arrays only in certain parent contexts.
105
+ * These need special handling because the same element name has different
106
+ * cardinality depending on where it appears.
74
107
  * @type {Set<string>}
75
108
  */
76
- static _resourceLevelArrayElements = new Set([
77
- 'property', // Array in CodeSystem.property and concept.property, but single in filter
109
+ static _contextDependentArrayElements = new Set([
110
+ 'property', // Array in CodeSystem.property and concept.property, but single in filter
111
+ 'profile', // Array in Meta.profile, but single in CS.rest.resource.profile, CS.document.profile
112
+ 'address', // Array in Patient.address etc, but single in CS.messaging.endpoint.address
113
+ 'target', // Array in ConceptMap.group.element.target, but single uri in ConceptMap.group.target (R4)
114
+ 'identifier', // Array on most resources, but 0..1 on ConceptMap in R4 (version-dependent)
115
+ 'version', // Array in TC.codeSystem.version, but single everywhere else
116
+ 'language', // Array in TC.codeSystem.version.language, but single on Resource.language
117
+ 'security', // Array in Meta.security, but single in CS.rest.security
118
+ 'tag', // Array in Meta.tag (handled specially in renderMeta, but needed for generic parsing)
78
119
  ]);
79
120
 
80
121
  /**
81
- * Element names that represent boolean types in FHIR
122
+ * Element names that represent boolean types in FHIR.
123
+ * These are converted from XML string "true"/"false" to JSON boolean.
82
124
  * @type {Set<string>}
83
125
  */
84
126
  static _booleanElements = new Set([
127
+ // value[x] boolean
85
128
  'valueBoolean',
129
+
130
+ // Common resource elements
86
131
  'experimental',
132
+
133
+ // CodeSystem elements
87
134
  'caseSensitive',
88
135
  'compositional',
89
136
  'versionNeeded',
90
- 'inactive',
91
- 'notSelectable',
92
- 'abstract',
93
- 'immutable',
94
- 'lockedDate',
95
- 'preferred',
137
+ 'inactive', // concept.property.valueBoolean (context: concept inactive)
138
+
139
+ // CodeSystem concept elements
140
+ 'notSelectable', // concept extension (deprecated but still in use)
141
+ 'abstract', // expansion.contains.abstract
142
+
143
+ // ValueSet elements
144
+ 'immutable', // ValueSet.immutable
145
+
146
+ // NamingSystem elements
147
+ 'preferred', // NamingSystem.uniqueId.preferred
148
+
149
+ // CapabilityStatement elements
150
+ 'cors', // CS.rest.security.cors
151
+ 'readHistory', // CS.rest.resource.readHistory
152
+ 'updateCreate', // CS.rest.resource.updateCreate
153
+ 'conditionalCreate', // CS.rest.resource.conditionalCreate
154
+ 'conditionalUpdate', // CS.rest.resource.conditionalUpdate
155
+ 'conditionalPatch',
156
+ 'multipleOr', // CS.rest.resource.searchParam (R5)
157
+ 'multipleAnd', // CS.rest.resource.searchParam (R5)
158
+
159
+ // ValueSet expansion contains
160
+ // 'abstract' already listed above
161
+ // 'inactive' already listed above
162
+
163
+ // Generic backbone elements
164
+ 'userSelected', // Coding.userSelected
96
165
  ]);
97
166
 
98
167
  /**
99
- * Element names that represent integer types in FHIR
168
+ * Element names that represent integer types in FHIR.
169
+ * These are converted from XML string to JSON number (parseInt).
100
170
  * @type {Set<string>}
101
171
  */
102
172
  static _integerElements = new Set([
173
+ // value[x] integer types
103
174
  'valueInteger',
104
175
  'valueUnsignedInt',
105
176
  'valuePositiveInt',
106
- 'count',
107
- 'offset',
108
- 'total',
177
+
178
+ // Common elements
179
+ 'count', // CodeSystem.count
180
+ 'offset', // ValueSet.expansion.offset
181
+ 'total', // ValueSet.expansion.total, Bundle.total
182
+
183
+ // CapabilityStatement elements
184
+ 'reliableCache', // CS.messaging.reliableCache
109
185
  ]);
110
186
 
111
187
  /**
112
- * Element names that represent decimal types in FHIR
188
+ * Element names that represent decimal types in FHIR.
189
+ * These are converted from XML string to JSON number (parseFloat).
113
190
  * @type {Set<string>}
114
191
  */
115
192
  static _decimalElements = new Set([
116
193
  'valueDecimal',
194
+ 'score', // Bundle.entry.search.score
195
+ ]);
196
+
197
+ /**
198
+ * Parent element contexts where 'value' is a decimal (Quantity-like types).
199
+ * In FHIR, Quantity.value, Money.value etc. are decimal, but most other
200
+ * uses of 'value' (Identifier.value, ContactPoint.value, etc.) are strings.
201
+ * @type {Set<string>}
202
+ */
203
+ static _quantityContexts = new Set([
204
+ 'valueQuantity',
205
+ 'valueMoney',
206
+ 'quantity', // generic Quantity backbone
207
+ 'amount', // Money amount contexts
208
+ 'Quantity',
209
+ 'Money',
210
+ 'Duration',
211
+ 'Age',
212
+ 'Distance',
213
+ 'Count',
214
+ 'SimpleQuantity',
117
215
  ]);
118
216
 
119
217
  // ==================== XML PARSING (XML -> JSON) ====================
@@ -122,18 +220,19 @@ class FhirXmlBase {
122
220
  * Convert a manually parsed XML element to FHIR JSON format
123
221
  * @param {Object} element - Element with {name, attributes, children}
124
222
  * @param {string} resourceType - The FHIR resource type
223
+ * @param {number} [fhirVersion] - FHIR version (3, 4, or 5) for version-dependent handling
125
224
  * @returns {Object} FHIR JSON object
126
225
  */
127
- static convertElementToFhirJson(element, resourceType) {
226
+ static convertElementToFhirJson(element, resourceType, fhirVersion) {
128
227
  const result = { resourceType };
129
228
 
130
229
  for (const child of element.children) {
131
230
  const key = child.name;
132
- const { value, primitiveExt } = this._convertChildElementWithExt(child, resourceType);
231
+ const { value, primitiveExt } = this._convertChildElementWithExt(child, resourceType, fhirVersion);
133
232
 
134
233
  // Handle the value
135
234
  if (value !== null) {
136
- if (this._isArrayElement(key, resourceType)) {
235
+ if (this._isArrayElement(key, resourceType, fhirVersion)) {
137
236
  if (!result[key]) {
138
237
  result[key] = [];
139
238
  }
@@ -157,21 +256,66 @@ class FhirXmlBase {
157
256
  * Check if an element should be an array based on its name and parent context
158
257
  * @param {string} elementName - The element name
159
258
  * @param {string} parentContext - The parent element name or context
259
+ * @param {number} [fhirVersion] - FHIR version (3, 4, or 5) for version-dependent handling
160
260
  * @returns {boolean}
161
261
  */
162
- static _isArrayElement(elementName, parentContext) {
262
+ static _isArrayElement(elementName, parentContext, fhirVersion) {
163
263
  // These are always arrays regardless of context
164
264
  if (this._arrayElements.has(elementName)) {
165
265
  return true;
166
266
  }
167
267
 
168
- // 'property' is an array in CodeSystem.property, CodeSystem.concept.property
169
- // but NOT in filter.property (which is a single code)
268
+ // Context-dependent array elements:
269
+
270
+ // 'property' is an array in CodeSystem.property, CodeSystem.concept.property,
271
+ // ConceptMap.property (R5), but NOT in filter.property (which is a single code)
170
272
  if (elementName === 'property') {
171
- // property is array at resource level or inside concept, but not inside filter
172
273
  return parentContext !== 'filter';
173
274
  }
174
275
 
276
+ // 'target' is 0..* in ConceptMap.group.element.target (backbone array)
277
+ // but 0..1 in ConceptMap.group.target (R4 uri) and ConceptMap.group.target (R5 uri)
278
+ if (elementName === 'target') {
279
+ return parentContext === 'element';
280
+ }
281
+
282
+ // 'identifier' is 0..* on most resources but 0..1 on ConceptMap in R4 and R3
283
+ if (elementName === 'identifier') {
284
+ if (parentContext === 'ConceptMap') {
285
+ return fhirVersion >= 5;
286
+ }
287
+ return true; // 0..* on all other resource types
288
+ }
289
+
290
+ // 'version' is 0..* in TerminologyCapabilities.codeSystem.version
291
+ // but 0..1 everywhere else (CodeSystem.version, ValueSet.version, etc.)
292
+ if (elementName === 'version') {
293
+ return parentContext === 'codeSystem';
294
+ }
295
+
296
+ // 'language' is 0..* in TerminologyCapabilities.codeSystem.version.language
297
+ // but 0..1 on Resource.language
298
+ if (elementName === 'language') {
299
+ return parentContext === 'version';
300
+ }
301
+
302
+ // 'profile' is 0..* in Meta.profile
303
+ // but 0..1 in CapabilityStatement.rest.resource.profile, CS.document.profile
304
+ if (elementName === 'profile') {
305
+ return parentContext === 'meta';
306
+ }
307
+
308
+ // 'security' is 0..* in Meta.security (Coding)
309
+ // but 0..1 in CapabilityStatement.rest.security (BackboneElement)
310
+ if (elementName === 'security') {
311
+ return parentContext === 'meta';
312
+ }
313
+
314
+ // 'tag' is 0..* in Meta.tag (Coding)
315
+ if (elementName === 'tag') {
316
+ return parentContext === 'meta';
317
+ }
318
+
175
319
  return false;
176
320
  }
177
321
 
@@ -179,11 +323,12 @@ class FhirXmlBase {
179
323
  * Converts a child element to appropriate JSON value, also handling primitive extensions
180
324
  * @param {Object} child - Child element with {name, attributes, children}
181
325
  * @param {string} parentContext - Parent element name for context-dependent array handling
326
+ * @param {number} [fhirVersion] - FHIR version for version-dependent handling
182
327
  * @returns {{value: *, primitiveExt: Object|null}} Converted value and primitive extension if any
183
328
  * @private
184
329
  */
185
330
  // eslint-disable-next-line no-unused-vars
186
- static _convertChildElementWithExt(child, parentContext = '') {
331
+ static _convertChildElementWithExt(child, parentContext = '', fhirVersion) {
187
332
  const hasValue = child.attributes.value !== undefined;
188
333
  const hasChildren = child.children.length > 0;
189
334
  const isExtensionElement = child.name === 'extension' || child.name === 'modifierExtension';
@@ -202,7 +347,7 @@ class FhirXmlBase {
202
347
 
203
348
  // Case 1: Simple primitive with value, no children
204
349
  if (hasValue && !hasChildren) {
205
- return { value: this._convertPrimitiveValue(child.name, child.attributes.value), primitiveExt: null };
350
+ return { value: this._convertPrimitiveValue(child.name, child.attributes.value, parentContext), primitiveExt: null };
206
351
  }
207
352
 
208
353
  // Case 2: Primitive with extension but no value - this is a primitive extension only
@@ -216,7 +361,7 @@ class FhirXmlBase {
216
361
  // This ONLY applies when there are NO non-extension children
217
362
  if (hasValue && hasOnlyExtensions) {
218
363
  const ext = this._buildExtensionObject(extensionChildren);
219
- return { value: this._convertPrimitiveValue(child.name, child.attributes.value), primitiveExt: ext };
364
+ return { value: this._convertPrimitiveValue(child.name, child.attributes.value, parentContext), primitiveExt: ext };
220
365
  }
221
366
  }
222
367
 
@@ -234,11 +379,11 @@ class FhirXmlBase {
234
379
  // Process ALL children (including extensions as normal array elements)
235
380
  for (const grandchild of child.children) {
236
381
  const key = grandchild.name;
237
- const { value, primitiveExt } = this._convertChildElementWithExt(grandchild, currentContext);
382
+ const { value, primitiveExt } = this._convertChildElementWithExt(grandchild, currentContext, fhirVersion);
238
383
 
239
384
  // Handle the value
240
385
  if (value !== null) {
241
- if (this._isArrayElement(key, currentContext)) {
386
+ if (this._isArrayElement(key, currentContext, fhirVersion)) {
242
387
  if (!obj[key]) {
243
388
  obj[key] = [];
244
389
  }
@@ -287,10 +432,11 @@ class FhirXmlBase {
287
432
  * Convert a primitive value to the appropriate JavaScript type
288
433
  * @param {string} elementName - The element name
289
434
  * @param {string} value - The string value from XML
435
+ * @param {string} [parentContext] - Parent element name for context-dependent typing
290
436
  * @returns {*} Converted value
291
437
  * @private
292
438
  */
293
- static _convertPrimitiveValue(elementName, value) {
439
+ static _convertPrimitiveValue(elementName, value, parentContext) {
294
440
  if (this._booleanElements.has(elementName)) {
295
441
  return value === 'true';
296
442
  }
@@ -300,6 +446,10 @@ class FhirXmlBase {
300
446
  if (this._decimalElements.has(elementName)) {
301
447
  return parseFloat(value);
302
448
  }
449
+ // 'value' is a decimal inside Quantity-like types (valueQuantity, valueMoney, Quantity, etc.)
450
+ if (elementName === 'value' && this._quantityContexts.has(parentContext)) {
451
+ return parseFloat(value);
452
+ }
303
453
  // Everything else stays as string
304
454
  return value;
305
455
  }
@@ -308,10 +458,11 @@ class FhirXmlBase {
308
458
  * Simple child element conversion (without tracking primitive extensions)
309
459
  * @param {Object} child - Child element with {name, attributes, children}
310
460
  * @param {string} parentContext - Parent context for array handling
461
+ * @param {number} [fhirVersion] - FHIR version for version-dependent handling
311
462
  * @returns {*} Converted value
312
463
  */
313
- static convertChildElement(child, parentContext = '') {
314
- return this._convertChildElementWithExt(child, parentContext).value;
464
+ static convertChildElement(child, parentContext = '', fhirVersion) {
465
+ return this._convertChildElementWithExt(child, parentContext, fhirVersion).value;
315
466
  }
316
467
 
317
468
  // ==================== XML GENERATION (JSON -> XML) ====================
@@ -0,0 +1,71 @@
1
+ const {VersionUtilities} = require("../../library/version-utilities");
2
+
3
+ /**
4
+ * Converts input Bundle to R5 format (modifies input object for performance)
5
+ * @param {Object} jsonObj - The input Bundle object
6
+ * @param {string} version - Source FHIR version
7
+ * @returns {Object} The same object, potentially modified to R5 format
8
+ * @private
9
+ */
10
+
11
+ function bundleToR5(jsonObj, sourceVersion) {
12
+ const { convertResourceToR5 } = require("./xv-resource");
13
+
14
+ if (VersionUtilities.isR5Ver(sourceVersion)) {
15
+ return jsonObj; // No conversion needed
16
+ }
17
+
18
+ for (let be of jsonObj.entry || []) {
19
+ convertResourceToR5(be.resource, sourceVersion);
20
+ }
21
+
22
+ throw new Error(`Unsupported FHIR version: ${sourceVersion}`);
23
+ }
24
+
25
+ /**
26
+ * Converts R5 Bundle to target version format (clones object first)
27
+ * @param {Object} r5Obj - The R5 format Bundle object
28
+ * @param {string} targetVersion - Target FHIR version
29
+ * @returns {Object} New object in target version format
30
+ * @private
31
+ */
32
+ function bundleFromR5(r5Obj, targetVersion) {
33
+ const {convertResourceFromR5} = require("./xv-resource");
34
+
35
+ if (VersionUtilities.isR5Ver(targetVersion)) {
36
+ return r5Obj; // No conversion needed
37
+ }
38
+
39
+ // Clone the object to avoid modifying the original
40
+ const bundle = {
41
+ resourceType: "Bundle"
42
+ }
43
+ bundle.id = r5Obj.id;
44
+ bundle.meta = r5Obj.meta;
45
+ bundle.implicitRules = r5Obj.implicitRules;
46
+ bundle.language = r5Obj.language;
47
+ bundle.identifier = r5Obj.identifier;
48
+ bundle.type = r5Obj.type;
49
+ if (VersionUtilities.isR4Ver(targetVersion)) {
50
+ bundle.timestamp = r5Obj.timestamp;
51
+ }
52
+ bundle.total = r5Obj.total;
53
+ bundle.link = r5Obj.link;
54
+ for (let be5 of r5Obj.entry || []) {
55
+ let be = {};
56
+ if (!bundle.entry) {
57
+ bundle.entry = [];
58
+ }
59
+ bundle.entry.push(be);
60
+ be.link = be5.link;
61
+ be.fullUrl = be5.fullUrl;
62
+ be.search = be5.search;
63
+ be.request = be5.request;
64
+ be.response = be5.response;
65
+ be.resource = convertResourceFromR5(be5.resource, targetVersion);
66
+ }
67
+
68
+ return bundle;
69
+ }
70
+
71
+ module.exports = { bundleToR5, bundleFromR5 };
@@ -0,0 +1,137 @@
1
+ const {VersionUtilities} = require("../../library/version-utilities");
2
+
3
+ /**
4
+ * Converts input CapabilityStatement to R5 format (modifies input object for performance)
5
+ * @param {Object} jsonObj - The input CapabilityStatement object
6
+ * @param {string} version - Source FHIR version
7
+ * @returns {Object} The same object, potentially modified to R5 format
8
+ * @private
9
+ */
10
+
11
+ function capabilityStatementToR5(jsonObj, sourceVersion) {
12
+ if (VersionUtilities.isR5Ver(sourceVersion)) {
13
+ return jsonObj; // No conversion needed
14
+ }
15
+
16
+ if (VersionUtilities.isR3Ver(sourceVersion)) {
17
+ // R3: resourceType was "CapabilityStatement" (same as R4/R5)
18
+ // Convert identifier from single object to array if present
19
+ if (jsonObj.identifier && !Array.isArray(jsonObj.identifier)) {
20
+ jsonObj.identifier = [jsonObj.identifier];
21
+ }
22
+ return jsonObj;
23
+ }
24
+
25
+ if (VersionUtilities.isR4Ver(sourceVersion)) {
26
+ // R4 to R5: No major structural changes needed
27
+ return jsonObj;
28
+ }
29
+ throw new Error(`Unsupported FHIR version: ${sourceVersion}`);
30
+ }
31
+
32
+ /**
33
+ * Converts R5 CapabilityStatement to target version format (clones object first)
34
+ * @param {Object} r5Obj - The R5 format CapabilityStatement object
35
+ * @param {string} targetVersion - Target FHIR version
36
+ * @returns {Object} New object in target version format
37
+ * @private
38
+ */
39
+ function capabilityStatementFromR5(r5Obj, targetVersion) {
40
+ if (VersionUtilities.isR5Ver(targetVersion)) {
41
+ return r5Obj; // No conversion needed
42
+ }
43
+
44
+ // Clone the object to avoid modifying the original
45
+ const cloned = JSON.parse(JSON.stringify(r5Obj));
46
+
47
+ if (VersionUtilities.isR4Ver(targetVersion)) {
48
+ return capabilityStatementR5ToR4(cloned);
49
+ } else if (VersionUtilities.isR3Ver(targetVersion)) {
50
+ return capabilityStatementR5ToR3(cloned);
51
+ }
52
+
53
+ throw new Error(`Unsupported target FHIR version: ${targetVersion}`);
54
+ }
55
+
56
+ /**
57
+ * Converts R5 CapabilityStatement to R4 format
58
+ * @param {Object} r5Obj - Cloned R5 CapabilityStatement object
59
+ * @returns {Object} R4 format CapabilityStatement
60
+ * @private
61
+ */
62
+ function capabilityStatementR5ToR4(r5Obj) {
63
+
64
+ // Remove R5-specific elements
65
+ if (r5Obj.versionAlgorithmString) {
66
+ delete r5Obj.versionAlgorithmString;
67
+ }
68
+ if (r5Obj.versionAlgorithmCoding) {
69
+ delete r5Obj.versionAlgorithmCoding;
70
+ }
71
+
72
+ return r5Obj;
73
+ }
74
+
75
+ /**
76
+ * Converts R5 CapabilityStatement to R3 format
77
+ * @param {Object} r5Obj - Cloned R5 CapabilityStatement object
78
+ * @returns {Object} R3 format CapabilityStatement
79
+ * @private
80
+ */
81
+ function capabilityStatementR5ToR3(r5Obj) {
82
+ // First apply R4 conversions
83
+ const r4Obj = capabilityStatementR5ToR4(r5Obj);
84
+
85
+ // Convert identifier array back to single object
86
+ if (r4Obj.identifier && Array.isArray(r4Obj.identifier)) {
87
+ if (r4Obj.identifier.length > 0) {
88
+ r4Obj.identifier = r4Obj.identifier[0];
89
+ } else {
90
+ delete r4Obj.identifier;
91
+ }
92
+ }
93
+
94
+ // Convert valueCanonical to valueUri throughout the object
95
+ convertCanonicalToUri(r5Obj);
96
+
97
+
98
+ // Convert rest.operation.definition from canonical string to Reference object
99
+ for (const rest of r4Obj.rest || []) {
100
+ for (const operation of rest.operation || []) {
101
+ if (typeof operation.definition === 'string') {
102
+ operation.definition = {reference: operation.definition};
103
+ }
104
+ for (const resource of rest.resource || []) {
105
+ delete resource.operation;
106
+ }
107
+ }
108
+ }
109
+
110
+ return r4Obj;
111
+ }
112
+
113
+ function convertCanonicalToUri(obj) {
114
+ if (!obj || typeof obj !== 'object') {
115
+ return;
116
+ }
117
+
118
+ if (Array.isArray(obj)) {
119
+ obj.forEach(item => convertCanonicalToUri(item));
120
+ return;
121
+ }
122
+
123
+ // Convert valueCanonical to valueUri
124
+ if (obj.valueCanonical !== undefined) {
125
+ obj.valueUri = obj.valueCanonical;
126
+ delete obj.valueCanonical;
127
+ }
128
+
129
+ // Recurse into all properties
130
+ for (const key of Object.keys(obj)) {
131
+ if (typeof obj[key] === 'object') {
132
+ convertCanonicalToUri(obj[key]);
133
+ }
134
+ }
135
+ }
136
+
137
+ module.exports = { capabilityStatementToR5, capabilityStatementFromR5 };