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,1029 @@
1
+ /**
2
+ * UCUM Parsers - JavaScript port of UCUM Java library parsers
3
+ * BSD 3-Clause License
4
+ * Copyright (c) 2006+, Health Intersections Pty Ltd
5
+ */
6
+
7
+ const {XMLParser} = require('fast-xml-parser');
8
+ const {
9
+ ConceptKind, Operator, TokenType, UcumException, Decimal, Utilities,
10
+ BaseUnit, DefinedUnit, Prefix, Value, Term, Symbol, Factor, Canonical, CanonicalUnit,
11
+ Registry
12
+ } = require('./ucum-types.js');
13
+
14
+ // Lexer for tokenizing UCUM expressions (port of Lexer.java)
15
+ class Lexer {
16
+ static NO_CHAR = '\0';
17
+
18
+ constructor(source) {
19
+ if (typeof source !== 'string') {
20
+ throw new Error("not a string");
21
+ }
22
+ this.source = source || '';
23
+ this.index = 0;
24
+ this.token = null;
25
+ this.type = TokenType.NONE;
26
+ this.start = 0;
27
+
28
+ this.consume();
29
+ }
30
+
31
+ consume() {
32
+ this.token = null;
33
+ this.type = TokenType.NONE;
34
+ this.start = this.index;
35
+
36
+ if (this.index < this.source.length) {
37
+ const ch = this._nextChar();
38
+
39
+ if (!(this._checkSingle(ch, '/', TokenType.SOLIDUS) ||
40
+ this._checkSingle(ch, '.', TokenType.PERIOD) ||
41
+ this._checkSingle(ch, '(', TokenType.OPEN) ||
42
+ this._checkSingle(ch, ')', TokenType.CLOSE) ||
43
+ this._checkAnnotation(ch) ||
44
+ this._checkNumber(ch) ||
45
+ this._checkNumberOrSymbol(ch))) {
46
+ throw new UcumException(`Error processing unit '${this.source}': unexpected character '${ch}' at character ${this.start+1}`);
47
+ }
48
+ }
49
+ }
50
+
51
+ _checkNumber(ch) {
52
+ if (ch === '+' || ch === '-') {
53
+ this.token = ch;
54
+ let nextCh = this._peekChar();
55
+
56
+ while (nextCh >= '0' && nextCh <= '9') {
57
+ this.token += nextCh;
58
+ this.index++;
59
+ nextCh = this._peekChar();
60
+ }
61
+
62
+ if (this.token.length === 1) {
63
+ throw new UcumException(`Error processing unit '${this.source}': unexpected character '${ch}' at character ${this.start+1}: a + or - must be followed by at least one digit`);
64
+ }
65
+
66
+ this.type = TokenType.NUMBER;
67
+ return true;
68
+ }
69
+ return false;
70
+ }
71
+
72
+ _checkNumberOrSymbol(ch) {
73
+ let isSymbol = false;
74
+ let inBrackets = false;
75
+
76
+ if (this._isValidSymbolChar(ch, true, false)) {
77
+ this.token = ch;
78
+ isSymbol = isSymbol || !(ch >= '0' && ch <= '9');
79
+ inBrackets = this._checkBrackets(ch, inBrackets);
80
+
81
+ let nextCh = this._peekChar();
82
+ inBrackets = this._checkBrackets(nextCh, inBrackets);
83
+
84
+ while (this._isValidSymbolChar(nextCh, !isSymbol || inBrackets, inBrackets)) {
85
+ this.token += nextCh;
86
+ isSymbol = isSymbol || (nextCh !== Lexer.NO_CHAR && !(nextCh >= '0' && nextCh <= '9'));
87
+ this.index++;
88
+ nextCh = this._peekChar();
89
+ inBrackets = this._checkBrackets(nextCh, inBrackets);
90
+ }
91
+
92
+ this.type = isSymbol ? TokenType.SYMBOL : TokenType.NUMBER;
93
+ return true;
94
+ }
95
+ return false;
96
+ }
97
+
98
+ _checkBrackets(ch, inBrackets) {
99
+ if (ch === '[') {
100
+ if (inBrackets) {
101
+ this.error('Nested [');
102
+ }
103
+ return true;
104
+ }
105
+ if (ch === ']') {
106
+ if (!inBrackets) {
107
+ this.error('] without [');
108
+ }
109
+ return false;
110
+ }
111
+ return inBrackets;
112
+ }
113
+
114
+ _isValidSymbolChar(ch, allowDigits, inBrackets) {
115
+ return (allowDigits && ch >= '0' && ch <= '9') ||
116
+ (ch >= 'a' && ch <= 'z') ||
117
+ (ch >= 'A' && ch <= 'Z') ||
118
+ ch === '[' || ch === ']' || ch === '%' || ch === '*' ||
119
+ ch === '^' || ch === "'" || ch === '"' || ch === '_' ||
120
+ (inBrackets && ch === '.');
121
+ }
122
+
123
+ _checkAnnotation(ch) {
124
+ if (ch === '{') {
125
+ const b = [];
126
+ let nextCh = this._nextChar();
127
+
128
+ while (nextCh !== '}') {
129
+ if (!Utilities.isAsciiChar(nextCh)) {
130
+ throw new UcumException(`Error processing unit '${this.source}': Annotation contains non-ascii characters`);
131
+ }
132
+ if (nextCh === Lexer.NO_CHAR) {
133
+ throw new UcumException(`Error processing unit '${this.source}': unterminated annotation`);
134
+ }
135
+ b.push(nextCh);
136
+ nextCh = this._nextChar();
137
+ }
138
+
139
+ this.token = b.join('');
140
+ this.type = TokenType.ANNOTATION;
141
+ return true;
142
+ }
143
+ return false;
144
+ }
145
+
146
+ _checkSingle(ch, test, type) {
147
+ if (ch === test) {
148
+ this.token = ch;
149
+ this.type = type;
150
+ return true;
151
+ }
152
+ return false;
153
+ }
154
+
155
+ _nextChar() {
156
+ const res = this.index < this.source.length ? this.source.charAt(this.index) : Lexer.NO_CHAR;
157
+ this.index++;
158
+ return res;
159
+ }
160
+
161
+ _peekChar() {
162
+ return this.index < this.source.length ? this.source.charAt(this.index) : Lexer.NO_CHAR;
163
+ }
164
+
165
+ getToken() {
166
+ return this.token;
167
+ }
168
+
169
+ getType() {
170
+ return this.type;
171
+ }
172
+
173
+ error(errMsg) {
174
+ throw new UcumException(`Error processing unit '${this.source}': ${errMsg} at character ${this.start+1}`);
175
+ }
176
+
177
+ getTokenAsInt() {
178
+ return this.token.charAt(0) === '+' ? parseInt(this.token.substring(1)) : parseInt(this.token);
179
+ }
180
+
181
+ finished() {
182
+ return this.index === this.source.length;
183
+ }
184
+ }
185
+
186
+ // Expression parser for UCUM unit expressions (port of ExpressionParser.java)
187
+ class ExpressionParser {
188
+ constructor(model) {
189
+ this.model = model;
190
+ }
191
+
192
+ parse(code) {
193
+ const lexer = new Lexer(code);
194
+ const res = this._parseTerm(lexer, true);
195
+
196
+ if (!lexer.finished()) {
197
+ throw new UcumException('Expression was not parsed completely. Syntax Error?');
198
+ }
199
+
200
+ return res;
201
+ }
202
+
203
+ _parseTerm(lexer, first) {
204
+ const res = new Term();
205
+
206
+ if (first && lexer.getType() === TokenType.NONE) {
207
+ res.comp = new Factor(1);
208
+ } else if (lexer.getType() === TokenType.SOLIDUS) {
209
+ res.op = Operator.DIVISION;
210
+ lexer.consume();
211
+ res.term = this._parseTerm(lexer, false);
212
+ } else {
213
+ if (lexer.getType() === TokenType.ANNOTATION) {
214
+ res.comp = new Factor(1); // still lose the annotation
215
+ lexer.consume();
216
+ } else {
217
+ res.comp = this._parseComp(lexer);
218
+ }
219
+
220
+ if (lexer.getType() !== TokenType.NONE && lexer.getType() !== TokenType.CLOSE) {
221
+ if (lexer.getType() === TokenType.SOLIDUS) {
222
+ res.op = Operator.DIVISION;
223
+ lexer.consume();
224
+ } else if (lexer.getType() === TokenType.PERIOD) {
225
+ res.op = Operator.MULTIPLICATION;
226
+ lexer.consume();
227
+ } else if (lexer.getType() === TokenType.ANNOTATION) {
228
+ res.op = Operator.MULTIPLICATION; // implicit
229
+ } else {
230
+ lexer.error("Expected '/' or '.'");
231
+ }
232
+ res.term = this._parseTerm(lexer, false);
233
+ }
234
+ }
235
+
236
+ return res;
237
+ }
238
+
239
+ _parseComp(lexer) {
240
+ if (lexer.getType() === TokenType.NUMBER) {
241
+ const fact = new Factor(lexer.getTokenAsInt());
242
+ lexer.consume();
243
+ return fact;
244
+ } else if (lexer.getType() === TokenType.SYMBOL) {
245
+ return this._parseSymbol(lexer);
246
+ } else if (lexer.getType() === TokenType.NONE) {
247
+ lexer.error('unexpected end of expression looking for a symbol or a number');
248
+ } else if (lexer.getType() === TokenType.OPEN) {
249
+ lexer.consume();
250
+ const res = this._parseTerm(lexer, true);
251
+ if (lexer.getType() === TokenType.CLOSE) {
252
+ lexer.consume();
253
+ } else {
254
+ lexer.error(`Unexpected Token Type '${lexer.getType()}' looking for a close bracket`);
255
+ }
256
+ return res;
257
+ } else {
258
+ lexer.error('unexpected token looking for a symbol or a number');
259
+ }
260
+ return null; // we never get to here
261
+ }
262
+
263
+ _parseSymbol(lexer) {
264
+ const symbol = new Symbol();
265
+ const sym = lexer.getToken();
266
+
267
+ // now, can we pick a prefix that leaves behind a metric unit?
268
+ let selected = null;
269
+ let unit = null;
270
+
271
+ for (const prefix of this.model.getPrefixes()) {
272
+ if (sym.startsWith(prefix.code)) {
273
+ unit = this.model.getUnit(sym.substring(prefix.code.length));
274
+ if (unit != null && (unit.kind === ConceptKind.BASEUNIT || unit.metric)) {
275
+ selected = prefix;
276
+ break;
277
+ }
278
+ }
279
+ }
280
+
281
+ if (selected !== null) {
282
+ symbol.prefix = selected;
283
+ symbol.unit = unit;
284
+ } else {
285
+ unit = this.model.getUnit(sym);
286
+ if (unit != null) {
287
+ symbol.unit = unit;
288
+ } else if (sym !== '1') {
289
+ lexer.error(`The unit '${sym}' is unknown`);
290
+ }
291
+ }
292
+
293
+ lexer.consume();
294
+ if (lexer.getType() === TokenType.NUMBER) {
295
+ symbol.exponent = lexer.getTokenAsInt();
296
+ lexer.consume();
297
+ } else {
298
+ symbol.exponent = 1;
299
+ }
300
+
301
+ return symbol;
302
+ }
303
+ }
304
+
305
+ // Expression composer for creating string representations (port of ExpressionComposer.java)
306
+ class ExpressionComposer {
307
+ compose(termOrCanonical, includeValue = true) {
308
+ if (termOrCanonical === null || termOrCanonical === undefined) {
309
+ return '1';
310
+ }
311
+
312
+ // Handle Canonical objects
313
+ if (termOrCanonical instanceof Canonical) {
314
+ return this.composeCanonical(termOrCanonical, includeValue);
315
+ }
316
+
317
+ // Handle Term objects
318
+ const bldr = [];
319
+ this._composeTerm(bldr, termOrCanonical);
320
+ return bldr.join('');
321
+ }
322
+
323
+ composeCanonical(can, includeValue = true) {
324
+ const b = [];
325
+ if (includeValue) {
326
+ b.push(can.value.toString());
327
+ }
328
+
329
+ let first = true;
330
+ for (const c of can.units) {
331
+ if (first) {
332
+ first = false;
333
+ } else {
334
+ b.push('.');
335
+ }
336
+ b.push(c.base.code);
337
+ if (c.exponent !== 1) {
338
+ b.push(c.exponent.toString());
339
+ }
340
+ }
341
+ return b.join('');
342
+ }
343
+
344
+ _composeTerm(bldr, term) {
345
+ if (term.comp !== null) {
346
+ this._composeComp(bldr, term.comp);
347
+ }
348
+ if (term.op !== null) {
349
+ this._composeOp(bldr, term.op);
350
+ }
351
+ if (term.term !== null) {
352
+ this._composeTerm(bldr, term.term);
353
+ }
354
+ }
355
+
356
+ _composeComp(bldr, comp) {
357
+ if (comp instanceof Factor) {
358
+ this._composeFactor(bldr, comp);
359
+ } else if (comp instanceof Symbol) {
360
+ this._composeSymbol(bldr, comp);
361
+ } else if (comp instanceof Term) {
362
+ bldr.push('(');
363
+ this._composeTerm(bldr, comp);
364
+ bldr.push(')');
365
+ } else {
366
+ bldr.push('?');
367
+ }
368
+ }
369
+
370
+ _composeSymbol(bldr, symbol) {
371
+ if (symbol.prefix !== null) {
372
+ bldr.push(symbol.prefix.code);
373
+ }
374
+ bldr.push(symbol.unit.code);
375
+ if (symbol.exponent !== 1) {
376
+ bldr.push(symbol.exponent.toString());
377
+ }
378
+ }
379
+
380
+ _composeFactor(bldr, comp) {
381
+ bldr.push(comp.value.toString());
382
+ }
383
+
384
+ _composeOp(bldr, op) {
385
+ if (op === Operator.DIVISION) {
386
+ bldr.push('/');
387
+ } else {
388
+ bldr.push('.');
389
+ }
390
+ }
391
+ }
392
+
393
+ // Formal structure composer for human-readable representations (port of FormalStructureComposer.java)
394
+ class FormalStructureComposer {
395
+ compose(term) {
396
+ const bldr = [];
397
+ this._composeTerm(bldr, term);
398
+ return bldr.join('');
399
+ }
400
+
401
+ _composeTerm(bldr, term) {
402
+ if (term.comp !== null) {
403
+ this._composeComp(bldr, term.comp);
404
+ }
405
+ if (term.op !== null) {
406
+ this._composeOp(bldr, term.op);
407
+ }
408
+ if (term.term !== null) {
409
+ this._composeTerm(bldr, term.term);
410
+ }
411
+ }
412
+
413
+ _composeComp(bldr, comp) {
414
+ if (comp instanceof Factor) {
415
+ this._composeFactor(bldr, comp);
416
+ } else if (comp instanceof Symbol) {
417
+ this._composeSymbol(bldr, comp);
418
+ } else if (comp instanceof Term) {
419
+ this._composeTerm(bldr, comp);
420
+ } else {
421
+ bldr.push('?');
422
+ }
423
+ }
424
+
425
+ _composeSymbol(bldr, symbol) {
426
+ bldr.push('(');
427
+ if (symbol.prefix !== null) {
428
+ bldr.push(symbol.prefix.names[0]);
429
+ }
430
+ bldr.push(symbol.unit.names[0]);
431
+ if (symbol.exponent !== 1) {
432
+ bldr.push(' ^ ');
433
+ bldr.push(symbol.exponent.toString());
434
+ }
435
+ bldr.push(')');
436
+ }
437
+
438
+ _composeFactor(bldr, comp) {
439
+ bldr.push(comp.value.toString());
440
+ }
441
+
442
+ _composeOp(bldr, op) {
443
+ if (op === Operator.DIVISION) {
444
+ bldr.push(' / ');
445
+ } else {
446
+ bldr.push(' * ');
447
+ }
448
+ }
449
+ }
450
+
451
+ // UCUM model for storing all units, prefixes, etc.
452
+ class UcumModel {
453
+ constructor(version = null, revision = null, revisionDate = null) {
454
+ this.version = version;
455
+ this.revision = revision;
456
+ this.revisionDate = revisionDate;
457
+ this.prefixes = [];
458
+ this.baseUnits = [];
459
+ this.definedUnits = [];
460
+ this.unitsMap = new Map(); // For quick lookup by code
461
+ }
462
+
463
+ addPrefix(prefix) {
464
+ this.prefixes.push(prefix);
465
+ }
466
+
467
+ addBaseUnit(unit) {
468
+ this.baseUnits.push(unit);
469
+ this.unitsMap.set(unit.code, unit);
470
+ if (unit.codeUC !== unit.code) {
471
+ this.unitsMap.set(unit.codeUC, unit);
472
+ }
473
+ }
474
+
475
+ addDefinedUnit(unit) {
476
+ this.definedUnits.push(unit);
477
+ this.unitsMap.set(unit.code, unit);
478
+ }
479
+
480
+ getUnit(code) {
481
+ return this.unitsMap.get(code);
482
+ }
483
+
484
+ getPrefixes() {
485
+ return this.prefixes;
486
+ }
487
+
488
+ getBaseUnits() {
489
+ return this.baseUnits;
490
+ }
491
+
492
+ getDefinedUnits() {
493
+ return this.definedUnits;
494
+ }
495
+
496
+ getAllUnits() {
497
+ return [...this.baseUnits, ...this.definedUnits];
498
+ }
499
+ }
500
+
501
+ // Parser for UCUM essence XML format using fast-xml-parser
502
+ class UcumEssenceParser {
503
+ constructor() {
504
+ this.parser = new XMLParser({
505
+ ignoreAttributes: false,
506
+ attributeNamePrefix: '@_',
507
+ textNodeName: '#text',
508
+ parseAttributeValue: false,
509
+ parseTagValue: false,
510
+ trimValues: true,
511
+ parseTrueNumberOnly: false,
512
+ arrayMode: false,
513
+ alwaysCreateTextNode: true
514
+ });
515
+ }
516
+
517
+ parse(xmlContent) {
518
+ let parsedXml;
519
+ try {
520
+ parsedXml = this.parser.parse(xmlContent);
521
+ } catch (e) {
522
+ e.message = `Invalid XML content: ${e.message}`;
523
+ throw e;
524
+ }
525
+
526
+ const root = parsedXml.root;
527
+ if (!root) {
528
+ throw new UcumException("Unable to process XML document: expected 'root' element not found");
529
+ }
530
+
531
+ // Parse revision date
532
+ let date = null;
533
+ if (root['@_revision-date']) {
534
+ const dt = root['@_revision-date'];
535
+ if (dt.length > 25) {
536
+ // old format: $Date: 2017-11-21 19:04:52 -0500"
537
+ const dateStr = dt.substring(7, 32);
538
+ date = new Date(dateStr);
539
+ } else {
540
+ date = new Date(dt);
541
+ }
542
+ }
543
+
544
+ const model = new UcumModel(
545
+ root['@_version'],
546
+ root['@_revision'],
547
+ date
548
+ );
549
+
550
+ // Parse prefixes
551
+ if (root.prefix) {
552
+ const prefixes = Array.isArray(root.prefix) ? root.prefix : [root.prefix];
553
+ for (const prefixData of prefixes) {
554
+ model.addPrefix(this._parsePrefix(prefixData));
555
+ }
556
+ }
557
+
558
+ // Parse base units
559
+ if (root['base-unit']) {
560
+ const baseUnits = Array.isArray(root['base-unit']) ? root['base-unit'] : [root['base-unit']];
561
+ for (const unitData of baseUnits) {
562
+ model.addBaseUnit(this._parseBaseUnit(unitData));
563
+ }
564
+ }
565
+
566
+ // Parse defined units
567
+ if (root.unit) {
568
+ const units = Array.isArray(root.unit) ? root.unit : [root.unit];
569
+ for (const unitData of units) {
570
+ model.addDefinedUnit(this._parseUnit(unitData));
571
+ }
572
+ }
573
+
574
+ return model;
575
+ }
576
+
577
+ _parseUnit(x) {
578
+ const unit = new DefinedUnit(x['@_Code'], x['@_CODE']);
579
+ unit.metric = x['@_isMetric'] === 'yes';
580
+ unit.isSpecial = x['@_isSpecial'] === 'yes';
581
+ unit.class_ = x['@_class'];
582
+
583
+ // Parse names
584
+ if (x.name) {
585
+ const names = Array.isArray(x.name) ? x.name : [x.name];
586
+ for (const nameData of names) {
587
+ const nameText = typeof nameData === 'string' ? nameData : nameData['#text'];
588
+ if (nameText) {
589
+ unit.names.push(nameText);
590
+ }
591
+ }
592
+ }
593
+
594
+ // Parse print symbol
595
+ if (x.printSymbol) {
596
+ unit.printSymbol = typeof x.printSymbol === 'string' ? x.printSymbol : x.printSymbol['#text'];
597
+ }
598
+
599
+ // Parse property
600
+ if (x.property) {
601
+ unit.property = typeof x.property === 'string' ? x.property : x.property['#text'];
602
+ }
603
+
604
+ // Parse value
605
+ if (x.value) {
606
+ unit.value = this._parseValue(x.value, `unit ${unit.code}`);
607
+ }
608
+
609
+
610
+ return unit;
611
+ }
612
+
613
+ _parseValue(x, context) {
614
+ let val = null;
615
+ const valueAttr = x['@_value'];
616
+ if (valueAttr !== null && valueAttr !== undefined && valueAttr) {
617
+ try {
618
+ if (valueAttr.includes("."))
619
+ val = new Decimal(valueAttr, 24); // unlimited precision for these
620
+ else
621
+ val = new Decimal(valueAttr);
622
+ } catch (e) {
623
+ e.message = "Error reading "+context+": "+e.message;
624
+ throw e;
625
+ }
626
+ }
627
+ const value = new Value(
628
+ x['@_Unit'] || '',
629
+ x['@_UNIT'] || '',
630
+ val
631
+ );
632
+ value.text = typeof x === 'string' ? x : x['#text'] || '';
633
+ return value;
634
+ }
635
+
636
+ _parseBaseUnit(x) {
637
+ const base = new BaseUnit(x['@_Code'], x['@_CODE']);
638
+ const dimAttr = x['@_dim'];
639
+ if (dimAttr) {
640
+ base.dim = String(dimAttr).charAt(0);
641
+ }
642
+
643
+ // Parse names
644
+ if (x.name) {
645
+ const names = Array.isArray(x.name) ? x.name : [x.name];
646
+ for (const nameData of names) {
647
+ const nameText = typeof nameData === 'string' ? nameData : nameData['#text'];
648
+ if (nameText) {
649
+ base.names.push(nameText);
650
+ }
651
+ }
652
+ }
653
+
654
+ // Parse print symbol
655
+ if (x.printSymbol) {
656
+ base.printSymbol = typeof x.printSymbol === 'string' ? x.printSymbol : x.printSymbol['#text'];
657
+ }
658
+
659
+ // Parse property
660
+ if (x.property) {
661
+ base.property = typeof x.property === 'string' ? x.property : x.property['#text'];
662
+ }
663
+
664
+ return base;
665
+ }
666
+
667
+ _parsePrefix(x) {
668
+ const prefix = new Prefix(x['@_Code'], x['@_CODE']);
669
+
670
+ // Parse names
671
+ if (x.name) {
672
+ const names = Array.isArray(x.name) ? x.name : [x.name];
673
+ for (const nameData of names) {
674
+ const nameText = typeof nameData === 'string' ? nameData : nameData['#text'];
675
+ if (nameText) {
676
+ prefix.names.push(nameText);
677
+ }
678
+ }
679
+ }
680
+
681
+ // Parse print symbol
682
+ if (x.printSymbol) {
683
+ prefix.printSymbol = typeof x.printSymbol === 'string' ? x.printSymbol : x.printSymbol['#text'];
684
+ }
685
+
686
+ // Parse value
687
+ if (x.value && x.value['@_value']) {
688
+ try {
689
+ const valueStr = String(x.value['@_value']);
690
+ // Handle scientific notation by converting to regular decimal first
691
+ if (valueStr.includes('e') || valueStr.includes('E')) {
692
+ const numValue = parseFloat(valueStr);
693
+ if (isNaN(numValue)) {
694
+ throw new Error(`Invalid numeric value: ${valueStr}`);
695
+ }
696
+ prefix.value = new Decimal(numValue.toString(), 24);
697
+ } else {
698
+ prefix.value = new Decimal(valueStr, 24);
699
+ }
700
+ } catch (e) {
701
+ e.message = `Error parsing prefix '${prefix.code}' value '${x.value['@_value']}': ${e.message}`;
702
+ throw e;
703
+ }
704
+ }
705
+
706
+ return prefix;
707
+ }
708
+ }
709
+
710
+ // Search functionality for finding concepts
711
+ class Search {
712
+ doSearch(model, kind = null, text = '', isRegex = false) {
713
+ const concepts = [];
714
+
715
+ if (!kind || kind === ConceptKind.PREFIX) {
716
+ this._searchPrefixes(concepts, model.getPrefixes(), text, isRegex);
717
+ }
718
+ if (!kind || kind === ConceptKind.BASEUNIT) {
719
+ this._searchUnits(concepts, model.getBaseUnits(), text, isRegex);
720
+ }
721
+ if (!kind || kind === ConceptKind.UNIT) {
722
+ this._searchUnits(concepts, model.getDefinedUnits(), text, isRegex);
723
+ }
724
+
725
+ return concepts;
726
+ }
727
+
728
+ _searchUnits(concepts, units, text, isRegex) {
729
+ for (const unit of units) {
730
+ if (this._matchesUnit(unit, text, isRegex)) {
731
+ concepts.push(unit);
732
+ }
733
+ }
734
+ }
735
+
736
+ _matchesUnit(unit, text, isRegex) {
737
+ return this._matches(unit.property, text, isRegex) ||
738
+ this._matchesConcept(unit, text, isRegex);
739
+ }
740
+
741
+ _searchPrefixes(concepts, prefixes, text, isRegex) {
742
+ for (const concept of prefixes) {
743
+ if (this._matchesConcept(concept, text, isRegex)) {
744
+ concepts.push(concept);
745
+ }
746
+ }
747
+ }
748
+
749
+ _matchesConcept(concept, text, isRegex) {
750
+ for (const name of concept.names) {
751
+ if (this._matches(name, text, isRegex)) {
752
+ return true;
753
+ }
754
+ }
755
+
756
+ return this._matches(concept.code, text, isRegex) ||
757
+ this._matches(concept.codeUC, text, isRegex) ||
758
+ this._matches(concept.printSymbol, text, isRegex);
759
+ }
760
+
761
+ _matches(value, text, isRegex) {
762
+ if (!value) return false;
763
+
764
+ if (isRegex) {
765
+ try {
766
+ const regex = new RegExp(text);
767
+ return regex.test(value);
768
+ } catch (e) {
769
+ this.log.error(e);
770
+ return false;
771
+ }
772
+ } else {
773
+ return value.toLowerCase().includes(text.toLowerCase());
774
+ }
775
+ }
776
+ }
777
+
778
+ // Converter for converting terms to canonical form (port of Converter.java)
779
+ class Converter {
780
+ constructor(model, handlers) {
781
+ this.model = model;
782
+ this.handlers = handlers || new Registry();
783
+ }
784
+
785
+ convert(term) {
786
+ return this._normalise(" ", term);
787
+ }
788
+
789
+ _normalise(indent, term) {
790
+ const result = new Canonical(new Decimal("1.000000000000000000000000000000"));
791
+
792
+ this._debug(indent, "canonicalise", term);
793
+ let div = false;
794
+ let t = term;
795
+
796
+ while (t != null) {
797
+ if (t.comp instanceof Term) {
798
+ const temp = this._normalise(indent + " ", t.comp);
799
+ if (div) {
800
+ result.divideValue(temp.getValue());
801
+ for (const c of temp.getUnits()) {
802
+ c.setExponent(0 - c.getExponent());
803
+ }
804
+ } else {
805
+ result.multiplyValue(temp.getValue());
806
+ }
807
+ result.getUnits().push(...temp.getUnits());
808
+ } else if (t.comp instanceof Factor) {
809
+ if (div) {
810
+ result.divideValue(t.comp.value);
811
+ } else {
812
+ result.multiplyValue(t.comp.value);
813
+ }
814
+ } else if (t.comp instanceof Symbol) {
815
+ this._debug(indent, "comp", t.comp.unit);
816
+ const temp = this._normaliseSymbol(indent, t.comp);
817
+ if (div) {
818
+ result.divideValue(temp.getValue());
819
+ for (const c of temp.getUnits()) {
820
+ c.setExponent(0 - c.getExponent());
821
+ }
822
+ } else {
823
+ result.multiplyValue(temp.getValue());
824
+ }
825
+ result.getUnits().push(...temp.getUnits());
826
+ }
827
+ div = t.op === Operator.DIVISION;
828
+ t = t.term;
829
+ }
830
+
831
+ this._debug(indent, "collate", result);
832
+
833
+ // Collate units of the same base
834
+ for (let i = result.getUnits().length - 1; i >= 0; i--) {
835
+ const sf = result.getUnits()[i];
836
+ for (let j = i - 1; j >= 0; j--) {
837
+ const st = result.getUnits()[j];
838
+ if (st.getBase() === sf.getBase()) {
839
+ st.setExponent(sf.getExponent() + st.getExponent());
840
+ result.getUnits().splice(i, 1);
841
+ break;
842
+ }
843
+ }
844
+ }
845
+
846
+ // Remove units with 0 exponent
847
+ for (let i = result.getUnits().length - 1; i >= 0; i--) {
848
+ const sf = result.getUnits()[i];
849
+ if (sf.getExponent() === 0) {
850
+ result.getUnits().splice(i, 1);
851
+ }
852
+ }
853
+
854
+ this._debug(indent, "sort", result);
855
+
856
+ // Sort units by base code
857
+ result.getUnits().sort((lhs, rhs) => {
858
+ return lhs.getBase().code.localeCompare(rhs.getBase().code);
859
+ });
860
+
861
+ this._debug(indent, "done", result);
862
+ return result;
863
+ }
864
+
865
+ _normaliseSymbol(indent, sym) {
866
+ const result = new Canonical(new Decimal("1.000000000000000000000000000000"));
867
+
868
+ if (sym.unit instanceof BaseUnit) {
869
+ result.getUnits().push(new CanonicalUnit(sym.unit, sym.exponent));
870
+ } else {
871
+ const can = this._expandDefinedUnit(indent, sym.unit);
872
+ for (const c of can.getUnits()) {
873
+ c.setExponent(c.getExponent() * sym.exponent);
874
+ }
875
+ result.getUnits().push(...can.getUnits());
876
+
877
+ if (sym.exponent > 0) {
878
+ for (let i = 0; i < sym.exponent; i++) {
879
+ result.multiplyValue(can.getValue());
880
+ }
881
+ } else {
882
+ for (let i = 0; i > sym.exponent; i--) {
883
+ result.divideValue(can.getValue());
884
+ }
885
+ }
886
+ }
887
+
888
+ if (sym.prefix != null) {
889
+ if (sym.exponent > 0) {
890
+ for (let i = 0; i < sym.exponent; i++) {
891
+ result.multiplyValue(sym.prefix.value);
892
+ }
893
+ } else {
894
+ for (let i = 0; i > sym.exponent; i--) {
895
+ result.divideValue(sym.prefix.value);
896
+ }
897
+ }
898
+ }
899
+ return result;
900
+ }
901
+
902
+ _expandDefinedUnit(indent, unit) {
903
+ let u = unit.value.unit;
904
+ let v = unit.value.value;
905
+
906
+ if (unit.isSpecial) {
907
+ if (!this.handlers.exists(unit.code)) {
908
+ throw new UcumException("Not handled yet (special unit)");
909
+ } else {
910
+ const handler = this.handlers.get(unit.code);
911
+ u = handler.getUnits();
912
+ v = handler.getValue();
913
+ if (handler.hasOffset()) {
914
+ throw new UcumException("Not handled yet (special unit with offset from 0 at intersect)");
915
+ }
916
+ }
917
+ }
918
+
919
+ const t = new ExpressionParser(this.model).parse(u);
920
+ this._debug(indent, "now handle", t);
921
+ const result = this._normalise(indent + " ", t);
922
+ result.multiplyValue(v);
923
+ return result;
924
+ }
925
+
926
+ // eslint-disable-next-line no-unused-vars
927
+ _debug(indent, state, unit) {
928
+ // Debug output - can be enabled for debugging
929
+ // if (unit instanceof DefinedUnit) {
930
+ // console.log(indent + state + ": " +unit.code+"="+unit.value.value+" "+unit.value.unit);
931
+ // } else if (unit instanceof Unit) {
932
+ // console.log(indent + state + ": " +unit.code);
933
+ // } else {
934
+ // console.log(indent + state + ": " + new ExpressionComposer().compose(unit));
935
+ // }
936
+ }
937
+ }
938
+
939
+ // UCUM Validator for validating the model (port of UcumValidator.java)
940
+ class UcumValidator {
941
+ constructor(model, handlers) {
942
+ this.model = model;
943
+ this.handlers = handlers || new Registry();
944
+ this.result = [];
945
+ }
946
+
947
+ validate() {
948
+ this.result = [];
949
+ this._checkCodes();
950
+ this._checkUnits();
951
+ return this.result;
952
+ }
953
+
954
+ _checkCodes() {
955
+ for (const unit of this.model.getBaseUnits()) {
956
+ this._checkUnitCode(unit.code, true);
957
+ }
958
+ for (const unit of this.model.getDefinedUnits()) {
959
+ this._checkUnitCode(unit.code, true);
960
+ }
961
+ }
962
+
963
+ _checkUnits() {
964
+ for (const unit of this.model.getDefinedUnits()) {
965
+ if (!unit.isSpecial) {
966
+ this._checkUnitCode(unit.value.unit, false);
967
+ } else if (!this.handlers.exists(unit.code)) {
968
+ this.result.push(`No Handler for ${unit.code}`);
969
+ }
970
+ }
971
+ }
972
+
973
+ _checkUnitCode(code, primary) {
974
+ try {
975
+ const term = new ExpressionParser(this.model).parse(code);
976
+ const c = new ExpressionComposer().compose(term);
977
+ if (c !== code) {
978
+ this.result.push(`Round trip failed: ${code} -> ${c}`);
979
+ }
980
+ new Converter(this.model, this.handlers).convert(term);
981
+ } catch (e) {
982
+ this.result.push(`${code}: ${e.message}`);
983
+ }
984
+
985
+ if (primary) {
986
+ try {
987
+ // Check that codes don't have ambiguous digits outside brackets
988
+ let inBrack = false;
989
+ let nonDigits = false;
990
+ for (let i = 0; i < code.length; i++) {
991
+ const ch = code.charAt(i);
992
+ if (ch === '[') {
993
+ if (inBrack) {
994
+ throw new Error("nested [");
995
+ } else {
996
+ inBrack = true;
997
+ }
998
+ }
999
+ if (ch === ']') {
1000
+ if (!inBrack) {
1001
+ throw new Error("] without [");
1002
+ } else {
1003
+ inBrack = false;
1004
+ }
1005
+ }
1006
+ nonDigits = nonDigits || !(ch >= '0' && ch <= '9');
1007
+ if (ch >= '0' && ch <= '9' && !inBrack && nonDigits) {
1008
+ throw new Error(`code ${code} is ambiguous because it has digits outside []`);
1009
+ }
1010
+ }
1011
+ } catch (e) {
1012
+ this.log.error(e);
1013
+ this.result.push(e.message);
1014
+ }
1015
+ }
1016
+ }
1017
+ }
1018
+
1019
+ module.exports = {
1020
+ Lexer,
1021
+ ExpressionParser,
1022
+ ExpressionComposer,
1023
+ FormalStructureComposer,
1024
+ UcumModel,
1025
+ UcumEssenceParser,
1026
+ Search,
1027
+ Converter,
1028
+ UcumValidator
1029
+ };