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,932 @@
1
+ const { LanguagePartType, Languages, Language, LanguageDefinitions} = require('../../library/languages');
2
+ const {validateParameter, validateOptionalParameter, validateArrayParameter} = require("../../library/utilities");
3
+
4
+ /**
5
+ * Display checking modes for concept designations
6
+ */
7
+ const DisplayCheckingStyle = {
8
+ EXACT: 'exact',
9
+ CASE_INSENSITIVE: 'caseInsensitive',
10
+ NORMALISED: 'normalised'
11
+ };
12
+
13
+ const allowedDesignationExtensions = [
14
+ 'http://hl7.org/fhir/StructureDefinition/coding-sctdescid',
15
+ 'http://hl7.org/fhir/StructureDefinition/rendering-style',
16
+ 'http://hl7.org/fhir/StructureDefinition/rendering-xhtml',
17
+ 'http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status',
18
+ 'http://hl7.org/fhir/StructureDefinition/codesystem-alternate'
19
+ ];
20
+ /**
21
+ * Text search filter with stemming support
22
+ */
23
+ class SearchFilterText {
24
+ constructor(filter) {
25
+ validateOptionalParameter(filter, 'filter', String);
26
+
27
+ this.filter = filter ? filter.toLowerCase() : null;
28
+ this.stems = [];
29
+ if (filter) {
30
+ this._process();
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Check if filter is empty
36
+ */
37
+ get isNull() {
38
+ return this.stems.length === 0;
39
+ }
40
+
41
+ /**
42
+ * Check if a value passes the filter
43
+ */
44
+ passes(value, returnRating = false) {
45
+ validateParameter(value, 'value', String);
46
+ validateOptionalParameter(returnRating, 'returnRating', Boolean);
47
+
48
+ if (this.null) {
49
+ return returnRating ? {passes: true, rating: 0} : true;
50
+ }
51
+
52
+ let rating = 0;
53
+ let i = 0;
54
+
55
+ while (i < value.length) {
56
+ if (this._isAlphaNumeric(value[i])) {
57
+ const j = i;
58
+ while (i < value.length && this._isAlphaNumeric(value[i])) {
59
+ i++;
60
+ }
61
+ const word = value.substring(j, i).toLowerCase();
62
+ const stemmed = this._stem(word);
63
+
64
+ if (this._find(stemmed)) {
65
+ if (returnRating) {
66
+ rating += value.length / this.stems.length;
67
+ return {passes: true, rating};
68
+ } else {
69
+ return true;
70
+ }
71
+ }
72
+ } else {
73
+ i++;
74
+ }
75
+ }
76
+
77
+ return returnRating ? {passes: false, rating: 0} : false;
78
+ }
79
+
80
+ /**
81
+ * Check if designations pass the filter
82
+ */
83
+ passesDesignations(cds) {
84
+ validateOptionalParameter(cds, 'cds', Designations);
85
+
86
+ if (this.isNull) {
87
+ return true;
88
+ }
89
+
90
+ if (!cds) return false;
91
+
92
+ for (const cd of cds.designations) {
93
+ if (cd.value && this.passes(cd.value)) {
94
+ return true;
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+
100
+ /**
101
+ * Calculate match score against stems
102
+ */
103
+ matches(stems) {
104
+ validateOptionalParameter(stems, 'stems', Array);
105
+
106
+ if (this.stems.length === 0) return 100;
107
+ if (!stems || stems.length === 0) return 0;
108
+
109
+ let result = 0;
110
+
111
+ for (const stem of stems) {
112
+ let incomplete = false;
113
+ let complete = false;
114
+
115
+ for (const filterStem of this.stems) {
116
+ if (filterStem === stem) {
117
+ complete = true;
118
+ } else if (stem.startsWith(filterStem)) {
119
+ incomplete = true;
120
+ }
121
+ }
122
+
123
+ if (complete) {
124
+ result += stem.length / this.stems.length;
125
+ } else if (incomplete) {
126
+ result += (stem.length / this.stems.length) / 2;
127
+ }
128
+ }
129
+
130
+ return result;
131
+ }
132
+
133
+ // Private methods
134
+
135
+ _process() {
136
+ let i = 0;
137
+
138
+ while (i < this.filter.length) {
139
+ if (this._isAlphaNumeric(this.filter[i])) {
140
+ const j = i;
141
+ while (i < this.filter.length && this._isAlphaNumeric(this.filter[i])) {
142
+ i++;
143
+ }
144
+ const word = this.filter.substring(j, i);
145
+ this.stems.push(this._stem(word));
146
+ } else {
147
+ i++;
148
+ }
149
+ }
150
+
151
+ this.stems.sort();
152
+ }
153
+
154
+ _isAlphaNumeric(char) {
155
+ return /[0-9a-zA-Z]/.test(char);
156
+ }
157
+
158
+ _stem(word) {
159
+ // Simple stemming - in practice you'd want a proper stemmer
160
+ return word.toLowerCase();
161
+ }
162
+
163
+ _find(stem) {
164
+ // Binary search
165
+ let left = 0;
166
+ let right = this.stems.length - 1;
167
+
168
+ while (left <= right) {
169
+ const mid = Math.floor((left + right) / 2);
170
+ const midStem = this.stems[mid];
171
+
172
+ if (stem.startsWith(midStem)) {
173
+ return true;
174
+ } else if (stem < midStem) {
175
+ right = mid - 1;
176
+ } else {
177
+ left = mid + 1;
178
+ }
179
+ }
180
+
181
+ return false;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Language matching types
187
+ */
188
+ const LangMatchType = {
189
+ LITERAL: 'literal',
190
+ FULL: 'full',
191
+ LANG_REGION: 'langRegion',
192
+ LANG: 'lang'
193
+ };
194
+
195
+ /**
196
+ * Display comparison sensitivity modes
197
+ */
198
+ const DisplayCompareSensitivity = {
199
+ CaseSensitive: 'case-sensitive',
200
+ CaseInsensitive: 'case-insensitive',
201
+ Normalized: 'normalized' // Ignore whitespace differences
202
+ };
203
+
204
+ /**
205
+ * Display difference results
206
+ */
207
+ const DisplayDifference = {
208
+ Exact: 'exact',
209
+ Case: 'case',
210
+ Normalized: 'normalized',
211
+ None: 'none'
212
+ };
213
+
214
+
215
+ /**
216
+ * Standard use codes for designations
217
+ */
218
+ const DesignationUse = {
219
+ DISPLAY: {
220
+ system: 'http://terminology.hl7.org/CodeSystem/hl7TermMaintInfra', // http://terminology.hl7.org/CodeSystem/designation-use',
221
+ code: 'preferredForLanguage' // 'display'
222
+ },
223
+ FSN: {
224
+ system: 'http://snomed.info/sct',
225
+ code: '900000000000003001' // Fully Specified Name
226
+ },
227
+ PREFERRED: {
228
+ system: 'http://snomed.info/sct',
229
+ code: '900000000000548007' // Preferred term
230
+ },
231
+ SYNONYM: {
232
+ system: 'http://snomed.info/sct',
233
+ code: '900000000000013009' // Synonym
234
+ }
235
+ };
236
+
237
+ /**
238
+ * Individual concept designation with language, use, and value
239
+ */
240
+ class Designation {
241
+ status;
242
+ language;
243
+ use; // Coding {system, version, code, display} - well be DesignationUse.DISPLAY if is display
244
+ value; // string
245
+ extensions = []; // extensions on the designation
246
+
247
+ constructor() {
248
+ this.status = null;
249
+ this.language = null;
250
+ this.use = null;
251
+ this.value = null;
252
+ this.extensions = [];
253
+ }
254
+
255
+ /**
256
+ * Get the display text for this designation
257
+ */
258
+ get display() {
259
+ return this.value ? this.value || '' : '';
260
+ }
261
+
262
+ /**
263
+ * Get a string representation of this designation
264
+ */
265
+ present() {
266
+ let result = this.value ? `"${this.value || ''}"` : '""';
267
+
268
+ if (this.language || this.use) {
269
+ result += ' (';
270
+ if (this.language) {
271
+ result += this.language.code;
272
+ }
273
+ if (this.use) {
274
+ result += '/' + this._renderCoding(this.use);
275
+ }
276
+ result += ')';
277
+ }
278
+
279
+ return result;
280
+ }
281
+
282
+ /**
283
+ * Render a coding object as text
284
+ */
285
+ _renderCoding(coding) {
286
+ if (!coding) return '--';
287
+ if (coding.display) return coding.display;
288
+ if (coding.code) return coding.code;
289
+ return coding.system || '--';
290
+ }
291
+
292
+ isPreferred() {
293
+ return this.use && this.use.system == DesignationUse.PREFERRED.system &&
294
+ this.use.code == DesignationUse.PREFERRED.code;
295
+ }
296
+
297
+ isActive() {
298
+ let inactive = ["withdrawn", "inactive"].includes(this.status);
299
+ return !inactive;
300
+ }
301
+
302
+ asObject() {
303
+ let obj = {}
304
+ if (this.language) {
305
+ obj.language = this.language.code;
306
+ }
307
+ if (this.use) {
308
+ obj.use = this.use;
309
+ }
310
+ if (this.value) {
311
+ obj.value = this.value;
312
+ }
313
+ for (let ext of this.extensions || []) {
314
+ if (allowedDesignationExtensions.includes(ext.url)) {
315
+ if (!obj.extension) {
316
+ obj.extension = [];
317
+ }
318
+ obj.extension.push(ext);
319
+ }
320
+ }
321
+ return obj;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Collection of concept designations with language matching and preference logic
327
+ */
328
+ class Designations {
329
+ constructor(languageDefinitions) {
330
+ validateParameter(languageDefinitions, "languageDefinitions", LanguageDefinitions);
331
+ this.languageDefinitions = languageDefinitions;
332
+ this.baseLang = null;
333
+ this.designations = [];
334
+ this.source = null; // Reference to CodeSystemProvider
335
+ }
336
+
337
+ /**
338
+ * Clear all designations and reset base language
339
+ */
340
+ clear() {
341
+ this.baseLang = null;
342
+ this.designations = [];
343
+ this.source = null; // Reference to CodeSystemProvider
344
+ }
345
+
346
+ /**
347
+ * Add a designation with string parameters
348
+ */
349
+ addDesignation(isDisplay, status, lang, use, display, extensions = []) {
350
+ validateParameter(status, "status", String);
351
+ validateParameter(isDisplay, "isDisplay", Boolean);
352
+ validateOptionalParameter(lang, "lang", String);
353
+ validateOptionalParameter(use, "use", Object);
354
+ validateParameter(display, "display", String);
355
+ validateArrayParameter(extensions, "extensions", Object);
356
+
357
+ const designation = new Designation();
358
+ designation.language = this.languageDefinitions.parse(lang);
359
+ designation.value = display;
360
+ designation.status = status;
361
+
362
+ if (isDisplay) {
363
+ designation.use = {
364
+ system: DesignationUse.DISPLAY.system,
365
+ code: DesignationUse.DISPLAY.code
366
+ };
367
+ } else if (use) {
368
+ designation.use = use;
369
+ }
370
+ if (extensions.length > 0) {
371
+ designation.extensions.push(...extensions)
372
+ }
373
+ this.designations.push(designation);
374
+ return designation;
375
+ }
376
+
377
+ /**
378
+ * Add designations from an array of displays
379
+ */
380
+ addDesignationsFromArray(status, isDisplay, lang, use, displays) {
381
+ validateParameter(status, "status", String);
382
+ validateParameter(isDisplay, "isDisplay", Boolean);
383
+ validateParameter(lang, "lang", String);
384
+ validateArrayParameter(displays, "displays", String, false);
385
+
386
+ if (displays) {
387
+ for (const display of displays) {
388
+ this.addDesignation(isDisplay, status, lang, use, display);
389
+ }
390
+ }
391
+ }
392
+
393
+ /**
394
+ * Add designation from FHIR CodeSystem concept designation
395
+ */
396
+ addDesignationFromConcept(concept) {
397
+ validateOptionalParameter(concept, 'concept', Object);
398
+ if (!concept) return;
399
+ this.addDesignation(false, "unknown", concept.language, concept.use, concept.value.trim(), concept.extension);
400
+ //
401
+ // if (context.display) {
402
+ // this.addDesignation(true, true, baseLanguage, null, context.display)
403
+ // }
404
+ // if (context.designation && Array.isArray(context.designation)) {
405
+ // for (const d of context.designation) {
406
+ // this.addDesignation(false, "unknown", d.language, d.use, d.value, d.extension);
407
+ // }
408
+ // }
409
+ }
410
+
411
+ /**
412
+ * Check if a display value exists with specified matching criteria
413
+ */
414
+ hasDisplay(langList, defLang, value, active, mode) {
415
+ validateOptionalParameter(langList, 'langList', Languages, true); // Allow null
416
+ validateOptionalParameter(defLang, 'defLang', Language);
417
+ validateParameter(value, 'value', String);
418
+ validateParameter(active, 'active', Boolean);
419
+ validateParameter(mode, 'mode', String);
420
+
421
+ const result = { found: false, difference: DisplayDifference.None };
422
+
423
+ for (const cd of this.designations) {
424
+ if (this._langsMatch(langList, cd.language, LangMatchType.LANG, defLang) &&
425
+ (!active || cd.isActive()) && cd.value && this._stringMatches(value, cd.value, mode, cd.language)) {
426
+ result.found = true;
427
+ return result;
428
+ }
429
+ }
430
+
431
+ if (mode === DisplayCheckingStyle.EXACT) {
432
+ for (const cd of this.designations) {
433
+ if (this._langsMatch(langList, cd.language, LangMatchType.LANG, defLang) &&
434
+ (!active || cd.isActive()) &&
435
+ cd.value &&
436
+ this._stringMatches(value, cd.value, DisplayCheckingStyle.CASE_INSENSITIVE, cd.language)) {
437
+ result.difference = DisplayDifference.Case;
438
+ return result;
439
+ }
440
+ }
441
+ }
442
+
443
+ if (mode !== DisplayCheckingStyle.NORMALISED) {
444
+ for (const cd of this.designations) {
445
+ if (this._langsMatch(langList, cd.language, LangMatchType.LANG, defLang) &&
446
+ (!active || cd.isActive()) &&
447
+ cd.value &&
448
+ this._stringMatches(value, cd.value, DisplayCheckingStyle.NORMALISED, cd.language)) {
449
+ result.difference = DisplayDifference.Normalized;
450
+ return result;
451
+ }
452
+ }
453
+ }
454
+
455
+ return result;
456
+ }
457
+
458
+ /**
459
+ * Count displays matching language criteria
460
+ */
461
+ displayCount(langList, defLang, displayOnly) {
462
+ validateOptionalParameter(langList, 'langList', Languages, true); // Allow null
463
+ validateOptionalParameter(defLang, 'defLang', Language);
464
+ validateParameter(displayOnly, 'displayOnly', Boolean);
465
+
466
+ let result = 0;
467
+
468
+ // Try full match first
469
+ for (const cd of this.designations) {
470
+ if ((!displayOnly || this.isDisplay(cd)) &&
471
+ this._langsMatch(langList, cd.language, LangMatchType.FULL, defLang) &&
472
+ cd.value) {
473
+ result++;
474
+ }
475
+ }
476
+
477
+ if (result === 0) {
478
+ // Try language-region match
479
+ for (const cd of this.designations) {
480
+ if ((!displayOnly || this.isDisplay(cd)) &&
481
+ this._langsMatch(langList, cd.language, LangMatchType.LANG_REGION, defLang) &&
482
+ cd.value) {
483
+ result++;
484
+ }
485
+ }
486
+ }
487
+
488
+ if (result === 0) {
489
+ // Try language-only match
490
+ for (const cd of this.designations) {
491
+ if ((!displayOnly || this.isDisplay(cd)) &&
492
+ this._langsMatch(langList, cd.language, LangMatchType.LANG, defLang) &&
493
+ cd.value) {
494
+ result++;
495
+ }
496
+ }
497
+ }
498
+
499
+ return result;
500
+ }
501
+
502
+ /**
503
+ * Present all matching designations as a formatted string
504
+ */
505
+ present(langList, defLang, displayOnly) {
506
+ validateOptionalParameter(langList, 'langList', Languages, true); // Allow null
507
+ validateOptionalParameter(defLang, 'defLang', Language);
508
+ validateParameter(displayOnly, 'displayOnly', Boolean);
509
+
510
+ const results = [];
511
+ let count = 0;
512
+
513
+ // Collect matching designations
514
+ for (const cd of this.designations) {
515
+ if ((!displayOnly || this.isDisplay(cd)) &&
516
+ this._langsMatch(langList, cd.language, LangMatchType.LANG, null) &&
517
+ cd.value) {
518
+ count++;
519
+ if (cd.language) {
520
+ results.push(`'${cd.display}' (${cd.language.code})`);
521
+ } else {
522
+ results.push(`'${cd.display}'`);
523
+ }
524
+ }
525
+ }
526
+
527
+ // If no language-specific matches, get all
528
+ if (count === 0) {
529
+ for (const cd of this.designations) {
530
+ if ((!displayOnly || this.isDisplay(cd)) && cd.value) {
531
+ count++;
532
+ if (cd.language) {
533
+ results.push(`'${cd.display}' (${cd.language.code})`);
534
+ } else {
535
+ results.push(`'${cd.display}'`);
536
+ }
537
+ }
538
+ }
539
+ }
540
+
541
+ return this._joinWithOr(results);
542
+ }
543
+
544
+ /**
545
+ * Check if designation should be included for given language criteria
546
+ */
547
+ include(cd, langList, defLang) {
548
+ validateParameter(cd, 'cd', Designation);
549
+ validateParameter(langList, 'langList', Languages, true); // Allow null
550
+ validateOptionalParameter(defLang, 'defLang', Language);
551
+
552
+ return this._langsMatch(langList, cd.language, LangMatchType.LANG, defLang);
553
+ }
554
+
555
+ /**
556
+ * Find the preferred designation for given language preferences
557
+ */
558
+ preferredDesignation(langList = null) {
559
+ if (this.designations.length === 0) {
560
+ return null;
561
+ }
562
+
563
+ if (!langList || langList.length == 0) {
564
+ // No language list, prefer base designations
565
+ for (const cd of this.designations) {
566
+ if (this.isDisplay(cd)) {
567
+ return cd;
568
+ }
569
+ }
570
+ for (const cd of this.designations) {
571
+ if (this._isPreferred(cd)) {
572
+ return cd;
573
+ }
574
+ }
575
+ return this.designations[0];
576
+ }
577
+
578
+ for (const lang of langList.languages) {
579
+ if (lang.quality <= 0) continue;
580
+
581
+ const matchTypes = [LangMatchType.FULL, LangMatchType.LANG_REGION, LangMatchType.LANG];
582
+
583
+ for (const matchType of matchTypes) {
584
+ for (const cd of this.designations) {
585
+ if (this._langMatches(lang, cd.language, matchType) && this.isDisplay(cd)) {
586
+ return cd;
587
+ }
588
+ }
589
+ for (const cd of this.designations) {
590
+ if (this._langMatches(lang, cd.language, matchType) && this._isPreferred(cd)) {
591
+ return cd;
592
+ }
593
+ }
594
+ for (const cd of this.designations) {
595
+ if (this._langMatches(lang, cd.language, matchType)) {
596
+ return cd;
597
+ }
598
+ }
599
+ }
600
+ }
601
+
602
+ return null;
603
+ }
604
+
605
+ /**
606
+ * Select the best designation from all collected matches
607
+ */ /*
608
+ _selectBestMatch(matches) {
609
+ // Sort by priority:
610
+ // 1. Match type (FULL > LANG_REGION > LANG)
611
+ // 2. Language quality
612
+ // 3. Designation type (base > display > other)
613
+ // 4. Language specificity (exact language > regional variant)
614
+
615
+ const matchTypePriority = {
616
+ [LangMatchType.FULL]: 3,
617
+ [LangMatchType.LANG_REGION]: 2,
618
+ [LangMatchType.LANG]: 1
619
+ };
620
+
621
+ const getDesignationTypePriority = (cd) => {
622
+ if (cd.base) return 3;
623
+ if (this.isDisplay(cd)) return 2;
624
+ return 1;
625
+ };
626
+
627
+ const getLanguageSpecificity = (cd) => {
628
+ // For same match type, prefer exact language over regional variants
629
+ // Shorter language codes are more general (fr vs fr-CA)
630
+ const code = cd.language?.code || '';
631
+ return -code.length; // Negative so shorter codes sort first
632
+ };
633
+
634
+ matches.sort((a, b) => {
635
+ // 1. Language quality
636
+ const qualityDiff = b.quality - a.quality;
637
+ if (qualityDiff !== 0) return qualityDiff;
638
+
639
+ // 2. Match type priority
640
+ const matchTypeDiff = matchTypePriority[b.matchType] - matchTypePriority[a.matchType];
641
+ if (matchTypeDiff !== 0) return matchTypeDiff;
642
+
643
+ // 3. Designation type
644
+ const designationTypeDiff = getDesignationTypePriority(b.designation) - getDesignationTypePriority(a.designation);
645
+ if (designationTypeDiff !== 0) return designationTypeDiff;
646
+
647
+ // 4. Language specificity (for same match type, prefer more specific matches)
648
+ return getLanguageSpecificity(b.designation) - getLanguageSpecificity(a.designation);
649
+ });
650
+
651
+ return matches[0].designation;
652
+ }
653
+ */
654
+ /**
655
+ * Get preferred display text
656
+ */
657
+ preferredDisplay(langList, defLang) {
658
+ const cd = this.preferredDesignation(langList, defLang);
659
+ return cd ? cd.display : '';
660
+ }
661
+
662
+ /**
663
+ * Get summary of all designations
664
+ */
665
+ summary() {
666
+ return this.designations.map(cd => cd.present()).join(', ');
667
+ }
668
+
669
+ /**
670
+ * Present this designations object
671
+ */
672
+ presentSelf() {
673
+ let result = this.baseLang ? `Lang: ${this.baseLang.code}` : 'Lang: ??';
674
+ if (this.source) {
675
+ result += `; source: ${this.source.constructor.name}`;
676
+ }
677
+ return result;
678
+ }
679
+
680
+ /**
681
+ * Get language code for base language
682
+ */
683
+ get langCode() {
684
+ return this.baseLang ? this.baseLang.code : 'en';
685
+ }
686
+
687
+ /**
688
+ * Get count of all designations
689
+ * @returns {number}
690
+ */
691
+ get count() {
692
+ return this.designations.length;
693
+ }
694
+ // Private helper methods
695
+
696
+ /**
697
+ * Check if designation is a display designation
698
+ */
699
+ isDisplay(cd) {
700
+ if (!cd.use) {
701
+ return true;
702
+ }
703
+ if ((cd.use.system === DesignationUse.DISPLAY.system &&
704
+ cd.use.code === DesignationUse.DISPLAY.code) ||
705
+ (cd.use.system === DesignationUse.PREFERRED.system &&
706
+ cd.use.code === DesignationUse.PREFERRED.code)) {
707
+ return true;
708
+ }
709
+ return this.source && this.source.isDisplay(cd);
710
+ }
711
+
712
+ /**
713
+ * Check if designation is a display designation
714
+ */
715
+ _isPreferred(cd) {
716
+ return !cd.use ||
717
+ (cd.use.system === DesignationUse.DISPLAY.system &&
718
+ cd.use.code === DesignationUse.DISPLAY.code) ||
719
+ (cd.use.system === DesignationUse.PREFERRED.system &&
720
+ cd.use.code === DesignationUse.PREFERRED.code);
721
+ }
722
+
723
+ /**
724
+ * Get depth for match type
725
+ */
726
+ _depthForMatchType(matchType) {
727
+ switch (matchType) {
728
+ case LangMatchType.LITERAL:
729
+ case LangMatchType.FULL:
730
+ return LanguagePartType.EXTENSION;
731
+ case LangMatchType.LANG_EXACT:
732
+ return LanguagePartType.REGION;
733
+ case LangMatchType.LANG_REGION:
734
+ return LanguagePartType.REGION;
735
+ case LangMatchType.LANG:
736
+ return LanguagePartType.LANGUAGE;
737
+ default:
738
+ return LanguagePartType.EXTENSION;
739
+ }
740
+ }
741
+
742
+ /**
743
+ * Check if a single language entry matches a stated language
744
+ */
745
+ _langMatches(langEntry, statedLang, matchType) {
746
+ const actualLang = statedLang || this.baseLang;
747
+
748
+ if (langEntry.quality <= 0) {
749
+ return false;
750
+ }
751
+
752
+ if (langEntry.code === '*' && matchType !== LangMatchType.LITERAL) {
753
+ return true;
754
+ }
755
+
756
+ if (actualLang) {
757
+ if (matchType === LangMatchType.LITERAL) {
758
+ return langEntry.code === actualLang.code;
759
+ }
760
+
761
+ // Parse the language entry if needed
762
+ let parsedLang = langEntry;
763
+ if (typeof langEntry.code === 'string') {
764
+ parsedLang = this.languageDefinitions.parse(langEntry.code);
765
+ }
766
+
767
+ if (parsedLang && parsedLang.matches(actualLang, this._depthForMatchType(matchType))) {
768
+ return true;
769
+ }
770
+ }
771
+
772
+ return false;
773
+ }
774
+
775
+ /**
776
+ * Check if language list matches stated language
777
+ */
778
+ _langsMatch(langList, statedLang, matchType, defLang) {
779
+ if (defLang && statedLang && statedLang.matches(defLang)) {
780
+ return true;
781
+ }
782
+
783
+ if (!statedLang || !langList) {
784
+ return true;
785
+ }
786
+
787
+ for (const langEntry of langList.languages) {
788
+ if (this._langMatches(langEntry, statedLang, matchType)) {
789
+ return true;
790
+ }
791
+ }
792
+
793
+ return false;
794
+ }
795
+
796
+ /**
797
+ * Check if strings match according to specified mode
798
+ */
799
+ _stringMatches(source, possible, mode) {
800
+ // We ignore lang parameter for now, like the Pascal version
801
+ switch (mode) {
802
+ case DisplayCheckingStyle.EXACT:
803
+ return source === possible;
804
+ case DisplayCheckingStyle.CASE_INSENSITIVE:
805
+ return source.toLowerCase() === possible.toLowerCase();
806
+ case DisplayCheckingStyle.NORMALISED:
807
+ return this._normalizeWhitespace(source).toLowerCase() ===
808
+ this._normalizeWhitespace(possible).toLowerCase();
809
+ default:
810
+ return false;
811
+ }
812
+ }
813
+
814
+ /**
815
+ * Normalize whitespace in a string
816
+ */
817
+ _normalizeWhitespace(str) {
818
+ return str.replace(/\s+/g, ' ').trim();
819
+ }
820
+
821
+ /**
822
+ * Join array with commas and final "or"
823
+ */
824
+ _joinWithOr(items) {
825
+ if (items.length === 0) return '';
826
+ if (items.length === 1) return items[0];
827
+ if (items.length === 2) return `${items[0]} or ${items[1]}`;
828
+
829
+ const lastItem = items.pop();
830
+ return `${items.join(', ')} or ${lastItem}`;
831
+ }
832
+
833
+ /**
834
+ * Get allowed displays as a list (for error messages)
835
+ * @param {Array<string>} output - Array to populate
836
+ * @param {Languages} languages - Languages to consider
837
+ * @param {Language} defaultLang - Default language fallback
838
+ */
839
+ allowedDisplays(output, languages = null, defaultLang = null) {
840
+ const seen = new Set();
841
+
842
+ for (const d of this.designations) {
843
+ if (!d.isActive() || !(this.isDisplay(d)) || !d.value) continue;
844
+ if (seen.has(d.value)) continue;
845
+
846
+ // Check language match
847
+ if (languages && languages.length > 0) {
848
+ let langMatch = false;
849
+ if (d.language) {
850
+ langMatch = languagesHasMatch(languages, d.language);
851
+ } else if (defaultLang || this.baseLang) {
852
+ langMatch = true;
853
+ }
854
+ if (!langMatch) continue;
855
+ }
856
+
857
+ seen.add(d.value);
858
+ output.push(d.value);
859
+ }
860
+ }
861
+
862
+
863
+ /**
864
+ * Get the inactive status string for a display
865
+ * @param {string} display - Display text to find
866
+ * @returns {string} Status string or empty
867
+ */
868
+ status(display) {
869
+ for (const d of this.designations) {
870
+ if (d.value === display) {
871
+ return d.status;
872
+ }
873
+ }
874
+ return '';
875
+ }
876
+
877
+ /**
878
+ * Check if this collection has any displays for the given languages
879
+ * @param {Languages} languages - Languages to check
880
+ * @returns {boolean}
881
+ */
882
+ hasAnyDisplays(languages) {
883
+ return this.displayCount(languages, null, true) > 0;
884
+ }
885
+
886
+ /**
887
+ * Get all designations as an array (for iteration)
888
+ * @returns {ConceptDesignation[]}
889
+ */
890
+ all() {
891
+ return [...this.designations];
892
+ }
893
+
894
+ /**
895
+ * Iterator support
896
+ */
897
+ [Symbol.iterator]() {
898
+ return this.designations[Symbol.iterator]();
899
+ }
900
+ }
901
+
902
+
903
+ /**
904
+ * Check if a Languages collection has a match for a given language
905
+ * @param {Languages} langs - Languages collection
906
+ * @param {Language|string} target - Target language to match
907
+ * @returns {boolean}
908
+ */
909
+ function languagesHasMatch(langs, target) {
910
+ if (!langs || langs.length === 0) return false;
911
+
912
+ const targetLang = typeof target === 'string' ? new Language(target) : target;
913
+
914
+ for (const lang of langs) {
915
+ if (lang.matchesForDisplay(targetLang)) {
916
+ return true;
917
+ }
918
+ }
919
+ return false;
920
+ }
921
+
922
+
923
+ module.exports = {
924
+ Designation,
925
+ Designations,
926
+ DesignationUse,
927
+ SearchFilterText,
928
+ DisplayCheckingStyle,
929
+ DisplayCompareSensitivity,
930
+ DisplayDifference,
931
+ LangMatchType
932
+ };