fhirsmith 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/FHIRsmith.png +0 -0
  3. package/README.md +277 -0
  4. package/config-template.json +144 -0
  5. package/library/folder-setup.js +58 -0
  6. package/library/html-server.js +166 -0
  7. package/library/html.js +835 -0
  8. package/library/i18nsupport.js +259 -0
  9. package/library/languages.js +779 -0
  10. package/library/logger-telnet.js +205 -0
  11. package/library/logger.js +279 -0
  12. package/library/package-manager.js +876 -0
  13. package/library/utilities.js +196 -0
  14. package/library/version-utilities.js +1056 -0
  15. package/npmprojector/config-example.json +13 -0
  16. package/npmprojector/indexer.js +394 -0
  17. package/npmprojector/npmprojector.js +395 -0
  18. package/npmprojector/readme.md +174 -0
  19. package/npmprojector/watcher.js +335 -0
  20. package/package.json +119 -0
  21. package/packages/package-crawler.js +846 -0
  22. package/packages/packages-template.html +126 -0
  23. package/packages/packages.js +2838 -0
  24. package/passwords.ini +2 -0
  25. package/publisher/publisher-template.html +208 -0
  26. package/publisher/publisher.js +2167 -0
  27. package/publisher/task-draft.js +458 -0
  28. package/registry/api.js +735 -0
  29. package/registry/crawler.js +637 -0
  30. package/registry/model.js +513 -0
  31. package/registry/readme.md +243 -0
  32. package/registry/registry-data.json +121015 -0
  33. package/registry/registry-template.html +126 -0
  34. package/registry/registry.js +1395 -0
  35. package/registry/test-runner.js +237 -0
  36. package/root-template.html +124 -0
  37. package/server.js +524 -0
  38. package/shl/private-key.pem +5 -0
  39. package/shl/public-key.pem +18 -0
  40. package/shl/shl.js +1125 -0
  41. package/shl/vhl.js +69 -0
  42. package/static/FHIRsmith128.png +0 -0
  43. package/static/FHIRsmith16.png +0 -0
  44. package/static/FHIRsmith32.png +0 -0
  45. package/static/FHIRsmith64.png +0 -0
  46. package/static/assets/css/bootstrap-fhir.css +5302 -0
  47. package/static/assets/css/bootstrap-glyphicons.css +2 -0
  48. package/static/assets/css/bootstrap.css +4097 -0
  49. package/static/assets/css/jquery-ui.css +523 -0
  50. package/static/assets/css/jquery-ui.structure.css +863 -0
  51. package/static/assets/css/jquery-ui.structure.min.css +5 -0
  52. package/static/assets/css/jquery-ui.theme.css +439 -0
  53. package/static/assets/css/jquery-ui.theme.min.css +5 -0
  54. package/static/assets/css/jquery.ui.all.css +7 -0
  55. package/static/assets/css/modules.css +18 -0
  56. package/static/assets/css/project.css +367 -0
  57. package/static/assets/css/pygments-manni.css +66 -0
  58. package/static/assets/css/tags.css +74 -0
  59. package/static/assets/css/xml.css +2 -0
  60. package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
  61. package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
  62. package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
  63. package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
  64. package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
  65. package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
  66. package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
  67. package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
  68. package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
  69. package/static/assets/ico/favicon.ico +0 -0
  70. package/static/assets/ico/favicon.png +0 -0
  71. package/static/assets/images/fhir-logo-www.png +0 -0
  72. package/static/assets/images/fhir-logo.png +0 -0
  73. package/static/assets/images/hl7-logo.png +0 -0
  74. package/static/assets/images/logo_ansinew.jpg +0 -0
  75. package/static/assets/images/search.png +0 -0
  76. package/static/assets/images/stripe.png +0 -0
  77. package/static/assets/images/target.png +0 -0
  78. package/static/assets/images/tx-registry-root.gif +0 -0
  79. package/static/assets/images/tx-registry.png +0 -0
  80. package/static/assets/images/tx-server.png +0 -0
  81. package/static/assets/images/tx-version.png +0 -0
  82. package/static/assets/js/bootstrap.min.js +6 -0
  83. package/static/assets/js/fhir-gw.js +259 -0
  84. package/static/assets/js/fhir.js +2 -0
  85. package/static/assets/js/html5shiv.js +8 -0
  86. package/static/assets/js/jcookie.js +96 -0
  87. package/static/assets/js/jquery-ui.min.js +6 -0
  88. package/static/assets/js/jquery.js +10716 -0
  89. package/static/assets/js/jquery.min.js +2 -0
  90. package/static/assets/js/jquery.ui.core.js +314 -0
  91. package/static/assets/js/jquery.ui.draggable.js +825 -0
  92. package/static/assets/js/jquery.ui.mouse.js +162 -0
  93. package/static/assets/js/jquery.ui.resizable.js +842 -0
  94. package/static/assets/js/jquery.ui.widget.js +268 -0
  95. package/static/assets/js/json2.js +487 -0
  96. package/static/assets/js/jtip.js +97 -0
  97. package/static/assets/js/respond.min.js +6 -0
  98. package/static/assets/js/statuspage.js +70 -0
  99. package/static/assets/js/xml.js +2 -0
  100. package/static/dist/js/bootstrap.js +1964 -0
  101. package/static/favicon.png +0 -0
  102. package/static/fhir.css +626 -0
  103. package/static/icon-fhir-16.png +0 -0
  104. package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  105. package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  106. package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
  107. package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  108. package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  109. package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  110. package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  111. package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  112. package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  113. package/static/images/ui-icons_222222_256x240.png +0 -0
  114. package/static/images/ui-icons_228ef1_256x240.png +0 -0
  115. package/static/images/ui-icons_ef8c08_256x240.png +0 -0
  116. package/static/images/ui-icons_ffd27a_256x240.png +0 -0
  117. package/static/images/ui-icons_ffffff_256x240.png +0 -0
  118. package/static/js/jquery.effects.blind.js +49 -0
  119. package/static/js/jquery.effects.bounce.js +78 -0
  120. package/static/js/jquery.effects.clip.js +54 -0
  121. package/static/js/jquery.effects.core.js +763 -0
  122. package/static/js/jquery.effects.drop.js +50 -0
  123. package/static/js/jquery.effects.explode.js +79 -0
  124. package/static/js/jquery.effects.fade.js +32 -0
  125. package/static/js/jquery.effects.fold.js +56 -0
  126. package/static/js/jquery.effects.highlight.js +50 -0
  127. package/static/js/jquery.effects.pulsate.js +51 -0
  128. package/static/js/jquery.effects.scale.js +178 -0
  129. package/static/js/jquery.effects.shake.js +57 -0
  130. package/static/js/jquery.effects.slide.js +50 -0
  131. package/static/js/jquery.effects.transfer.js +45 -0
  132. package/static/js/jquery.ui.accordion.js +611 -0
  133. package/static/js/jquery.ui.autocomplete.js +612 -0
  134. package/static/js/jquery.ui.button.js +416 -0
  135. package/static/js/jquery.ui.datepicker.js +1823 -0
  136. package/static/js/jquery.ui.dialog.js +878 -0
  137. package/static/js/jquery.ui.droppable.js +296 -0
  138. package/static/js/jquery.ui.position.js +252 -0
  139. package/static/js/jquery.ui.progressbar.js +109 -0
  140. package/static/js/jquery.ui.selectable.js +266 -0
  141. package/static/js/jquery.ui.slider.js +666 -0
  142. package/static/js/jquery.ui.sortable.js +1077 -0
  143. package/static/js/jquery.ui.tabs.js +758 -0
  144. package/stats.js +80 -0
  145. package/test-cache/vsac/vsac-valuesets.db +0 -0
  146. package/token/nginx_passport_setup.md +383 -0
  147. package/token/security_guide.md +294 -0
  148. package/token/token-template.html +330 -0
  149. package/token/token.js +1300 -0
  150. package/translations/Messages.properties +1510 -0
  151. package/translations/Messages_ar.properties +1399 -0
  152. package/translations/Messages_de.properties +836 -0
  153. package/translations/Messages_es.properties +737 -0
  154. package/translations/Messages_fr.properties +1 -0
  155. package/translations/Messages_ja.properties +893 -0
  156. package/translations/Messages_nl.properties +1357 -0
  157. package/translations/Messages_pt.properties +1302 -0
  158. package/translations/Messages_ru.properties +1 -0
  159. package/translations/Messages_uz.properties +1 -0
  160. package/translations/Messages_zh.properties +1 -0
  161. package/translations/rendering-phrases.properties +1128 -0
  162. package/translations/rendering-phrases_ar.properties +1091 -0
  163. package/translations/rendering-phrases_de.properties +6 -0
  164. package/translations/rendering-phrases_es.properties +6 -0
  165. package/translations/rendering-phrases_fr.properties +624 -0
  166. package/translations/rendering-phrases_ja.properties +21 -0
  167. package/translations/rendering-phrases_nl.properties +970 -0
  168. package/translations/rendering-phrases_pt.properties +1020 -0
  169. package/translations/rendering-phrases_ru.properties +1094 -0
  170. package/translations/rendering-phrases_uz.properties +1 -0
  171. package/translations/rendering-phrases_zh.properties +1 -0
  172. package/tx/README.md +418 -0
  173. package/tx/cm/cm-api.js +110 -0
  174. package/tx/cm/cm-database.js +735 -0
  175. package/tx/cm/cm-package.js +325 -0
  176. package/tx/cs/cs-api.js +789 -0
  177. package/tx/cs/cs-areacode.js +615 -0
  178. package/tx/cs/cs-country.js +1110 -0
  179. package/tx/cs/cs-cpt.js +785 -0
  180. package/tx/cs/cs-cs.js +1579 -0
  181. package/tx/cs/cs-currency.js +539 -0
  182. package/tx/cs/cs-db.js +1321 -0
  183. package/tx/cs/cs-hgvs.js +329 -0
  184. package/tx/cs/cs-lang.js +465 -0
  185. package/tx/cs/cs-loinc.js +1485 -0
  186. package/tx/cs/cs-mimetypes.js +238 -0
  187. package/tx/cs/cs-ndc.js +704 -0
  188. package/tx/cs/cs-omop.js +1025 -0
  189. package/tx/cs/cs-provider-api.js +43 -0
  190. package/tx/cs/cs-provider-list.js +37 -0
  191. package/tx/cs/cs-rxnorm.js +808 -0
  192. package/tx/cs/cs-snomed.js +1102 -0
  193. package/tx/cs/cs-ucum.js +514 -0
  194. package/tx/cs/cs-unii.js +271 -0
  195. package/tx/cs/cs-uri.js +218 -0
  196. package/tx/cs/cs-usstates.js +305 -0
  197. package/tx/dev.fhir.org.yml +14 -0
  198. package/tx/fixtures/test-cases-setup.json +18 -0
  199. package/tx/fixtures/test-cases.yml +16 -0
  200. package/tx/html/codesystem-operations.liquid +25 -0
  201. package/tx/html/home-metrics.liquid +247 -0
  202. package/tx/html/operations-form.liquid +148 -0
  203. package/tx/html/search-form.liquid +62 -0
  204. package/tx/html/tx-template.html +133 -0
  205. package/tx/html/valueset-operations.liquid +54 -0
  206. package/tx/importers/atc-to-fhir.js +316 -0
  207. package/tx/importers/import-loinc.module.js +1536 -0
  208. package/tx/importers/import-ndc.module.js +1088 -0
  209. package/tx/importers/import-rxnorm.module.js +898 -0
  210. package/tx/importers/import-sct.module.js +2457 -0
  211. package/tx/importers/import-unii.module.js +601 -0
  212. package/tx/importers/readme.md +453 -0
  213. package/tx/importers/subset-loinc.module.js +1081 -0
  214. package/tx/importers/subset-rxnorm.module.js +938 -0
  215. package/tx/importers/tx-import-base.js +351 -0
  216. package/tx/importers/tx-import-settings.js +310 -0
  217. package/tx/importers/tx-import.js +357 -0
  218. package/tx/library/canonical-resource.js +88 -0
  219. package/tx/library/capabilitystatement.js +292 -0
  220. package/tx/library/codesystem.js +774 -0
  221. package/tx/library/conceptmap.js +568 -0
  222. package/tx/library/designations.js +932 -0
  223. package/tx/library/errors.js +77 -0
  224. package/tx/library/extensions.js +117 -0
  225. package/tx/library/namingsystem.js +322 -0
  226. package/tx/library/operation-outcome.js +127 -0
  227. package/tx/library/parameters.js +105 -0
  228. package/tx/library/renderer.js +1559 -0
  229. package/tx/library/terminologycapabilities.js +418 -0
  230. package/tx/library/ucum-parsers.js +1029 -0
  231. package/tx/library/ucum-service.js +370 -0
  232. package/tx/library/ucum-types.js +1099 -0
  233. package/tx/library/valueset.js +543 -0
  234. package/tx/library.js +676 -0
  235. package/tx/ocl/cm-ocl.js +106 -0
  236. package/tx/ocl/cs-ocl.js +39 -0
  237. package/tx/ocl/vs-ocl.js +105 -0
  238. package/tx/operation-context.js +568 -0
  239. package/tx/params.js +613 -0
  240. package/tx/provider.js +403 -0
  241. package/tx/sct/ecl.js +1560 -0
  242. package/tx/sct/expressions.js +2077 -0
  243. package/tx/sct/structures.js +1396 -0
  244. package/tx/tx-html.js +1063 -0
  245. package/tx/tx.fhir.org.yml +39 -0
  246. package/tx/tx.js +927 -0
  247. package/tx/vs/vs-api.js +112 -0
  248. package/tx/vs/vs-database.js +786 -0
  249. package/tx/vs/vs-package.js +358 -0
  250. package/tx/vs/vs-vsac.js +366 -0
  251. package/tx/workers/batch-validate.js +129 -0
  252. package/tx/workers/batch.js +361 -0
  253. package/tx/workers/closure.js +32 -0
  254. package/tx/workers/expand.js +1845 -0
  255. package/tx/workers/lookup.js +407 -0
  256. package/tx/workers/metadata.js +467 -0
  257. package/tx/workers/operations.js +34 -0
  258. package/tx/workers/read.js +164 -0
  259. package/tx/workers/search.js +384 -0
  260. package/tx/workers/subsumes.js +334 -0
  261. package/tx/workers/translate.js +492 -0
  262. package/tx/workers/validate.js +2504 -0
  263. package/tx/workers/worker.js +904 -0
  264. package/tx/xml/capabilitystatement-xml.js +63 -0
  265. package/tx/xml/codesystem-xml.js +62 -0
  266. package/tx/xml/conceptmap-xml.js +65 -0
  267. package/tx/xml/namingsystem-xml.js +65 -0
  268. package/tx/xml/operationoutcome-xml.js +127 -0
  269. package/tx/xml/parameters-xml.js +312 -0
  270. package/tx/xml/terminologycapabilities-xml.js +64 -0
  271. package/tx/xml/valueset-xml.js +64 -0
  272. package/tx/xml/xml-base.js +603 -0
  273. package/vcl/vcl-parser.js +1098 -0
  274. package/vcl/vcl.js +253 -0
  275. package/windows-install.js +19 -0
  276. package/xig/xig-template.html +124 -0
  277. package/xig/xig.js +3049 -0
@@ -0,0 +1,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 };