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,1559 @@
|
|
|
1
|
+
const {CodeSystemProvider} = require("../cs/cs-api");
|
|
2
|
+
const {Extensions} = require("./extensions");
|
|
3
|
+
const {div} = require("../../library/html");
|
|
4
|
+
const {getValuePrimitive} = require("../../library/utilities");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} TerminologyLinkResolver
|
|
8
|
+
* @property {function(OperationContext, string, string=): {description: string, link: string}|null} resolveURL
|
|
9
|
+
* Given a URL and optional version, returns description and link, or null if not found
|
|
10
|
+
* @property {function(OperationContext, string, string, string=): {display: string, link: string}|null} resolveCode
|
|
11
|
+
* Given a URL, code, and optional version, returns display and link, or null if not found
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class Renderer {
|
|
15
|
+
|
|
16
|
+
constructor(opContext, linkResolver = null) {
|
|
17
|
+
this.opContext = opContext;
|
|
18
|
+
this.linkResolver = linkResolver;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
displayCoded(...args) {
|
|
22
|
+
if (args.length === 1) {
|
|
23
|
+
const arg = args[0];
|
|
24
|
+
if (arg.systemUri !== undefined && arg.version !== undefined && arg.code !== undefined && arg.display !== undefined) {
|
|
25
|
+
// It's a Coding
|
|
26
|
+
return this.displayCodedCoding(arg);
|
|
27
|
+
} else if (arg.coding !== undefined) {
|
|
28
|
+
// It's a CodeableConcept
|
|
29
|
+
return this.displayCodedCodeableConcept(arg);
|
|
30
|
+
} else if (arg.systemUri !== undefined && arg.version !== undefined) {
|
|
31
|
+
// It's a CodeSystemProvider
|
|
32
|
+
return this.displayCodedProvider(arg);
|
|
33
|
+
} else if (arg instanceof CodeSystemProvider) {
|
|
34
|
+
let cs = arg;
|
|
35
|
+
return cs.system() + "|" + cs.version();
|
|
36
|
+
}
|
|
37
|
+
} else if (args.length === 2) {
|
|
38
|
+
return this.displayCodedSystemVersion(args[0], args[1]);
|
|
39
|
+
} else if (args.length === 3) {
|
|
40
|
+
return this.displayCodedSystemVersionCode(args[0], args[1], args[2]);
|
|
41
|
+
} else if (args.length === 4) {
|
|
42
|
+
return this.displayCodedSystemVersionCodeDisplay(args[0], args[1], args[2], args[3]);
|
|
43
|
+
}
|
|
44
|
+
throw new Error('Invalid arguments to renderCoded');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
displayCodedProvider(system) {
|
|
48
|
+
let result = system.systemUri + '|' + system.version;
|
|
49
|
+
if (system.sourcePackage) {
|
|
50
|
+
result = result + ' (from ' + system.sourcePackage + ')';
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
displayCodedSystemVersion(system, version) {
|
|
56
|
+
if (!version) {
|
|
57
|
+
return system;
|
|
58
|
+
} else {
|
|
59
|
+
return system + '|' + version;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
displayCodedSystemVersionCode(system, version, code) {
|
|
64
|
+
return this.displayCodedSystemVersion(system, version) + '#' + code;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
displayCodedSystemVersionCodeDisplay(system, version, code, display) {
|
|
68
|
+
return this.displayCodedSystemVersionCode(system, version, code) + ' ("' + display + '")';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
displayCodedCoding(code) {
|
|
72
|
+
return this.displayCodedSystemVersionCodeDisplay(code.systemUri, code.version, code.code, code.display);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
displayCodedCodeableConcept(code) {
|
|
76
|
+
let result = '';
|
|
77
|
+
for (const c of code.coding) {
|
|
78
|
+
if (result) {
|
|
79
|
+
result = result + ', ';
|
|
80
|
+
}
|
|
81
|
+
result = result + this.displayCodedCoding(c);
|
|
82
|
+
}
|
|
83
|
+
return '[' + result + ']';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
displayValueSetInclude(inc) {
|
|
87
|
+
let result;
|
|
88
|
+
if (inc.systemUri) {
|
|
89
|
+
result = '(' + inc.systemUri + ')';
|
|
90
|
+
if (inc.hasConcepts) {
|
|
91
|
+
result = result + '(';
|
|
92
|
+
let first = true;
|
|
93
|
+
for (const cc of inc.concepts) {
|
|
94
|
+
if (first) {
|
|
95
|
+
first = false;
|
|
96
|
+
} else {
|
|
97
|
+
result = result + ',';
|
|
98
|
+
}
|
|
99
|
+
result = result + cc.code;
|
|
100
|
+
}
|
|
101
|
+
result = result + ')';
|
|
102
|
+
}
|
|
103
|
+
if (inc.hasFilters) {
|
|
104
|
+
result = result + '(';
|
|
105
|
+
let first = true;
|
|
106
|
+
for (const ci of inc.filters) {
|
|
107
|
+
if (first) {
|
|
108
|
+
first = false;
|
|
109
|
+
} else {
|
|
110
|
+
result = result + ',';
|
|
111
|
+
}
|
|
112
|
+
result = result + ci.prop + ci.op + ci.value;
|
|
113
|
+
}
|
|
114
|
+
result = result + ')';
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
result = '(';
|
|
118
|
+
let first = true;
|
|
119
|
+
for (const s of inc.valueSets || []) {
|
|
120
|
+
if (first) {
|
|
121
|
+
first = false;
|
|
122
|
+
} else {
|
|
123
|
+
result = result + ',';
|
|
124
|
+
}
|
|
125
|
+
result = result + '^' + s;
|
|
126
|
+
}
|
|
127
|
+
result = result + ')';
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async renderMetadataTable(res, tbl) {
|
|
133
|
+
this.renderMetadataVersion(res, tbl);
|
|
134
|
+
await this.renderMetadataProfiles(res, tbl);
|
|
135
|
+
this.renderMetadataTags(res, tbl);
|
|
136
|
+
this.renderMetadataLabels(res, tbl);
|
|
137
|
+
this.renderMetadataLastUpdated(res, tbl);
|
|
138
|
+
this.renderMetadataSource(res, tbl);
|
|
139
|
+
this.renderProperty(tbl, 'TEST_PLAN_LANG', res.language);
|
|
140
|
+
this.renderProperty(tbl, 'GENERAL_DEFINING_URL', res.url);
|
|
141
|
+
this.renderProperty(tbl, 'GENERAL_VER', res.version);
|
|
142
|
+
this.renderProperty(tbl, 'GENERAL_NAME', res.name);
|
|
143
|
+
this.renderProperty(tbl, 'GENERAL_TITLE', res.title);
|
|
144
|
+
this.renderProperty(tbl, 'GENERAL_STATUS', res.status);
|
|
145
|
+
this.renderPropertyMD(tbl, 'GENERAL_DEFINITION', res.description);
|
|
146
|
+
this.renderProperty(tbl, 'CANON_REND_PUBLISHER', res.publisher);
|
|
147
|
+
this.renderProperty(tbl, 'CANON_REND_COMMITTEE', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-wg'));
|
|
148
|
+
this.renderProperty(tbl, 'GENERAL_COPYRIGHT', res.copyright);
|
|
149
|
+
this.renderProperty(tbl, 'EXT_FMM_LEVEL', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm'));
|
|
150
|
+
this.renderProperty(tbl, 'PAT_PERIOD', res.effectivePeriod);
|
|
151
|
+
|
|
152
|
+
// capability statement things
|
|
153
|
+
this.renderProperty(tbl, 'Kind', res.kind);
|
|
154
|
+
if (res.software?.name) {
|
|
155
|
+
let s = res.software.name;
|
|
156
|
+
if (res.software.version) {
|
|
157
|
+
s = s+" v"+res.software.version;
|
|
158
|
+
this.renderProperty(tbl, 'Software', s);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
this.renderProperty(tbl, 'GENERAL_URL', res.implementation?.url);
|
|
162
|
+
this.renderProperty(tbl, 'Kind', res.kind);
|
|
163
|
+
this.renderProperty(tbl, 'EX_SCEN_FVER', res.fhirVersion);
|
|
164
|
+
|
|
165
|
+
if (res.content === 'supplement' && res.supplements) {
|
|
166
|
+
const tr = tbl.tr();
|
|
167
|
+
tr.td().b().tx(this.translate('CODESYSTEM_SUPPLEMENTS'));
|
|
168
|
+
await this.renderLink(tr.td(), res.supplements);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (res.valueSet) {
|
|
172
|
+
const tr = tbl.tr();
|
|
173
|
+
tr.td().b().tx(this.translate('GENERAL_VALUESET'));
|
|
174
|
+
await this.renderLink(tr.td(), res.valueSet);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async renderMetadataProfiles(res, tbl) {
|
|
179
|
+
if (res.meta?.profile) {
|
|
180
|
+
let tr = tbl.tr();
|
|
181
|
+
tr.td().b().tx(this.translate('GENERAL_PROF'));
|
|
182
|
+
if (res.meta.profile.length > 1) {
|
|
183
|
+
let ul = tr.td();
|
|
184
|
+
for (let u of res.meta.profile) {
|
|
185
|
+
await this.renderLink(ul.li(), u);
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
await this.renderLink(tr.td(), res.meta.profile[0]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
renderMetadataTags(res, tbl) {
|
|
194
|
+
if (res.meta?.tag) {
|
|
195
|
+
let tr = tbl.tr();
|
|
196
|
+
tr.td().b().tx(this.translate('GENERAL_PROF'));
|
|
197
|
+
if (res.meta.tag.length > 1) {
|
|
198
|
+
let ul = tr.td();
|
|
199
|
+
for (let u of res.meta.tag) {
|
|
200
|
+
this.renderCoding(ul.li(), u);
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
this.renderCoding(tr.td(), res.meta.tag[0]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
renderMetadataLabels(res, tbl) {
|
|
209
|
+
if (res.meta?.label) {
|
|
210
|
+
let tr = tbl.tr();
|
|
211
|
+
tr.td().b().tx(this.translate('GENERAL_PROF'));
|
|
212
|
+
if (res.meta.label.length > 1) {
|
|
213
|
+
let ul = tr.td();
|
|
214
|
+
for (let u of res.meta.label) {
|
|
215
|
+
this.renderCodin(ul.li(), u);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
this.renderCoding(tr.td(), res.meta.label[0]);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
renderMetadataVersion(res, tbl) {
|
|
224
|
+
if (res.meta?.version) {
|
|
225
|
+
let tr = tbl.tr();
|
|
226
|
+
tr.td().b().tx(this.translate('RES_REND_VER'));
|
|
227
|
+
tr.td().tx(res.meta.version);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
renderMetadataLastUpdated(res, tbl) {
|
|
232
|
+
if (res.meta?.version) {
|
|
233
|
+
let tr = tbl.tr();
|
|
234
|
+
tr.td().b().tx(this.translate('RES_REND_UPDATED'));
|
|
235
|
+
tr.td().tx(this.displayDate(res.meta.version));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
renderProperty(tbl, msgId, value) {
|
|
240
|
+
if (value) {
|
|
241
|
+
let tr = tbl.tr();
|
|
242
|
+
tr.td().b().tx(this.translate(msgId));
|
|
243
|
+
if (value instanceof Object) {
|
|
244
|
+
tr.td().tx("todo");
|
|
245
|
+
} else {
|
|
246
|
+
tr.td().tx(value);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async renderPropertyLink(tbl, msgId, value) {
|
|
252
|
+
if (value) {
|
|
253
|
+
let tr = tbl.tr();
|
|
254
|
+
tr.td().b().tx(this.translate(msgId));
|
|
255
|
+
const linkinfo = await this.linkResolver.resolveURL(this.opContext, value);
|
|
256
|
+
if (linkinfo) {
|
|
257
|
+
tr.td().ah(linkinfo.link).tx(linkinfo.description);
|
|
258
|
+
} else {
|
|
259
|
+
tr.td().tx(value);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
renderPropertyMD(tbl, msgId, value) {
|
|
265
|
+
if (value) {
|
|
266
|
+
let tr = tbl.tr();
|
|
267
|
+
tr.td().b().tx(this.translate(msgId));
|
|
268
|
+
if (value instanceof Object) {
|
|
269
|
+
tr.td().tx("todo");
|
|
270
|
+
} else {
|
|
271
|
+
tr.td().markdown(value);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
renderMetadataSource(res, tbl) {
|
|
277
|
+
if (res.meta?.source) {
|
|
278
|
+
let tr = tbl.tr();
|
|
279
|
+
tr.td().b().tx(this.translate('RES_REND_INFO_SOURCE'));
|
|
280
|
+
tr.td().tx(res.meta.source);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async renderLink(x, uri) {
|
|
285
|
+
const result = this.linkResolver ? await this.linkResolver.resolveURL(this.opContext, uri) : null;
|
|
286
|
+
if (result) {
|
|
287
|
+
x.ah(result.link).tx(result.description);
|
|
288
|
+
} else {
|
|
289
|
+
x.code().tx(uri);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
renderLinkComma(x, uri) {
|
|
294
|
+
let {desc, url} = this.linkResolver ? this.linkResolver.resolveURL(this.opContext, uri) : null;
|
|
295
|
+
if (url) {
|
|
296
|
+
x.commaItem(desc, url);
|
|
297
|
+
} else {
|
|
298
|
+
x.commaItem(uri);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
async renderCoding(x, coding) {
|
|
304
|
+
let {
|
|
305
|
+
desc,
|
|
306
|
+
url
|
|
307
|
+
} = this.linkResolver ? await this.linkResolver.resolveCode(this.opContext, coding.system, coding.version, coding.code) : null;
|
|
308
|
+
if (url) {
|
|
309
|
+
x.ah(url).tx(desc);
|
|
310
|
+
} else {
|
|
311
|
+
x.code(coding.code);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
translate(msgId) {
|
|
316
|
+
return this.opContext.i18n.formatPhrase(msgId, this.opContext.langs, []);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
translatePlural(num, msgId) {
|
|
320
|
+
return this.opContext.i18n.formatPhrasePlural(msgId, this.opContext.langs, num,[]);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async renderValueSet(vs) {
|
|
324
|
+
if (vs.json) {
|
|
325
|
+
vs = vs.json;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let div_ = div();
|
|
329
|
+
div_.h2().tx("Properties");
|
|
330
|
+
let tbl = div_.table("grid");
|
|
331
|
+
await this.renderMetadataTable(vs, tbl);
|
|
332
|
+
if (vs.compose) {
|
|
333
|
+
div_.h2().tx("Logical Definition");
|
|
334
|
+
await this.renderCompose(vs, div_.table("grid"));
|
|
335
|
+
}
|
|
336
|
+
if (vs.expansion) {
|
|
337
|
+
div_.h2().tx("Expansion");
|
|
338
|
+
await this.renderExpansion(div_.table("grid"), vs, tbl);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return div_.toString();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async renderCodeSystem(cs) {
|
|
345
|
+
if (cs.json) {
|
|
346
|
+
cs = cs.json;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let div_ = div();
|
|
350
|
+
|
|
351
|
+
// Metadata table
|
|
352
|
+
div_.h3().tx("Properties");
|
|
353
|
+
await this.renderMetadataTable(cs, div_.table("grid"));
|
|
354
|
+
|
|
355
|
+
// Code system properties
|
|
356
|
+
const hasProps = this.generateProperties(div_, cs);
|
|
357
|
+
|
|
358
|
+
// Filters
|
|
359
|
+
this.generateFilters(div_, cs);
|
|
360
|
+
|
|
361
|
+
// Concepts
|
|
362
|
+
await this.generateCodeSystemContent(div_, cs, hasProps);
|
|
363
|
+
|
|
364
|
+
return div_.toString();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async renderCompose(vs, x) {
|
|
368
|
+
let supplements = Extensions.list(vs, 'http://hl7.org/fhir/StructureDefinition/valueset-supplement');
|
|
369
|
+
if (supplements && supplements.length > 0) {
|
|
370
|
+
let p = x.para();
|
|
371
|
+
p.tx(this.translatePlural(supplements.length, 'VALUE_SET_NEEDS_SUPPL'));
|
|
372
|
+
p.tx(" ");
|
|
373
|
+
p.startCommaList("and");
|
|
374
|
+
for (let ext of supplements) {
|
|
375
|
+
this.renderLinkComma(p, ext);
|
|
376
|
+
}
|
|
377
|
+
p.stopCommaList();
|
|
378
|
+
p.tx(".");
|
|
379
|
+
}
|
|
380
|
+
let parameters = Extensions.list(vs, 'http://hl7.org/fhir/tools/StructureDefinition/valueset-parameter');
|
|
381
|
+
if (parameters && parameters.length > 0) {
|
|
382
|
+
x.para().b().tx("This ValueSet has parameters");
|
|
383
|
+
const tbl = x.table("grid");
|
|
384
|
+
const tr = tbl.tr();
|
|
385
|
+
tr.th().tx("Name");
|
|
386
|
+
tr.th().tx("Documentation");
|
|
387
|
+
for (let ext of parameters) {
|
|
388
|
+
const tr = tbl.tr();
|
|
389
|
+
tr.td().tx(Extensions.readValue(ext, "name"));
|
|
390
|
+
tr.td().markdown(Extensions.readValue(ext, "documentation"));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
let comp = vs.compose;
|
|
394
|
+
if (comp.include) {
|
|
395
|
+
let p = x.para();
|
|
396
|
+
p.tx(this.translatePlural(supplements.length, 'VALUE_SET_RULES_INC'));
|
|
397
|
+
let ul = x.ul();
|
|
398
|
+
for (let inc of comp.include) {
|
|
399
|
+
await this.renderInclude(ul.li(), inc);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (comp.exclude) {
|
|
403
|
+
let p = x.para();
|
|
404
|
+
p.tx(this.translatePlural(supplements.length, 'VALUE_SET_RULES_EXC'));
|
|
405
|
+
let ul = x.ul();
|
|
406
|
+
for (let inc of comp.exclude) {
|
|
407
|
+
await this.renderInclude(ul.li(), inc);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async renderInclude(li, inc) {
|
|
413
|
+
if (inc.system) {
|
|
414
|
+
if (!inc.concept && !inc.filter) {
|
|
415
|
+
li.tx(this.translate('VALUE_SET_ALL_CODES_DEF')+" ");
|
|
416
|
+
await this.renderLink(li,inc.system+(inc.version ? "|"+inc.version : ""));
|
|
417
|
+
} else if (inc.concept) {
|
|
418
|
+
li.tx(this.translate('VALUE_SET_THESE_CODES_DEF'));
|
|
419
|
+
await this.renderLink(li,inc.system+(inc.version ? "|"+inc.version : ""));
|
|
420
|
+
li.tx(":");
|
|
421
|
+
const ul = li.ul();
|
|
422
|
+
for (let c of inc.concept) {
|
|
423
|
+
const li = ul.li();
|
|
424
|
+
const link = this.linkResolver ? await this.linkResolver.resolveCode(this.opContext, inc.system, inc.version, c.code) : null;
|
|
425
|
+
if (link) {
|
|
426
|
+
li.ah(link.link).tx(c.code);
|
|
427
|
+
} else {
|
|
428
|
+
li.tx(c.code);
|
|
429
|
+
}
|
|
430
|
+
if (c.display) {
|
|
431
|
+
li.tx(": "+c.display);
|
|
432
|
+
} else if (link) {
|
|
433
|
+
li.span("opaque: 0.5").tx(": "+link.description);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
li.tx(this.translate('VALUE_SET_CODES_FROM'));
|
|
438
|
+
await this.renderLink(li,inc.system+(inc.version ? "|"+inc.version : ""));
|
|
439
|
+
li.tx(" "+ this.translate('VALUE_SET_WHERE')+" ");
|
|
440
|
+
li.startCommaList("and");
|
|
441
|
+
for (let f of inc.filter) {
|
|
442
|
+
if (f.op == 'exists') {
|
|
443
|
+
if (f.value == "true") {
|
|
444
|
+
li.commaItem(f.property+" "+ this.translate('VALUE_SET_EXISTS'));
|
|
445
|
+
} else {
|
|
446
|
+
li.commaItem(f.property+" "+ this.translate('VALUE_SET_DOESNT_EXIST'));
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
li.commaItem(f.property + " " + f.op + " ");
|
|
450
|
+
const loc = this.linkResolver ? await this.linkResolver.resolveCode(this.opContext, inc.system, inc.version, f.value) : null;
|
|
451
|
+
if (loc) {
|
|
452
|
+
li.ah(loc.link).tx(loc.description);
|
|
453
|
+
} else {
|
|
454
|
+
li.tx(f.value);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
li.stopCommaList();
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
li.tx(this.translatePlural(inc.valueSet.length, 'VALUE_SET_RULES_INC'));
|
|
462
|
+
li.startCommaList("and");
|
|
463
|
+
for (let vs of inc.valueSet) {
|
|
464
|
+
this.renderLinkComma(li, vs);
|
|
465
|
+
}
|
|
466
|
+
li.stopCommaList();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
generateProperties(x, cs) {
|
|
471
|
+
if (!cs.property || cs.property.length === 0) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Check what columns we need
|
|
476
|
+
let hasURI = false;
|
|
477
|
+
let hasDescription = false;
|
|
478
|
+
|
|
479
|
+
for (const p of cs.property) {
|
|
480
|
+
hasURI = hasURI || !!p.uri;
|
|
481
|
+
hasDescription = hasDescription || !!p.description;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
x.para().b().tx(this.translate('GENERAL_PROPS'));
|
|
485
|
+
x.para().tx(this.translate('CODESYSTEM_PROPS_DESC'));
|
|
486
|
+
|
|
487
|
+
const tbl = x.table("grid");
|
|
488
|
+
const tr = tbl.tr();
|
|
489
|
+
tr.th().tx(this.translate('GENERAL_CODE'));
|
|
490
|
+
if (hasURI) {
|
|
491
|
+
tr.th().tx(this.translate('GENERAL_URI'));
|
|
492
|
+
}
|
|
493
|
+
tr.th().tx(this.translate('GENERAL_TYPE'));
|
|
494
|
+
if (hasDescription) {
|
|
495
|
+
tr.th().tx(this.translate('GENERAL_DESC'));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
for (const p of cs.property) {
|
|
499
|
+
const row = tbl.tr();
|
|
500
|
+
row.td().tx(p.code);
|
|
501
|
+
if (hasURI) {
|
|
502
|
+
row.td().tx(p.uri || '');
|
|
503
|
+
}
|
|
504
|
+
row.td().tx(p.type || '');
|
|
505
|
+
if (hasDescription) {
|
|
506
|
+
row.td().tx(p.description || '');
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
generateFilters(x, cs) {
|
|
514
|
+
if (!cs.filter || cs.filter.length === 0) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
x.para().b().tx(this.translate('CODESYSTEM_FILTERS'));
|
|
519
|
+
|
|
520
|
+
const tbl = x.table("grid");
|
|
521
|
+
const tr = tbl.tr();
|
|
522
|
+
tr.th().tx(this.translate('GENERAL_CODE'));
|
|
523
|
+
tr.th().tx(this.translate('GENERAL_DESC'));
|
|
524
|
+
tr.th().tx(this.translate('CODESYSTEM_FILTER_OP'));
|
|
525
|
+
tr.th().tx(this.translate('GENERAL_VALUE'));
|
|
526
|
+
|
|
527
|
+
for (const f of cs.filter) {
|
|
528
|
+
const row = tbl.tr();
|
|
529
|
+
row.td().tx(f.code);
|
|
530
|
+
row.td().tx(f.description || '');
|
|
531
|
+
row.td().tx(f.operator ? f.operator.join(' ') : '');
|
|
532
|
+
row.td().tx(f.value || '');
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async generateCodeSystemContent(x, cs, hasProps) {
|
|
537
|
+
if (hasProps) {
|
|
538
|
+
x.para().b().tx(this.translate('CODESYSTEM_CONCEPTS'));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const p = x.para();
|
|
542
|
+
p.startScript("csc");
|
|
543
|
+
p.param("cs").code().tx(cs.url);
|
|
544
|
+
this.makeCasedParam(p.param("cased"), cs, cs.caseSensitive);
|
|
545
|
+
this.makeHierarchyParam(p.param("h"), cs, cs.hierarchyMeaning);
|
|
546
|
+
p.paramValue("code-count", this.countConcepts(cs.concept));
|
|
547
|
+
p.execScript(this.sentenceForContent(cs.content, cs));
|
|
548
|
+
p.closeScript();
|
|
549
|
+
|
|
550
|
+
if (cs.content === 'not-present') {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (!cs.concept || cs.concept.length === 0) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Determine table columns needed
|
|
559
|
+
const columnInfo = this.analyzeConceptColumns(cs);
|
|
560
|
+
|
|
561
|
+
// Build the concepts table
|
|
562
|
+
const tbl = x.table("codes");
|
|
563
|
+
|
|
564
|
+
// Header row
|
|
565
|
+
const headerRow = tbl.tr();
|
|
566
|
+
if (columnInfo.hasHierarchy) {
|
|
567
|
+
headerRow.th().tx(this.translate('CODESYSTEM_LVL'));
|
|
568
|
+
}
|
|
569
|
+
headerRow.th().tx(this.translate('GENERAL_CODE'));
|
|
570
|
+
if (columnInfo.hasDisplay) {
|
|
571
|
+
headerRow.th().tx(this.translate('TX_DISPLAY'));
|
|
572
|
+
}
|
|
573
|
+
if (columnInfo.hasDefinition) {
|
|
574
|
+
headerRow.th().tx(this.translate('GENERAL_DEFINITION'));
|
|
575
|
+
}
|
|
576
|
+
if (columnInfo.hasDeprecated) {
|
|
577
|
+
headerRow.th().tx(this.translate('CODESYSTEM_DEPRECATED'));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Property columns
|
|
581
|
+
for (const prop of columnInfo.properties) {
|
|
582
|
+
headerRow.th().tx(this.getDisplayForProperty(prop) || prop.code);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Render concepts recursively
|
|
586
|
+
for (const concept of cs.concept) {
|
|
587
|
+
await this.addConceptRow(tbl, concept, 0, cs, columnInfo);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
makeCasedParam(x, cs, caseSensitive) {
|
|
592
|
+
if (caseSensitive) {
|
|
593
|
+
let s = caseSensitive ? "case-sensitive" : "case-insensitive";
|
|
594
|
+
x.tx(s);
|
|
595
|
+
} else {
|
|
596
|
+
x.tx("");
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
makeHierarchyParam(x, cs, hm) {
|
|
601
|
+
if (hm) {
|
|
602
|
+
let s = hm; // look it up?
|
|
603
|
+
x.tx(" "+this.translate('CODE_SYS_IN_A_HIERARCHY', [s]));
|
|
604
|
+
} else if ((cs.concept || []).find(c => (c.concept || []).length > 0)) {
|
|
605
|
+
x.tx(" "+ this.translate('CODE_SYS_UNDEF_HIER'));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
analyzeConceptColumns(cs) {
|
|
610
|
+
const info = {
|
|
611
|
+
hasHierarchy: false,
|
|
612
|
+
hasDisplay: false,
|
|
613
|
+
hasDefinition: false,
|
|
614
|
+
hasDeprecated: false,
|
|
615
|
+
hasComment: false,
|
|
616
|
+
properties: []
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// Check which properties are actually used
|
|
620
|
+
const usedProperties = new Set();
|
|
621
|
+
|
|
622
|
+
const analyzeConceptList = (concepts) => {
|
|
623
|
+
for (const c of concepts) {
|
|
624
|
+
if (c.display && c.display !== c.code) {
|
|
625
|
+
info.hasDisplay = true;
|
|
626
|
+
}
|
|
627
|
+
if (c.definition) {
|
|
628
|
+
info.hasDefinition = true;
|
|
629
|
+
}
|
|
630
|
+
if (c.concept && c.concept.length > 0) {
|
|
631
|
+
info.hasHierarchy = true;
|
|
632
|
+
analyzeConceptList(c.concept);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Check for deprecated
|
|
636
|
+
if (this.isDeprecated(c)) {
|
|
637
|
+
info.hasDeprecated = true;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Track used properties
|
|
641
|
+
if (c.property) {
|
|
642
|
+
for (const prop of c.property) {
|
|
643
|
+
usedProperties.add(prop.code);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
analyzeConceptList(cs.concept || []);
|
|
650
|
+
|
|
651
|
+
// Filter to properties that are actually used
|
|
652
|
+
if (cs.property) {
|
|
653
|
+
for (const prop of cs.property) {
|
|
654
|
+
if (usedProperties.has(prop.code) && this.showPropertyInTable(prop)) {
|
|
655
|
+
info.properties.push(prop);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return info;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
showPropertyInTable(prop) {
|
|
664
|
+
// Skip certain internal properties
|
|
665
|
+
const skipCodes = ['status', 'inactive', 'deprecated', 'notSelectable'];
|
|
666
|
+
return !skipCodes.includes(prop.code);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
getDisplayForProperty(prop) {
|
|
670
|
+
// Could look up a display name for well-known properties
|
|
671
|
+
return prop.description || prop.code;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
isDeprecated(concept) {
|
|
675
|
+
if (concept.property) {
|
|
676
|
+
for (const prop of concept.property) {
|
|
677
|
+
if ((prop.code === 'status' && prop.valueCode === 'deprecated') ||
|
|
678
|
+
(prop.code === 'deprecated' && prop.valueBoolean === true) ||
|
|
679
|
+
(prop.code === 'inactive' && prop.valueBoolean === true)) {
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async addConceptRow(tbl, concept, level, cs, columnInfo) {
|
|
688
|
+
const tr = tbl.tr();
|
|
689
|
+
|
|
690
|
+
// Apply styling for deprecated concepts
|
|
691
|
+
if (this.isDeprecated(concept)) {
|
|
692
|
+
tr.style("background-color: #ffeeee");
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Level column
|
|
696
|
+
if (columnInfo.hasHierarchy) {
|
|
697
|
+
tr.td().tx(String(level + 1));
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Code column
|
|
701
|
+
const codeTd = tr.td();
|
|
702
|
+
if (level > 0) {
|
|
703
|
+
codeTd.tx('\u00A0'.repeat(level * 2)); // Non-breaking spaces for indentation
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Link code if it's a supplement
|
|
707
|
+
if (cs.content === 'supplement' && cs.supplements) {
|
|
708
|
+
const link = this.linkResolver ?
|
|
709
|
+
await this.linkResolver.resolveCode(this.opContext, cs.supplements, null, concept.code) : null;
|
|
710
|
+
if (link) {
|
|
711
|
+
codeTd.ah(link.link).tx(concept.code);
|
|
712
|
+
} else {
|
|
713
|
+
codeTd.tx(concept.code);
|
|
714
|
+
}
|
|
715
|
+
} else {
|
|
716
|
+
codeTd.code().tx(concept.code);
|
|
717
|
+
}
|
|
718
|
+
codeTd.an(concept.code);
|
|
719
|
+
|
|
720
|
+
// Display column
|
|
721
|
+
if (columnInfo.hasDisplay) {
|
|
722
|
+
tr.td().tx(concept.display || '');
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Definition column
|
|
726
|
+
if (columnInfo.hasDefinition) {
|
|
727
|
+
tr.td().tx(concept.definition || '');
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Deprecated column
|
|
731
|
+
if (columnInfo.hasDeprecated) {
|
|
732
|
+
const td = tr.td();
|
|
733
|
+
if (this.isDeprecated(concept)) {
|
|
734
|
+
td.tx(this.translate('CODESYSTEM_DEPRECATED_TRUE'));
|
|
735
|
+
|
|
736
|
+
// Check for replacement
|
|
737
|
+
const replacedBy = this.getPropertyValue(concept, 'replacedBy');
|
|
738
|
+
if (replacedBy) {
|
|
739
|
+
td.tx(' ' + this.translate('CODESYSTEM_REPLACED_BY') + ' ');
|
|
740
|
+
td.code().tx(replacedBy);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Property columns
|
|
746
|
+
for (const prop of columnInfo.properties) {
|
|
747
|
+
const td = tr.td();
|
|
748
|
+
const values = this.getPropertyValues(concept, prop.code);
|
|
749
|
+
|
|
750
|
+
let first = true;
|
|
751
|
+
for (const val of values) {
|
|
752
|
+
if (!first) {
|
|
753
|
+
td.tx(', ');
|
|
754
|
+
}
|
|
755
|
+
first = false;
|
|
756
|
+
|
|
757
|
+
await this.renderPropertyValue(td, val, prop, cs);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Recurse for child concepts
|
|
762
|
+
if (concept.concept) {
|
|
763
|
+
for (const child of concept.concept) {
|
|
764
|
+
await this.addConceptRow(tbl, child, level + 1, cs, columnInfo);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
getPropertyValue(concept, code) {
|
|
770
|
+
if (!concept.property) return null;
|
|
771
|
+
const prop = concept.property.find(p => p.code === code);
|
|
772
|
+
return prop ? this.extractPropertyValue(prop) : null;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
getPropertyValues(concept, code) {
|
|
776
|
+
if (!concept.property) return [];
|
|
777
|
+
return concept.property
|
|
778
|
+
.filter(p => p.code === code)
|
|
779
|
+
.map(p => this.extractPropertyValue(p))
|
|
780
|
+
.filter(v => v !== null);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
extractPropertyValue(prop) {
|
|
784
|
+
if (prop.valueCode !== undefined) return { type: 'code', value: prop.valueCode };
|
|
785
|
+
if (prop.valueString !== undefined) return { type: 'string', value: prop.valueString };
|
|
786
|
+
if (prop.valueBoolean !== undefined) return { type: 'boolean', value: prop.valueBoolean };
|
|
787
|
+
if (prop.valueInteger !== undefined) return { type: 'integer', value: prop.valueInteger };
|
|
788
|
+
if (prop.valueDecimal !== undefined) return { type: 'decimal', value: prop.valueDecimal };
|
|
789
|
+
if (prop.valueDateTime !== undefined) return { type: 'dateTime', value: prop.valueDateTime };
|
|
790
|
+
if (prop.valueCoding !== undefined) return { type: 'coding', value: prop.valueCoding };
|
|
791
|
+
return null;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
async renderPropertyValue(td, val, propDef, cs) {
|
|
795
|
+
if (!val) return;
|
|
796
|
+
|
|
797
|
+
switch (val.type) {
|
|
798
|
+
case 'code': {
|
|
799
|
+
// If it's a parent reference, link to it
|
|
800
|
+
if (propDef.code === 'parent' || propDef.code === 'child') {
|
|
801
|
+
td.ah('#' + cs.id + '-' + val.value).tx(val.value);
|
|
802
|
+
} else {
|
|
803
|
+
td.code().tx(val.value);
|
|
804
|
+
}
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
case 'coding': {
|
|
808
|
+
const coding = val.value;
|
|
809
|
+
const link = this.linkResolver ?
|
|
810
|
+
await this.linkResolver.resolveCode(this.opContext, coding.system, coding.version, coding.code) : null;
|
|
811
|
+
if (link) {
|
|
812
|
+
td.ah(link.link).tx(coding.code);
|
|
813
|
+
} else {
|
|
814
|
+
td.tx(coding.code);
|
|
815
|
+
}
|
|
816
|
+
if (coding.display) {
|
|
817
|
+
td.tx(' "' + coding.display + '"');
|
|
818
|
+
}
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
case 'boolean': {
|
|
822
|
+
td.tx(val.value ? 'true' : 'false');
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
case 'string': {
|
|
826
|
+
// Check if it's a URL
|
|
827
|
+
if (val.value.startsWith('http://') || val.value.startsWith('https://')) {
|
|
828
|
+
td.ah(val.value).tx(val.value);
|
|
829
|
+
} else {
|
|
830
|
+
td.tx(val.value);
|
|
831
|
+
}
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
default:
|
|
835
|
+
td.tx(String(val.value));
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
sentenceForContent(mode, cs) {
|
|
840
|
+
switch (mode) {
|
|
841
|
+
case 'complete':
|
|
842
|
+
return this.translate('CODESYSTEM_CONTENT_COMPLETE');
|
|
843
|
+
case 'example':
|
|
844
|
+
return this.translate('CODESYSTEM_CONTENT_EXAMPLE');
|
|
845
|
+
case 'fragment':
|
|
846
|
+
return this.translate('CODESYSTEM_CONTENT_FRAGMENT');
|
|
847
|
+
case 'not-present':
|
|
848
|
+
return this.translate('CODESYSTEM_CONTENT_NOTPRESENT');
|
|
849
|
+
case 'supplement': {
|
|
850
|
+
const hasProperties = cs.property && cs.property.length > 0;
|
|
851
|
+
const hasDesignations = this.hasDesignations(cs);
|
|
852
|
+
let features;
|
|
853
|
+
if (hasProperties && hasDesignations) {
|
|
854
|
+
features = this.translate('CODE_SYS_DISP_PROP');
|
|
855
|
+
} else if (hasProperties) {
|
|
856
|
+
features = this.translate('CODE_SYS_PROP');
|
|
857
|
+
} else if (hasDesignations) {
|
|
858
|
+
features = this.translate('CODE_SYS_DISP');
|
|
859
|
+
} else {
|
|
860
|
+
features = this.translate('CODE_SYS_FEAT');
|
|
861
|
+
}
|
|
862
|
+
return this.translate('CODESYSTEM_CONTENT_SUPPLEMENT', [features]);
|
|
863
|
+
}
|
|
864
|
+
default:
|
|
865
|
+
return this.translate('CODESYSTEM_CONTENT_NOTPRESENT');
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
hasDesignations(cs) {
|
|
870
|
+
const checkConcepts = (concepts) => {
|
|
871
|
+
for (const c of concepts) {
|
|
872
|
+
if (c.designation && c.designation.length > 0) {
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
if (c.concept && checkConcepts(c.concept)) {
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return false;
|
|
880
|
+
};
|
|
881
|
+
return checkConcepts(cs.concept || []);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
countConcepts(concepts) {
|
|
885
|
+
if (!concepts) {
|
|
886
|
+
return 0;
|
|
887
|
+
}
|
|
888
|
+
let count = concepts.length;
|
|
889
|
+
for (const c of concepts) {
|
|
890
|
+
if (c.concept) {
|
|
891
|
+
count += this.countConcepts(c.concept);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return count;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
async renderExpansion(x, vs, tbl) {
|
|
898
|
+
this.renderProperty(tbl, 'Expansion Identifier', vs.expansion.identifier);
|
|
899
|
+
this.renderProperty(tbl, 'Expansion Timestamp', vs.expansion.timestamp);
|
|
900
|
+
this.renderProperty(tbl, 'Expansion Total', vs.expansion.total);
|
|
901
|
+
this.renderProperty(tbl, 'Expansion Offset', vs.expansion.offset);
|
|
902
|
+
for (let p of vs.expansion.parameter || []) {
|
|
903
|
+
await this.renderPropertyLink(tbl, "Parameter: " + p.name, getValuePrimitive(p));
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (!vs.expansion.contains || vs.expansion.contains.length === 0) {
|
|
907
|
+
x.para().i().tx('No concepts in expansion');
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Analyze columns needed
|
|
912
|
+
const columnInfo = this.analyzeExpansionColumns(vs.expansion);
|
|
913
|
+
|
|
914
|
+
// Build the expansion table
|
|
915
|
+
const expTbl = x.table("codes");
|
|
916
|
+
|
|
917
|
+
// Header row
|
|
918
|
+
const headerRow = expTbl.tr();
|
|
919
|
+
|
|
920
|
+
if (columnInfo.hasHierarchy) {
|
|
921
|
+
headerRow.th().tx(this.translate('CODESYSTEM_LVL'));
|
|
922
|
+
}
|
|
923
|
+
headerRow.th().tx(this.translate('GENERAL_CODE'));
|
|
924
|
+
headerRow.th().tx(this.translate('VALUE_SET_SYSTEM'));
|
|
925
|
+
if (columnInfo.hasVersion) {
|
|
926
|
+
headerRow.th().tx(this.translate('GENERAL_VER'));
|
|
927
|
+
}
|
|
928
|
+
headerRow.th().tx(this.translate('TX_DISPLAY'));
|
|
929
|
+
if (columnInfo.hasAbstract) {
|
|
930
|
+
headerRow.th().tx('Abstract');
|
|
931
|
+
}
|
|
932
|
+
if (columnInfo.hasInactive) {
|
|
933
|
+
headerRow.th().tx('Inactive');
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// Property columns (from expansion.property definitions)
|
|
937
|
+
for (const prop of columnInfo.properties) {
|
|
938
|
+
headerRow.th().tx(prop.code);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Designation columns (use|language combinations)
|
|
942
|
+
for (const desig of columnInfo.designations) {
|
|
943
|
+
headerRow.th().tx(this.formatDesignationHeader(desig));
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Render contains recursively
|
|
947
|
+
for (const contains of vs.expansion.contains) {
|
|
948
|
+
await this.addExpansionRow(expTbl, contains, 0, columnInfo);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Analyze expansion contains to determine which columns are needed
|
|
954
|
+
*/
|
|
955
|
+
analyzeExpansionColumns(expansion) {
|
|
956
|
+
const info = {
|
|
957
|
+
hasHierarchy: false,
|
|
958
|
+
hasVersion: false,
|
|
959
|
+
hasAbstract: false,
|
|
960
|
+
hasInactive: false,
|
|
961
|
+
properties: [],
|
|
962
|
+
designations: []
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// Build map of property codes from expansion.property
|
|
966
|
+
const propertyDefs = new Map();
|
|
967
|
+
for (const prop of expansion.property || []) {
|
|
968
|
+
propertyDefs.set(prop.code, prop);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Track which properties and designations are actually used
|
|
972
|
+
const usedProperties = new Set();
|
|
973
|
+
const usedDesignations = new Map(); // key: "use|language", value: {use, language}
|
|
974
|
+
|
|
975
|
+
const analyzeContains = (containsList, level) => {
|
|
976
|
+
for (const c of containsList) {
|
|
977
|
+
if (c.version) {
|
|
978
|
+
info.hasVersion = true;
|
|
979
|
+
}
|
|
980
|
+
if (c.abstract === true) {
|
|
981
|
+
info.hasAbstract = true;
|
|
982
|
+
}
|
|
983
|
+
if (c.inactive === true) {
|
|
984
|
+
info.hasInactive = true;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Check for nested contains (hierarchy)
|
|
988
|
+
if (c.contains && c.contains.length > 0) {
|
|
989
|
+
info.hasHierarchy = true;
|
|
990
|
+
analyzeContains(c.contains, level + 1);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Track used properties
|
|
994
|
+
if (c.property) {
|
|
995
|
+
for (const prop of c.property) {
|
|
996
|
+
usedProperties.add(prop.code);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Track used designations
|
|
1001
|
+
if (c.designation) {
|
|
1002
|
+
for (const desig of c.designation) {
|
|
1003
|
+
const key = this.getDesignationKey(desig);
|
|
1004
|
+
if (!usedDesignations.has(key)) {
|
|
1005
|
+
usedDesignations.set(key, {
|
|
1006
|
+
use: desig.use,
|
|
1007
|
+
language: desig.language
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
analyzeContains(expansion.contains || [], 0);
|
|
1016
|
+
|
|
1017
|
+
// Filter to properties that are defined and used
|
|
1018
|
+
for (const [code, def] of propertyDefs) {
|
|
1019
|
+
if (usedProperties.has(code)) {
|
|
1020
|
+
info.properties.push(def);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// Convert designation map to array, sorted for consistent ordering
|
|
1025
|
+
info.designations = Array.from(usedDesignations.values()).sort((a, b) => {
|
|
1026
|
+
const keyA = this.getDesignationKey(a);
|
|
1027
|
+
const keyB = this.getDesignationKey(b);
|
|
1028
|
+
return keyA.localeCompare(keyB);
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
return info;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Get a unique key for a designation based on use and language
|
|
1036
|
+
*/
|
|
1037
|
+
getDesignationKey(desig) {
|
|
1038
|
+
const useCode = desig.use?.code || '';
|
|
1039
|
+
const useSystem = desig.use?.system || '';
|
|
1040
|
+
const lang = desig.language || '';
|
|
1041
|
+
return `${useSystem}|${useCode}|${lang}`;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Format a designation header for display
|
|
1046
|
+
*/
|
|
1047
|
+
formatDesignationHeader(desig) {
|
|
1048
|
+
const parts = [];
|
|
1049
|
+
if (desig.use?.display) {
|
|
1050
|
+
parts.push(desig.use.display);
|
|
1051
|
+
} else if (desig.use?.code) {
|
|
1052
|
+
parts.push(desig.use.code);
|
|
1053
|
+
}
|
|
1054
|
+
if (desig.language) {
|
|
1055
|
+
parts.push(`(${desig.language})`);
|
|
1056
|
+
}
|
|
1057
|
+
return parts.length > 0 ? parts.join(' ') : 'Designation';
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Add a row for an expansion contains entry
|
|
1062
|
+
*/
|
|
1063
|
+
async addExpansionRow(tbl, contains, level, columnInfo) {
|
|
1064
|
+
const tr = tbl.tr();
|
|
1065
|
+
|
|
1066
|
+
// Apply styling for abstract or inactive concepts
|
|
1067
|
+
if (contains.abstract === true) {
|
|
1068
|
+
tr.style("font-style: italic");
|
|
1069
|
+
}
|
|
1070
|
+
if (contains.inactive === true) {
|
|
1071
|
+
tr.style("background-color: #ffeeee");
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Level column
|
|
1075
|
+
if (columnInfo.hasHierarchy) {
|
|
1076
|
+
tr.td().tx(String(level + 1));
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Code column
|
|
1080
|
+
const codeTd = tr.td();
|
|
1081
|
+
if (level > 0) {
|
|
1082
|
+
codeTd.tx('\u00A0'.repeat(level * 2)); // Non-breaking spaces for indentation
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// Try to link the code
|
|
1086
|
+
if (contains.code) {
|
|
1087
|
+
const link = this.linkResolver ?
|
|
1088
|
+
await this.linkResolver.resolveCode(this.opContext, contains.system, contains.version, contains.code) : null;
|
|
1089
|
+
if (link) {
|
|
1090
|
+
codeTd.ah(link.link).tx(contains.code);
|
|
1091
|
+
} else {
|
|
1092
|
+
codeTd.code().tx(contains.code);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// System column
|
|
1097
|
+
const systemTd = tr.td();
|
|
1098
|
+
if (contains.system) {
|
|
1099
|
+
systemTd.code().tx(contains.system);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Version column
|
|
1103
|
+
if (columnInfo.hasVersion) {
|
|
1104
|
+
tr.td().tx(contains.version || '');
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Display column
|
|
1108
|
+
tr.td().tx(contains.display || '');
|
|
1109
|
+
|
|
1110
|
+
// Abstract column
|
|
1111
|
+
if (columnInfo.hasAbstract) {
|
|
1112
|
+
tr.td().tx(contains.abstract === true ? 'true' : '');
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Inactive column
|
|
1116
|
+
if (columnInfo.hasInactive) {
|
|
1117
|
+
tr.td().tx(contains.inactive === true ? 'true' : '');
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Property columns
|
|
1121
|
+
for (const propDef of columnInfo.properties) {
|
|
1122
|
+
const td = tr.td();
|
|
1123
|
+
const values = this.getContainsPropertyValues(contains, propDef.code);
|
|
1124
|
+
|
|
1125
|
+
let first = true;
|
|
1126
|
+
for (const val of values) {
|
|
1127
|
+
if (!first) {
|
|
1128
|
+
td.tx(', ');
|
|
1129
|
+
}
|
|
1130
|
+
first = false;
|
|
1131
|
+
await this.renderExpansionPropertyValue(td, val, propDef);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Designation columns
|
|
1136
|
+
for (const desigDef of columnInfo.designations) {
|
|
1137
|
+
const td = tr.td();
|
|
1138
|
+
const value = this.getDesignationValue(contains, desigDef);
|
|
1139
|
+
if (value) {
|
|
1140
|
+
td.tx(value);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Recurse for nested contains
|
|
1145
|
+
if (contains.contains) {
|
|
1146
|
+
for (const child of contains.contains) {
|
|
1147
|
+
await this.addExpansionRow(tbl, child, level + 1, columnInfo);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* Get property values from a contains entry
|
|
1154
|
+
*/
|
|
1155
|
+
getContainsPropertyValues(contains, code) {
|
|
1156
|
+
if (!contains.property) return [];
|
|
1157
|
+
return contains.property
|
|
1158
|
+
.filter(p => p.code === code)
|
|
1159
|
+
.map(p => this.extractExpansionPropertyValue(p))
|
|
1160
|
+
.filter(v => v !== null);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* Extract the value from an expansion property
|
|
1165
|
+
*/
|
|
1166
|
+
extractExpansionPropertyValue(prop) {
|
|
1167
|
+
if (prop.valueCode !== undefined) return { type: 'code', value: prop.valueCode };
|
|
1168
|
+
if (prop.valueString !== undefined) return { type: 'string', value: prop.valueString };
|
|
1169
|
+
if (prop.valueBoolean !== undefined) return { type: 'boolean', value: prop.valueBoolean };
|
|
1170
|
+
if (prop.valueInteger !== undefined) return { type: 'integer', value: prop.valueInteger };
|
|
1171
|
+
if (prop.valueDecimal !== undefined) return { type: 'decimal', value: prop.valueDecimal };
|
|
1172
|
+
if (prop.valueDateTime !== undefined) return { type: 'dateTime', value: prop.valueDateTime };
|
|
1173
|
+
if (prop.valueCoding !== undefined) return { type: 'coding', value: prop.valueCoding };
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* Render an expansion property value
|
|
1179
|
+
*/
|
|
1180
|
+
// eslint-disable-next-line no-unused-vars
|
|
1181
|
+
async renderExpansionPropertyValue(td, val, propDef) {
|
|
1182
|
+
if (!val) return;
|
|
1183
|
+
|
|
1184
|
+
switch (val.type) {
|
|
1185
|
+
case 'code': {
|
|
1186
|
+
td.code().tx(val.value);
|
|
1187
|
+
break;
|
|
1188
|
+
}
|
|
1189
|
+
case 'coding': {
|
|
1190
|
+
const coding = val.value;
|
|
1191
|
+
const link = this.linkResolver ?
|
|
1192
|
+
await this.linkResolver.resolveCode(this.opContext, coding.system, coding.version, coding.code) : null;
|
|
1193
|
+
if (link) {
|
|
1194
|
+
td.ah(link.link).tx(coding.code);
|
|
1195
|
+
} else {
|
|
1196
|
+
td.code().tx(coding.code);
|
|
1197
|
+
}
|
|
1198
|
+
if (coding.display) {
|
|
1199
|
+
td.tx(' "' + coding.display + '"');
|
|
1200
|
+
}
|
|
1201
|
+
break;
|
|
1202
|
+
}
|
|
1203
|
+
case 'boolean': {
|
|
1204
|
+
td.tx(val.value ? 'true' : 'false');
|
|
1205
|
+
break;
|
|
1206
|
+
}
|
|
1207
|
+
case 'string': {
|
|
1208
|
+
if (val.value.startsWith('http://') || val.value.startsWith('https://')) {
|
|
1209
|
+
td.ah(val.value).tx(val.value);
|
|
1210
|
+
} else {
|
|
1211
|
+
td.tx(val.value);
|
|
1212
|
+
}
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
default:
|
|
1216
|
+
td.tx(String(val.value));
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Get a designation value matching the given use/language
|
|
1222
|
+
*/
|
|
1223
|
+
getDesignationValue(contains, desigDef) {
|
|
1224
|
+
if (!contains.designation) return null;
|
|
1225
|
+
|
|
1226
|
+
for (const desig of contains.designation) {
|
|
1227
|
+
// Match on use and language
|
|
1228
|
+
const useMatches = this.codingMatches(desig.use, desigDef.use);
|
|
1229
|
+
const langMatches = (desig.language || '') === (desigDef.language || '');
|
|
1230
|
+
|
|
1231
|
+
if (useMatches && langMatches) {
|
|
1232
|
+
return desig.value;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return null;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Check if two codings match (both null, or same system/code)
|
|
1240
|
+
*/
|
|
1241
|
+
codingMatches(a, b) {
|
|
1242
|
+
if (!a && !b) return true;
|
|
1243
|
+
if (!a || !b) return false;
|
|
1244
|
+
return (a.system || '') === (b.system || '') && (a.code || '') === (b.code || '');
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
async renderCapabilityStatement(cs) {
|
|
1248
|
+
if (cs.json) {
|
|
1249
|
+
cs = cs.json;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
let div_ = div();
|
|
1253
|
+
|
|
1254
|
+
// Metadata table
|
|
1255
|
+
div_.h3().tx("Properties");
|
|
1256
|
+
await this.renderMetadataTable(cs, div_.table("grid"));
|
|
1257
|
+
|
|
1258
|
+
// Formats
|
|
1259
|
+
if (cs.format && cs.format.length > 0) {
|
|
1260
|
+
div_.h3().tx(this.translate('Formats') || 'Formats');
|
|
1261
|
+
div_.para().tx(cs.format.join(', '));
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// Implementation Guides
|
|
1265
|
+
if (cs.implementationGuide && cs.implementationGuide.length > 0) {
|
|
1266
|
+
div_.h3().tx(this.translate('CAPABILITY_IMP_GUIDES') || 'Implementation Guides');
|
|
1267
|
+
const ul = div_.ul();
|
|
1268
|
+
for (const ig of cs.implementationGuide) {
|
|
1269
|
+
await this.renderLink(ul.li(), ig);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// REST definitions
|
|
1274
|
+
if (cs.rest && cs.rest.length > 0) {
|
|
1275
|
+
for (const rest of cs.rest) {
|
|
1276
|
+
await this.renderCapabilityRest(div_, rest, cs);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
return div_.toString();
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
async renderCapabilityRest(x, rest, cs) {
|
|
1284
|
+
x.h3().tx(`REST ${rest.mode || 'server'} Definition`);
|
|
1285
|
+
|
|
1286
|
+
if (rest.documentation) {
|
|
1287
|
+
x.para().markdown(rest.documentation);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// Security
|
|
1291
|
+
if (rest.security) {
|
|
1292
|
+
await this.renderCapabilitySecurity(x, rest.security);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Resources table
|
|
1296
|
+
if (rest.resource && rest.resource.length > 0) {
|
|
1297
|
+
await this.renderCapabilityResources(x, rest);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// System-level operations
|
|
1301
|
+
if (rest.operation && rest.operation.length > 0) {
|
|
1302
|
+
x.h4().tx(this.translate('CAPABILITY_OP') || 'System Operations');
|
|
1303
|
+
const ul = x.ul();
|
|
1304
|
+
for (const op of rest.operation) {
|
|
1305
|
+
const li = ul.li();
|
|
1306
|
+
if (op.definition) {
|
|
1307
|
+
li.ah(op.definition).tx(op.name);
|
|
1308
|
+
} else {
|
|
1309
|
+
li.tx(op.name);
|
|
1310
|
+
}
|
|
1311
|
+
if (op.documentation) {
|
|
1312
|
+
li.tx(': ');
|
|
1313
|
+
li.tx(op.documentation);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// Compartments
|
|
1319
|
+
if (rest.compartment && rest.compartment.length > 0) {
|
|
1320
|
+
x.h4().tx(this.translate('CAPABILITY_COMPARTMENTS') || 'Compartments');
|
|
1321
|
+
const ul = x.ul();
|
|
1322
|
+
for (const comp of rest.compartment) {
|
|
1323
|
+
await this.renderLink(ul.li(), comp);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
async renderCapabilitySecurity(x, security) {
|
|
1329
|
+
x.h4().tx('Security');
|
|
1330
|
+
|
|
1331
|
+
const tbl = x.table("grid");
|
|
1332
|
+
|
|
1333
|
+
if (security.cors !== undefined) {
|
|
1334
|
+
const tr = tbl.tr();
|
|
1335
|
+
tr.td().b().tx('CORS');
|
|
1336
|
+
tr.td().tx(security.cors ? 'enabled' : 'disabled');
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
if (security.service && security.service.length > 0) {
|
|
1340
|
+
const tr = tbl.tr();
|
|
1341
|
+
tr.td().b().tx(this.translate('CAPABILITY_SEC_SERVICES') || 'Security Services');
|
|
1342
|
+
const td = tr.td();
|
|
1343
|
+
let first = true;
|
|
1344
|
+
for (const svc of security.service) {
|
|
1345
|
+
if (!first) {
|
|
1346
|
+
td.tx(', ');
|
|
1347
|
+
}
|
|
1348
|
+
first = false;
|
|
1349
|
+
// Render CodeableConcept - prefer text, then first coding display, then first coding code
|
|
1350
|
+
if (svc.text) {
|
|
1351
|
+
td.tx(svc.text);
|
|
1352
|
+
} else if (svc.coding && svc.coding.length > 0) {
|
|
1353
|
+
const coding = svc.coding[0];
|
|
1354
|
+
td.tx(coding.display || coding.code || '');
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (security.description) {
|
|
1360
|
+
const tr = tbl.tr();
|
|
1361
|
+
tr.td().b().tx(this.translate('GENERAL_DESC') || 'Description');
|
|
1362
|
+
tr.td().markdown(security.description);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
async renderCapabilityResources(x, rest) {
|
|
1367
|
+
x.h4().tx('Resources');
|
|
1368
|
+
|
|
1369
|
+
// Analyze what columns we need
|
|
1370
|
+
const columnInfo = this.analyzeCapabilityResourceColumns(rest.resource);
|
|
1371
|
+
|
|
1372
|
+
const tbl = x.table("grid");
|
|
1373
|
+
|
|
1374
|
+
// Header row
|
|
1375
|
+
const headerRow = tbl.tr();
|
|
1376
|
+
headerRow.th().tx(this.translate('GENERAL_TYPE') || 'Type');
|
|
1377
|
+
|
|
1378
|
+
if (columnInfo.hasProfile) {
|
|
1379
|
+
headerRow.th().tx(this.translate('GENERAL_PROF') || 'Profile');
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// Interaction columns
|
|
1383
|
+
for (const intCode of columnInfo.interactions) {
|
|
1384
|
+
headerRow.th().tx(intCode);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (columnInfo.hasSearchParams) {
|
|
1388
|
+
headerRow.th().tx(this.translate('CAPABILITY_SEARCH_PARS') || 'Search Parameters');
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
if (columnInfo.hasOperations) {
|
|
1392
|
+
headerRow.th().tx(this.translate('CAPABILITY_OP') || 'Operations');
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// Resource rows
|
|
1396
|
+
for (const resource of rest.resource) {
|
|
1397
|
+
await this.addCapabilityResourceRow(tbl, resource, columnInfo);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
analyzeCapabilityResourceColumns(resources) {
|
|
1402
|
+
const info = {
|
|
1403
|
+
hasProfile: false,
|
|
1404
|
+
interactions: new Set(),
|
|
1405
|
+
hasSearchParams: false,
|
|
1406
|
+
hasOperations: false
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
for (const res of resources) {
|
|
1410
|
+
if (res.profile) {
|
|
1411
|
+
info.hasProfile = true;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
if (res.interaction) {
|
|
1415
|
+
for (const int of res.interaction) {
|
|
1416
|
+
info.interactions.add(int.code);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
if (res.searchParam && res.searchParam.length > 0) {
|
|
1421
|
+
info.hasSearchParams = true;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
if (res.operation && res.operation.length > 0) {
|
|
1425
|
+
info.hasOperations = true;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// Convert interactions to sorted array for consistent column ordering
|
|
1430
|
+
const interactionOrder = ['read', 'vread', 'update', 'patch', 'delete', 'history-instance', 'history-type', 'create', 'search-type'];
|
|
1431
|
+
info.interactions = interactionOrder.filter(i => info.interactions.has(i));
|
|
1432
|
+
|
|
1433
|
+
// Add any other interactions not in our predefined order
|
|
1434
|
+
for (const res of resources) {
|
|
1435
|
+
if (res.interaction) {
|
|
1436
|
+
for (const int of res.interaction) {
|
|
1437
|
+
if (!info.interactions.includes(int.code)) {
|
|
1438
|
+
info.interactions.push(int.code);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
return info;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
async addCapabilityResourceRow(tbl, resource, columnInfo) {
|
|
1448
|
+
const tr = tbl.tr();
|
|
1449
|
+
|
|
1450
|
+
// Type column
|
|
1451
|
+
tr.td().b().tx(resource.type);
|
|
1452
|
+
|
|
1453
|
+
// Profile column
|
|
1454
|
+
if (columnInfo.hasProfile) {
|
|
1455
|
+
const td = tr.td();
|
|
1456
|
+
if (resource.profile) {
|
|
1457
|
+
await this.renderLink(td, resource.profile);
|
|
1458
|
+
}
|
|
1459
|
+
// Also show supportedProfile if present
|
|
1460
|
+
if (resource.supportedProfile && resource.supportedProfile.length > 0) {
|
|
1461
|
+
if (resource.profile) {
|
|
1462
|
+
td.br();
|
|
1463
|
+
td.tx('Also: ');
|
|
1464
|
+
}
|
|
1465
|
+
let first = true;
|
|
1466
|
+
for (const sp of resource.supportedProfile) {
|
|
1467
|
+
if (!first) {
|
|
1468
|
+
td.tx(', ');
|
|
1469
|
+
}
|
|
1470
|
+
first = false;
|
|
1471
|
+
await this.renderLink(td, sp);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// Interaction columns - render checkmarks
|
|
1477
|
+
const supportedInteractions = new Set(
|
|
1478
|
+
(resource.interaction || []).map(i => i.code)
|
|
1479
|
+
);
|
|
1480
|
+
|
|
1481
|
+
for (const intCode of columnInfo.interactions) {
|
|
1482
|
+
const td = tr.td();
|
|
1483
|
+
td.style("text-align: center");
|
|
1484
|
+
if (supportedInteractions.has(intCode)) {
|
|
1485
|
+
td.tx('✓');
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// Search parameters column
|
|
1490
|
+
if (columnInfo.hasSearchParams) {
|
|
1491
|
+
const td = tr.td();
|
|
1492
|
+
if (resource.searchParam && resource.searchParam.length > 0) {
|
|
1493
|
+
const paramNames = resource.searchParam.map(sp => sp.name);
|
|
1494
|
+
td.tx(paramNames.join(', '));
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// Operations column
|
|
1499
|
+
if (columnInfo.hasOperations) {
|
|
1500
|
+
const td = tr.td();
|
|
1501
|
+
if (resource.operation && resource.operation.length > 0) {
|
|
1502
|
+
let first = true;
|
|
1503
|
+
for (const op of resource.operation) {
|
|
1504
|
+
if (!first) {
|
|
1505
|
+
td.tx(', ');
|
|
1506
|
+
}
|
|
1507
|
+
first = false;
|
|
1508
|
+
if (op.definition) {
|
|
1509
|
+
td.ah(op.definition).tx(op.name);
|
|
1510
|
+
} else {
|
|
1511
|
+
td.tx(op.name);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
async renderTerminologyCapabilities(tc) {
|
|
1519
|
+
if (tc.json) {
|
|
1520
|
+
tc = tc.json;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
let div_ = div();
|
|
1524
|
+
|
|
1525
|
+
// Metadata table
|
|
1526
|
+
div_.h3().tx("Properties");
|
|
1527
|
+
await this.renderMetadataTable(tc, div_.table("grid"));
|
|
1528
|
+
|
|
1529
|
+
// Code Systems
|
|
1530
|
+
if (tc.codeSystem && tc.codeSystem.length > 0) {
|
|
1531
|
+
div_.h3().tx('Code Systems');
|
|
1532
|
+
const ul = div_.ul();
|
|
1533
|
+
for (const cs of tc.codeSystem) {
|
|
1534
|
+
if (cs.uri) {
|
|
1535
|
+
const li = ul.li();
|
|
1536
|
+
if (cs.version && cs.version.length > 0) {
|
|
1537
|
+
// List each version
|
|
1538
|
+
let first = true;
|
|
1539
|
+
for (const v of cs.version) {
|
|
1540
|
+
if (!first) {
|
|
1541
|
+
li.tx(', ');
|
|
1542
|
+
}
|
|
1543
|
+
first = false;
|
|
1544
|
+
const versionedUri = v.code ? `${cs.uri}|${v.code}` : cs.uri;
|
|
1545
|
+
await this.renderLink(li, versionedUri);
|
|
1546
|
+
}
|
|
1547
|
+
} else {
|
|
1548
|
+
// No versions specified
|
|
1549
|
+
await this.renderLink(li, cs.uri);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
return div_.toString();
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
module.exports = { Renderer };
|