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