001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.util.ArrayList; 006import java.util.List; 007 008import javax.annotation.Nonnull; 009 010import org.hl7.fhir.exceptions.DefinitionException; 011import org.hl7.fhir.exceptions.FHIRException; 012import org.hl7.fhir.exceptions.FHIRFormatError; 013import org.hl7.fhir.r5.context.ContextUtilities; 014import org.hl7.fhir.r5.model.CanonicalResource; 015import org.hl7.fhir.r5.model.Resource; 016import org.hl7.fhir.r5.model.StructureDefinition; 017import org.hl7.fhir.r5.model.ValueSet; 018import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 019import org.hl7.fhir.r5.renderers.utils.RenderingContext; 020import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 021import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType; 022import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 023import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 024import org.hl7.fhir.r5.utils.EOperationOutcome; 025import org.hl7.fhir.r5.utils.ToolingExtensions; 026import org.hl7.fhir.utilities.Utilities; 027import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 028import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 029import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 030import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 031import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 032import org.hl7.fhir.utilities.xhtml.NodeType; 033import org.hl7.fhir.utilities.xhtml.XhtmlNode; 034 035public class QuestionnaireRenderer extends TerminologyRenderer { 036 public static final String EXT_QUESTIONNAIRE_ITEM_TYPE_ORIGINAL = "http://hl7.org/fhir/4.0/StructureDefinition/extension-questionnaire.item.type"; 037 038 public QuestionnaireRenderer(RenderingContext context) { 039 super(context); 040 } 041 042 @Override 043 public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 044 return canonicalTitle(r); 045 } 046 047 @Override 048 public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { 049 renderResourceTechDetails(q, x); 050 genSummaryTable(status, x, (CanonicalResource) q.getResourceNative()); 051 switch (context.getQuestionnaireMode()) { 052 case FORM: 053 renderForm(status, x, q); 054 break; 055 case LINKS: 056 renderLinks(status, x, q); 057 break; 058 case LOGIC: 059 renderLogic(status, x, q); 060 break; 061 case DEFNS: 062 renderDefns(status, x, q); 063 break; 064 case TREE: 065 renderTree(status, x, q); 066 break; 067 default: 068 throw new Error("Unknown questionnaire Renderer Mode"); 069 } 070 071 boolean first = true; 072 for (ResourceWrapper cont : q.children("contained")) { 073 if (first) { 074 x.h2().tx("Contained Resources"); 075 first = false; 076 } 077 x.hr(); 078 RendererFactory.factory(cont, context.forContained()).buildNarrative(status, x, cont); 079 } 080 } 081 082 public void renderTree(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws UnsupportedEncodingException, IOException { 083 boolean hasFlags = checkForFlags(q.children("item")); 084 boolean doOpts = context.getDefinitionsTarget() == null && hasAnyOptions(q.children("item")); 085 086 if (doOpts) { 087 x.b().tx(context.formatPhrase(RenderingContext.QUEST_STRUCT)); 088 } 089 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true, ""); 090 TableModel model = gen.new TableModel("qtree="+q.getId(), context.getRules() == GenerationRules.IG_PUBLISHER); 091 model.setAlternating(true); 092 if (context.getRules() == GenerationRules.VALID_RESOURCE || context.isInlineGraphics()) { 093 model.setDocoImg(HierarchicalTableGenerator.help16AsData()); 094 } else { 095 model.setDocoImg(Utilities.pathURL(context.getLink(KnownLinkType.SPEC), "help16.png")); 096 } 097 model.setDocoRef(context.getLink(KnownLinkType.SPEC)+"formats.html#table"); 098 model.getTitles().add(gen.new Title(null, model.getDocoRef(), (context.formatPhrase(RenderingContext.QUEST_LINKID)), (context.formatPhrase(RenderingContext.QUEST_LINK)), null, 0)); 099 model.getTitles().add(gen.new Title(null, model.getDocoRef(), (context.formatPhrase(RenderingContext.QUEST_TEXT)), (context.formatPhrase(RenderingContext.QUEST_TEXTFOR)), null, 0)); 100 model.getTitles().add(gen.new Title(null, model.getDocoRef(), (context.formatPhrase(RenderingContext.GENERAL_CARDINALITY)), (context.formatPhrase(RenderingContext.QUEST_TIMES)), null, 0)); 101 model.getTitles().add(gen.new Title(null, model.getDocoRef(), (context.formatPhrase(RenderingContext.GENERAL_TYPE)), (context.formatPhrase(RenderingContext.QUEST_TYPE_ITEM)), null, 0)); 102 if (hasFlags) { 103 model.getTitles().add(gen.new Title(null, model.getDocoRef(), (context.formatPhrase(RenderingContext.GENERAL_FLAGS)), (context.formatPhrase(RenderingContext.QUEST_ATTRIBUTES)), null, 0)); 104 } 105 model.getTitles().add(gen.new Title(null, model.getDocoRef(), (context.formatPhrase(RenderingContext.GENERAL_DESC_CONST)), (context.formatPhrase(RenderingContext.QUEST_ADD_INFO)), null, 0)); 106 107 // first we add a root for the questionaire itself 108 Row row = addTreeRoot(gen, model.getRows(), q, hasFlags); 109 for (ResourceWrapper i : q.children("item")) { 110 renderTreeItem(status, gen, row.getSubRows(), q, i, hasFlags); 111 } 112 XhtmlNode xn = gen.generate(model, context.getLocalPrefix(), 1, null); 113 x.addChildNode(xn); 114 if (doOpts) { 115 renderOptions(q, x); 116 } 117 } 118 119 private void renderOptions(ResourceWrapper q, XhtmlNode x) { 120 if (hasAnyOptions(q.children("item"))) { 121 x.hr(); 122 x.para().b().tx(context.formatPhrase(RenderingContext.QUEST_OPT)); 123 renderOptions(q.children("item"), x); 124 } 125 } 126 127 private void renderOptions(List<ResourceWrapper> items, XhtmlNode x) { 128 for (ResourceWrapper i : items) { 129 renderItemOptionsList(x, i); 130 renderOptions(i.children("item"), x); 131 } 132 } 133 134 public void renderItemOptions(XhtmlNode x, ResourceWrapper i) { 135 if (i.has("answerOption")) { 136 for (ResourceWrapper opt : i.children("answerOption")) { 137 String value = "??"; 138 String text = "??"; 139 ResourceWrapper v = opt.child("value"); 140 if (v.isPrimitive()) { 141 value = v.primitiveValue(); 142 text = v.primitiveValue(); 143 } else if (v.fhirType().equals("Coding")) { 144 if (v.has("system")) { 145 value = v.primitiveValue("system")+"#"+v.primitiveValue("code"); 146 } else { 147 value = v.primitiveValue("code"); 148 } 149 if (v.has("display")) { 150 text = v.primitiveValue("display"); 151 } else { 152 text = v.primitiveValue("code"); 153 } 154 } 155 if (value == null) { 156 value = "??"; 157 } 158 if (text == null) { 159 text = "??"; 160 } 161 boolean selected = "true".equals(opt.primitiveValue("initialSelected")); 162 x.option(value, text, selected); 163 } 164 } 165 } 166 167 public void renderItemOptionsList(XhtmlNode x, ResourceWrapper i) { 168 if (i.has("answerOption")) { 169 x.an(context.prefixAnchor("opt-item."+i.primitiveValue("linkId"))); 170 x.para().b().tx(context.formatPhrase(RenderingContext.QUEST_ANSW, i.primitiveValue("linkId"))+" "); 171 XhtmlNode ul = x.ul(); 172 for (ResourceWrapper opt : i.children("answerOption")) { 173 XhtmlNode li = ul.li(); 174 li.style("font-size: 11px"); 175 ResourceWrapper v = opt.child("value"); 176 if (v.isPrimitive()) { 177 li.tx(v.primitiveValue()); 178 } else if (v.fhirType().equals("Coding")) { 179 String link = v.has("system") ? new ContextUtilities(context.getWorker()).getLinkForUrl(context.getLink(KnownLinkType.SPEC), v.primitiveValue("system")) : null; 180 if (link == null) { 181 li.tx(v.primitiveValue("system")+"#"+v.primitiveValue("code")); 182 } else { 183 li.ah(link).tx(displaySystem(v.primitiveValue("system"))); 184 li.tx(": "+v.primitiveValue("code")); 185 } 186 if (v.has("display")) { 187 li.tx(" (\""+v.primitiveValue("display")+"\")"); 188 } 189 } else { 190 li.tx("??"); 191 } 192 } 193 } 194 } 195 196 private boolean hasAnyOptions(List<ResourceWrapper> items) { 197 for (ResourceWrapper i : items) { 198 if (i.has("answerOption")) { 199 return true; 200 } 201 if (hasAnyOptions(i.children("item"))) { 202 return true; 203 } 204 } 205 return false; 206 } 207 208 private boolean checkForFlags(List<ResourceWrapper> items) { 209 for (ResourceWrapper i : items) { 210 if (checkForFlags(i)) { 211 return true; 212 } 213 } 214 return false; 215 } 216 217 private boolean checkForFlags(ResourceWrapper i) { 218 if (i.has("readOnly")) { 219 return true; 220 } 221 if ("true".equals(i.extensionString(ToolingExtensions.EXT_Q_IS_SUBJ))) { 222 return true; 223 } 224 if ("true".equals(i.extensionString(ToolingExtensions.EXT_Q_HIDDEN))) { 225 return true; 226 } 227 if ("true".equals(i.extensionString(ToolingExtensions.EXT_Q_OTP_DISP))) { 228 return true; 229 } 230 if (i.hasExtension(ToolingExtensions.EXT_O_LINK_PERIOD)) { 231 return true; 232 } 233 if (i.hasExtension(ToolingExtensions.EXT_Q_CHOICE_ORIENT)) { 234 return true; 235 } 236 if (i.hasExtension(ToolingExtensions.EXT_Q_DISPLAY_CAT)) { 237 return true; 238 } 239 return checkForFlags(i.children("item")); 240 } 241 242 243 244 private Row addTreeRoot(HierarchicalTableGenerator gen, List<Row> rows, ResourceWrapper q, boolean hasFlags) throws IOException { 245 Row r = gen.new Row(); 246 rows.add(r); 247 248 r.setIcon("icon_q_root.gif", context.formatPhrase(RenderingContext.QUEST_ROOT)); 249 r.getCells().add(gen.new Cell(null, null, q.primitiveValue("name"), null, null)); 250 r.getCells().add(gen.new Cell(null, null, q.primitiveValue("description"), null, null)); 251 r.getCells().add(gen.new Cell(null, null, "", null, null)); 252 r.getCells().add(gen.new Cell(null, null, context.formatPhrase(RenderingContext.QUEST_QUEST), null, null)); 253 if (hasFlags) { 254 r.getCells().add(gen.new Cell(null, null, "", null, null)); 255 } 256 r.getCells().add(gen.new Cell(null, null, q.has("url") ? q.has("version") ? q.primitiveValue("url")+"#"+q.primitiveValue("version") : q.primitiveValue("url") : "", null, null)); 257 return r; 258 } 259 260 private String getSpecLink(String path) { 261 return Utilities.pathURL(context.getLink(KnownLinkType.SPEC), path); 262 } 263 264 private String getSDCLink(String url, String path) { 265 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, url); 266 if (sd == null) { 267 sd = context.getContext().fetchResource(StructureDefinition.class, path); 268 } 269 if (sd != null && sd.hasWebPath()) { 270 return sd.getWebPath(); 271 } else if (Utilities.isAbsoluteUrl(path)) { 272 return path.replace("StructureDefinition/", "StructureDefinition-")+".html"; 273 } else if ("http://hl7.org/fhir/uv/sdc".equals(context.getPkp().getCanonicalForDefaultContext())) { 274 return Utilities.pathURL(path); // for now? 275 } else { 276 return Utilities.pathURL("http://hl7.org/fhir/uv/sdc", path); // for now? 277 } 278 } 279 280 private void renderTreeItem(RenderingStatus status, HierarchicalTableGenerator gen, List<Row> rows, ResourceWrapper q, ResourceWrapper i, boolean hasFlags) throws IOException { 281 Row r = gen.new Row(); 282 rows.add(r); 283 String type = i.primitiveValue("type"); 284 285 r.setIcon("icon-q-"+type.toLowerCase()+".png", type); 286 Cell c1 = gen.new Cell(null, context.getDefinitionsTarget() == null ? "" : context.getDefinitionsTarget()+"#item."+i.primitiveValue("linkId"), i.primitiveValue("linkId"), null, null); 287 c1.setId("item."+i.primitiveValue("linkId")); 288 r.getCells().add(c1); 289 String txt = (i.has("prefix") ? i.primitiveValue("prefix") + ". " : "") + i.primitiveValue("text"); 290 r.getCells().add(gen.new Cell(null, null, txt, null, null)); 291 r.getCells().add(gen.new Cell(null, null, ("true".equals(i.primitiveValue("required")) ? "1" : "0")+".."+("true".equals(i.primitiveValue("repeats")) ? "*" : "1"), null, null)); 292 if (i.child("type").hasExtension(EXT_QUESTIONNAIRE_ITEM_TYPE_ORIGINAL)) { 293 status.setExtensions(true); 294 String t = i.child("type").extensionString(EXT_QUESTIONNAIRE_ITEM_TYPE_ORIGINAL); 295 r.getCells().add(gen.new Cell(null, context.getLink(KnownLinkType.SPEC)+"codesystem-item-type.html#item-type-"+t, t, null, null)); 296 } else { 297 r.getCells().add(gen.new Cell(null, context.getLink(KnownLinkType.SPEC)+"codesystem-item-type.html#item-type-"+type, type, null, null)); 298 } 299 300 if (hasFlags) { 301 // flags: 302 Cell flags = gen.new Cell(); 303 r.getCells().add(flags); 304 if ("true".equals(i.primitiveValue("readOnly"))) { 305 flags.addPiece(gen.new Piece(Utilities.pathURL(context.getLink(KnownLinkType.SPEC), "questionnaire-definitions.html#Questionnaire.item.readOnly"), null, context.formatPhrase(RenderingContext.QUEST_READONLY)).addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", getImgPath("icon-qi-readonly.png")))); 306 } 307 if ("true".equals(i.extensionString("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-isSubject"))) { 308 status.setExtensions(true); 309 flags.addPiece(gen.new Piece(getSDCLink("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-isSubject", "StructureDefinition-sdc-questionnaire-isSubject.html"), null, context.formatPhrase(RenderingContext.QUEST_SUBJECT)).addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", getImgPath("icon-qi-subject.png")))); 310 } 311 if ("true".equals(i.extensionString(ToolingExtensions.EXT_Q_HIDDEN))) { 312 status.setExtensions(true); 313 flags.addPiece(gen.new Piece(getSpecLink("extension-questionnaire-hidden.html"), null, context.formatPhrase(RenderingContext.QUEST_HIDDEN)).addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", getImgPath("icon-qi-hidden.png")))); 314 } 315 if ("true".equals(i.extensionString(ToolingExtensions.EXT_Q_OTP_DISP))) { 316 status.setExtensions(true); 317 flags.addPiece(gen.new Piece(getSDCLink("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-optionalDisplay", "StructureDefinition-sdc-questionnaire-optionalDisplay.html"), null, context.formatPhrase(RenderingContext.QUEST_DISPLAY)).addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", getImgPath("icon-qi-optional.png")))); 318 } 319 if (i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-observationLinkPeriod")) { 320 status.setExtensions(true); 321 flags.addPiece(gen.new Piece(getSDCLink("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-observationLinkPeriod", "StructureDefinition-sdc-questionnaire-observationLinkPeriod.html"), null, context.formatPhrase(RenderingContext.QUEST_LINKED)).addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", getImgPath("icon-qi-observation.png")))); 322 } 323 if (i.hasExtension(ToolingExtensions.EXT_Q_CHOICE_ORIENT)) { 324 status.setExtensions(true); 325 String code = i.extensionString(ToolingExtensions.EXT_Q_CHOICE_ORIENT); 326 flags.addPiece(gen.new Piece(getSpecLink("extension-questionnaire-choiceorientation.html"), null, context.formatPhrase(RenderingContext.QUEST_ORIENTATION, code)+" ").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", getImgPath("icon-qi-" + code + ".png")))); 327 } 328 if (i.hasExtension(ToolingExtensions.EXT_Q_DISPLAY_CAT)) { 329 status.setExtensions(true); 330 ResourceWrapper cc = i.extensionValue(ToolingExtensions.EXT_Q_DISPLAY_CAT); 331 String code = getCodeFromCC(cc, "http://hl7.org/fhir/questionnaire-display-category"); 332 flags.addPiece(gen.new Piece("https://hl7.org/fhir/R4/extension-questionnaire-displayCategory.html", null, context.formatPhrase(RenderingContext.QUEST_CAT, code)+" ").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", getImgPath("icon-qi-" + code + ".png")))); 333 } 334 } 335 Cell defn = gen.new Cell(); 336 r.getCells().add(defn); 337 338 if (i.has("maxLength")) { 339 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.GENERAL_MAX_LENGTH)+" "), null)); 340 defn.getPieces().add(gen.new Piece(null, i.primitiveValue("maxLength"), null)); 341 } 342 if (i.has("definition")) { 343 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 344 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.GENERAL_DEFINITION_COLON)+" "), null)); 345 genDefinitionLink(gen, i, defn, q); 346 } 347 if (i.has("enableWhen")) { 348 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 349 Piece p = gen.new Piece(null, (context.formatPhrase(RenderingContext.QUEST_ENABLE)+" "), null); 350 defn.getPieces().add(p); 351 if (i.children("enableWhen").size() == 1) { 352 XhtmlNode x = new XhtmlNode(NodeType.Element, "span"); 353 p.getChildren().add(x); 354 renderEnableWhen(x, i.firstChild("enableWhen")); 355 } else { 356 XhtmlNode x = new XhtmlNode(NodeType.Element, "ul"); 357 p.getChildren().add(x); 358 for (ResourceWrapper qi : i.children("enableWhen")) { 359 renderEnableWhen(x.li(), qi); 360 } 361 } 362 } 363 if (i.has("answerValueSet")) { 364 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 365 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.QUEST_VALUE)+" "), null)); 366 if (i.hasPrimitiveValue("answerValueSet") && i.primitiveValue("answerValueSet").startsWith("#")) { 367 ResourceWrapper vs = q.getContained(i.primitiveValue("answerValueSet").substring(1)); 368 if (vs == null) { 369 defn.getPieces().add(gen.new Piece(null, i.primitiveValue("answerValueSet"), null)); 370 } else { 371 defn.getPieces().add(gen.new Piece(vs.getWebPath(), RendererFactory.factory(vs, context.forContained()).buildSummary(vs), null)); 372 } 373 } else { 374 ValueSet vs = context.getWorker().findTxResource(ValueSet.class, i.primitiveValue("answerValueSet")); 375 if (vs == null || !vs.hasWebPath()) { 376 defn.getPieces().add(gen.new Piece(null, i.primitiveValue("answerValueSet"), null)); 377 } else { 378 defn.getPieces().add(gen.new Piece(vs.getWebPath(), vs.present(), null)); 379 } 380 } 381 } 382 if (i.has("answerOption")) { 383 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 384 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.QUEST_OPTIONS)+" "), null)); 385 if (context.getDefinitionsTarget() == null) { 386 // if we don't have a definitions target, we'll add them below. 387 defn.getPieces().add(gen.new Piece("#"+context.prefixAnchor("opt-item."+i.primitiveValue("linkId")), Integer.toString(i.children("answerOption").size())+" "+Utilities.pluralize("option", i.children("answerOption").size()), null)); 388 } else { 389 defn.getPieces().add(gen.new Piece(context.getDefinitionsTarget()+"#item."+i.primitiveValue("linkId"), Integer.toString(i.children("answerOption").size())+" "+Utilities.pluralize("option", i.children("answerOption").size()), null)); 390 } 391 } 392 if (i.has("initial")) { 393 for (ResourceWrapper v : i.children("initial")) { 394 ResourceWrapper vv = v.child("value"); 395 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 396 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.QUEST_INITIAL)+" "), null)); 397 defn.getPieces().add(gen.new Piece(null, vv.fhirType(), null)); 398 defn.getPieces().add(gen.new Piece(null, " = ", null)); 399 if (vv.isPrimitive()) { 400 defn.getPieces().add(gen.new Piece(null, vv.primitiveValue(), null)); 401 } else if (vv.fhirType().equals("Coding")) { 402 renderCoding(gen, defn.getPieces(), vv); 403 } else if (vv.fhirType().equals("Quantity")) { 404 renderQuantity(gen, defn.getPieces(), vv, false); 405 } else if (vv.fhirType().equals("Reference")) { 406 renderReference(q, gen, defn.getPieces(), vv, true); 407 } else if (vv.fhirType().equals("Attachment")) { 408 // renderAttachment(gen, defn.getPieces(), vv); 409 } 410 } 411 } 412 // still todo 413 414 // 415 //http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-choiceColumn 416 // 417 //http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-width 418 //http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-observationLinkPeriod 419 //http://hl7.org/fhir/StructureDefinition/Questionnaire-itemControl 420 //http://hl7.org/fhir/StructureDefinition/Questionnaire-sliderStepValue 421 422 if (i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-enableWhenExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-itemContext") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-calculatedExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-contextExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-candidateExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-initialExpression")) { 423 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 424 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.QUEST_EXP)+" "), null)); 425 Piece p = gen.new Piece("ul"); 426 defn.getPieces().add(p); 427 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-initialExpression")) { 428 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_INT), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-initialExpression"); 429 } 430 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-contextExpression")) { 431 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_CONT), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-contextExpression"); 432 } 433 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-itemContext")) { 434 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_ITEM_CONT), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-itemContext"); 435 } 436 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-enableWhenExpression")) { 437 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_EN), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-enableWhenExpression"); 438 } 439 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-calculatedExpression")) { 440 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_CALC), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-calculatedExpression"); 441 } 442 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-candidateExpression")) { 443 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_CAND), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-candidateExpression"); 444 } 445 } 446 447 for (ResourceWrapper c : i.children("item")) { 448 renderTreeItem(status, gen, r.getSubRows(), q, c, hasFlags); 449 } 450 } 451 452 private String getCodeFromCC(ResourceWrapper cc, String system) { 453 for (ResourceWrapper coding : cc.children("coding")) { 454 if (system.equals(coding.primitiveValue("system"))) { 455 return coding.primitiveValue("code"); 456 } 457 } 458 return null; 459 460 } 461 462 public void genDefinitionLink(HierarchicalTableGenerator gen, ResourceWrapper i, Cell defn, ResourceWrapper q) { 463 // can we resolve the definition? 464 String path = null; 465 String d = i.primitiveValue("definition"); 466 if (d.contains("#")) { 467 path = d.substring(d.indexOf("#")+1); 468 d = d.substring(0, d.indexOf("#")); 469 } 470 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, d, q.getResourceNative()); 471 if (sd != null) { 472 String url = sd.getWebPath(); 473 if (url != null) { 474 defn.getPieces().add(gen.new Piece(url+"#"+path, path, null)); 475 } else { 476 defn.getPieces().add(gen.new Piece(null, i.primitiveValue("definition"), null)); 477 } 478 } else { 479 defn.getPieces().add(gen.new Piece(null, i.primitiveValue("definition"), null)); 480 } 481 } 482 483 public void genDefinitionLink(XhtmlNode x, ResourceWrapper i, ResourceWrapper q) { 484 // can we resolve the definition? 485 String path = null; 486 String d = i.primitiveValue("definition"); 487 if (d.contains("#")) { 488 path = d.substring(d.indexOf("#")+1); 489 d = d.substring(0, d.indexOf("#")); 490 } 491 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, d, q.getResourceNative()); 492 if (sd != null) { 493 String url = sd.getWebPath(); 494 if (url != null) { 495 x.ah(url+"#"+path).tx(path); 496 } else { 497 x.tx(i.primitiveValue("definition")); 498 } 499 } else { 500 x.tx(i.primitiveValue("definition")); 501 } 502 } 503 504 private void addExpression(Piece p, ResourceWrapper exp, String label, String url) { 505 XhtmlNode x = new XhtmlNode(NodeType.Element, "li").style("font-size: 11px"); 506 p.addHtml(x); 507 CanonicalResource cr = (CanonicalResource) context.getContext().fetchResource(Resource.class, url); 508 if (cr != null && cr.hasWebPath()) { 509 x.ah(cr.getWebPath()).tx(label); 510 } else { 511 x.ah(url).tx(label); 512 } 513 x.tx(": "); 514 x.code(exp.primitiveValue("expression")); 515 } 516 517 private void renderLogic(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws FHIRException, IOException { 518 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true, ""); 519 TableModel model = gen.new TableModel("qtree="+q.getId(), true); 520 model.setAlternating(true); 521 if (context.getRules() == GenerationRules.VALID_RESOURCE || context.isInlineGraphics()) { 522 model.setDocoImg(HierarchicalTableGenerator.help16AsData()); 523 } else { 524 model.setDocoImg(Utilities.pathURL(context.getLink(KnownLinkType.SPEC), "help16.png")); 525 } 526 model.setDocoRef(context.getLink(KnownLinkType.SPEC)+"formats.html#table"); 527 model.getTitles().add(gen.new Title(null, model.getDocoRef(), context.formatPhrase(RenderingContext.QUEST_LINKID), context.formatPhrase(RenderingContext.QUEST_LINK), null, 0)); 528 model.getTitles().add(gen.new Title(null, model.getDocoRef(), context.formatPhrase(RenderingContext.GENERAL_DESC_CONST), context.formatPhrase(RenderingContext.QUEST_ADD_INFO), null, 0)); 529 530 if (!q.has("item")) { 531 gen.emptyRow(model, 2); 532 } else { 533 for (ResourceWrapper i : q.children("item")) { 534 renderLogicItem(status, gen, model.getRows(), q, i); 535 } 536 } 537 XhtmlNode xn = gen.generate(model, context.getLocalPrefix(), 1, null); 538 x.addChildNode(xn); 539 } 540 541 private void renderLogicItem(RenderingStatus status, HierarchicalTableGenerator gen, List<Row> rows, ResourceWrapper q, ResourceWrapper i) throws IOException { 542 Row r = gen.new Row(); 543 rows.add(r); 544 String type = i.primitiveValue("type"); 545 546 r.setIcon("icon-q-"+type.toLowerCase()+".png", type); 547 r.getCells().add(gen.new Cell(null, context.getDefinitionsTarget() == null ? "" : context.getDefinitionsTarget()+"#item."+i.primitiveValue("linkId"), i.primitiveValue("linkId"), null, null)); 548 Cell defn = gen.new Cell(); 549 r.getCells().add(defn); 550 551 if (i.has("maxLength")) { 552 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.GENERAL_MAX_LENGTH)+" "), null)); 553 defn.getPieces().add(gen.new Piece(null, i.primitiveValue("maxLength"), null)); 554 } 555 if (i.has("definition")) { 556 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 557 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.GENERAL_DEFINITION_COLON)+" "), null)); 558 genDefinitionLink(gen, i, defn, q); 559 } 560 if (i.has("enableWhen")) { 561 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 562 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.QUEST_ENABLE)+" "), null)); 563 defn.getPieces().add(gen.new Piece(null, context.formatPhrase(RenderingContext.GENERAL_TODO), null)); 564 } 565 if (i.has("answerValueSet")) { 566 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 567 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.QUEST_VALUE)+" "), null)); 568 if (Utilities.noString(i.primitiveValue("answerValueSet")) && i.primitiveValue("answerValueSet").startsWith("#")) { 569 ResourceWrapper vs = q.getContained(i.primitiveValue("answerValueSet").substring(1)); 570 if (vs == null) { 571 defn.getPieces().add(gen.new Piece(null, i.primitiveValue("answerValueSet"), null)); 572 } else { 573 defn.getPieces().add(gen.new Piece(vs.getWebPath(), RendererFactory.factory(vs, context.forContained()).buildSummary(vs), null)); 574 } 575 } else { 576 ValueSet vs = context.getWorker().findTxResource(ValueSet.class, i.primitiveValue("answerValueSet"), q.getResourceNative()); 577 if (vs == null || !vs.hasWebPath()) { 578 defn.getPieces().add(gen.new Piece(null, i.primitiveValue("answerValueSet"), null)); 579 } else { 580 defn.getPieces().add(gen.new Piece(vs.getWebPath(), vs.present(), null)); 581 } 582 } 583 } 584 if (i.has("answerOption")) { 585 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 586 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.QUEST_OPTIONS)+" "), null)); 587 defn.getPieces().add(gen.new Piece(context.getDefinitionsTarget()+"#item."+i.primitiveValue("linkId"), Integer.toString(i.children("answerOption").size())+" "+Utilities.pluralize("option", i.children("answerOption").size()), null)); 588 } 589 if (i.has("initial")) { 590 for (ResourceWrapper v : i.children("initial")) { 591 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 592 ResourceWrapper vv = v.child("value"); 593 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.QUEST_INITIAL)+" "), null)); 594 defn.getPieces().add(gen.new Piece(null, vv.fhirType(), null)); 595 defn.getPieces().add(gen.new Piece(null, " = ", null)); 596 if (vv.isPrimitive()) { 597 defn.getPieces().add(gen.new Piece(null, vv.primitiveValue(), null)); 598 } else if (vv.fhirType().equals("Coding")) { 599 renderCoding(gen, defn.getPieces(), vv); 600 } else if (vv.fhirType().equals("Coding")) { 601 renderQuantity(gen, defn.getPieces(), vv, false); 602 } else if (vv.fhirType().equals("Coding")) { 603 renderReference(q, gen, defn.getPieces(), vv, false); 604 // } else if (v.hasValueAttachment()) { 605 // renderAttachment(gen, defn.getPieces(), v.getValueAttachment()); 606 } 607 } 608 } 609 // still todo 610 611 // 612 //http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-choiceColumn 613 // 614 //http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-width 615 //http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-observationLinkPeriod 616 //http://hl7.org/fhir/StructureDefinition/Questionnaire-itemControl 617 //http://hl7.org/fhir/StructureDefinition/Questionnaire-sliderStepValue 618 619 if (i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-enableWhenExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-itemContext") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-calculatedExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-contextExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-candidateExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-initialExpression")) { 620 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 621 defn.getPieces().add(gen.new Piece(null, (context.formatPhrase(RenderingContext.QUEST_EXP)+" "), null)); 622 Piece p = gen.new Piece("ul"); 623 defn.getPieces().add(p); 624 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-initialExpression")) { 625 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_INT), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-initialExpression"); 626 } 627 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-contextExpression")) { 628 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_CONT), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-contextExpression"); 629 } 630 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-itemContext")) { 631 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_ITEM_CONT), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-itemContext"); 632 } 633 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-enableWhenExpression")) { 634 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_EN), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-enableWhenExpression"); 635 } 636 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-calculatedExpression")) { 637 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_CALC), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-calculatedExpression"); 638 } 639 for (ResourceWrapper e : i.extensions("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-candidateExpression")) { 640 addExpression(p, e.child("value"), context.formatPhrase(RenderingContext.QUEST_CAND), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-candidateExpression"); 641 } 642 } 643 644 for (ResourceWrapper c : i.children("item")) { 645 renderLogicItem(status, gen, r.getSubRows(), q, c); 646 } 647 648 } 649 650 651 public void renderForm(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws UnsupportedEncodingException, IOException { 652 XhtmlNode d = x.div(); 653 boolean hasPrefix = false; 654 for (ResourceWrapper c : q.children("item")) { 655 hasPrefix = hasPrefix || doesItemHavePrefix(c); 656 } 657 int i = 1; 658 for (ResourceWrapper c : q.children("item")) { 659 renderFormItem(status, d, q, c, hasPrefix ? null : Integer.toString(i), 0); 660 i++; 661 } 662 } 663 664 private boolean doesItemHavePrefix(ResourceWrapper i) { 665 if (i.has("prefix")) { 666 return true; 667 } 668 for (ResourceWrapper c : i.children("item")) { 669 if (doesItemHavePrefix(c)) { 670 return true; 671 } 672 } 673 return false; 674 } 675 676 private void renderFormItem(RenderingStatus status, XhtmlNode x, ResourceWrapper q, ResourceWrapper i, String pfx, int indent) throws IOException { 677 boolean hasExt = false; 678 XhtmlNode d = x.div().style("width: "+Integer.toString(900-indent*10)+"px; border-top: 1px #eeeeee solid"); 679 if (indent > 0) { 680 d.style("margin-left: "+Integer.toString(10*indent)+"px"); 681 } 682 XhtmlNode display = d.div().style("display: inline-block; width: "+Integer.toString(500-indent*10)+"px"); 683 XhtmlNode details = d.div().style("border: 1px #ccccff solid; padding: 2px; display: inline-block; background-color: #fefce7; width: 380px"); 684 XhtmlNode p = display.para(); 685 686 String type = i.primitiveValue("type"); 687 String typeT = getTranslatedCode(i.child("type")); 688 if ("group".equals(type)) { 689 p = p.b(); 690 } 691 if (i.has("prefix")) { 692 p.tx(i.primitiveValue("prefix")); 693 p.tx(": "); 694 } 695 p.span(null, "linkId: "+i.primitiveValue("linkId")).tx(i.primitiveValue("text")); 696 if ("true".equals(i.primitiveValue("required"))) { 697 p.span("color: red", context.formatPhrase(RenderingContext.QUEST_MAND)).tx("*"); 698 } 699 700 XhtmlNode input = null; 701 switch (type) { 702 case "string": 703 p.tx(" "); 704 input = p.input(i.primitiveValue("linkId"), "text", typeT, 60); 705 break; 706 case "attachment": 707 break; 708 case "boolean": 709 p.tx(" "); 710 input = p.input(i.primitiveValue("linkId"), "checkbox", typeT, 1); 711 break; 712 case "coding": 713 input = p.select(i.primitiveValue("linkId")); 714 listOptions(q, i, input); 715 break; 716 case "date": 717 p.tx(" "); 718 input = p.input(i.primitiveValue("linkId"), "date", typeT, 10); 719 break; 720 case "dateTime": 721 p.tx(" "); 722 input = p.input(i.primitiveValue("linkId"), "datetime-local", typeT, 25); 723 break; 724 case "decimal": 725 p.tx(" "); 726 input = p.input(i.primitiveValue("linkId"), "number", typeT, 15); 727 break; 728 case "display": 729 break; 730 case "group": 731 break; 732 case "integer": 733 p.tx(" "); 734 input = p.input(i.primitiveValue("linkId"), "number", typeT, 10); 735 break; 736 case "qantity": 737 p.tx(" "); 738 input = p.input(i.primitiveValue("linkId"), "number", "value", 15); 739 p.tx(" "); 740 input = p.input(i.primitiveValue("linkId"), "unit", "unit", 10); 741 break; 742 case "question": 743 break; 744 case "reference": 745 break; 746 case "text": 747 break; 748 case "time": 749 break; 750 case "url": 751 break; 752 default: 753 break; 754 } 755 if (input != null) { 756 if ("true".equals(i.primitiveValue("readOnly"))) { 757 input.attribute("readonly", "1"); 758 input.style("background-color: #eeeeee"); 759 } 760 } 761 762 // if (i.hasExtension(ToolingExtensions.EXT_Q_CHOICE_ORIENT)) { 763 // String code = ToolingExtensions.readStringExtension(i, ToolingExtensions.EXT_Q_CHOICE_ORIENT); 764 // flags.addPiece(gen.new Piece("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-observationLinkPeriod", null, "Orientation: "+code).addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", Utilities.path(context.getLocalPrefix(), "icon-qi-"+code+".png")))); 765 //} 766 767 768 XhtmlNode ul = details.ul(); 769 boolean hasFlag = false; 770 XhtmlNode flags = item(ul, "Flags"); 771 item(ul, "linkId", i.primitiveValue("linkId")); 772 773 if ("true".equals(i.extensionString("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-isSubject"))) { 774 hasFlag = true; 775 flags.ah(getSDCLink("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-isSubject", "StructureDefinition-sdc-questionnaire-isSubject.html"), context.formatPhrase(RenderingContext.QUEST_SUBJECT)).img(getImgPath("icon-qi-subject.png"), "icon"); 776 } 777 if ("true".equals(i.extensionString(ToolingExtensions.EXT_Q_HIDDEN))) { 778 hasFlag = true; 779 flags.ah(Utilities.pathURL(context.getLink(KnownLinkType.SPEC), "extension-questionnaire-hidden.html"), context.formatPhrase(RenderingContext.QUEST_HIDDEN)).img(getImgPath("icon-qi-hidden.png"), "icon"); 780 d.style("background-color: #eeeeee"); 781 } 782 if ("true".equals(i.extensionString(ToolingExtensions.EXT_Q_OTP_DISP))) { 783 hasFlag = true; 784 flags.ah(getSDCLink("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-optionalDisplay", "StructureDefinition-sdc-questionnaire-optionalDisplay.html"), context.formatPhrase(RenderingContext.QUEST_DISPLAY)).img(getImgPath("icon-qi-optional.png"), "icon"); 785 } 786 if (i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-observationLinkPeriod")) { 787 hasFlag = true; 788 flags.ah(getSDCLink("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-observationLinkPeriod", "StructureDefinition-sdc-questionnaire-observationLinkPeriod.html"), context.formatPhrase(RenderingContext.QUEST_LINKED)).img(getImgPath("icon-qi-observation.png"), "icon"); 789 } 790 if (i.hasExtension(ToolingExtensions.EXT_Q_DISPLAY_CAT)) { 791 ResourceWrapper cc = i.extension(ToolingExtensions.EXT_Q_DISPLAY_CAT).child("value"); 792 String code = getCodeFromCC(cc, "http://hl7.org/fhir/questionnaire-display-category"); 793 hasFlag = true; 794 flags.ah("https://hl7.org/fhir/R4/extension-questionnaire-displayCategory.html", (context.formatPhrase(RenderingContext.QUEST_CAT, code)+" ")).img(getImgPath("icon-qi-" + code + ".png"), "icon"); 795 } 796 797 if (i.has("maxLength")) { 798 item(ul, context.formatPhrase(RenderingContext.GENERAL_MAX_LENGTH), i.primitiveValue("maxLength")); 799 } 800 if (i.has("definition")) { 801 genDefinitionLink(item(ul, context.formatPhrase(RenderingContext.GENERAL_DEFINITION_COLON)), i, q); 802 } 803 if (i.has("enableWhen")) { 804 item(ul, context.formatPhrase(RenderingContext.QUEST_EN), "todo"); 805 } 806 if (i.has("answerValueSet")) { 807 XhtmlNode ans = item(ul, context.formatPhrase(RenderingContext.QUEST_ANSWERS)); 808 if (!Utilities.noString(i.primitiveValue("answerValueSet")) && i.primitiveValue("answerValueSet").startsWith("#")) { 809 ResourceWrapper vs = q.getContained(i.primitiveValue("answerValueSet").substring(1)); 810 if (vs == null) { 811 ans.tx(i.primitiveValue("answerValueSet")); 812 } else if (vs.getWebPath() == null) { 813 ans.ah(context.prefixLocalHref("#hc"+vs.getScopedId())).tx(RendererFactory.factory(vs, context.forContained()).buildSummary(vs)); 814 } else { 815 ans.ah(context.prefixLocalHref(vs.getWebPath())).tx(RendererFactory.factory(vs, context.forContained()).buildSummary(vs)); 816 } 817 } else { 818 ValueSet vs = context.getWorker().findTxResource(ValueSet.class, i.primitiveValue("answerValueSet"), q.getResourceNative()); 819 if (vs == null || !vs.hasWebPath()) { 820 ans.tx(i.primitiveValue("answerValueSet")); 821 } else { 822 ans.ah(vs.getWebPath()).tx(vs.present()); 823 } 824 } 825 } 826 if (i.has("answerOption")) { 827 item(ul, context.formatPhrase(RenderingContext.QUEST_ANSWERS), Integer.toString(i.children("answerOption").size())+" "+Utilities.pluralize("option", i.children("answerOption").size()), context.getDefinitionsTarget()+"#item."+i.primitiveValue("linkId")); 828 } 829 if (i.has("initial")) { 830 XhtmlNode vi = item(ul, context.formatPhrase(RenderingContext.QUEST_INT)); 831 boolean first = true; 832 for (ResourceWrapper v : i.children("initial")) { 833 if (first) first = false; else vi.tx(", "); 834 ResourceWrapper vv = v.child("value"); 835 if (vv.isPrimitive()) { 836 vi.tx(vv.primitiveValue()); 837 } else if (vv.fhirType().equals("Coding")) { 838 renderCoding(status, vi, vv); 839 } else if (vv.fhirType().equals("Reference")) { 840 renderReference(status, vi, vv); 841 } else if (vv.fhirType().equals("Quantity")) { 842 renderQuantity(status, vi, vv); 843 // } else if (v.hasValueAttachment()) { 844 // renderAttachment(vi, v.getValueAttachment()); 845 } 846 } 847 } 848 if (!hasFlag) { 849 ul.remove(flags); 850 } 851 // if (i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-enableWhenExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-itemContext") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-calculatedExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-contextExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-candidateExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-initialExpression")) { 852 // if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 853 // defn.getPieces().add(gen.new Piece(null, "Expressions: ", null)); 854 // Piece p = gen.new Piece("ul"); 855 // defn.getPieces().add(p); 856 // for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-initialExpression")) { 857 // addExpression(p, e.getValueExpression(), "Initial Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-initialExpression"); 858 // } 859 // for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-contextExpression")) { 860 // addExpression(p, e.getValueExpression(), "Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-contextExpression"); 861 // } 862 // for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-itemContext")) { 863 // addExpression(p, e.getValueExpression(), "Item Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-itemContext"); 864 // } 865 // for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-enableWhenExpression")) { 866 // addExpression(p, e.getValueExpression(), "Enable When", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-enableWhenExpression"); 867 // } 868 // for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-calculatedExpression")) { 869 // addExpression(p, e.getValueExpression(), "Calculated Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-calculatedExpression"); 870 // } 871 // for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-candidateExpression")) { 872 // addExpression(p, e.getValueExpression(), "Candidates", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-candidateExpression"); 873 // } 874 // } 875 // 876 877 int t = 1; 878 for (ResourceWrapper c : i.children("item")) { 879 renderFormItem(status, x, q, c, pfx == null ? null : pfx+"."+Integer.toString(t), indent+1); 880 t++; 881 } 882 } 883 884 @Nonnull 885 private String getImgPath(String code) throws IOException { 886 return context.getLocalPrefix().length() > 0 887 ? Utilities.path(context.getLocalPrefix(), code) 888 : Utilities.path(code); 889 } 890 891 private void item(XhtmlNode ul, String name, String value, String valueLink) { 892 if (!Utilities.noString(value)) { 893 ul.li().style("font-size: 10px").ah(context.prefixLocalHref(valueLink)).tx(name+": "+value); 894 } 895 } 896 897 private void item(XhtmlNode ul, String name, String value) { 898 if (!Utilities.noString(value)) { 899 ul.li().style("font-size: 10px").tx(name+": "+value); 900 } 901 } 902 private XhtmlNode item(XhtmlNode ul, String name) { 903 XhtmlNode li = ul.li(); 904 li.style("font-size: 10px").tx(name+": "); 905 return li; 906 } 907 908 909 private void listOptions(ResourceWrapper q, ResourceWrapper i, XhtmlNode select) { 910 if (i.has("answerValueSet")) { 911 ValueSet vs = null; 912 if (!Utilities.noString(i.primitiveValue("answerValueSet")) && i.primitiveValue("answerValueSet").startsWith("#")) { 913 ResourceWrapper contained = q.getContained(i.primitiveValue("answerValueSet").substring(1)); 914 vs = contained == null ? null : (ValueSet) contained.getResourceNative(); 915 if (vs != null && !vs.hasUrl()) { 916 vs = vs.copy(); 917 vs.setUrl(q.primitiveValue("url")+"--"+contained); 918 } 919 } else { 920 vs = context.getContext().findTxResource(ValueSet.class, i.primitiveValue("answerValueSet"), q.getResourceNative()); 921 } 922 if (vs != null) { 923 ValueSetExpansionOutcome exp = context.getContext().expandVS(vs, true, false); 924 if (exp.getValueset() != null) { 925 for (ValueSetExpansionContainsComponent cc : exp.getValueset().getExpansion().getContains()) { 926 select.option(cc.getCode(), cc.hasDisplay() ? cc.getDisplay() : cc.getCode(), false); 927 } 928 return; 929 } 930 } 931 } else if (i.has("answerOption")) { 932 renderItemOptions(select, i); 933 } 934 select.option("a", "??", false); 935 } 936 937 private void renderLinks(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { 938 x.para().tx(context.formatPhrase(RenderingContext.QUEST_TRY)); 939 XhtmlNode ul = x.ul(); 940 ul.li().ah("http://todo.nlm.gov/path?mode=ig&src="+Utilities.pathURL(context.getLink(KnownLinkType.SELF), "package.tgz")+"&q="+q.getId()+".json").tx(context.formatPhrase(RenderingContext.QUEST_NLM)); 941 } 942 943 private void renderDefns(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws IOException { 944 XhtmlNode tbl = x.table("dict"); 945 renderRootDefinition(status, tbl, q, new ArrayList<>()); 946 for (ResourceWrapper qi : q.children("item")) { 947 renderDefinition(status, tbl, q, qi, new ArrayList<>()); 948 } 949 } 950 951 private void renderRootDefinition(RenderingStatus status, XhtmlNode tbl, ResourceWrapper q, List<ResourceWrapper> parents) throws IOException { 952 boolean ext = false; 953 XhtmlNode td = tbl.tr().td("structure").colspan("2").span(null, null).attribute("class", "self-link-parent"); 954 td.an(context.prefixAnchor(q.getId())); 955 td.img(getImgPath("icon_q_root.gif"), "icon"); 956 td.tx(" "+(context.formatPhrase(RenderingContext.QUEST_QUEST)+" ")); 957 td.b().tx(q.getId()); 958 959 // general information 960 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_URL), q.primitiveValue("url")); 961 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_VER), q.primitiveValue("version")); 962 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_NAME), q.primitiveValue("name")); 963 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_TITLE), q.primitiveValue("title")); 964 if (q.has("derivedFrom")) { 965 td = defn(tbl, context.formatPhrase(RenderingContext.QUEST_DERIVED)); 966 boolean first = true; 967 for (ResourceWrapper c : q.children("derivedFrom")) { 968 if (first) first = false; else td.tx(", "); 969 td.tx(c.primitiveValue()); // todo: make these a reference 970 } 971 } 972 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_STATUS), q.primitiveValue("status")); 973 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_EXPER), q.primitiveValue("experimental")); 974 defn(tbl, context.formatPhrase(RenderingContext.QUEST_PUB), q.primitiveValue("date")); 975 defn(tbl, context.formatPhrase(RenderingContext.QUEST_APP), q.primitiveValue("approvalDate")); 976 defn(tbl, context.formatPhrase(RenderingContext.QUEST_REV_DATE), q.primitiveValue("lastReviewDate")); 977 if (q.has("effectivePeriod")) { 978 renderPeriod(status, defn(tbl, context.formatPhrase(RenderingContext.QUEST_EFF_PERIOD)), q.child("effectivePeriod")); 979 } 980 981 if (q.has("subjectType")) { 982 td = defn(tbl, context.formatPhrase(RenderingContext.QUEST_SUB_TYPE)); 983 boolean first = true; 984 for (ResourceWrapper c : q.children("subjectType")) { 985 if (first) first = false; else td.tx(", "); 986 td.tx(c.primitiveValue()); 987 } 988 } 989 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_DESC), q.primitiveValue("description")); 990 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_PURPOSE), q.primitiveValue("purpose")); 991 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_COPYRIGHT), q.primitiveValue("copyright")); 992 if (q.has("code")) { 993 td = defn(tbl, Utilities.pluralize("Code", q.children("code").size())); 994 boolean first = true; 995 for (ResourceWrapper c : q.children("code")) { 996 if (first) first = false; else td.tx(", "); 997 renderCodingWithDetails(status, td, c); 998 } 999 } 1000 } 1001 1002 private void renderDefinition(RenderingStatus status, XhtmlNode tbl, ResourceWrapper q, ResourceWrapper qi, List<ResourceWrapper> parents) throws IOException { 1003 XhtmlNode td = tbl.tr().td("structure").colspan("2").span(null, null).attribute("class", "self-link-parent"); 1004 td.an(context.prefixAnchor("item."+qi.primitiveValue("linkId"))); 1005 for (ResourceWrapper p : parents) { 1006 td.ah(context.prefixLocalHref("#item."+p.primitiveValue("linkId"))).img(getImgPath("icon_q_item.png"), "icon"); 1007 td.tx(" > "); 1008 } 1009 td.img(getImgPath("icon_q_item.png"), "icon"); 1010 td.tx(" Item "); 1011 td.b().tx(qi.primitiveValue("linkId")); 1012 String type = qi.primitiveValue("type"); 1013 1014 // general information 1015 defn(tbl, context.formatPhrase(RenderingContext.QUEST_ID), qi.primitiveValue("linkId")); 1016 defn(tbl, context.formatPhrase(RenderingContext.QUEST_PREFIX), qi.primitiveValue("prefix")); 1017 defn(tbl, context.formatPhrase(RenderingContext.QUEST_TEXT), qi.primitiveValue("text")); 1018 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_TYPE), type); 1019 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_REQUIRED), qi.primitiveValue("required")); 1020 defn(tbl, context.formatPhrase(RenderingContext.QUEST_REP), qi.primitiveValue("repeats")); 1021 defn(tbl, context.formatPhrase(RenderingContext.QUEST_READ_ONLY), qi.primitiveValue("readOnly")); 1022 if ("true".equals(qi.extensionString("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-isSubject"))) { 1023 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_SUBJ), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-isSubject", "This element changes who the subject of the question is", null); 1024 } 1025 1026 // content control 1027 defn(tbl, context.formatPhrase(RenderingContext.QUEST_MAX_LENGTH), qi.primitiveValue("maxLength")); 1028 if (qi.has("answerValueSet")) { 1029 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_VALUESET), qi.primitiveValue("definition"), context.getWorker().findTxResource(ValueSet.class, qi.primitiveValue("answerValueSet"), q.getResourceNative())); 1030 } 1031 if (qi.has("answerOption")) { 1032 XhtmlNode tr = tbl.tr(); 1033 tr.td().tx(context.formatPhrase(RenderingContext.QUEST_ALLOWED)); 1034 XhtmlNode ul = tr.td().ul(); 1035 for (ResourceWrapper ans : qi.children("answerOption")) { 1036 XhtmlNode li = ul.li(); 1037 renderDataType(status, li, ans.child("value")); 1038 if ("true".equals(ans.primitiveValue("initialSelected"))) { 1039 li.tx(" "+(context.formatPhrase(RenderingContext.QUEST_INITIALLY))); 1040 } 1041 } 1042 } 1043 if (qi.has("initial")) { 1044 XhtmlNode tr = tbl.tr(); 1045 tr.td().tx(Utilities.pluralize((context.formatPhrase(RenderingContext.QUEST_INITIAL_ANSWER)), qi.children("initial").size())); 1046 if (qi.children("initial").size() == 1) { 1047 renderDataType(status, tr.td(), qi.firstChild("initial").child("value")); 1048 } else { 1049 XhtmlNode ul = tr.td().ul(); 1050 for (ResourceWrapper ans : qi.children("initial")) { 1051 XhtmlNode li = ul.li(); 1052 renderDataType(status, li, ans.child("value")); 1053 } 1054 } 1055 } 1056 1057 // appearance 1058 if (qi.hasExtension(ToolingExtensions.EXT_Q_DISPLAY_CAT)) { 1059 XhtmlNode tr = tbl.tr(); 1060 tr.td().ah(ToolingExtensions.EXT_Q_DISPLAY_CAT).tx("Display Category"); 1061 renderDataType(status, tr.td(), qi.extension(ToolingExtensions.EXT_Q_DISPLAY_CAT).child("value")); 1062 } 1063 if ("true".equals(qi.extensionString(ToolingExtensions.EXT_Q_HIDDEN))) { 1064 defn(tbl, context.formatPhrase(RenderingContext.QUEST_HIDDEN_ITEM), ToolingExtensions.EXT_Q_DISPLAY_CAT, "This item is a hidden question", null); 1065 } 1066 if ("true".equals(qi.extensionString(ToolingExtensions.EXT_Q_OTP_DISP))) { 1067 defn(tbl, context.formatPhrase(RenderingContext.QUEST_HIDDEN_ITEM), ToolingExtensions.EXT_Q_OTP_DISP, "This item is optional to display", null); 1068 } 1069 1070 // formal definitions 1071 if (qi.has("definition")) { 1072 genDefinitionLink(defn(tbl, context.formatPhrase(RenderingContext.GENERAL_DEFINITION)), qi, q); 1073 } 1074 1075 if (qi.has("code")) { 1076 XhtmlNode tr = tbl.tr(); 1077 tr.td().tx(Utilities.pluralize(context.formatPhrase(RenderingContext.GENERAL_CODE), qi.children("code").size())); 1078 XhtmlNode ul = tr.td().ul(); 1079 for (ResourceWrapper c : qi.children("code")) { 1080 renderCodingWithDetails(status, ul.li(), c); 1081 } 1082 } 1083 if (qi.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-observationLinkPeriod")) { 1084 XhtmlNode tr = tbl.tr(); 1085 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, ToolingExtensions.EXT_O_LINK_PERIOD); 1086 if (sd != null && sd.hasWebPath()) { 1087 tr.td().ah(sd.getWebPath()).tx(context.formatPhrase(RenderingContext.QUEST_OBSERVATION)); 1088 } else { 1089 tr.td().ah("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-observationLinkPeriod").tx(context.formatPhrase(RenderingContext.QUEST_OBSERVATION)); 1090 } 1091 renderDataType(status, tr.td(), qi.extension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-Questionnaire-observationLinkPeriod").child("value")); 1092 } 1093 1094 // dynamic management 1095 if (qi.has("enableWhen")) { 1096 XhtmlNode tr = tbl.tr(); 1097 tr.td().tx(context.formatPhrase(RenderingContext.QUEST_EN)); 1098 td = tr.td(); 1099 if (qi.children("enableWhen").size() == 1) { 1100 renderEnableWhen(td, qi.children("enableWhen").get(0)); 1101 } else { 1102 if (qi.has("enableBehavior")) { 1103 td.tx(qi.primitiveValue("enableBehavior")+" "+(context.formatPhrase(RenderingContext.QUEST_TRUE))); 1104 } else { 1105 td.tx(context.formatPhrase(RenderingContext.QUEST_ARE_TRUE)); 1106 } 1107 XhtmlNode ul = td.ul(); 1108 for (ResourceWrapper ew : qi.children("enableWhen")) { 1109 renderEnableWhen(ul.li(), ew); 1110 } 1111 } 1112 } 1113 1114 1115 // other stuff 1116 1117 1118 1119 List<ResourceWrapper> curr = new ArrayList<>(); 1120 curr.addAll(parents); 1121 curr.add(qi); 1122 for (ResourceWrapper qic : qi.children("item")) { 1123 renderDefinition(status, tbl, q, qic, curr); 1124 } 1125 } 1126 1127 private void defn(XhtmlNode tbl, String name, String url, Resource res) throws UnsupportedEncodingException, IOException { 1128 if (res != null && res.hasWebPath()) { 1129 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_DEFINITION), RendererFactory.factory(res, context.forContained()).buildSummary(wrap(res)), res.getWebPath()); 1130 } else if (Utilities.isAbsoluteUrlLinkable(url)) { 1131 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_DEFINITION), url, url); 1132 } { 1133 defn(tbl, context.formatPhrase(RenderingContext.GENERAL_DEFINITION), url); 1134 } 1135 1136 } 1137 1138 private void renderEnableWhen(XhtmlNode x, ResourceWrapper ew) { 1139 x.ah(context.prefixLocalHref("#item."+ew.primitiveValue("question"))).tx(ew.primitiveValue("question")); 1140 x.tx(" "); 1141 x.tx(ew.primitiveValue("operator")); 1142 x.tx(" "); 1143 x.tx(displayDataType(ew.child("Answer"))); 1144 } 1145 1146 private XhtmlNode defn(XhtmlNode tbl, String name) { 1147 XhtmlNode tr = tbl.tr(); 1148 tr.td().tx(name); 1149 return tr.td(); 1150 } 1151 1152 private void defn(XhtmlNode tbl, String name, int value) { 1153 if (value > 0) { 1154 XhtmlNode tr = tbl.tr(); 1155 tr.td().tx(name); 1156 tr.td().tx(value); 1157 } 1158 } 1159 1160 1161 private void defn(XhtmlNode tbl, String name, boolean value) { 1162 XhtmlNode tr = tbl.tr(); 1163 tr.td().tx(name); 1164 tr.td().tx(Boolean.toString(value)); 1165 } 1166 1167 private void defn(XhtmlNode tbl, String name, String value) { 1168 if (!Utilities.noString(value)) { 1169 XhtmlNode tr = tbl.tr(); 1170 tr.td().tx(name); 1171 tr.td().tx(value); 1172 } 1173 } 1174 1175 private void defn(XhtmlNode tbl, String name, String value, String url) { 1176 if (!Utilities.noString(value)) { 1177 XhtmlNode tr = tbl.tr(); 1178 tr.td().tx(name); 1179 tr.td().ah(context.prefixLocalHref(url)).tx(value); 1180 } 1181 } 1182 1183 private void defn(XhtmlNode tbl, String name, String nurl, String value, String url) { 1184 if (!Utilities.noString(value)) { 1185 XhtmlNode tr = tbl.tr(); 1186 tr.td().ah(context.prefixLocalHref(nurl)).tx(name); 1187 if (url != null) { 1188 tr.td().ah(context.prefixLocalHref(url)).tx(value); 1189 } else { 1190 tr.td().tx(value); 1191 } 1192 } 1193 } 1194 1195 private void defn(XhtmlNode tbl, String name, boolean value, boolean ifFalse) { 1196 if (ifFalse || value) { 1197 XhtmlNode tr = tbl.tr(); 1198 tr.td().tx(name); 1199 tr.td().tx(Boolean.toString(value)); 1200 } 1201 } 1202 1203}