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,1098 @@
1
+ //
2
+ // Copyright 2025, Health Intersections Pty Ltd (http://www.healthintersections.com.au)
3
+ //
4
+ // Licensed under BSD-3: https://opensource.org/license/bsd-3-clause
5
+ //
6
+
7
+ /**
8
+ * Value Set Composition Language (VCL) Parser for JavaScript FHIR
9
+ * Based on the Java VCL parser but adapted for JavaScript
10
+ * Compatible with ES6+ and both Node.js and browser environments
11
+ */
12
+
13
+ class VCLParseException extends Error {
14
+ constructor(message, position = -1) {
15
+ super(position >= 0 ? `${message} at position ${position}` : message);
16
+ this.name = 'VCLParseException';
17
+ this.position = position;
18
+ }
19
+ }
20
+
21
+ const TokenType = {
22
+ DASH: 'DASH',
23
+ OPEN: 'OPEN',
24
+ CLOSE: 'CLOSE',
25
+ LCRLY: 'LCRLY',
26
+ RCRLY: 'RCRLY',
27
+ SEMI: 'SEMI',
28
+ COMMA: 'COMMA',
29
+ DOT: 'DOT',
30
+ STAR: 'STAR',
31
+ EQ: 'EQ',
32
+ IS_A: 'IS_A',
33
+ IS_NOT_A: 'IS_NOT_A',
34
+ DESC_OF: 'DESC_OF',
35
+ REGEX: 'REGEX',
36
+ IN: 'IN',
37
+ NOT_IN: 'NOT_IN',
38
+ GENERALIZES: 'GENERALIZES',
39
+ CHILD_OF: 'CHILD_OF',
40
+ DESC_LEAF: 'DESC_LEAF',
41
+ EXISTS: 'EXISTS',
42
+ URI: 'URI',
43
+ SCODE: 'SCODE',
44
+ QUOTED_VALUE: 'QUOTED_VALUE',
45
+ EOF: 'EOF'
46
+ };
47
+
48
+ const FilterOperator = {
49
+ EQUAL: '=',
50
+ IS_A: 'is-a',
51
+ IS_NOT_A: 'is-not-a',
52
+ DESCENDENT_OF: 'descendent-of',
53
+ REGEX: 'regex',
54
+ IN: 'in',
55
+ NOT_IN: 'not-in',
56
+ GENERALIZES: 'generalizes',
57
+ CHILD_OF: 'child-of',
58
+ DESCENDENT_LEAF: 'descendent-leaf',
59
+ EXISTS: 'exists'
60
+ };
61
+
62
+ class Token {
63
+ constructor(type, value, position) {
64
+ this.type = type;
65
+ this.value = value;
66
+ this.position = position;
67
+ }
68
+
69
+ toString() {
70
+ return `${this.type}(${this.value})`;
71
+ }
72
+ }
73
+
74
+ class VCLLexer {
75
+ constructor(input) {
76
+ this.input = input.trim();
77
+ this.pos = 0;
78
+ }
79
+
80
+ current() {
81
+ return this.pos < this.input.length ? this.input[this.pos] : '\0';
82
+ }
83
+
84
+ peek(offset = 0) {
85
+ const peekPos = this.pos + 1 + offset;
86
+ return peekPos < this.input.length ? this.input[peekPos] : '\0';
87
+ }
88
+
89
+ skipWhitespace() {
90
+ while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) {
91
+ this.pos++;
92
+ }
93
+ }
94
+
95
+ isIdentifierChar(c) {
96
+ return /[a-zA-Z0-9:?&%+\-.@#$!{}_]/.test(c);
97
+ }
98
+
99
+ isUriChar(c) {
100
+ return /[a-zA-Z0-9?&%+\-.@#$!{}_/]/.test(c);
101
+ }
102
+
103
+ isCodeChar(c) {
104
+ return /[a-zA-Z0-9\-_]/.test(c);
105
+ }
106
+
107
+ isVersionChar(c) {
108
+ return /[a-zA-Z0-9\-_.+]/.test(c);
109
+ }
110
+
111
+ readIdentifierChars() {
112
+ let result = '';
113
+ while (this.pos < this.input.length && this.isIdentifierChar(this.input[this.pos])) {
114
+ result += this.input[this.pos];
115
+ this.pos++;
116
+ }
117
+ return result;
118
+ }
119
+
120
+ readUriChars() {
121
+ let result = '';
122
+ while (this.pos < this.input.length && this.isUriChar(this.input[this.pos])) {
123
+ result += this.input[this.pos];
124
+ this.pos++;
125
+ }
126
+ return result;
127
+ }
128
+
129
+ readCodeChars() {
130
+ let result = '';
131
+ while (this.pos < this.input.length && this.isCodeChar(this.input[this.pos])) {
132
+ result += this.input[this.pos];
133
+ this.pos++;
134
+ }
135
+ return result;
136
+ }
137
+
138
+ readVersionChars() {
139
+ let result = '';
140
+ while (this.pos < this.input.length && this.isVersionChar(this.input[this.pos])) {
141
+ result += this.input[this.pos];
142
+ this.pos++;
143
+ }
144
+ return result;
145
+ }
146
+
147
+ readQuotedValue(startPos) {
148
+ let value = '';
149
+ this.pos++; // Skip opening quote
150
+
151
+ while (this.pos < this.input.length) {
152
+ const ch = this.input[this.pos];
153
+ if (ch === '"') {
154
+ this.pos++;
155
+ return new Token(TokenType.QUOTED_VALUE, value, startPos);
156
+ } else if (ch === '\\' && this.pos + 1 < this.input.length) {
157
+ this.pos++;
158
+ const escaped = this.input[this.pos];
159
+ if (escaped === '"' || escaped === '\\') {
160
+ value += escaped;
161
+ } else {
162
+ value += '\\' + escaped;
163
+ }
164
+ this.pos++;
165
+ } else {
166
+ value += ch;
167
+ this.pos++;
168
+ }
169
+ }
170
+
171
+ throw new VCLParseException('Unterminated quoted string', startPos);
172
+ }
173
+
174
+ tokenize() {
175
+ const tokens = [];
176
+
177
+ while (this.pos < this.input.length) {
178
+ this.skipWhitespace();
179
+ if (this.pos >= this.input.length) break;
180
+
181
+ const startPos = this.pos;
182
+ const ch = this.input[this.pos];
183
+
184
+ switch (ch) {
185
+ case '-':
186
+ tokens.push(new Token(TokenType.DASH, '-', startPos));
187
+ this.pos++;
188
+ break;
189
+ case '(':
190
+ tokens.push(new Token(TokenType.OPEN, '(', startPos));
191
+ this.pos++;
192
+ break;
193
+ case ')':
194
+ tokens.push(new Token(TokenType.CLOSE, ')', startPos));
195
+ this.pos++;
196
+ break;
197
+ case '{':
198
+ tokens.push(new Token(TokenType.LCRLY, '{', startPos));
199
+ this.pos++;
200
+ break;
201
+ case '}':
202
+ tokens.push(new Token(TokenType.RCRLY, '}', startPos));
203
+ this.pos++;
204
+ break;
205
+ case ';':
206
+ tokens.push(new Token(TokenType.SEMI, ';', startPos));
207
+ this.pos++;
208
+ break;
209
+ case ',':
210
+ tokens.push(new Token(TokenType.COMMA, ',', startPos));
211
+ this.pos++;
212
+ break;
213
+ case '.':
214
+ tokens.push(new Token(TokenType.DOT, '.', startPos));
215
+ this.pos++;
216
+ break;
217
+ case '*':
218
+ tokens.push(new Token(TokenType.STAR, '*', startPos));
219
+ this.pos++;
220
+ break;
221
+ case '=':
222
+ tokens.push(new Token(TokenType.EQ, '=', startPos));
223
+ this.pos++;
224
+ break;
225
+ case '/':
226
+ tokens.push(new Token(TokenType.REGEX, '/', startPos));
227
+ this.pos++;
228
+ break;
229
+ case '^':
230
+ tokens.push(new Token(TokenType.IN, '^', startPos));
231
+ this.pos++;
232
+ break;
233
+ case '>':
234
+ if (this.peek() === '>') {
235
+ tokens.push(new Token(TokenType.GENERALIZES, '>>', startPos));
236
+ this.pos += 2;
237
+ } else {
238
+ throw new VCLParseException(`Unexpected character: ${ch}`, this.pos);
239
+ }
240
+ break;
241
+ case '<':
242
+ if (this.peek() === '<') {
243
+ tokens.push(new Token(TokenType.IS_A, '<<', startPos));
244
+ this.pos += 2;
245
+ } else if (this.peek() === '!') {
246
+ tokens.push(new Token(TokenType.CHILD_OF, '<!', startPos));
247
+ this.pos += 2;
248
+ } else {
249
+ tokens.push(new Token(TokenType.DESC_OF, '<', startPos));
250
+ this.pos++;
251
+ }
252
+ break;
253
+ case '~':
254
+ if (this.peek() === '<' && this.peek(1) === '<') {
255
+ tokens.push(new Token(TokenType.IS_NOT_A, '~<<', startPos));
256
+ this.pos += 3;
257
+ } else if (this.peek() === '^') {
258
+ tokens.push(new Token(TokenType.NOT_IN, '~^', startPos));
259
+ this.pos += 2;
260
+ } else {
261
+ throw new VCLParseException(`Unexpected character: ${ch}`, this.pos);
262
+ }
263
+ break;
264
+ case '!':
265
+ if (this.peek() === '!' && this.peek(1) === '<') {
266
+ tokens.push(new Token(TokenType.DESC_LEAF, '!!<', startPos));
267
+ this.pos += 3;
268
+ } else {
269
+ throw new VCLParseException(`Unexpected character: ${ch}`, this.pos);
270
+ }
271
+ break;
272
+ case '?':
273
+ tokens.push(new Token(TokenType.EXISTS, '?', startPos));
274
+ this.pos++;
275
+ break;
276
+ case '"':
277
+ tokens.push(this.readQuotedValue(startPos));
278
+ break;
279
+ default:
280
+ if (/[a-zA-Z]/.test(ch)) {
281
+ const value = this.readCodeChars();
282
+
283
+ if (this.current() === ':') {
284
+ this.pos++;
285
+ // Read rest of URI
286
+ const uriRest = this.readUriChars();
287
+ let fullValue = value + ':' + uriRest;
288
+
289
+ // Check for version
290
+ if (this.pos < this.input.length && this.input[this.pos] === '|') {
291
+ this.pos++;
292
+ fullValue += '|' + this.readVersionChars();
293
+ }
294
+ tokens.push(new Token(TokenType.URI, fullValue, startPos));
295
+ } else {
296
+ tokens.push(new Token(TokenType.SCODE, value, startPos));
297
+ }
298
+ } else if (/[0-9]/.test(ch)) {
299
+ const value = this.readCodeChars();
300
+ tokens.push(new Token(TokenType.SCODE, value, startPos));
301
+ } else {
302
+ throw new VCLParseException(`Unexpected character: ${ch}`, this.pos);
303
+ }
304
+ }
305
+ }
306
+
307
+ tokens.push(new Token(TokenType.EOF, '', this.pos));
308
+ return tokens;
309
+ }
310
+ }
311
+
312
+ class VCLParserClass {
313
+ constructor(tokens, fhirFactory = null) {
314
+ this.tokens = tokens;
315
+ this.pos = 0;
316
+ this.fhirFactory = fhirFactory;
317
+ this.valueSet = this.createValueSet();
318
+ }
319
+
320
+ createValueSet() {
321
+ if (this.fhirFactory && typeof this.fhirFactory.createValueSet === 'function') {
322
+ return this.fhirFactory.createValueSet();
323
+ }
324
+
325
+ // Default FHIR ValueSet structure
326
+ return {
327
+ resourceType: 'ValueSet',
328
+ status: 'draft',
329
+ compose: {
330
+ include: [],
331
+ exclude: []
332
+ }
333
+ };
334
+ }
335
+
336
+ current() {
337
+ return this.pos < this.tokens.length ? this.tokens[this.pos] : new Token(TokenType.EOF, '', -1);
338
+ }
339
+
340
+ peek() {
341
+ return this.pos + 1 < this.tokens.length ? this.tokens[this.pos + 1] : new Token(TokenType.EOF, '', -1);
342
+ }
343
+
344
+ consume(expected) {
345
+ const current = this.current();
346
+ if (current.type !== expected) {
347
+ throw new VCLParseException(`Expected ${expected} but got ${current.type}`, current.position);
348
+ }
349
+ this.pos++;
350
+ }
351
+
352
+ expect(expected) {
353
+ const current = this.current();
354
+ if (current.type !== expected) {
355
+ throw new VCLParseException(`Expected ${expected} but got ${current.type}`, current.position);
356
+ }
357
+ }
358
+
359
+ isFilterOperator(tokenType) {
360
+ return [
361
+ TokenType.EQ, TokenType.IS_A, TokenType.IS_NOT_A, TokenType.DESC_OF,
362
+ TokenType.REGEX, TokenType.IN, TokenType.NOT_IN, TokenType.GENERALIZES,
363
+ TokenType.CHILD_OF, TokenType.DESC_LEAF, TokenType.EXISTS
364
+ ].includes(tokenType);
365
+ }
366
+
367
+ tokenTypeToFilterOperator(tokenType) {
368
+ const mapping = {
369
+ [TokenType.EQ]: FilterOperator.EQUAL,
370
+ [TokenType.IS_A]: FilterOperator.IS_A,
371
+ [TokenType.IS_NOT_A]: FilterOperator.IS_NOT_A,
372
+ [TokenType.DESC_OF]: FilterOperator.DESCENDENT_OF,
373
+ [TokenType.REGEX]: FilterOperator.REGEX,
374
+ [TokenType.IN]: FilterOperator.IN,
375
+ [TokenType.NOT_IN]: FilterOperator.NOT_IN,
376
+ [TokenType.GENERALIZES]: FilterOperator.GENERALIZES,
377
+ [TokenType.CHILD_OF]: FilterOperator.CHILD_OF,
378
+ [TokenType.DESC_LEAF]: FilterOperator.DESCENDENT_LEAF,
379
+ [TokenType.EXISTS]: FilterOperator.EXISTS
380
+ };
381
+ return mapping[tokenType];
382
+ }
383
+
384
+ isSimpleCodeList() {
385
+ let lookahead = this.pos;
386
+
387
+ while (lookahead < this.tokens.length) {
388
+ const token = this.tokens[lookahead];
389
+
390
+ if (token.type === TokenType.CLOSE) {
391
+ return true;
392
+ }
393
+
394
+ if (token.type === TokenType.OPEN && lookahead + 2 < this.tokens.length) {
395
+ if (this.tokens[lookahead + 1].type === TokenType.URI &&
396
+ this.tokens[lookahead + 2].type === TokenType.CLOSE) {
397
+ lookahead += 3;
398
+ continue;
399
+ }
400
+ }
401
+
402
+ if (token.type === TokenType.OPEN || token.type === TokenType.DASH || this.isFilterOperator(token.type)) {
403
+ return false;
404
+ }
405
+
406
+ lookahead++;
407
+ }
408
+
409
+ return true;
410
+ }
411
+
412
+ createConceptSet(systemUri, isExclusion) {
413
+ const conceptSet = {
414
+ system: '',
415
+ concept: [],
416
+ filter: [],
417
+ valueSet: []
418
+ };
419
+
420
+ if (systemUri) {
421
+ const pipePos = systemUri.indexOf('|');
422
+ if (pipePos >= 0) {
423
+ conceptSet.system = systemUri.substring(0, pipePos);
424
+ conceptSet.version = systemUri.substring(pipePos + 1);
425
+ } else {
426
+ conceptSet.system = systemUri;
427
+ }
428
+ }
429
+
430
+ if (isExclusion) {
431
+ this.valueSet.compose.exclude.push(conceptSet);
432
+ } else {
433
+ this.valueSet.compose.include.push(conceptSet);
434
+ }
435
+
436
+ return conceptSet;
437
+ }
438
+
439
+ getCurrentConceptSet(isExclusion) {
440
+ const list = isExclusion ? this.valueSet.compose.exclude : this.valueSet.compose.include;
441
+ return list.length > 0 ? list[list.length - 1] : this.createConceptSet('', isExclusion);
442
+ }
443
+
444
+ toImplicitVcl(system, expression) {
445
+ return 'http://fhir.org/VCL?v1='
446
+ + encodeURIComponent(`(${system})(${expression})`)
447
+ .replace(/\(/g, '%28')
448
+ .replace(/\)/g, '%29'); // need to encode parentheses for VCL as well
449
+ }
450
+
451
+ parseExpr() {
452
+ this.parseSubExpr(false);
453
+
454
+ switch (this.current().type) {
455
+ case TokenType.COMMA:
456
+ this.parseConjunction();
457
+ break;
458
+ case TokenType.SEMI:
459
+ this.parseDisjunction();
460
+ break;
461
+ case TokenType.DASH:
462
+ this.parseExclusion();
463
+ break;
464
+ }
465
+ }
466
+
467
+ parseSubExpr(isExclusion) {
468
+ let systemUri = '';
469
+
470
+ // Check for system URI in parentheses
471
+ if (this.current().type === TokenType.OPEN && this.peek().type === TokenType.URI) {
472
+ this.consume(TokenType.OPEN);
473
+ systemUri = this.current().value;
474
+ this.consume(TokenType.URI);
475
+ this.consume(TokenType.CLOSE);
476
+ }
477
+
478
+ if (this.current().type === TokenType.OPEN) {
479
+ this.consume(TokenType.OPEN);
480
+
481
+ // Check for nested system URI
482
+ if (this.current().type === TokenType.OPEN && this.peek().type === TokenType.URI) {
483
+ this.consume(TokenType.OPEN);
484
+ systemUri = this.current().value;
485
+ this.consume(TokenType.URI);
486
+ this.consume(TokenType.CLOSE);
487
+ }
488
+
489
+ if (this.isSimpleCodeList()) {
490
+ this.parseSimpleCodeList(systemUri, isExclusion);
491
+ } else {
492
+ this.parseExprWithinParentheses(isExclusion);
493
+ }
494
+
495
+ // This should fail for unmatched parentheses
496
+ this.consume(TokenType.CLOSE);
497
+ } else {
498
+ this.parseSimpleExpr(systemUri, isExclusion);
499
+ }
500
+ }
501
+
502
+ parseSimpleCodeList(systemUri, isExclusion) {
503
+ const conceptSet = this.createConceptSet(systemUri, isExclusion);
504
+
505
+ if (this.current().type === TokenType.STAR) {
506
+ this.consume(TokenType.STAR);
507
+ conceptSet.filter.push({
508
+ property: 'concept',
509
+ op: FilterOperator.EXISTS,
510
+ value: 'true'
511
+ });
512
+ return;
513
+ } else if (this.current().type === TokenType.IN) {
514
+ this.parseIncludeVs(conceptSet);
515
+ return;
516
+ } else {
517
+ const code = this.parseCode();
518
+ conceptSet.concept.push({code});
519
+ }
520
+
521
+ while ([TokenType.SEMI, TokenType.COMMA].includes(this.current().type)) {
522
+ this.consume(this.current().type);
523
+
524
+ if (this.current().type === TokenType.STAR) {
525
+ this.consume(TokenType.STAR);
526
+ conceptSet.filter.push({
527
+ property: 'concept',
528
+ op: FilterOperator.EXISTS,
529
+ value: 'true'
530
+ });
531
+ } else if (this.current().type === TokenType.IN) {
532
+ this.parseIncludeVs(conceptSet);
533
+ } else {
534
+ const code = this.parseCode();
535
+ conceptSet.concept.push({code});
536
+ }
537
+ }
538
+ }
539
+
540
+ parseSimpleExpr(systemUri, isExclusion) {
541
+ const conceptSet = this.createConceptSet(systemUri, isExclusion);
542
+
543
+ if (this.peek().type === TokenType.DOT) {
544
+ this.parseOf(systemUri, conceptSet);
545
+ } else if (this.current().type === TokenType.STAR) {
546
+ this.consume(TokenType.STAR);
547
+ conceptSet.filter.push({
548
+ property: 'concept',
549
+ op: FilterOperator.EXISTS,
550
+ value: 'true'
551
+ });
552
+ } else if ([TokenType.SCODE, TokenType.QUOTED_VALUE].includes(this.current().type)) {
553
+ if (this.current().type === TokenType.SCODE && this.current().value.includes('.')) {
554
+ this.parseOf(systemUri, conceptSet);
555
+ } else {
556
+ const code = this.parseCode();
557
+
558
+ if (this.isFilterOperator(this.current().type)) {
559
+ this.parseFilter(conceptSet, code);
560
+ } else {
561
+ conceptSet.concept.push({ code });
562
+ }
563
+ }
564
+ } else if (this.current().type === TokenType.IN) {
565
+ this.parseIncludeVs(conceptSet);
566
+ } else {
567
+ this.parseOf(systemUri, conceptSet);
568
+ }
569
+ }
570
+
571
+ parseOf(systemUri, conceptSet) {
572
+ let isVcl = false;
573
+ const sb = [];
574
+
575
+ switch (this.current().type) {
576
+ case TokenType.LCRLY: {
577
+ // codeList or filterList
578
+ this.consume(TokenType.LCRLY);
579
+ if (this.peek().type === TokenType.COMMA) {
580
+ sb.push(this.parseCode());
581
+ while (this.current().type === TokenType.COMMA) {
582
+ this.consume(TokenType.COMMA);
583
+ sb.push(this.parseCode());
584
+ }
585
+ } else {
586
+ isVcl = true;
587
+ this.parseFilterList(sb);
588
+ }
589
+ this.consume(TokenType.RCRLY);
590
+ break;
591
+ }
592
+ case TokenType.STAR: {
593
+ sb.push(this.current().value);
594
+ this.consume(TokenType.STAR);
595
+ break;
596
+ }
597
+ case TokenType.URI: {
598
+ sb.push(this.current().value);
599
+ this.consume(TokenType.URI);
600
+ break;
601
+ }
602
+ case TokenType.SCODE:
603
+ case TokenType.QUOTED_VALUE: {
604
+ sb.push(this.current().value);
605
+ this.parseCode();
606
+ break;
607
+ }
608
+ default:
609
+ throw new VCLParseException("Expected code, codeList, STAR, URI or filterList", this.current().position);
610
+ }
611
+
612
+ this.consume(TokenType.DOT);
613
+
614
+ const property = this.parseCode();
615
+ const implicitVcl = isVcl ? this.toImplicitVcl(systemUri, sb.join(',')) : sb.join(',');
616
+
617
+ conceptSet.filter.push({
618
+ property: property,
619
+ op: FilterOperator.EQUAL, // FIXME: Is this really the value to use?
620
+ _op: {
621
+ extension: [{
622
+ // WARNING: pre-adopting an extension that may change in before R6
623
+ url: 'http://hl7.org/fhir/6.0/StructureDefinition/extension-ValueSet.compose.include.filter.op',
624
+ valueString: 'of'
625
+ }]
626
+ },
627
+ value: implicitVcl,
628
+ });
629
+ }
630
+
631
+ parseExprWithinParentheses(isExclusion) {
632
+ this.parseSubExpr(isExclusion);
633
+
634
+ while ([TokenType.COMMA, TokenType.SEMI, TokenType.DASH].includes(this.current().type)) {
635
+ switch (this.current().type) {
636
+ case TokenType.COMMA:
637
+ this.parseConjunctionWithFlag(isExclusion);
638
+ break;
639
+ case TokenType.SEMI:
640
+ this.parseDisjunctionWithFlag(isExclusion);
641
+ break;
642
+ case TokenType.DASH:
643
+ this.parseExclusion();
644
+ break;
645
+ }
646
+ }
647
+ }
648
+
649
+ parseFilter(conceptSet, propertyCode) {
650
+ const op = this.current().type;
651
+ this.consume(op);
652
+
653
+ const filter = {
654
+ property: propertyCode,
655
+ op: this.tokenTypeToFilterOperator(op)
656
+ };
657
+
658
+ switch (op) {
659
+ case TokenType.EQ:
660
+ case TokenType.IS_A:
661
+ case TokenType.IS_NOT_A:
662
+ case TokenType.DESC_OF:
663
+ case TokenType.GENERALIZES:
664
+ case TokenType.CHILD_OF:
665
+ case TokenType.DESC_LEAF:
666
+ case TokenType.EXISTS:
667
+ filter.value = this.parseCode();
668
+ break;
669
+ case TokenType.REGEX:
670
+ filter.value = this.parseQuotedString();
671
+ break;
672
+ case TokenType.IN:
673
+ case TokenType.NOT_IN:
674
+ filter.value = this.parseFilterValue(conceptSet.system);
675
+ break;
676
+ default:
677
+ throw new VCLParseException(`Unexpected filter operator: ${op}`, this.current().position);
678
+ }
679
+
680
+ conceptSet.filter.push(filter);
681
+ }
682
+
683
+ parseIncludeVs(conceptSet) {
684
+ this.consume(TokenType.IN);
685
+
686
+ let uri;
687
+ if (this.current().type === TokenType.URI) {
688
+ uri = this.current().value;
689
+ this.consume(TokenType.URI);
690
+ } else if (this.current().type === TokenType.OPEN) {
691
+ this.consume(TokenType.OPEN);
692
+ uri = this.current().value;
693
+ this.consume(TokenType.URI);
694
+ this.consume(TokenType.CLOSE);
695
+ } else {
696
+ throw new VCLParseException('Expected URI after ^', this.current().position);
697
+ }
698
+
699
+ conceptSet.valueSet.push(uri);
700
+ }
701
+
702
+ parseConjunction() {
703
+ const currentConceptSet = this.getCurrentConceptSet(false);
704
+
705
+ while (this.current().type === TokenType.COMMA) {
706
+ this.consume(TokenType.COMMA);
707
+
708
+ if ([TokenType.SCODE, TokenType.QUOTED_VALUE].includes(this.current().type)) {
709
+ const code = this.parseCode();
710
+ if (this.isFilterOperator(this.current().type)) {
711
+ this.parseFilter(currentConceptSet, code);
712
+ } else {
713
+ currentConceptSet.concept.push({code});
714
+ }
715
+ } else {
716
+ this.parseSubExpr(false);
717
+ }
718
+ }
719
+ }
720
+
721
+ parseConjunctionWithFlag(isExclusion) {
722
+ const currentConceptSet = this.getCurrentConceptSet(isExclusion);
723
+
724
+ while (this.current().type === TokenType.COMMA) {
725
+ this.consume(TokenType.COMMA);
726
+
727
+ if ([TokenType.SCODE, TokenType.QUOTED_VALUE].includes(this.current().type)) {
728
+ const code = this.parseCode();
729
+ if (this.isFilterOperator(this.current().type)) {
730
+ this.parseFilter(currentConceptSet, code);
731
+ } else {
732
+ currentConceptSet.concept.push({code});
733
+ }
734
+ } else {
735
+ this.parseSubExpr(isExclusion);
736
+ }
737
+ }
738
+ }
739
+
740
+ parseDisjunction() {
741
+ while (this.current().type === TokenType.SEMI) {
742
+ this.consume(TokenType.SEMI);
743
+ this.parseSubExpr(false);
744
+ }
745
+ }
746
+
747
+ parseDisjunctionWithFlag(isExclusion) {
748
+ while (this.current().type === TokenType.SEMI) {
749
+ this.consume(TokenType.SEMI);
750
+ this.parseSubExpr(isExclusion);
751
+ }
752
+ }
753
+
754
+ parseExclusion() {
755
+ this.consume(TokenType.DASH);
756
+ this.parseSubExpr(true);
757
+ }
758
+
759
+ parseCode() {
760
+ if (this.current().type === TokenType.SCODE) {
761
+ const code = this.current().value;
762
+ this.consume(TokenType.SCODE);
763
+ return code;
764
+ } else if (this.current().type === TokenType.QUOTED_VALUE) {
765
+ const code = this.current().value;
766
+ this.consume(TokenType.QUOTED_VALUE);
767
+ return code;
768
+ } else {
769
+ throw new VCLParseException('Expected code', this.current().position);
770
+ }
771
+ }
772
+
773
+ parseQuotedString() {
774
+ if (this.current().type === TokenType.QUOTED_VALUE) {
775
+ const value = this.current().value;
776
+ this.consume(TokenType.QUOTED_VALUE);
777
+ return value;
778
+ } else {
779
+ throw new VCLParseException('Expected quoted string', this.current().position);
780
+ }
781
+ }
782
+
783
+ cleanupEmptyArrays(obj) {
784
+ if (Array.isArray(obj)) {
785
+ return obj.map(item => this.cleanupEmptyArrays(item));
786
+ } else if (obj && typeof obj === 'object') {
787
+ const cleaned = {};
788
+ for (const [key, value] of Object.entries(obj)) {
789
+ if (Array.isArray(value)) {
790
+ if (value.length > 0) {
791
+ cleaned[key] = this.cleanupEmptyArrays(value);
792
+ }
793
+ } else if (value && typeof value === 'object') {
794
+ cleaned[key] = this.cleanupEmptyArrays(value);
795
+ } else if (value !== undefined && value !== null && value !== '') {
796
+ cleaned[key] = value;
797
+ }
798
+ }
799
+ return cleaned;
800
+ }
801
+ return obj;
802
+ }
803
+
804
+ parseFilterValue(systemUri) {
805
+ if (this.current().type === TokenType.LCRLY) {
806
+ this.consume(TokenType.LCRLY);
807
+ const codes = [this.parseCode()];
808
+
809
+ if (this.isFilterOperator(this.current().type)) {
810
+ this.parseFilterList(codes);
811
+ this.consume(TokenType.RCRLY);
812
+ return this.toImplicitVcl(systemUri, codes.join(''));
813
+ } else {
814
+ while (this.current().type === TokenType.COMMA) {
815
+ this.consume(TokenType.COMMA);
816
+ codes.push(this.parseCode());
817
+ }
818
+
819
+ this.consume(TokenType.RCRLY);
820
+ return codes.join(',');
821
+ }
822
+ } else if (this.current().type === TokenType.URI) {
823
+ const uri = this.current().value;
824
+ this.consume(TokenType.URI);
825
+ return uri;
826
+ } else {
827
+ return this.parseCode();
828
+ }
829
+ }
830
+
831
+ parseFilterList(terms) {
832
+ let depth = 1;
833
+ while (depth > 0) {
834
+ const tokenType = this.current().type;
835
+
836
+ switch (tokenType) {
837
+ case TokenType.LCRLY:
838
+ depth++;
839
+ break;
840
+ case TokenType.RCRLY:
841
+ depth--;
842
+ break;
843
+ default:
844
+ // do nothing
845
+ }
846
+
847
+ if (depth > 0) {
848
+ terms.push(this.current().value);
849
+ this.consume(tokenType);
850
+ }
851
+ }
852
+ }
853
+
854
+ parse() {
855
+ try {
856
+ this.parseExpr();
857
+ this.expect(TokenType.EOF);
858
+ return this.cleanupEmptyArrays(this.valueSet);
859
+ } catch (error) {
860
+ // Make sure we're throwing VCLParseException for any parsing error
861
+ if (error instanceof VCLParseException) {
862
+ throw error;
863
+ } else {
864
+ throw new VCLParseException(`Parse error: ${error.message}`);
865
+ }
866
+ }
867
+ }
868
+ }
869
+
870
+ // Main parsing functions
871
+ function parseVCL(vclExpression, fhirFactory = null) {
872
+ if (!vclExpression || vclExpression.trim() === '') {
873
+ throw new VCLParseException('VCL expression cannot be empty');
874
+ }
875
+
876
+ const lexer = new VCLLexer(vclExpression);
877
+ const tokens = lexer.tokenize();
878
+
879
+ const parser = new VCLParserClass(tokens, fhirFactory);
880
+ const result = parser.parse();
881
+
882
+ if (!result) {
883
+ throw new VCLParseException('Parser returned null result');
884
+ }
885
+
886
+ return result;
887
+ }
888
+
889
+ function parseVCLAndSetId(vclExpression, fhirFactory = null) {
890
+ // Use the same parsing logic as parseVCL to avoid scope issues
891
+ if (!vclExpression || vclExpression.trim() === '') {
892
+ throw new VCLParseException('VCL expression cannot be empty');
893
+ }
894
+
895
+ const lexer = new VCLLexer(vclExpression);
896
+ const tokens = lexer.tokenize();
897
+ const parser = new VCLParserClass(tokens, fhirFactory);
898
+ const valueSet = parser.parse();
899
+
900
+ if (!valueSet) {
901
+ throw new VCLParseException('Failed to create ValueSet');
902
+ }
903
+
904
+ // Generate hash-based ID (similar to Java version)
905
+ const jsonString = JSON.stringify(valueSet);
906
+ if (!jsonString) {
907
+ throw new VCLParseException('Failed to serialize ValueSet to JSON');
908
+ }
909
+
910
+ // Create hash directly inline to avoid scoping issues
911
+ let hash = 0;
912
+ for (let i = 0; i < jsonString.length; i++) {
913
+ const char = jsonString.charCodeAt(i);
914
+ hash = ((hash << 5) - hash) + char;
915
+ hash = hash & hash; // Convert to 32-bit integer
916
+ }
917
+ const hashCode = Math.abs(hash);
918
+
919
+ valueSet.url = `cid:${hashCode}`;
920
+
921
+ return valueSet;
922
+ }
923
+
924
+ // Utility functions
925
+ function validateVCLExpression(vclExpression) {
926
+ if (!vclExpression || vclExpression.trim() === '') {
927
+ return false;
928
+ }
929
+
930
+ try {
931
+ // Make sure we call the same parseVCL function that's being exported
932
+ const lexer = new VCLLexer(vclExpression);
933
+ const tokens = lexer.tokenize();
934
+ const parser = new VCLParserClass(tokens, null);
935
+ parser.parse();
936
+ return true;
937
+ } catch (e) {
938
+ // Return false for any parsing error, regardless of type
939
+ return false;
940
+ }
941
+ }
942
+
943
+ function createVCLValueSet(id, name, description) {
944
+ const valueSet = {
945
+ resourceType: 'ValueSet',
946
+ status: 'draft',
947
+ experimental: true,
948
+ compose: {
949
+ include: [],
950
+ exclude: []
951
+ }
952
+ };
953
+
954
+ if (id) valueSet.id = id;
955
+ if (name) valueSet.name = name;
956
+ if (description) valueSet.description = description;
957
+
958
+ return valueSet;
959
+ }
960
+
961
+ function splitSystemUri(systemUri) {
962
+ const pipePos = systemUri.indexOf('|');
963
+ if (pipePos >= 0) {
964
+ return {
965
+ system: systemUri.substring(0, pipePos),
966
+ version: systemUri.substring(pipePos + 1)
967
+ };
968
+ }
969
+ return {
970
+ system: systemUri,
971
+ version: ''
972
+ };
973
+ }
974
+
975
+ function isVCLCompatible(valueSet) {
976
+ if (!valueSet.compose) {
977
+ return false;
978
+ }
979
+
980
+ const supportedOps = [
981
+ FilterOperator.EQUAL, FilterOperator.IS_A, FilterOperator.IS_NOT_A,
982
+ FilterOperator.DESCENDENT_OF, FilterOperator.REGEX, FilterOperator.IN,
983
+ FilterOperator.NOT_IN, FilterOperator.GENERALIZES, FilterOperator.CHILD_OF,
984
+ FilterOperator.DESCENDENT_LEAF, FilterOperator.EXISTS
985
+ ];
986
+
987
+ // Check includes
988
+ if (valueSet.compose.include) {
989
+ for (const include of valueSet.compose.include) {
990
+ if (include.filter) {
991
+ for (const filter of include.filter) {
992
+ if (!supportedOps.includes(filter.op)) {
993
+ return false;
994
+ }
995
+ }
996
+ }
997
+ }
998
+ }
999
+
1000
+ // Check excludes
1001
+ if (valueSet.compose.exclude) {
1002
+ for (const exclude of valueSet.compose.exclude) {
1003
+ if (exclude.filter) {
1004
+ for (const filter of exclude.filter) {
1005
+ if (!supportedOps.includes(filter.op)) {
1006
+ return false;
1007
+ }
1008
+ }
1009
+ }
1010
+ }
1011
+ }
1012
+
1013
+ return true;
1014
+ }
1015
+
1016
+ // Simple hash function for generating IDs
1017
+ function simpleHash(str) {
1018
+ let hash = 0;
1019
+ for (let i = 0; i < str.length; i++) {
1020
+ const char = str.charCodeAt(i);
1021
+ hash = ((hash << 5) - hash) + char;
1022
+ hash = hash & hash; // Convert to 32-bit integer
1023
+ }
1024
+ return Math.abs(hash);
1025
+ }
1026
+
1027
+ // Export for different module systems
1028
+ if (typeof module !== 'undefined' && module.exports) {
1029
+ // Node.js
1030
+ module.exports = {
1031
+ parseVCL,
1032
+ parseVCLAndSetId,
1033
+ validateVCLExpression,
1034
+ createVCLValueSet,
1035
+ splitSystemUri,
1036
+ isVCLCompatible,
1037
+ VCLParseException,
1038
+ TokenType,
1039
+ FilterOperator,
1040
+ // Export classes for debugging
1041
+ VCLLexer,
1042
+ VCLParserClass,
1043
+ // Export utility functions
1044
+ simpleHash
1045
+ };
1046
+ } else if (typeof window !== 'undefined') {
1047
+ // Browser - attach everything to window.VCLParser
1048
+ window.VCLParser = {
1049
+ parseVCL,
1050
+ parseVCLAndSetId,
1051
+ validateVCLExpression,
1052
+ createVCLValueSet,
1053
+ splitSystemUri,
1054
+ isVCLCompatible,
1055
+ VCLParseException,
1056
+ TokenType,
1057
+ FilterOperator,
1058
+ // Export classes for debugging
1059
+ VCLLexer,
1060
+ VCLParserClass,
1061
+ // Export utility functions
1062
+ simpleHash
1063
+ };
1064
+
1065
+ // Also make classes available globally for debugging
1066
+ window.VCLLexer = VCLLexer;
1067
+ window.VCLParseException = VCLParseException;
1068
+ }
1069
+
1070
+ // Examples of usage:
1071
+ /*
1072
+ // Basic parsing
1073
+ const valueSet = parseVCL('(http://snomed.info/sct) 123456789; 987654321');
1074
+
1075
+ // With filters
1076
+ const valueSet2 = parseVCL('(http://snomed.info/sct) 123456789 << 64572001');
1077
+
1078
+ // With version and exclusions
1079
+ const valueSet3 = parseVCL('(http://snomed-alike.info/sct|20210131) * - 123456789');
1080
+
1081
+ // With auto-generated ID
1082
+ const valueSet4 = parseVCLAndSetId('(http://snomed.info/sct) 123456789');
1083
+
1084
+ // Validation
1085
+ if (validateVCLExpression(myExpression)) {
1086
+ const valueSet = parseVCL(myExpression);
1087
+ }
1088
+
1089
+ // With custom FHIR factory
1090
+ const customFactory = {
1091
+ createValueSet: () => ({
1092
+ resourceType: 'ValueSet',
1093
+ status: 'active', // Different default
1094
+ compose: { include: [], exclude: [] }
1095
+ })
1096
+ };
1097
+ const valueSet5 = parseVCL('(http://snomed.info/sct) 123456789', customFactory);
1098
+ */