fhirsmith 0.3.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 (277) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/FHIRsmith.png +0 -0
  3. package/README.md +277 -0
  4. package/config-template.json +144 -0
  5. package/library/folder-setup.js +58 -0
  6. package/library/html-server.js +166 -0
  7. package/library/html.js +835 -0
  8. package/library/i18nsupport.js +259 -0
  9. package/library/languages.js +779 -0
  10. package/library/logger-telnet.js +205 -0
  11. package/library/logger.js +279 -0
  12. package/library/package-manager.js +876 -0
  13. package/library/utilities.js +196 -0
  14. package/library/version-utilities.js +1056 -0
  15. package/npmprojector/config-example.json +13 -0
  16. package/npmprojector/indexer.js +394 -0
  17. package/npmprojector/npmprojector.js +395 -0
  18. package/npmprojector/readme.md +174 -0
  19. package/npmprojector/watcher.js +335 -0
  20. package/package.json +119 -0
  21. package/packages/package-crawler.js +846 -0
  22. package/packages/packages-template.html +126 -0
  23. package/packages/packages.js +2838 -0
  24. package/passwords.ini +2 -0
  25. package/publisher/publisher-template.html +208 -0
  26. package/publisher/publisher.js +2167 -0
  27. package/publisher/task-draft.js +458 -0
  28. package/registry/api.js +735 -0
  29. package/registry/crawler.js +637 -0
  30. package/registry/model.js +513 -0
  31. package/registry/readme.md +243 -0
  32. package/registry/registry-data.json +121015 -0
  33. package/registry/registry-template.html +126 -0
  34. package/registry/registry.js +1395 -0
  35. package/registry/test-runner.js +237 -0
  36. package/root-template.html +124 -0
  37. package/server.js +524 -0
  38. package/shl/private-key.pem +5 -0
  39. package/shl/public-key.pem +18 -0
  40. package/shl/shl.js +1125 -0
  41. package/shl/vhl.js +69 -0
  42. package/static/FHIRsmith128.png +0 -0
  43. package/static/FHIRsmith16.png +0 -0
  44. package/static/FHIRsmith32.png +0 -0
  45. package/static/FHIRsmith64.png +0 -0
  46. package/static/assets/css/bootstrap-fhir.css +5302 -0
  47. package/static/assets/css/bootstrap-glyphicons.css +2 -0
  48. package/static/assets/css/bootstrap.css +4097 -0
  49. package/static/assets/css/jquery-ui.css +523 -0
  50. package/static/assets/css/jquery-ui.structure.css +863 -0
  51. package/static/assets/css/jquery-ui.structure.min.css +5 -0
  52. package/static/assets/css/jquery-ui.theme.css +439 -0
  53. package/static/assets/css/jquery-ui.theme.min.css +5 -0
  54. package/static/assets/css/jquery.ui.all.css +7 -0
  55. package/static/assets/css/modules.css +18 -0
  56. package/static/assets/css/project.css +367 -0
  57. package/static/assets/css/pygments-manni.css +66 -0
  58. package/static/assets/css/tags.css +74 -0
  59. package/static/assets/css/xml.css +2 -0
  60. package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
  61. package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
  62. package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
  63. package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
  64. package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
  65. package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
  66. package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
  67. package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
  68. package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
  69. package/static/assets/ico/favicon.ico +0 -0
  70. package/static/assets/ico/favicon.png +0 -0
  71. package/static/assets/images/fhir-logo-www.png +0 -0
  72. package/static/assets/images/fhir-logo.png +0 -0
  73. package/static/assets/images/hl7-logo.png +0 -0
  74. package/static/assets/images/logo_ansinew.jpg +0 -0
  75. package/static/assets/images/search.png +0 -0
  76. package/static/assets/images/stripe.png +0 -0
  77. package/static/assets/images/target.png +0 -0
  78. package/static/assets/images/tx-registry-root.gif +0 -0
  79. package/static/assets/images/tx-registry.png +0 -0
  80. package/static/assets/images/tx-server.png +0 -0
  81. package/static/assets/images/tx-version.png +0 -0
  82. package/static/assets/js/bootstrap.min.js +6 -0
  83. package/static/assets/js/fhir-gw.js +259 -0
  84. package/static/assets/js/fhir.js +2 -0
  85. package/static/assets/js/html5shiv.js +8 -0
  86. package/static/assets/js/jcookie.js +96 -0
  87. package/static/assets/js/jquery-ui.min.js +6 -0
  88. package/static/assets/js/jquery.js +10716 -0
  89. package/static/assets/js/jquery.min.js +2 -0
  90. package/static/assets/js/jquery.ui.core.js +314 -0
  91. package/static/assets/js/jquery.ui.draggable.js +825 -0
  92. package/static/assets/js/jquery.ui.mouse.js +162 -0
  93. package/static/assets/js/jquery.ui.resizable.js +842 -0
  94. package/static/assets/js/jquery.ui.widget.js +268 -0
  95. package/static/assets/js/json2.js +487 -0
  96. package/static/assets/js/jtip.js +97 -0
  97. package/static/assets/js/respond.min.js +6 -0
  98. package/static/assets/js/statuspage.js +70 -0
  99. package/static/assets/js/xml.js +2 -0
  100. package/static/dist/js/bootstrap.js +1964 -0
  101. package/static/favicon.png +0 -0
  102. package/static/fhir.css +626 -0
  103. package/static/icon-fhir-16.png +0 -0
  104. package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  105. package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  106. package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
  107. package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  108. package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  109. package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  110. package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  111. package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  112. package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  113. package/static/images/ui-icons_222222_256x240.png +0 -0
  114. package/static/images/ui-icons_228ef1_256x240.png +0 -0
  115. package/static/images/ui-icons_ef8c08_256x240.png +0 -0
  116. package/static/images/ui-icons_ffd27a_256x240.png +0 -0
  117. package/static/images/ui-icons_ffffff_256x240.png +0 -0
  118. package/static/js/jquery.effects.blind.js +49 -0
  119. package/static/js/jquery.effects.bounce.js +78 -0
  120. package/static/js/jquery.effects.clip.js +54 -0
  121. package/static/js/jquery.effects.core.js +763 -0
  122. package/static/js/jquery.effects.drop.js +50 -0
  123. package/static/js/jquery.effects.explode.js +79 -0
  124. package/static/js/jquery.effects.fade.js +32 -0
  125. package/static/js/jquery.effects.fold.js +56 -0
  126. package/static/js/jquery.effects.highlight.js +50 -0
  127. package/static/js/jquery.effects.pulsate.js +51 -0
  128. package/static/js/jquery.effects.scale.js +178 -0
  129. package/static/js/jquery.effects.shake.js +57 -0
  130. package/static/js/jquery.effects.slide.js +50 -0
  131. package/static/js/jquery.effects.transfer.js +45 -0
  132. package/static/js/jquery.ui.accordion.js +611 -0
  133. package/static/js/jquery.ui.autocomplete.js +612 -0
  134. package/static/js/jquery.ui.button.js +416 -0
  135. package/static/js/jquery.ui.datepicker.js +1823 -0
  136. package/static/js/jquery.ui.dialog.js +878 -0
  137. package/static/js/jquery.ui.droppable.js +296 -0
  138. package/static/js/jquery.ui.position.js +252 -0
  139. package/static/js/jquery.ui.progressbar.js +109 -0
  140. package/static/js/jquery.ui.selectable.js +266 -0
  141. package/static/js/jquery.ui.slider.js +666 -0
  142. package/static/js/jquery.ui.sortable.js +1077 -0
  143. package/static/js/jquery.ui.tabs.js +758 -0
  144. package/stats.js +80 -0
  145. package/test-cache/vsac/vsac-valuesets.db +0 -0
  146. package/token/nginx_passport_setup.md +383 -0
  147. package/token/security_guide.md +294 -0
  148. package/token/token-template.html +330 -0
  149. package/token/token.js +1300 -0
  150. package/translations/Messages.properties +1510 -0
  151. package/translations/Messages_ar.properties +1399 -0
  152. package/translations/Messages_de.properties +836 -0
  153. package/translations/Messages_es.properties +737 -0
  154. package/translations/Messages_fr.properties +1 -0
  155. package/translations/Messages_ja.properties +893 -0
  156. package/translations/Messages_nl.properties +1357 -0
  157. package/translations/Messages_pt.properties +1302 -0
  158. package/translations/Messages_ru.properties +1 -0
  159. package/translations/Messages_uz.properties +1 -0
  160. package/translations/Messages_zh.properties +1 -0
  161. package/translations/rendering-phrases.properties +1128 -0
  162. package/translations/rendering-phrases_ar.properties +1091 -0
  163. package/translations/rendering-phrases_de.properties +6 -0
  164. package/translations/rendering-phrases_es.properties +6 -0
  165. package/translations/rendering-phrases_fr.properties +624 -0
  166. package/translations/rendering-phrases_ja.properties +21 -0
  167. package/translations/rendering-phrases_nl.properties +970 -0
  168. package/translations/rendering-phrases_pt.properties +1020 -0
  169. package/translations/rendering-phrases_ru.properties +1094 -0
  170. package/translations/rendering-phrases_uz.properties +1 -0
  171. package/translations/rendering-phrases_zh.properties +1 -0
  172. package/tx/README.md +418 -0
  173. package/tx/cm/cm-api.js +110 -0
  174. package/tx/cm/cm-database.js +735 -0
  175. package/tx/cm/cm-package.js +325 -0
  176. package/tx/cs/cs-api.js +789 -0
  177. package/tx/cs/cs-areacode.js +615 -0
  178. package/tx/cs/cs-country.js +1110 -0
  179. package/tx/cs/cs-cpt.js +785 -0
  180. package/tx/cs/cs-cs.js +1579 -0
  181. package/tx/cs/cs-currency.js +539 -0
  182. package/tx/cs/cs-db.js +1321 -0
  183. package/tx/cs/cs-hgvs.js +329 -0
  184. package/tx/cs/cs-lang.js +465 -0
  185. package/tx/cs/cs-loinc.js +1485 -0
  186. package/tx/cs/cs-mimetypes.js +238 -0
  187. package/tx/cs/cs-ndc.js +704 -0
  188. package/tx/cs/cs-omop.js +1025 -0
  189. package/tx/cs/cs-provider-api.js +43 -0
  190. package/tx/cs/cs-provider-list.js +37 -0
  191. package/tx/cs/cs-rxnorm.js +808 -0
  192. package/tx/cs/cs-snomed.js +1102 -0
  193. package/tx/cs/cs-ucum.js +514 -0
  194. package/tx/cs/cs-unii.js +271 -0
  195. package/tx/cs/cs-uri.js +218 -0
  196. package/tx/cs/cs-usstates.js +305 -0
  197. package/tx/dev.fhir.org.yml +14 -0
  198. package/tx/fixtures/test-cases-setup.json +18 -0
  199. package/tx/fixtures/test-cases.yml +16 -0
  200. package/tx/html/codesystem-operations.liquid +25 -0
  201. package/tx/html/home-metrics.liquid +247 -0
  202. package/tx/html/operations-form.liquid +148 -0
  203. package/tx/html/search-form.liquid +62 -0
  204. package/tx/html/tx-template.html +133 -0
  205. package/tx/html/valueset-operations.liquid +54 -0
  206. package/tx/importers/atc-to-fhir.js +316 -0
  207. package/tx/importers/import-loinc.module.js +1536 -0
  208. package/tx/importers/import-ndc.module.js +1088 -0
  209. package/tx/importers/import-rxnorm.module.js +898 -0
  210. package/tx/importers/import-sct.module.js +2457 -0
  211. package/tx/importers/import-unii.module.js +601 -0
  212. package/tx/importers/readme.md +453 -0
  213. package/tx/importers/subset-loinc.module.js +1081 -0
  214. package/tx/importers/subset-rxnorm.module.js +938 -0
  215. package/tx/importers/tx-import-base.js +351 -0
  216. package/tx/importers/tx-import-settings.js +310 -0
  217. package/tx/importers/tx-import.js +357 -0
  218. package/tx/library/canonical-resource.js +88 -0
  219. package/tx/library/capabilitystatement.js +292 -0
  220. package/tx/library/codesystem.js +774 -0
  221. package/tx/library/conceptmap.js +568 -0
  222. package/tx/library/designations.js +932 -0
  223. package/tx/library/errors.js +77 -0
  224. package/tx/library/extensions.js +117 -0
  225. package/tx/library/namingsystem.js +322 -0
  226. package/tx/library/operation-outcome.js +127 -0
  227. package/tx/library/parameters.js +105 -0
  228. package/tx/library/renderer.js +1559 -0
  229. package/tx/library/terminologycapabilities.js +418 -0
  230. package/tx/library/ucum-parsers.js +1029 -0
  231. package/tx/library/ucum-service.js +370 -0
  232. package/tx/library/ucum-types.js +1099 -0
  233. package/tx/library/valueset.js +543 -0
  234. package/tx/library.js +676 -0
  235. package/tx/ocl/cm-ocl.js +106 -0
  236. package/tx/ocl/cs-ocl.js +39 -0
  237. package/tx/ocl/vs-ocl.js +105 -0
  238. package/tx/operation-context.js +568 -0
  239. package/tx/params.js +613 -0
  240. package/tx/provider.js +403 -0
  241. package/tx/sct/ecl.js +1560 -0
  242. package/tx/sct/expressions.js +2077 -0
  243. package/tx/sct/structures.js +1396 -0
  244. package/tx/tx-html.js +1063 -0
  245. package/tx/tx.fhir.org.yml +39 -0
  246. package/tx/tx.js +927 -0
  247. package/tx/vs/vs-api.js +112 -0
  248. package/tx/vs/vs-database.js +786 -0
  249. package/tx/vs/vs-package.js +358 -0
  250. package/tx/vs/vs-vsac.js +366 -0
  251. package/tx/workers/batch-validate.js +129 -0
  252. package/tx/workers/batch.js +361 -0
  253. package/tx/workers/closure.js +32 -0
  254. package/tx/workers/expand.js +1845 -0
  255. package/tx/workers/lookup.js +407 -0
  256. package/tx/workers/metadata.js +467 -0
  257. package/tx/workers/operations.js +34 -0
  258. package/tx/workers/read.js +164 -0
  259. package/tx/workers/search.js +384 -0
  260. package/tx/workers/subsumes.js +334 -0
  261. package/tx/workers/translate.js +492 -0
  262. package/tx/workers/validate.js +2504 -0
  263. package/tx/workers/worker.js +904 -0
  264. package/tx/xml/capabilitystatement-xml.js +63 -0
  265. package/tx/xml/codesystem-xml.js +62 -0
  266. package/tx/xml/conceptmap-xml.js +65 -0
  267. package/tx/xml/namingsystem-xml.js +65 -0
  268. package/tx/xml/operationoutcome-xml.js +127 -0
  269. package/tx/xml/parameters-xml.js +312 -0
  270. package/tx/xml/terminologycapabilities-xml.js +64 -0
  271. package/tx/xml/valueset-xml.js +64 -0
  272. package/tx/xml/xml-base.js +603 -0
  273. package/vcl/vcl-parser.js +1098 -0
  274. package/vcl/vcl.js +253 -0
  275. package/windows-install.js +19 -0
  276. package/xig/xig-template.html +124 -0
  277. package/xig/xig.js +3049 -0
