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,835 @@
1
+ const {validateParameter, validateOptionalParameter} = require("./utilities");
2
+ const NodeType = {
3
+ Document: 'Document',
4
+ Element: 'Element',
5
+ Text: 'Text',
6
+ Comment: 'Comment',
7
+ DocType: 'DocType',
8
+ Instruction: 'Instruction'
9
+ };
10
+
11
+ class XhtmlNode {
12
+ constructor(nodeType, name = null) {
13
+ this.nodeType = nodeType;
14
+ this.name = name;
15
+ this.attributes = new Map();
16
+ this.childNodes = [];
17
+ this.content = null; // for text nodes
18
+ this.inPara = false;
19
+ this.inLink = false;
20
+ this.pretty = true;
21
+ }
22
+
23
+ // Attribute methods
24
+ setAttribute(name, value) {
25
+ if (value != null) {
26
+ this.attributes.set(name, value);
27
+ }
28
+ return this;
29
+ }
30
+
31
+ attribute(name, value) {
32
+ return this.setAttribute(name, value);
33
+ }
34
+
35
+ attr(name, value) {
36
+ return this.setAttribute(name, value);
37
+ }
38
+
39
+ getAttribute(name) {
40
+ return this.attributes.get(name) || null;
41
+ }
42
+
43
+ hasAttribute(name) {
44
+ return this.attributes.has(name);
45
+ }
46
+
47
+ removeAttribute(name) {
48
+ this.attributes.delete(name);
49
+ return this;
50
+ }
51
+
52
+ // Class helpers
53
+ clss(className) {
54
+ if (className) {
55
+ const existing = this.attributes.get('class');
56
+ if (existing) {
57
+ this.attributes.set('class', existing + ' ' + className);
58
+ } else {
59
+ this.attributes.set('class', className);
60
+ }
61
+ }
62
+ return this;
63
+ }
64
+
65
+ style(style) {
66
+ if (style) {
67
+ this.attributes.set('style', style);
68
+ }
69
+ return this;
70
+ }
71
+
72
+ id(id) {
73
+ if (id) {
74
+ this.attributes.set('id', id);
75
+ }
76
+ return this;
77
+ }
78
+
79
+ title(title) {
80
+ if (title) {
81
+ this.attributes.set('title', title);
82
+ }
83
+ return this;
84
+ }
85
+
86
+ // Child node management
87
+ #makeTag(name) {
88
+ const node = new XhtmlNode(NodeType.Element, name);
89
+ if (this.inPara || name === 'p') {
90
+ node.inPara = true;
91
+ }
92
+ if (this.inLink || name === 'a') {
93
+ node.inLink = true;
94
+ }
95
+ const inlineElements = ['b', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'cite', 'code',
96
+ 'dfn', 'em', 'kbd', 'strong', 'samp', 'var', 'a', 'bdo', 'br', 'img', 'map', 'object',
97
+ 'q', 'script', 'span', 'sub', 'sup', 'button', 'input', 'label', 'select', 'textarea'];
98
+ if (inlineElements.includes(name)) {
99
+ node.pretty = false;
100
+ }
101
+ return node;
102
+ }
103
+
104
+ addTag(nameOrIndex, name = null) {
105
+ if (typeof nameOrIndex === 'number') {
106
+ const node = this.#makeTag(name);
107
+ this.childNodes.splice(nameOrIndex, 0, node);
108
+ return node;
109
+ } else {
110
+ const node = this.#makeTag(nameOrIndex);
111
+ this.childNodes.push(node);
112
+ return node;
113
+ }
114
+ }
115
+
116
+ addText(content) {
117
+ if (content != null) {
118
+ const node = new XhtmlNode(NodeType.Text);
119
+ node.content = String(content);
120
+ this.childNodes.push(node);
121
+ return node;
122
+ }
123
+ return null;
124
+ }
125
+
126
+ addComment(content) {
127
+ if (content != null) {
128
+ const node = new XhtmlNode(NodeType.Comment);
129
+ node.content = content;
130
+ this.childNodes.push(node);
131
+ return node;
132
+ }
133
+ return null;
134
+ }
135
+
136
+ addChildren(nodes) {
137
+ if (nodes) {
138
+ for (const node of nodes) {
139
+ this.childNodes.push(node);
140
+ }
141
+ }
142
+ return this;
143
+ }
144
+
145
+ addChild(node) {
146
+ if (node) {
147
+ this.childNodes.push(node);
148
+ }
149
+ return this;
150
+ }
151
+
152
+ clear() {
153
+ this.childNodes = [];
154
+ return this;
155
+ }
156
+
157
+ indexOf(node) {
158
+ return this.childNodes.indexOf(node);
159
+ }
160
+
161
+ hasChildren() {
162
+ return this.childNodes.length > 0;
163
+ }
164
+
165
+ getFirstElement() {
166
+ for (const child of this.childNodes) {
167
+ if (child.nodeType === NodeType.Element) {
168
+ return child;
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+
174
+ // Text content helpers
175
+ tx(content) {
176
+ return this.addText(content);
177
+ }
178
+
179
+ txN(content) {
180
+ this.addText(content);
181
+ return this;
182
+ }
183
+
184
+ stx(content) {
185
+ if (content) {
186
+ this.addText(' ' + content);
187
+ }
188
+ return this;
189
+ }
190
+
191
+ // Fluent element creation methods
192
+ h(level, id = null) {
193
+ if (level < 1 || level > 6) {
194
+ throw new Error('Illegal Header level ' + level);
195
+ }
196
+ const node = this.addTag('h' + level);
197
+ if (id) {
198
+ node.setAttribute('id', id);
199
+ }
200
+ return node;
201
+ }
202
+
203
+ h1() { return this.addTag('h1'); }
204
+ h2() { return this.addTag('h2'); }
205
+ h3() { return this.addTag('h3'); }
206
+ h4() { return this.addTag('h4'); }
207
+ h5() { return this.addTag('h5'); }
208
+ h6() { return this.addTag('h6'); }
209
+
210
+ div(style = null) {
211
+ const node = this.addTag('div');
212
+ if (style) {
213
+ node.setAttribute('style', style);
214
+ }
215
+ return node;
216
+ }
217
+
218
+ span(style = null, title = null) {
219
+ const node = this.addTag('span');
220
+ if (style) {
221
+ node.setAttribute('style', style);
222
+ }
223
+ if (title) {
224
+ node.setAttribute('title', title);
225
+ }
226
+ return node;
227
+ }
228
+
229
+ spanClss(className) {
230
+ const node = this.addTag('span');
231
+ if (className) {
232
+ node.setAttribute('class', className);
233
+ }
234
+ return node;
235
+ }
236
+
237
+ para() { return this.addTag('p'); }
238
+ p() { return this.addTag('p'); }
239
+
240
+ pre(clss = null) {
241
+ const node = this.addTag('pre');
242
+ if (clss) {
243
+ node.setAttribute('class', clss);
244
+ }
245
+ return node;
246
+ }
247
+
248
+ blockquote() { return this.addTag('blockquote'); }
249
+
250
+ // Lists
251
+ ul() { return this.addTag('ul'); }
252
+ ol() { return this.addTag('ol'); }
253
+ li() { return this.addTag('li'); }
254
+
255
+ // Tables
256
+ table(clss = null, forPresentation = false) {
257
+ const node = this.addTag('table');
258
+ if (clss) {
259
+ node.clss(clss);
260
+ }
261
+ if (forPresentation) {
262
+ node.clss('presentation');
263
+ }
264
+ return node;
265
+ }
266
+
267
+ tr(afterRow = null) {
268
+ if (afterRow) {
269
+ const index = this.indexOf(afterRow);
270
+ return this.addTag(index + 1, 'tr');
271
+ }
272
+ return this.addTag('tr');
273
+ }
274
+
275
+ th(index = null) {
276
+ if (index !== null) {
277
+ return this.addTag(index, 'th');
278
+ }
279
+ return this.addTag('th');
280
+ }
281
+
282
+ td(clss = null) {
283
+ const node = this.addTag('td');
284
+ if (clss) {
285
+ node.setAttribute('class', clss);
286
+ }
287
+ return node;
288
+ }
289
+
290
+ thead() { return this.addTag('thead'); }
291
+ tbody() { return this.addTag('tbody'); }
292
+ tfoot() { return this.addTag('tfoot'); }
293
+
294
+ // Inline elements
295
+ b() { return this.addTag('b'); }
296
+ i() { return this.addTag('i'); }
297
+ em() { return this.addTag('em'); }
298
+ strong() { return this.addTag('strong'); }
299
+ small() { return this.addTag('small'); }
300
+ sub() { return this.addTag('sub'); }
301
+ sup() { return this.addTag('sup'); }
302
+
303
+ code(text = null) {
304
+ const node = this.addTag('code');
305
+ if (text) {
306
+ node.tx(text);
307
+ }
308
+ return node;
309
+ }
310
+
311
+ codeWithText(preText, text, postText) {
312
+ this.tx(preText);
313
+ const code = this.addTag('code');
314
+ code.tx(text);
315
+ this.tx(postText);
316
+ return this;
317
+ }
318
+
319
+ // Line breaks
320
+ br() {
321
+ this.addTag('br');
322
+ return this;
323
+ }
324
+
325
+ hr() {
326
+ this.addTag('hr');
327
+ return this;
328
+ }
329
+
330
+ // Links
331
+ ah(href, title = null) {
332
+ if (href == null) {
333
+ return this.addTag('span');
334
+ }
335
+ const node = this.addTag('a').setAttribute('href', href);
336
+ if (title) {
337
+ node.setAttribute('title', title);
338
+ }
339
+ return node;
340
+ }
341
+
342
+ ahWithText(preText, href, title, text, postText) {
343
+ this.tx(preText);
344
+ const a = this.addTag('a').setAttribute('href', href);
345
+ if (title) {
346
+ a.setAttribute('title', title);
347
+ }
348
+ a.tx(text);
349
+ this.tx(postText);
350
+ return a;
351
+ }
352
+
353
+ ahOrCode(href, title = null) {
354
+ if (href != null) {
355
+ return this.ah(href, title);
356
+ } else if (title != null) {
357
+ return this.code().setAttribute('title', title);
358
+ } else {
359
+ return this.code();
360
+ }
361
+ }
362
+
363
+ an(name, text = ' ') {
364
+ const a = this.addTag('a').setAttribute('name', name);
365
+ a.tx(text);
366
+ return a;
367
+ }
368
+
369
+ // Images
370
+ img(src, alt, title = null) {
371
+ const node = this.addTag('img')
372
+ .setAttribute('src', src)
373
+ .setAttribute('alt', alt || '.');
374
+ if (title) {
375
+ node.setAttribute('title', title);
376
+ }
377
+ return node;
378
+ }
379
+
380
+ imgT(src, alt) {
381
+ return this.img(src, alt, alt);
382
+ }
383
+
384
+ // Forms
385
+ input(type, name, value = null) {
386
+ const node = this.addTag('input')
387
+ .setAttribute('type', type)
388
+ .setAttribute('name', name);
389
+ if (value != null) {
390
+ node.setAttribute('value', value);
391
+ }
392
+ return node;
393
+ }
394
+
395
+ button(text) {
396
+ const node = this.addTag('button');
397
+ node.tx(text);
398
+ return node;
399
+ }
400
+
401
+ select(name) {
402
+ return this.addTag('select').setAttribute('name', name);
403
+ }
404
+
405
+ option(value, text, selected = false) {
406
+ const node = this.addTag('option').setAttribute('value', value);
407
+ node.tx(text);
408
+ if (selected) {
409
+ node.setAttribute('selected', 'selected');
410
+ }
411
+ return node;
412
+ }
413
+
414
+ textarea(name, rows = null, cols = null) {
415
+ const node = this.addTag('textarea').setAttribute('name', name);
416
+ if (rows != null) {
417
+ node.setAttribute('rows', String(rows));
418
+ }
419
+ if (cols != null) {
420
+ node.setAttribute('cols', String(cols));
421
+ }
422
+ return node;
423
+ }
424
+
425
+ label(forId) {
426
+ return this.addTag('label').setAttribute('for', forId);
427
+ }
428
+
429
+ // Conditional
430
+ iff(test) {
431
+ if (test) {
432
+ return this;
433
+ } else {
434
+ return new XhtmlNode(NodeType.Element, 'span'); // disconnected node
435
+ }
436
+ }
437
+
438
+ // Separator helper
439
+ sep(text) {
440
+ if (this.hasChildren()) {
441
+ this.addText(text);
442
+ }
443
+ return this;
444
+ }
445
+
446
+ // Rendering
447
+ notPretty() {
448
+ this.pretty = false;
449
+ return this;
450
+ }
451
+
452
+ allText() {
453
+ let result = '';
454
+ for (const child of this.childNodes) {
455
+ if (child.nodeType === NodeType.Text) {
456
+ result += child.content || '';
457
+ } else if (child.nodeType === NodeType.Element) {
458
+ result += child.allText();
459
+ }
460
+ }
461
+ return result;
462
+ }
463
+
464
+ startCommaList(lastWord) {
465
+ validateParameter(lastWord, 'lastWord', String);
466
+ if (this.lastWord) {
467
+ throw new Error('Unclosed list');
468
+ }
469
+ this.lastWord = lastWord;
470
+ this.commaItems = [];
471
+ this.commaFirst = true;
472
+ }
473
+
474
+ commaItem(text, link) {
475
+ validateParameter(text, 'text', String);
476
+ validateOptionalParameter(link, 'link', String);
477
+
478
+ if (!this.commaFirst) {
479
+ this.commaItems.push(this.tx(", "));
480
+ }
481
+ this.commaFirst = false;
482
+ if (link) {
483
+ this.ah(link).tx(text);
484
+ } else {
485
+ this.tx(text);
486
+ }
487
+ }
488
+
489
+ stopCommaList() {
490
+ if (this.commaItems && this.commaItems.length > 0) {
491
+ this.commaItems[this.commaItems.length-1].content = " "+this.lastWord+" ";
492
+ }
493
+ this.lastWord = undefined;
494
+ this.commaItems = undefined;
495
+ }
496
+
497
+ // Script execution methods
498
+
499
+ startScript(name) {
500
+ if (this.namedParams) {
501
+ throw new Error(`Sequence Error - script is already open @ ${name}`);
502
+ }
503
+ this.namedParams = new Map();
504
+ this.namedParamValues = new Map();
505
+ }
506
+
507
+ param(name) {
508
+ if (!this.namedParams) {
509
+ throw new Error('Sequence Error - script is not already open');
510
+ }
511
+ // Create a detached node that will be inserted when the script executes
512
+ const node = new XhtmlNode(NodeType.Element, 'p');
513
+ node.inPara = true;
514
+ this.namedParams.set(name, node);
515
+ return node;
516
+ }
517
+
518
+ paramValue(name, value) {
519
+ if (!this.namedParamValues) {
520
+ throw new Error('Sequence Error - script is not already open');
521
+ }
522
+ this.namedParamValues.set(name, String(value));
523
+ }
524
+
525
+ execScript(structure) {
526
+ const scriptNodes = this.#parseFragment(`<div>${structure}</div>`);
527
+ this.#parseNodes(scriptNodes, this.childNodes);
528
+ }
529
+
530
+ #parseNodes(source, dest) {
531
+ for (const n of source) {
532
+ if (n.name === 'param') {
533
+ const paramName = n.getAttribute('name');
534
+ const node = this.namedParams.get(paramName);
535
+ if (node) {
536
+ this.#parseNodes(node.childNodes, dest);
537
+ }
538
+ } else if (n.name === 'if') {
539
+ const test = n.getAttribute('test');
540
+ if (this.#passesTest(test)) {
541
+ this.#parseNodes(n.childNodes, dest);
542
+ }
543
+ } else {
544
+ dest.push(n);
545
+ }
546
+ }
547
+ }
548
+
549
+ #passesTest(test) {
550
+ const parts = test.trim().split(/\s+/);
551
+ if (parts.length !== 3) {
552
+ return false;
553
+ }
554
+
555
+ const [paramName, operator, compareValue] = parts;
556
+
557
+ if (!this.namedParamValues.has(paramName)) {
558
+ return false;
559
+ }
560
+
561
+ const paramValue = this.namedParamValues.get(paramName);
562
+
563
+ switch (operator) {
564
+ case '=':
565
+ return compareValue.toLowerCase() === paramValue.toLowerCase();
566
+ case '!=':
567
+ return compareValue.toLowerCase() !== paramValue.toLowerCase();
568
+ case '<':
569
+ return this.#isInteger(paramValue) && this.#isInteger(compareValue) &&
570
+ parseInt(paramValue, 10) < parseInt(compareValue, 10);
571
+ case '<=':
572
+ return this.#isInteger(paramValue) && this.#isInteger(compareValue) &&
573
+ parseInt(paramValue, 10) <= parseInt(compareValue, 10);
574
+ case '>':
575
+ return this.#isInteger(paramValue) && this.#isInteger(compareValue) &&
576
+ parseInt(paramValue, 10) > parseInt(compareValue, 10);
577
+ case '>=':
578
+ return this.#isInteger(paramValue) && this.#isInteger(compareValue) &&
579
+ parseInt(paramValue, 10) >= parseInt(compareValue, 10);
580
+ default:
581
+ return false;
582
+ }
583
+ }
584
+
585
+ #isInteger(str) {
586
+ return /^-?\d+$/.test(str);
587
+ }
588
+
589
+ #parseFragment(html) {
590
+ const nodes = [];
591
+ const stack = [{ children: nodes }];
592
+ let current = stack[0];
593
+ let i = 0;
594
+
595
+ while (i < html.length) {
596
+ if (html[i] === '<') {
597
+ // Check for closing tag
598
+ if (html[i + 1] === '/') {
599
+ const endTag = html.indexOf('>', i);
600
+ stack.pop();
601
+ current = stack[stack.length - 1];
602
+ i = endTag + 1;
603
+ continue;
604
+ }
605
+
606
+ // Find tag end
607
+ const tagEnd = html.indexOf('>', i);
608
+ const tagContent = html.substring(i + 1, tagEnd);
609
+ const selfClosing = tagContent.endsWith('/');
610
+ const cleanContent = selfClosing ? tagContent.slice(0, -1).trim() : tagContent.trim();
611
+
612
+ // Parse tag name and attributes
613
+ const spaceIndex = cleanContent.indexOf(' ');
614
+ const tagName = spaceIndex === -1 ? cleanContent : cleanContent.substring(0, spaceIndex);
615
+ const attrString = spaceIndex === -1 ? '' : cleanContent.substring(spaceIndex + 1);
616
+
617
+ const node = new XhtmlNode(NodeType.Element, tagName);
618
+
619
+ // Parse attributes
620
+ const attrRegex = /(\w+)=["']([^"']*)["']/g;
621
+ let match;
622
+ while ((match = attrRegex.exec(attrString)) !== null) {
623
+ node.setAttribute(match[1], match[2]);
624
+ }
625
+
626
+ current.children.push(node);
627
+
628
+ if (!selfClosing) {
629
+ stack.push({ children: node.childNodes });
630
+ current = stack[stack.length - 1];
631
+ }
632
+
633
+ i = tagEnd + 1;
634
+ } else {
635
+ // Text content
636
+ const nextTag = html.indexOf('<', i);
637
+ const textContent = nextTag === -1 ? html.substring(i) : html.substring(i, nextTag);
638
+
639
+ if (textContent.trim()) {
640
+ const textNode = new XhtmlNode(NodeType.Text);
641
+ textNode.content = textContent;
642
+ current.children.push(textNode);
643
+ }
644
+
645
+ i = nextTag === -1 ? html.length : nextTag;
646
+ }
647
+ }
648
+
649
+ // Return children of the wrapper div
650
+ return nodes.length > 0 && nodes[0].childNodes ? nodes[0].childNodes : nodes;
651
+ }
652
+
653
+ closeScript() {
654
+ if (!this.namedParams) {
655
+ throw new Error('Sequence Error - script is not already open');
656
+ }
657
+ this.namedParams = null;
658
+ this.namedParamValues = null;
659
+ }
660
+
661
+ /**
662
+ * Process markdown content and add it as HTML child nodes
663
+ * @param {string} md - Markdown content to process
664
+ * @returns {XhtmlNode} - this node for chaining
665
+ */
666
+ markdown(md) {
667
+ if (!md) {
668
+ return this;
669
+ }
670
+
671
+ const commonmark = require('commonmark');
672
+ const reader = new commonmark.Parser();
673
+ const writer = new commonmark.HtmlRenderer({ safe: true });
674
+
675
+ const parsed = reader.parse(md);
676
+ const html = writer.render(parsed);
677
+
678
+ // Parse the HTML and add as children
679
+ const nodes = this.#parseFragment(`<div>${html}</div>`);
680
+ for (const node of nodes) {
681
+ this.childNodes.push(node);
682
+ }
683
+
684
+ return this;
685
+ }
686
+
687
+ /**
688
+ * Process markdown content and add it inline (strips block-level wrapper)
689
+ * Useful when you want to add markdown content within a paragraph
690
+ * @param {string} md - Markdown content to process
691
+ * @returns {XhtmlNode} - this node for chaining
692
+ */
693
+ markdownInline(md) {
694
+ if (!md) {
695
+ return this;
696
+ }
697
+
698
+ const commonmark = require('commonmark');
699
+ const reader = new commonmark.Parser();
700
+ const writer = new commonmark.HtmlRenderer({ safe: true });
701
+
702
+ const parsed = reader.parse(md);
703
+ const html = writer.render(parsed);
704
+
705
+ // Strip outer <p> tags if present for inline usage
706
+ const trimmedHtml = html.trim().replace(/^<p>/, '').replace(/<\/p>\s*$/, '');
707
+
708
+ // Parse the HTML and add as children
709
+ const nodes = this.#parseFragment(`<span>${trimmedHtml}</span>`);
710
+ for (const node of nodes) {
711
+ // Add children of the wrapper span, not the span itself
712
+ for (const child of node.childNodes) {
713
+ this.childNodes.push(child);
714
+ }
715
+ }
716
+
717
+ return this;
718
+ }
719
+
720
+ render(indent = 0, pretty = true) {
721
+ const effectivePretty = pretty && this.pretty;
722
+ const indentStr = effectivePretty ? ' '.repeat(indent) : '';
723
+ const newline = effectivePretty ? '\n' : '';
724
+
725
+ if (this.nodeType === NodeType.Text) {
726
+ return this.#escapeHtml(this.content || '');
727
+ }
728
+
729
+ if (this.nodeType === NodeType.Comment) {
730
+ return `${indentStr}<!-- ${this.content || ''} -->${newline}`;
731
+ }
732
+
733
+ if (this.nodeType === NodeType.Element) {
734
+ const voidElements = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
735
+ 'link', 'meta', 'param', 'source', 'track', 'wbr'];
736
+ const isVoid = voidElements.includes(this.name);
737
+
738
+ let attrs = '';
739
+ for (const [key, value] of this.attributes) {
740
+ attrs += ` ${key}="${this.#escapeAttr(value)}"`;
741
+ }
742
+
743
+ if (isVoid) {
744
+ return `${indentStr}<${this.name}${attrs}/>${newline}`;
745
+ }
746
+
747
+ if (this.childNodes.length === 0) {
748
+ return `${indentStr}<${this.name}${attrs}></${this.name}>${newline}`;
749
+ }
750
+
751
+ // Check if all children are text/inline
752
+ const allInline = this.childNodes.every(c =>
753
+ c.nodeType === NodeType.Text || !c.pretty
754
+ );
755
+
756
+ if (allInline || !effectivePretty) {
757
+ let content = '';
758
+ for (const child of this.childNodes) {
759
+ content += child.render(0, false);
760
+ }
761
+ return `${indentStr}<${this.name}${attrs}>${content}</${this.name}>${newline}`;
762
+ } else {
763
+ let content = '';
764
+ for (const child of this.childNodes) {
765
+ content += child.render(indent + 1, true);
766
+ }
767
+ return `${indentStr}<${this.name}${attrs}>${newline}${content}${indentStr}</${this.name}>${newline}`;
768
+ }
769
+ }
770
+
771
+ return '';
772
+ }
773
+
774
+ #escapeHtml(text) {
775
+ return text
776
+ .replace(/&/g, '&amp;')
777
+ .replace(/</g, '&lt;')
778
+ .replace(/>/g, '&gt;');
779
+ }
780
+
781
+ #escapeAttr(text) {
782
+ return String(text)
783
+ .replace(/&/g, '&amp;')
784
+ .replace(/</g, '&lt;')
785
+ .replace(/>/g, '&gt;')
786
+ .replace(/"/g, '&quot;');
787
+ }
788
+
789
+ toString() {
790
+ return this.render(0, true);
791
+ }
792
+
793
+ toStringPretty() {
794
+ return this.render(0, true);
795
+ }
796
+
797
+ toStringCompact() {
798
+ return this.render(0, false);
799
+ }
800
+ }
801
+
802
+ // Factory functions
803
+ function div(style = null) {
804
+ const node = new XhtmlNode(NodeType.Element, 'div');
805
+ node.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
806
+ if (style) {
807
+ node.setAttribute('style', style);
808
+ }
809
+ return node;
810
+ }
811
+
812
+ function element(name) {
813
+ return new XhtmlNode(NodeType.Element, name);
814
+ }
815
+
816
+ function text(content) {
817
+ const node = new XhtmlNode(NodeType.Text);
818
+ node.content = content;
819
+ return node;
820
+ }
821
+
822
+ function comment(content) {
823
+ const node = new XhtmlNode(NodeType.Comment);
824
+ node.content = content;
825
+ return node;
826
+ }
827
+
828
+ module.exports = {
829
+ XhtmlNode,
830
+ NodeType,
831
+ div,
832
+ element,
833
+ text,
834
+ comment
835
+ };