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.
- package/CHANGELOG.md +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- 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
|
+
};
|