@@ -0,0 +1,603 @@
1
+ //
2
+ // FHIR XML Base Class
3
+ // Common functionality for all FHIR resource XML serialization/deserialization
4
+ //
5
+
6
+ /**
7
+ * Base class for FHIR XML serialization/deserialization
8
+ * Contains shared methods for converting between FHIR JSON and XML formats
9
+ */
10
+ class FhirXmlBase {
11
+
12
+ /**
13
+ * FHIR elements that should always be arrays in JSON
14
+ * These are elements that are arrays at the TOP LEVEL of resources or in unambiguous contexts
15
+ * @type {Set<string>}
16
+ */
17
+ static _arrayElements = new Set([
18
+ // Common resource elements
19
+ 'identifier',
20
+ 'contact',
21
+ 'useContext',
22
+ 'jurisdiction',
23
+ 'extension',
24
+ 'modifierExtension',
25
+
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
31
+
32
+ // ValueSet elements
33
+ 'include',
34
+ 'exclude',
35
+ 'contains',
36
+ 'parameter',
37
+ 'valueSet',
38
+
39
+ // ConceptMap elements
40
+ 'group',
41
+ 'element',
42
+ 'target',
43
+ 'dependsOn',
44
+ 'product',
45
+ 'additionalAttribute',
46
+
47
+ // OperationOutcome elements
48
+ 'issue',
49
+ 'location',
50
+ 'expression',
51
+
52
+ // Parameters elements
53
+ 'part',
54
+
55
+ // Common data type elements
56
+ 'coding',
57
+ 'telecom',
58
+ 'address',
59
+ 'given',
60
+ 'prefix',
61
+ 'suffix',
62
+ 'line',
63
+ 'link',
64
+ 'entry',
65
+
66
+ // NamingSystem elements
67
+ 'uniqueId',
68
+ ]);
69
+
70
+ /**
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)
74
+ * @type {Set<string>}
75
+ */
76
+ static _resourceLevelArrayElements = new Set([
77
+ 'property', // Array in CodeSystem.property and concept.property, but single in filter
78
+ ]);
79
+
80
+ /**
81
+ * Element names that represent boolean types in FHIR
82
+ * @type {Set<string>}
83
+ */
84
+ static _booleanElements = new Set([
85
+ 'valueBoolean',
86
+ 'experimental',
87
+ 'caseSensitive',
88
+ 'compositional',
89
+ 'versionNeeded',
90
+ 'inactive',
91
+ 'notSelectable',
92
+ 'abstract',
93
+ 'immutable',
94
+ 'lockedDate',
95
+ 'preferred',
96
+ ]);
97
+
98
+ /**
99
+ * Element names that represent integer types in FHIR
100
+ * @type {Set<string>}
101
+ */
102
+ static _integerElements = new Set([
103
+ 'valueInteger',
104
+ 'valueUnsignedInt',
105
+ 'valuePositiveInt',
106
+ 'count',
107
+ 'offset',
108
+ 'total',
109
+ ]);
110
+
111
+ /**
112
+ * Element names that represent decimal types in FHIR
113
+ * @type {Set<string>}
114
+ */
115
+ static _decimalElements = new Set([
116
+ 'valueDecimal',
117
+ ]);
118
+
119
+ // ==================== XML PARSING (XML -> JSON) ====================
120
+
121
+ /**
122
+ * Convert a manually parsed XML element to FHIR JSON format
123
+ * @param {Object} element - Element with {name, attributes, children}
124
+ * @param {string} resourceType - The FHIR resource type
125
+ * @returns {Object} FHIR JSON object
126
+ */
127
+ static convertElementToFhirJson(element, resourceType) {
128
+ const result = { resourceType };
129
+
130
+ for (const child of element.children) {
131
+ const key = child.name;
132
+ const { value, primitiveExt } = this._convertChildElementWithExt(child, resourceType);
133
+
134
+ // Handle the value
135
+ if (value !== null) {
136
+ if (this._isArrayElement(key, resourceType)) {
137
+ if (!result[key]) {
138
+ result[key] = [];
139
+ }
140
+ result[key].push(value);
141
+ } else {
142
+ result[key] = value;
143
+ }
144
+ }
145
+
146
+ // Handle primitive extension (e.g., _value with extension)
147
+ if (primitiveExt !== null) {
148
+ const extKey = '_' + key;
149
+ result[extKey] = primitiveExt;
150
+ }
151
+ }
152
+
153
+ return result;
154
+ }
155
+
156
+ /**
157
+ * Check if an element should be an array based on its name and parent context
158
+ * @param {string} elementName - The element name
159
+ * @param {string} parentContext - The parent element name or context
160
+ * @returns {boolean}
161
+ */
162
+ static _isArrayElement(elementName, parentContext) {
163
+ // These are always arrays regardless of context
164
+ if (this._arrayElements.has(elementName)) {
165
+ return true;
166
+ }
167
+
168
+ // 'property' is an array in CodeSystem.property, CodeSystem.concept.property
169
+ // but NOT in filter.property (which is a single code)
170
+ if (elementName === 'property') {
171
+ // property is array at resource level or inside concept, but not inside filter
172
+ return parentContext !== 'filter';
173
+ }
174
+
175
+ return false;
176
+ }
177
+
178
+ /**
179
+ * Converts a child element to appropriate JSON value, also handling primitive extensions
180
+ * @param {Object} child - Child element with {name, attributes, children}
181
+ * @param {string} parentContext - Parent element name for context-dependent array handling
182
+ * @returns {{value: *, primitiveExt: Object|null}} Converted value and primitive extension if any
183
+ * @private
184
+ */
185
+ // eslint-disable-next-line no-unused-vars
186
+ static _convertChildElementWithExt(child, parentContext = '') {
187
+ const hasValue = child.attributes.value !== undefined;
188
+ const hasChildren = child.children.length > 0;
189
+ const isExtensionElement = child.name === 'extension' || child.name === 'modifierExtension';
190
+
191
+ // Extension elements are NEVER primitive extensions - they are always complex elements
192
+ // Only primitive FHIR elements (like string, code, uri, etc.) can have primitive extensions
193
+ if (!isExtensionElement) {
194
+ // Check if children are only extensions (for primitive extension detection)
195
+ const extensionChildren = child.children.filter(
196
+ c => c.name === 'extension' || c.name === 'modifierExtension'
197
+ );
198
+ const nonExtensionChildren = child.children.filter(
199
+ c => c.name !== 'extension' && c.name !== 'modifierExtension'
200
+ );
201
+ const hasOnlyExtensions = hasChildren && nonExtensionChildren.length === 0;
202
+
203
+ // Case 1: Simple primitive with value, no children
204
+ if (hasValue && !hasChildren) {
205
+ return { value: this._convertPrimitiveValue(child.name, child.attributes.value), primitiveExt: null };
206
+ }
207
+
208
+ // Case 2: Primitive with extension but no value - this is a primitive extension only
209
+ // This ONLY applies when there are NO non-extension children
210
+ if (!hasValue && hasOnlyExtensions) {
211
+ const ext = this._buildExtensionObject(extensionChildren);
212
+ return { value: null, primitiveExt: ext };
213
+ }
214
+
215
+ // Case 3: Primitive with both value and extensions (no other children)
216
+ // This ONLY applies when there are NO non-extension children
217
+ if (hasValue && hasOnlyExtensions) {
218
+ const ext = this._buildExtensionObject(extensionChildren);
219
+ return { value: this._convertPrimitiveValue(child.name, child.attributes.value), primitiveExt: ext };
220
+ }
221
+ }
222
+
223
+ // Case 4: Complex element - process normally (includes extensions as regular children)
224
+ const obj = {};
225
+ const currentContext = child.name; // Use current element name as context for children
226
+
227
+ // Copy non-value attributes (like url for extensions)
228
+ for (const [attrName, attrValue] of Object.entries(child.attributes)) {
229
+ if (attrName !== 'value' && attrName !== 'xmlns') {
230
+ obj[attrName] = attrValue;
231
+ }
232
+ }
233
+
234
+ // Process ALL children (including extensions as normal array elements)
235
+ for (const grandchild of child.children) {
236
+ const key = grandchild.name;
237
+ const { value, primitiveExt } = this._convertChildElementWithExt(grandchild, currentContext);
238
+
239
+ // Handle the value
240
+ if (value !== null) {
241
+ if (this._isArrayElement(key, currentContext)) {
242
+ if (!obj[key]) {
243
+ obj[key] = [];
244
+ }
245
+ obj[key].push(value);
246
+ } else if (obj[key] !== undefined) {
247
+ // Convert to array if we see the same key twice
248
+ if (!Array.isArray(obj[key])) {
249
+ obj[key] = [obj[key]];
250
+ }
251
+ obj[key].push(value);
252
+ } else {
253
+ obj[key] = value;
254
+ }
255
+ }
256
+
257
+ // Handle primitive extension
258
+ if (primitiveExt !== null) {
259
+ const extKey = '_' + key;
260
+ obj[extKey] = primitiveExt;
261
+ }
262
+ }
263
+
264
+ return { value: Object.keys(obj).length > 0 ? obj : null, primitiveExt: null };
265
+ }
266
+
267
+ /**
268
+ * Build an extension object from extension children
269
+ * @param {Array} extensionChildren - Array of extension child elements
270
+ * @returns {Object} Extension object with extension/modifierExtension arrays
271
+ * @private
272
+ */
273
+ static _buildExtensionObject(extensionChildren) {
274
+ const ext = {};
275
+ for (const extChild of extensionChildren) {
276
+ const key = extChild.name;
277
+ const { value } = this._convertChildElementWithExt(extChild);
278
+ if (!ext[key]) {
279
+ ext[key] = [];
280
+ }
281
+ ext[key].push(value);
282
+ }
283
+ return ext;
284
+ }
285
+
286
+ /**
287
+ * Convert a primitive value to the appropriate JavaScript type
288
+ * @param {string} elementName - The element name
289
+ * @param {string} value - The string value from XML
290
+ * @returns {*} Converted value
291
+ * @private
292
+ */
293
+ static _convertPrimitiveValue(elementName, value) {
294
+ if (this._booleanElements.has(elementName)) {
295
+ return value === 'true';
296
+ }
297
+ if (this._integerElements.has(elementName)) {
298
+ return parseInt(value, 10);
299
+ }
300
+ if (this._decimalElements.has(elementName)) {
301
+ return parseFloat(value);
302
+ }
303
+ // Everything else stays as string
304
+ return value;
305
+ }
306
+
307
+ /**
308
+ * Simple child element conversion (without tracking primitive extensions)
309
+ * @param {Object} child - Child element with {name, attributes, children}
310
+ * @param {string} parentContext - Parent context for array handling
311
+ * @returns {*} Converted value
312
+ */
313
+ static convertChildElement(child, parentContext = '') {
314
+ return this._convertChildElementWithExt(child, parentContext).value;
315
+ }
316
+
317
+ // ==================== XML GENERATION (JSON -> XML) ====================
318
+
319
+ /**
320
+ * Get the FHIR namespace
321
+ * @returns {string}
322
+ */
323
+ static getNamespace() {
324
+ return 'http://hl7.org/fhir';
325
+ }
326
+
327
+ /**
328
+ * Get indentation string
329
+ * @param {number} level - Indentation level
330
+ * @returns {string}
331
+ */
332
+ static indent(level) {
333
+ return ' '.repeat(level);
334
+ }
335
+
336
+ /**
337
+ * Escape special characters for XML
338
+ * @param {*} value - Value to escape
339
+ * @returns {string}
340
+ */
341
+ static escapeXml(value) {
342
+ if (value === null || value === undefined) return '';
343
+ return String(value)
344
+ .replace(/&/g, '&amp;')
345
+ .replace(/</g, '&lt;')
346
+ .replace(/>/g, '&gt;')
347
+ .replace(/"/g, '&quot;')
348
+ .replace(/'/g, '&apos;');
349
+ }
350
+
351
+ /**
352
+ * Unescape XML entities
353
+ * @param {string} str - String to unescape
354
+ * @returns {string}
355
+ */
356
+ static unescapeXml(str) {
357
+ return str
358
+ .replace(/&amp;/g, '&')
359
+ .replace(/&lt;/g, '<')
360
+ .replace(/&gt;/g, '>')
361
+ .replace(/&quot;/g, '"')
362
+ .replace(/&apos;/g, "'");
363
+ }
364
+
365
+ /**
366
+ * Render an element to XML
367
+ * @param {string} name - Element name
368
+ * @param {*} value - Element value
369
+ * @param {number} level - Indentation level
370
+ * @returns {string} XML string
371
+ */
372
+ static renderElement(name, value, level) {
373
+ if (value === null || value === undefined) {
374
+ return '';
375
+ }
376
+
377
+ let xml = '';
378
+
379
+ if (Array.isArray(value)) {
380
+ for (const item of value) {
381
+ xml += this.renderElement(name, item, level);
382
+ }
383
+ } else if (typeof value === 'object') {
384
+ // Special handling for extension - url is an attribute
385
+ if (name === 'extension' || name === 'modifierExtension') {
386
+ const url = value.url ? ` url="${this.escapeXml(value.url)}"` : '';
387
+ xml += `${this.indent(level)}<${name}${url}>\n`;
388
+ xml += this.renderObject(value, level + 1, ['url']);
389
+ xml += `${this.indent(level)}</${name}>\n`;
390
+ } else {
391
+ xml += `${this.indent(level)}<${name}>\n`;
392
+ xml += this.renderObject(value, level + 1);
393
+ xml += `${this.indent(level)}</${name}>\n`;
394
+ }
395
+ } else if (typeof value === 'boolean' || typeof value === 'number') {
396
+ xml += `${this.indent(level)}<${name} value="${value}"/>\n`;
397
+ } else {
398
+ xml += `${this.indent(level)}<${name} value="${this.escapeXml(value)}"/>\n`;
399
+ }
400
+
401
+ return xml;
402
+ }
403
+
404
+ /**
405
+ * Render an object's properties to XML
406
+ * @param {Object} obj - Object to render
407
+ * @param {number} level - Indentation level
408
+ * @param {Array<string>} skipKeys - Keys to skip
409
+ * @returns {string} XML string
410
+ */
411
+ static renderObject(obj, level, skipKeys = []) {
412
+ let xml = '';
413
+
414
+ for (const [key, value] of Object.entries(obj)) {
415
+ // Skip primitive extension keys (handled separately)
416
+ if (key.startsWith('_')) {
417
+ continue;
418
+ }
419
+ if (skipKeys.includes(key)) {
420
+ continue;
421
+ }
422
+ xml += this.renderElement(key, value, level);
423
+ }
424
+
425
+ return xml;
426
+ }
427
+
428
+ /**
429
+ * Render elements in a specific order
430
+ * @param {Object} obj - Object to render
431
+ * @param {number} level - Indentation level
432
+ * @param {Array<string>} elementOrder - Ordered list of element names
433
+ * @returns {string} XML string
434
+ */
435
+ static renderElementsInOrder(obj, level, elementOrder) {
436
+ let xml = '';
437
+
438
+ // Process elements in order
439
+ for (const key of elementOrder) {
440
+ if (Object.hasOwn(obj, key) && key !== 'resourceType') {
441
+ xml += this.renderElement(key, obj[key], level);
442
+ }
443
+ }
444
+
445
+ // Process any remaining elements not in the order list
446
+ for (const key of Object.keys(obj)) {
447
+ if (!elementOrder.includes(key) && key !== 'resourceType' && !key.startsWith('_')) {
448
+ xml += this.renderElement(key, obj[key], level);
449
+ }
450
+ }
451
+
452
+ return xml;
453
+ }
454
+
455
+ /**
456
+ * Generate XML declaration and root element wrapper
457
+ * @param {string} resourceType - FHIR resource type
458
+ * @param {string} content - Inner XML content
459
+ * @returns {string} Complete XML document
460
+ */
461
+ static wrapInRootElement(resourceType, content) {
462
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
463
+ xml += `<${resourceType} xmlns="${this.getNamespace()}">\n`;
464
+ xml += content;
465
+ xml += `</${resourceType}>`;
466
+ return xml;
467
+ }
468
+
469
+ // ==================== XML STRING PARSING ====================
470
+
471
+ /**
472
+ * Manual XML parser - parses XML string to element tree
473
+ */
474
+ static parseXmlString(xml) {
475
+ const parser = new FhirXmlParser(xml);
476
+ return parser.parse();
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Simple XML parser for FHIR resources
482
+ * Parses XML string into {name, attributes, children} structure
483
+ */
484
+ class FhirXmlParser {
485
+ constructor(xml) {
486
+ this.xml = xml;
487
+ this.pos = 0;
488
+ }
489
+
490
+ parse() {
491
+ this._skipDeclaration();
492
+ this._skipWhitespace();
493
+ return this._parseElement();
494
+ }
495
+
496
+ _skipDeclaration() {
497
+ this._skipWhitespace();
498
+ if (this.xml.substring(this.pos, this.pos + 5) === '<?xml') {
499
+ const end = this.xml.indexOf('?>', this.pos);
500
+ if (end !== -1) {
501
+ this.pos = end + 2;
502
+ }
503
+ }
504
+ }
505
+
506
+ _skipWhitespace() {
507
+ while (this.pos < this.xml.length && /\s/.test(this.xml[this.pos])) {
508
+ this.pos++;
509
+ }
510
+ }
511
+
512
+ _parseElement() {
513
+ this._skipWhitespace();
514
+
515
+ if (this.xml[this.pos] !== '<') {
516
+ throw new Error(`Expected '<' at position ${this.pos}`);
517
+ }
518
+ this.pos++; // Skip '<'
519
+
520
+ // Parse element name
521
+ const nameEnd = this.xml.substring(this.pos).search(/[\s/>]/);
522
+ const name = this.xml.substring(this.pos, this.pos + nameEnd);
523
+ this.pos += nameEnd;
524
+
525
+ // Parse attributes
526
+ const attributes = {};
527
+ this._skipWhitespace();
528
+
529
+ while (this.pos < this.xml.length && this.xml[this.pos] !== '>' && this.xml[this.pos] !== '/') {
530
+ const attr = this._parseAttribute();
531
+ if (attr) {
532
+ attributes[attr.name] = attr.value;
533
+ }
534
+ this._skipWhitespace();
535
+ }
536
+
537
+ const children = [];
538
+
539
+ // Self-closing tag
540
+ if (this.xml[this.pos] === '/') {
541
+ this.pos += 2; // Skip '/>'
542
+ return { name, attributes, children };
543
+ }
544
+
545
+ this.pos++; // Skip '>'
546
+
547
+ // Parse children
548
+ while (this.pos < this.xml.length) {
549
+ this._skipWhitespace();
550
+
551
+ if (this.xml.substring(this.pos, this.pos + 2) === '</') {
552
+ // Closing tag
553
+ const closeEnd = this.xml.indexOf('>', this.pos);
554
+ this.pos = closeEnd + 1;
555
+ break;
556
+ }
557
+
558
+ if (this.xml[this.pos] === '<') {
559
+ // Check for comment
560
+ if (this.xml.substring(this.pos, this.pos + 4) === '<!--') {
561
+ const commentEnd = this.xml.indexOf('-->', this.pos);
562
+ this.pos = commentEnd + 3;
563
+ continue;
564
+ }
565
+
566
+ children.push(this._parseElement());
567
+ } else {
568
+ // Text content - skip for FHIR as values are in attributes
569
+ const textEnd = this.xml.indexOf('<', this.pos);
570
+ this.pos = textEnd;
571
+ }
572
+ }
573
+
574
+ return { name, attributes, children };
575
+ }
576
+
577
+ _parseAttribute() {
578
+ this._skipWhitespace();
579
+
580
+ if (this.xml[this.pos] === '>' || this.xml[this.pos] === '/') {
581
+ return null;
582
+ }
583
+
584
+ // Parse attribute name
585
+ const eqPos = this.xml.indexOf('=', this.pos);
586
+ const name = this.xml.substring(this.pos, eqPos).trim();
587
+ this.pos = eqPos + 1;
588
+
589
+ // Skip whitespace and opening quote
590
+ this._skipWhitespace();
591
+ const quote = this.xml[this.pos];
592
+ this.pos++;
593
+
594
+ // Parse attribute value
595
+ const valueEnd = this.xml.indexOf(quote, this.pos);
596
+ const value = FhirXmlBase.unescapeXml(this.xml.substring(this.pos, valueEnd));
597
+ this.pos = valueEnd + 1;
598
+
599
+ return { name, value };
600
+ }
601
+ }
602
+
603
+ module.exports = { FhirXmlBase, FhirXmlParser };