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,779 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const {validateParameter, validateOptionalParameter, Utilities} = require("./utilities");
4
+
5
+ /**
6
+ * Language part types for matching depth
7
+ */
8
+ const LanguagePartType = {
9
+ NONE: 0,
10
+ LANGUAGE: 1,
11
+ EXTLANG: 2,
12
+ SCRIPT: 3,
13
+ REGION: 4,
14
+ VARIANT: 5,
15
+ EXTENSION: 6
16
+ };
17
+
18
+ /**
19
+ * Base class for language entries
20
+ */
21
+ class LanguageEntry {
22
+ constructor() {
23
+ this.code = '';
24
+ this.displays = [];
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Language definition entry
30
+ */
31
+ class LanguageLanguage extends LanguageEntry {
32
+ constructor() {
33
+ super();
34
+ this.suppressScript = '';
35
+ this.scope = '';
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Extended language definition entry
41
+ */
42
+ class LanguageExtLang extends LanguageEntry {}
43
+
44
+ /**
45
+ * Script definition entry
46
+ */
47
+ class LanguageScript extends LanguageEntry {}
48
+
49
+ /**
50
+ * Region definition entry
51
+ */
52
+ class LanguageRegion extends LanguageEntry {}
53
+
54
+ /**
55
+ * Variant definition entry
56
+ */
57
+ class LanguageVariant extends LanguageEntry {}
58
+
59
+ /**
60
+ * Individual language representation based on BCP 47
61
+ */
62
+ class Language {
63
+ constructor(code = '', languageDefinitions = null) {
64
+ this.code = code;
65
+ this.language = '';
66
+ this.extLang = [];
67
+ this.script = '';
68
+ this.region = '';
69
+ this.variant = '';
70
+ this.extension = '';
71
+ this.privateUse = [];
72
+ this.quality = 1.0; // For Accept-Language header quality values
73
+
74
+ if (this.code) {
75
+ this._parse(languageDefinitions);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Get system default language, fallback to en-US
81
+ */
82
+ static _getDefaultLanguage() {
83
+ try {
84
+ // Try to get system locale
85
+ const locale = Intl.DateTimeFormat().resolvedOptions().locale ||
86
+ process.env.LANG ||
87
+ process.env.LANGUAGE ||
88
+ os.platform() === 'win32' ? 'en-US' : 'en-US';
89
+
90
+ return locale.replace('_', '-'); // Convert underscore to hyphen
91
+ } catch (error) {
92
+ return 'en-US';
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Create Language from xml:lang attribute
98
+ */
99
+ static fromXmlLang(xmlLang) {
100
+ return new Language(xmlLang || 'en-US');
101
+ }
102
+
103
+ /**
104
+ * Create Language from system default
105
+ */
106
+ static fromSystemDefault() {
107
+ return new Language(this._getDefaultLanguage());
108
+ }
109
+
110
+ /**
111
+ * Parse the language code according to BCP 47
112
+ */
113
+ _parse(languageDefinitions) {
114
+ if (!this.code) return;
115
+
116
+ const parts = this.code.split('-');
117
+ let index = 0;
118
+
119
+ // Language (required)
120
+ if (index < parts.length) {
121
+ this.language = parts[index];
122
+ if (this.language != '*' && languageDefinitions && !languageDefinitions.languages.has(this.language)) {
123
+ throw new Error("The language '"+this.language+"' in the code '"+this.language+"' is not valid");
124
+ }
125
+ index++;
126
+ }
127
+
128
+ // Extended language (up to 3)
129
+ for (let i = 0; i < 3 && index < parts.length; i++) {
130
+ const part = parts[index];
131
+ if (part.length === 3 && /^[a-zA-Z]{3}$/.test(part)) {
132
+ if (languageDefinitions && !languageDefinitions.extLanguages.has(part)) {
133
+ throw new Error("The extLanguage '"+part+"' in the code '"+code+"' is not valid");
134
+ }
135
+ this.extLang.push(part.toLowerCase());
136
+ index++;
137
+ } else {
138
+ break;
139
+ }
140
+ }
141
+
142
+ // Script (4 letters)
143
+ if (index < parts.length) {
144
+ const part = parts[index];
145
+ if (part.length === 4 && /^[a-zA-Z]{4}$/.test(part)) {
146
+ if (languageDefinitions && !languageDefinitions.scripts.has(part)) {
147
+ throw new Error("The script '"+part+"' in the code '"+code+"' is not valid");
148
+ }
149
+ this.script = part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
150
+ index++;
151
+ }
152
+ }
153
+
154
+ // Region (2 letters or 3 digits)
155
+ if (index < parts.length) {
156
+ const part = parts[index];
157
+ if ((part.length === 2 && /^[a-zA-Z]{2}$/.test(part)) ||
158
+ (part.length === 3 && /^[0-9]{3}$/.test(part))) {
159
+ if (languageDefinitions && !languageDefinitions.regions.has(part)) {
160
+ throw new Error("The region '"+part+"' in the code '"+code+"' is not valid");
161
+ }
162
+ this.region = part.toUpperCase();
163
+ index++;
164
+ }
165
+ }
166
+
167
+ // Variant (5-8 chars or 4 chars starting with digit)
168
+ if (index < parts.length) {
169
+ const part = parts[index];
170
+ if ((part.length >= 5 && part.length <= 8) ||
171
+ (part.length === 4 && /^[0-9]/.test(part))) {
172
+ this.variant = part.toLowerCase();
173
+ index++;
174
+ }
175
+ }
176
+
177
+ // Extensions and private use
178
+ while (index < parts.length) {
179
+ const part = parts[index];
180
+ if (part === 'x') {
181
+ // Private use
182
+ index++;
183
+ while (index < parts.length) {
184
+ this.privateUse.push(parts[index]);
185
+ index++;
186
+ }
187
+ } else if (part.length === 1 && part !== 'x') {
188
+ // Extension
189
+ this.extension = part + '-' + parts.slice(index + 1).join('-');
190
+ break;
191
+ } else {
192
+ index++;
193
+ }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Check if this language matches another to a given depth
199
+ */
200
+ matches(other, depth = LanguagePartType.LANGUAGE) {
201
+ if (!other) return false;
202
+
203
+ if (depth >= LanguagePartType.EXTENSION) {
204
+ if (this.extension !== other.extension) return false;
205
+ }
206
+
207
+ if (depth >= LanguagePartType.VARIANT) {
208
+ if (this.variant !== other.variant) return false;
209
+ }
210
+
211
+ if (depth >= LanguagePartType.REGION) {
212
+ if (this.region !== other.region) return false;
213
+ }
214
+
215
+ if (depth >= LanguagePartType.SCRIPT) {
216
+ if (this.script !== other.script) return false;
217
+ }
218
+
219
+ if (depth >= LanguagePartType.EXTLANG) {
220
+ if (JSON.stringify(this.extLang) !== JSON.stringify(other.extLang)) return false;
221
+ }
222
+
223
+ if (depth >= LanguagePartType.LANGUAGE) {
224
+ return this.language === other.language;
225
+ }
226
+
227
+ return true;
228
+ }
229
+
230
+ /**
231
+ * Simple matching (all non-empty parts must match)
232
+ */
233
+ matchesSimple(other) {
234
+ if (!other) return false;
235
+
236
+ if (this.extension && this.extension !== other.extension) return false;
237
+ if (this.variant && this.variant !== other.variant) return false;
238
+ if (this.region && this.region !== other.region) return false;
239
+ if (this.script && this.script !== other.script) return false;
240
+ if (this.extLang.length > 0 &&
241
+ JSON.stringify(this.extLang) !== JSON.stringify(other.extLang)) return false;
242
+
243
+ return this.language === other.language;
244
+ }
245
+
246
+ /**
247
+ * Check if this is a simple language-region tag
248
+ */
249
+ isLangRegion() {
250
+ return (this.language !== '' && this.region !== '' &&
251
+ this.extLang.length === 0 && this.script === '' &&
252
+ this.variant === '' && this.extension === '' && this.privateUse.length === 0);
253
+ }
254
+
255
+ /**
256
+ * Check if this language matches another according to display matching rules:
257
+ * - Same language (e.g. en = en)
258
+ * - This language has more details than the target (e.g. en-AU matches en)
259
+ * - Either is blank and the other is en or en-US
260
+ */
261
+ matchesForDisplay(other) {
262
+ if (!other) return false;
263
+
264
+ const thisLang = typeof other === 'string' ? new Language(other) : other;
265
+
266
+ // Handle blank languages - match only if other is exactly 'en' or 'en-US'
267
+ if (!this.language || this.language === '') {
268
+ return thisLang.code === 'en' || thisLang.code === 'en-US';
269
+ }
270
+ if (!thisLang.language || thisLang.language === '') {
271
+ return this.code === 'en' || this.code === 'en-US';
272
+ }
273
+
274
+ // Must match at language level
275
+ if (this.language !== thisLang.language) {
276
+ return false;
277
+ }
278
+
279
+ // Check each component: if target specifies it, this must match exactly
280
+ // If target doesn't specify it, this can have any value (more specific is OK)
281
+
282
+ // Check script
283
+ if (thisLang.script && thisLang.script !== '') {
284
+ if (this.script !== thisLang.script) {
285
+ return false;
286
+ }
287
+ }
288
+
289
+ // Check region
290
+ if (thisLang.region && thisLang.region !== '') {
291
+ if (this.region !== thisLang.region) {
292
+ return false;
293
+ }
294
+ }
295
+
296
+ // Check variant
297
+ if (thisLang.variant && thisLang.variant !== '') {
298
+ if (this.variant !== thisLang.variant) {
299
+ return false;
300
+ }
301
+ }
302
+
303
+ return true;
304
+ }
305
+
306
+
307
+ isEnglishOrNothing() {
308
+ return !this.code || this.code === 'en' || this.code === 'en-US';
309
+ }
310
+
311
+ toString() {
312
+ const parts = [];
313
+
314
+ if (this.language) parts.push(this.language);
315
+ parts.push(...this.extLang);
316
+ if (this.script) parts.push(this.script);
317
+ if (this.region) parts.push(this.region);
318
+ if (this.variant) parts.push(this.variant);
319
+ if (this.extension) parts.push(this.extension);
320
+ if (this.privateUse.length > 0) {
321
+ parts.push('x');
322
+ parts.push(...this.privateUse);
323
+ }
324
+
325
+ return parts.join('-');
326
+ }
327
+
328
+ asString() {
329
+ if (this.quality != undefined && this.quality != 1) {
330
+ return this.code+"; q="+this.quality;
331
+ } else {
332
+ return this.code;
333
+ }
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Collection of languages with preference ordering
339
+ */
340
+ class Languages {
341
+ definitions;
342
+
343
+ constructor(definitions) {
344
+ validateOptionalParameter(definitions, "definitions", LanguageDefinitions);
345
+ this.definitions = definitions;
346
+ this.languages = [];
347
+
348
+ }
349
+
350
+ /**
351
+ * Parse Accept-Language header
352
+ * Format: "en-US,en;q=0.9,fr;q=0.8"
353
+ */
354
+ static fromAcceptLanguage(acceptLanguageHeader, languageDefinitions, addWildcard) {
355
+ const languages = new Languages(languageDefinitions);
356
+ let wc = false;
357
+
358
+ if (!acceptLanguageHeader) {
359
+ languages.add(Language.fromSystemDefault());
360
+ return languages;
361
+ }
362
+
363
+ const entries = acceptLanguageHeader.split(',').map(s => s.trim());
364
+ const parsed = [];
365
+
366
+ for (const entry of entries) {
367
+ const [langCode, qValue] = entry.split(';');
368
+ const quality = qValue ? Utilities.parseFloatOrDefault(qValue.split('=')[1], 1.0) : 1.0;
369
+
370
+ let lc = langCode.trim();
371
+ wc = wc || lc == '*';
372
+ const lang = new Language(lc, languageDefinitions);
373
+ lang.quality = quality;
374
+ parsed.push(lang);
375
+ }
376
+ if (addWildcard && !wc) {
377
+ const lang = new Language('*', languageDefinitions);
378
+ lang.quality = 0.01;
379
+ lang.implicit = true;
380
+ parsed.push(lang);
381
+ }
382
+ // Sort by quality (descending)
383
+ parsed.sort((a, b) => b.quality - a.quality);
384
+
385
+ for (const lang of parsed) {
386
+ languages.add(lang);
387
+ }
388
+
389
+ return languages;
390
+ }
391
+
392
+ /**
393
+ * Add a language to the collection
394
+ */
395
+ add(language) {
396
+ this.languages.push(language);
397
+ }
398
+
399
+ /**
400
+ * Get iterator for languages in preference order
401
+ */
402
+ [Symbol.iterator]() {
403
+ return this.languages[Symbol.iterator]();
404
+ }
405
+
406
+ /**
407
+ * Get language by index
408
+ */
409
+ get(index) {
410
+ return this.languages[index];
411
+ }
412
+
413
+ /**
414
+ * Get number of languages
415
+ */
416
+ get length() {
417
+ return this.languages.length;
418
+ }
419
+
420
+ /**
421
+ * Find best match for a given language
422
+ */
423
+ findBestMatch(target, depth = LanguagePartType.LANGUAGE) {
424
+ for (const lang of this.languages) {
425
+ if (lang.matches(target, depth)) {
426
+ return lang;
427
+ }
428
+ }
429
+ return null;
430
+ }
431
+
432
+ /**
433
+ * Check if any language matches the target
434
+ */
435
+ matches(target, depth = LanguagePartType.LANGUAGE) {
436
+ return this.findBestMatch(target, depth) !== null;
437
+ }
438
+
439
+ /**
440
+ * Get primary language (first in preference order)
441
+ */
442
+ getPrimary() {
443
+ return this.languages.length > 0 ? this.languages[0] : new Language('en-US');
444
+ }
445
+
446
+ isEnglishOrNothing() {
447
+ for (const lang of this.languages) {
448
+ if (!lang.isEnglishOrNothing()) {
449
+ return false;
450
+ }
451
+ }
452
+ return true;
453
+ }
454
+
455
+ /**
456
+ * Convert to string representation (similar to Accept-Language header format)
457
+ */
458
+ toString() {
459
+ if (this.languages.length === 0) {
460
+ return '';
461
+ }
462
+
463
+ return this.languages.map(lang => {
464
+ if (lang.quality === 1.0) {
465
+ return lang.code;
466
+ } else {
467
+ return `${lang.code};q=${lang.quality}`;
468
+ }
469
+ }).join(',');
470
+ }
471
+
472
+ asString(incWildcard) {
473
+ const parts = [];
474
+ for (const lang of this.languages) {
475
+ if ((incWildcard || lang.code !== '*') && !lang.implicit) {
476
+ parts.push(lang.asString());
477
+ }
478
+ }
479
+ return parts.join(', ');
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Global language definitions loaded from IETF registry
485
+ */
486
+ class LanguageDefinitions {
487
+ constructor() {
488
+ this.languages = new Map();
489
+ this.extLanguages = new Map();
490
+ this.scripts = new Map();
491
+ this.regions = new Map();
492
+ this.variants = new Map();
493
+ this.parsed = new Map(); // Cache for parsed languages
494
+ }
495
+
496
+ /**
497
+ * Load definitions from IETF language subtag registry file
498
+ */
499
+ static async fromFile(filePath) {
500
+ const definitions = new LanguageDefinitions();
501
+ const content = fs.readFileSync(filePath, 'utf8');
502
+ definitions._load(content);
503
+ return definitions;
504
+ }
505
+
506
+ /**
507
+ * Load definitions from content string
508
+ */
509
+ static fromContent(content) {
510
+ const definitions = new LanguageDefinitions();
511
+ definitions._load(content);
512
+ return definitions;
513
+ }
514
+
515
+ /**
516
+ * Check if source content is valid
517
+ */
518
+ static checkSource(source) {
519
+ return source.startsWith('%%') ? 'Ok' : 'Invalid';
520
+ }
521
+
522
+ /**
523
+ * Parse the IETF registry format
524
+ */
525
+ _load(source) {
526
+ const lines = source.split('\n');
527
+ let i = 0;
528
+
529
+ while (i < lines.length) {
530
+ if (lines[i].trim() === '%%') {
531
+ i++;
532
+ const [vars, nextIndex] = this._readVars(lines, i);
533
+ i = nextIndex;
534
+
535
+ switch (vars.Type) {
536
+ case 'language':
537
+ this._loadLanguage(vars);
538
+ break;
539
+ case 'extlang':
540
+ this._loadExtLang(vars);
541
+ break;
542
+ case 'script':
543
+ this._loadScript(vars);
544
+ break;
545
+ case 'region':
546
+ this._loadRegion(vars);
547
+ break;
548
+ case 'variant':
549
+ this._loadVariant(vars);
550
+ break;
551
+ case 'grandfathered':
552
+ case 'redundant':
553
+ // Skip these for now
554
+ break;
555
+ default:
556
+ throw new Error(`Unknown type: ${vars.Type} at line ${i + 1}`);
557
+ }
558
+ } else {
559
+ i++;
560
+ }
561
+ }
562
+ }
563
+
564
+ /**
565
+ * Read variables from the registry format
566
+ */
567
+ _readVars(lines, startIndex) {
568
+ const vars = {};
569
+ let i = startIndex;
570
+
571
+ while (i < lines.length && lines[i].trim() !== '%%') {
572
+ const line = lines[i].trim();
573
+ if (line && !line.startsWith(' ')) {
574
+ const colonIndex = line.indexOf(':');
575
+ if (colonIndex > 0) {
576
+ const key = line.substring(0, colonIndex).trim();
577
+ const value = line.substring(colonIndex + 1).trim();
578
+
579
+ if (vars[key]) {
580
+ vars[key] += '|' + value;
581
+ } else {
582
+ vars[key] = value;
583
+ }
584
+ }
585
+ }
586
+ i++;
587
+ }
588
+
589
+ return [vars, i];
590
+ }
591
+
592
+ /**
593
+ * Load language entry
594
+ */
595
+ _loadLanguage(vars) {
596
+ const lang = new LanguageLanguage();
597
+ lang.code = vars.Subtag;
598
+ lang.displays = vars.Description ? vars.Description.split('|') : [];
599
+ lang.suppressScript = vars['Suppress-Script'] || '';
600
+ lang.scope = vars.Scope || '';
601
+
602
+ if (this.languages.has(lang.code)) {
603
+ throw new Error(`Duplicate language code: ${lang.code}`);
604
+ }
605
+
606
+ this.languages.set(lang.code, lang);
607
+ }
608
+
609
+ /**
610
+ * Load extended language entry
611
+ */
612
+ _loadExtLang(vars) {
613
+ const extLang = new LanguageExtLang();
614
+ extLang.code = vars.Subtag;
615
+ extLang.displays = vars.Description ? vars.Description.split('|') : [];
616
+
617
+ if (this.extLanguages.has(extLang.code)) {
618
+ throw new Error(`Duplicate extlang code: ${extLang.code}`);
619
+ }
620
+
621
+ this.extLanguages.set(extLang.code, extLang);
622
+ }
623
+
624
+ /**
625
+ * Load script entry
626
+ */
627
+ _loadScript(vars) {
628
+ const script = new LanguageScript();
629
+ script.code = vars.Subtag;
630
+ script.displays = vars.Description ? vars.Description.split('|') : [];
631
+
632
+ if (this.scripts.has(script.code)) {
633
+ throw new Error(`Duplicate script code: ${script.code}`);
634
+ }
635
+
636
+ this.scripts.set(script.code, script);
637
+ }
638
+
639
+ /**
640
+ * Load region entry
641
+ */
642
+ _loadRegion(vars) {
643
+ const region = new LanguageRegion();
644
+ region.code = vars.Subtag;
645
+ region.displays = vars.Description ? vars.Description.split('|') : [];
646
+
647
+ if (this.regions.has(region.code)) {
648
+ throw new Error(`Duplicate region code: ${region.code}`);
649
+ }
650
+
651
+ this.regions.set(region.code, region);
652
+ }
653
+
654
+ /**
655
+ * Load variant entry
656
+ */
657
+ _loadVariant(vars) {
658
+ const variant = new LanguageVariant();
659
+ variant.code = vars.Subtag;
660
+ variant.displays = vars.Description ? vars.Description.split('|') : [];
661
+
662
+ if (this.variants.has(variant.code)) {
663
+ throw new Error(`Duplicate variant code: ${variant.code}`);
664
+ }
665
+
666
+ this.variants.set(variant.code, variant);
667
+ }
668
+
669
+ /**
670
+ * Parse and validate a language code
671
+ *
672
+ * @return {Language} parsed language (or null)
673
+ */
674
+ parse(code) {
675
+ if (!code) return null;
676
+
677
+ // Check cache first
678
+ if (this.parsed.has(code)) {
679
+ return this.parsed.get(code);
680
+ }
681
+
682
+ const parts = code.split('-');
683
+ let index = 0;
684
+
685
+ // Validate language
686
+ if (index >= parts.length) return null;
687
+ const langCode = parts[index].toLowerCase();
688
+ if (!this.languages.has(langCode) && langCode !== '*') {
689
+ return null; // Invalid language code
690
+ }
691
+
692
+ const lang = new Language(code);
693
+
694
+ // Cache the result
695
+ this.parsed.set(code, lang);
696
+ return lang;
697
+ }
698
+
699
+ /**
700
+ * Get display name for language
701
+ */
702
+ getDisplayForLang(code, displayIndex = 0) {
703
+ const lang = this.languages.get(code);
704
+ return lang && lang.displays[displayIndex] ? lang.displays[displayIndex] : code;
705
+ }
706
+
707
+ /**
708
+ * Get display name for region
709
+ */
710
+ getDisplayForRegion(code, displayIndex = 0) {
711
+ const region = this.regions.get(code);
712
+ return region && region.displays[displayIndex] ? region.displays[displayIndex] : code;
713
+ }
714
+
715
+ /**
716
+ * Get display name for script
717
+ */
718
+ getDisplayForScript(code, displayIndex = 0) {
719
+ const script = this.scripts.get(code);
720
+ return script && script.displays[displayIndex] ? script.displays[displayIndex] : code;
721
+ }
722
+
723
+ /**
724
+ * Present a language with full display names
725
+ */
726
+ present(lang, displayIndex = 0, template = null) {
727
+ if (!lang) return '';
728
+
729
+ if (template) {
730
+ return template
731
+ .replace('{{lang}}', this.getDisplayForLang(lang.language, displayIndex))
732
+ .replace('{{region}}', this.getDisplayForRegion(lang.region, displayIndex))
733
+ .replace('{{script}}', this.getDisplayForScript(lang.script, displayIndex));
734
+ }
735
+
736
+ let result = this.getDisplayForLang(lang.language, displayIndex);
737
+
738
+ const parts = [];
739
+ if (lang.script) {
740
+ parts.push(`Script=${this.getDisplayForScript(lang.script, 0)}`);
741
+ }
742
+ if (lang.region) {
743
+ parts.push(`Region=${this.getDisplayForRegion(lang.region, 0)}`);
744
+ }
745
+ if (lang.variant) {
746
+ const variant = this.variants.get(lang.variant);
747
+ const variantDisplay = variant && variant.displays[0] ? variant.displays[0] : lang.variant;
748
+ parts.push(`Variant=${variantDisplay}`);
749
+ }
750
+
751
+ if (parts.length > 0) {
752
+ result += ` (${parts.join(', ')})`;
753
+ }
754
+
755
+ return result;
756
+ }
757
+
758
+ /**
759
+ * Get number of available displays for a language
760
+ */
761
+ displayCount(lang) {
762
+ if (!lang) return 0;
763
+ const language = this.languages.get(lang.language);
764
+ return language ? language.displays.length : 0;
765
+ }
766
+ }
767
+
768
+ module.exports = {
769
+ Language,
770
+ Languages,
771
+ LanguageDefinitions,
772
+ LanguagePartType,
773
+ LanguageEntry,
774
+ LanguageLanguage,
775
+ LanguageExtLang,
776
+ LanguageScript,
777
+ LanguageRegion,
778
+ LanguageVariant
779
+ };