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.
- package/CHANGELOG.md +24 -0
- package/README.md +1 -1
- package/library/cron-utilities.js +136 -0
- package/library/html-server.js +13 -29
- package/library/html.js +3 -8
- package/library/languages.js +160 -37
- package/library/package-manager.js +48 -1
- package/library/utilities.js +100 -19
- package/package.json +2 -2
- package/packages/package-crawler.js +6 -1
- package/packages/packages.js +38 -54
- package/publisher/publisher.js +19 -27
- package/registry/api.js +11 -10
- package/registry/crawler.js +31 -29
- package/registry/model.js +5 -26
- package/registry/registry.js +32 -41
- package/server.js +89 -12
- package/shl/shl.js +0 -18
- package/static/assets/js/statuspage.js +1 -9
- package/stats.js +39 -1
- package/token/token.js +14 -9
- package/translations/Messages.properties +2 -1
- package/tx/README.md +17 -6
- package/tx/cs/cs-api.js +19 -1
- package/tx/cs/cs-base.js +77 -0
- package/tx/cs/cs-country.js +46 -0
- package/tx/cs/cs-cpt.js +9 -5
- package/tx/cs/cs-cs.js +27 -13
- package/tx/cs/cs-lang.js +60 -22
- package/tx/cs/cs-loinc.js +69 -98
- package/tx/cs/cs-mimetypes.js +4 -0
- package/tx/cs/cs-ndc.js +6 -0
- package/tx/cs/cs-omop.js +16 -15
- package/tx/cs/cs-rxnorm.js +23 -1
- package/tx/cs/cs-snomed.js +283 -40
- package/tx/cs/cs-ucum.js +90 -70
- package/tx/importers/import-sct.module.js +371 -35
- package/tx/importers/readme.md +117 -7
- package/tx/library/bundle.js +5 -0
- package/tx/library/capabilitystatement.js +3 -142
- package/tx/library/codesystem.js +19 -173
- package/tx/library/conceptmap.js +4 -218
- package/tx/library/designations.js +14 -1
- package/tx/library/extensions.js +7 -0
- package/tx/library/namingsystem.js +3 -89
- package/tx/library/operation-outcome.js +8 -3
- package/tx/library/parameters.js +3 -2
- package/tx/library/renderer.js +10 -6
- package/tx/library/terminologycapabilities.js +3 -243
- package/tx/library/valueset.js +3 -235
- package/tx/library.js +100 -13
- package/tx/operation-context.js +23 -4
- package/tx/params.js +35 -38
- package/tx/provider.js +6 -5
- package/tx/sct/expressions.js +12 -3
- package/tx/tx-html.js +80 -89
- package/tx/tx.fhir.org.yml +6 -5
- package/tx/tx.js +163 -13
- package/tx/vs/vs-database.js +56 -39
- package/tx/vs/vs-package.js +21 -2
- package/tx/vs/vs-vsac.js +175 -39
- package/tx/workers/batch-validate.js +2 -0
- package/tx/workers/batch.js +2 -0
- package/tx/workers/expand.js +132 -112
- package/tx/workers/lookup.js +33 -14
- package/tx/workers/metadata.js +2 -2
- package/tx/workers/read.js +3 -2
- package/tx/workers/related.js +574 -0
- package/tx/workers/search.js +46 -9
- package/tx/workers/subsumes.js +13 -3
- package/tx/workers/translate.js +7 -3
- package/tx/workers/validate.js +258 -285
- package/tx/workers/worker.js +43 -39
- package/tx/xml/bundle-xml.js +237 -0
- package/tx/xml/xml-base.js +215 -64
- package/tx/xversion/xv-bundle.js +71 -0
- package/tx/xversion/xv-capabiliityStatement.js +137 -0
- package/tx/xversion/xv-codesystem.js +169 -0
- package/tx/xversion/xv-conceptmap.js +224 -0
- package/tx/xversion/xv-namingsystem.js +88 -0
- package/tx/xversion/xv-operationoutcome.js +27 -0
- package/tx/xversion/xv-parameters.js +87 -0
- package/tx/xversion/xv-resource.js +45 -0
- package/tx/xversion/xv-terminologyCapabilities.js +214 -0
- package/tx/xversion/xv-valueset.js +234 -0
- package/utilities/dev-proxy-server.js +126 -0
- package/utilities/explode-results.js +58 -0
- package/utilities/split-by-system.js +198 -0
- package/utilities/vsac-cs-fetcher.js +0 -0
- package/{windows-install.js → utilities/windows-install.js} +2 -0
- package/vcl/vcl.js +0 -18
- package/xig/xig.js +241 -230
package/tx/xml/xml-base.js
CHANGED
|
@@ -15,105 +15,203 @@ class FhirXmlBase {
|
|
|
15
15
|
* @type {Set<string>}
|
|
16
16
|
*/
|
|
17
17
|
static _arrayElements = new Set([
|
|
18
|
-
// Common resource elements
|
|
19
|
-
'
|
|
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
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
44
|
-
'
|
|
45
|
-
'
|
|
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
|
-
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
'
|
|
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
|
|
72
|
-
*
|
|
73
|
-
*
|
|
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
|
|
77
|
-
'property',
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
'
|
|
94
|
-
'
|
|
95
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
'
|
|
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
|
-
//
|
|
169
|
-
|
|
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 };
|