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 org.hl7.fhir.exceptions.FHIRException; 009import org.hl7.fhir.r5.context.ContextUtilities; 010import org.hl7.fhir.r5.model.CanonicalType; 011import org.hl7.fhir.r5.model.CodeType; 012import org.hl7.fhir.r5.model.CodeableConcept; 013import org.hl7.fhir.r5.model.Coding; 014import org.hl7.fhir.r5.model.Expression; 015import org.hl7.fhir.r5.model.Extension; 016import org.hl7.fhir.r5.model.Questionnaire; 017import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemAnswerOptionComponent; 018import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemComponent; 019import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemEnableWhenComponent; 020import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemInitialComponent; 021import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemType; 022import org.hl7.fhir.r5.model.Resource; 023import org.hl7.fhir.r5.model.StructureDefinition; 024import org.hl7.fhir.r5.model.ValueSet; 025import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 026import org.hl7.fhir.r5.renderers.utils.RenderingContext; 027import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 028import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType; 029import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 030import org.hl7.fhir.r5.utils.ToolingExtensions; 031import org.hl7.fhir.utilities.Utilities; 032import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 033import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 034import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 035import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 036import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 037import org.hl7.fhir.utilities.xhtml.NodeType; 038import org.hl7.fhir.utilities.xhtml.XhtmlNode; 039 040public class QuestionnaireRenderer extends TerminologyRenderer { 041 public static final String EXT_QUESTIONNAIRE_ITEM_TYPE_ORIGINAL = "http://hl7.org/fhir/tools/StructureDefinition/original-item-type"; 042 043 public QuestionnaireRenderer(RenderingContext context) { 044 super(context); 045 } 046 047 public boolean render(XhtmlNode x, Resource q) throws UnsupportedEncodingException, IOException { 048 return render(x, (Questionnaire) q); 049 } 050 051 public boolean render(XhtmlNode x, Questionnaire q) throws UnsupportedEncodingException, IOException { 052 switch (context.getQuestionnaireMode()) { 053 case FORM: return renderForm(x, q); 054 case LINKS: return renderLinks(x, q); 055 case LOGIC: return renderLogic(x, q); 056 case DEFNS: return renderDefns(x, q); 057 case TREE: return renderTree(x, q); 058 default: 059 throw new Error("Unknown Questionnaire Renderer Mode"); 060 } 061 } 062 063 public boolean renderTree(XhtmlNode x, Questionnaire q) throws UnsupportedEncodingException, IOException { 064 boolean hasFlags = checkForFlags(q.getItem()); 065 boolean doOpts = context.getDefinitionsTarget() == null && hasAnyOptions(q.getItem()); 066 067 if (doOpts) { 068 x.b().tx("Structure"); 069 } 070 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context.getDestDir(), context.isInlineGraphics(), true); 071 TableModel model = gen.new TableModel("qtree="+q.getId(), context.getRules() == GenerationRules.IG_PUBLISHER); 072 model.setAlternating(true); 073 model.setDocoImg(context.getLink(KnownLinkType.SPEC) +"help16.png"); 074 model.setDocoRef(context.getLink(KnownLinkType.SPEC)+"formats.html#table"); 075 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "LinkId"), translate("sd.hint", "The linkId for the item"), null, 0)); 076 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Text"), translate("sd.hint", "Text for the item"), null, 0)); 077 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Cardinality"), translate("sd.hint", "Minimum and Maximum # of times the the itemcan appear in the instance"), null, 0)); 078 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Type"), translate("sd.hint", "The type of the item"), null, 0)); 079 if (hasFlags) { 080 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Flags"), translate("sd.hint", "Other attributes of the item"), null, 0)); 081 } 082 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Description & Constraints"), translate("sd.hint", "Additional information about the item"), null, 0)); 083 084 boolean hasExt = false; 085 // first we add a root for the questionaire itself 086 Row row = addTreeRoot(gen, model.getRows(), q, hasFlags); 087 for (QuestionnaireItemComponent i : q.getItem()) { 088 hasExt = renderTreeItem(gen, row.getSubRows(), q, i, hasFlags) || hasExt; 089 } 090 XhtmlNode xn = gen.generate(model, context.getLocalPrefix(), 1, null); 091 x.getChildNodes().add(xn); 092 if (doOpts) { 093 renderOptions(q, x); 094 } 095 return hasExt; 096 } 097 098 private void renderOptions(Questionnaire q, XhtmlNode x) { 099 if (hasAnyOptions(q.getItem())) { 100 x.hr(); 101 x.para().b().tx("Option Sets"); 102 renderOptions(q.getItem(), x); 103 } 104 } 105 106 private void renderOptions(List<QuestionnaireItemComponent> items, XhtmlNode x) { 107 for (QuestionnaireItemComponent i : items) { 108 renderItemOptions(x, i); 109 renderOptions(i.getItem(), x); 110 } 111 } 112 113 public void renderItemOptions(XhtmlNode x, QuestionnaireItemComponent i) { 114 if (i.hasAnswerOption()) { 115 boolean useSelect = false; 116 for (QuestionnaireItemAnswerOptionComponent opt : i.getAnswerOption()) { 117 useSelect = useSelect || opt.getInitialSelected(); 118 } 119 x.an("opt-item."+i.getLinkId()); 120 x.para().b().tx("Answer options for "+i.getLinkId()); 121 XhtmlNode ul = x.ul(); 122 for (QuestionnaireItemAnswerOptionComponent opt : i.getAnswerOption()) { 123 XhtmlNode li = ul.li(); 124 li.style("font-size: 11px"); 125 if (useSelect) { 126 if (opt.getInitialSelected()) { 127 li.img("icon-selected.png", "icon"); 128 } else { 129 li.img("icon-not-selected.png", "icon"); 130 } 131 } 132 if (opt.getValue().isPrimitive()) { 133 li.tx(opt.getValue().primitiveValue()); 134 } else if (opt.getValue() instanceof Coding) { 135 Coding c = (Coding) opt.getValue(); 136 String link = c.hasSystem() ? new ContextUtilities(context.getWorker()).getLinkForUrl(context.getLink(KnownLinkType.SPEC), c.getSystem()) : null; 137 if (link == null) { 138 li.tx(c.getSystem()+"#"+c.getCode()); 139 } else { 140 li.ah(link).tx(describeSystem(c.getSystem())); 141 li.tx(": "+c.getCode()); 142 } 143 if (c.hasDisplay()) { 144 li.tx(" (\""+c.getDisplay()+"\")"); 145 } 146 } else { 147 li.tx("??"); 148 } 149 } 150 } 151 } 152 153 private boolean hasAnyOptions(List<QuestionnaireItemComponent> items) { 154 for (QuestionnaireItemComponent i : items) { 155 if (i.hasAnswerOption()) { 156 return true; 157 } 158 if (hasAnyOptions(i.getItem())) { 159 return true; 160 } 161 } 162 return false; 163 } 164 165 private boolean checkForFlags(List<QuestionnaireItemComponent> items) { 166 for (QuestionnaireItemComponent i : items) { 167 if (checkForFlags(i)) { 168 return true; 169 } 170 } 171 return false; 172 } 173 174 private boolean checkForFlags(QuestionnaireItemComponent i) { 175 if (i.getReadOnly()) { 176 return true; 177 } 178 if (ToolingExtensions.readBoolExtension(i, ToolingExtensions.EXT_Q_IS_SUBJ)) { 179 return true; 180 } 181 if (ToolingExtensions.readBoolExtension(i, ToolingExtensions.EXT_Q_HIDDEN)) { 182 return true; 183 } 184 if (ToolingExtensions.readBoolExtension(i, ToolingExtensions.EXT_Q_OTP_DISP)) { 185 return true; 186 } 187 if (i.hasExtension(ToolingExtensions.EXT_O_LINK_PERIOD)) { 188 return true; 189 } 190 if (i.hasExtension(ToolingExtensions.EXT_Q_CHOICE_ORIENT)) { 191 return true; 192 } 193 if (i.hasExtension(ToolingExtensions.EXT_Q_DISPLAY_CAT)) { 194 return true; 195 } 196 return checkForFlags(i.getItem()); 197 } 198 199 200 201 private Row addTreeRoot(HierarchicalTableGenerator gen, List<Row> rows, Questionnaire q, boolean hasFlags) throws IOException { 202 Row r = gen.new Row(); 203 rows.add(r); 204 205 r.setIcon("icon_q_root.gif", "QuestionnaireRoot"); 206 r.getCells().add(gen.new Cell(null, null, q.getName(), null, null)); 207 r.getCells().add(gen.new Cell(null, null, q.getDescription(), null, null)); 208 r.getCells().add(gen.new Cell(null, null, "", null, null)); 209 r.getCells().add(gen.new Cell(null, null, "Questionnaire", null, null)); 210 if (hasFlags) { 211 r.getCells().add(gen.new Cell(null, null, "", null, null)); 212 } 213 r.getCells().add(gen.new Cell(null, null, q.hasUrl() ? q.hasVersion() ? q.getUrl()+"#"+q.getVersion() : q.getUrl() : "", null, null)); 214 return r; 215 } 216 217 private String getSpecLink(String path) { 218 return Utilities.pathURL(context.getLink(KnownLinkType.SPEC), path); 219 } 220 221 private String getSDCLink(String path) { 222 if (Utilities.isAbsoluteUrl(path)) { 223 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, path); 224 if (sd != null) { 225 return sd.getUserString("path"); 226 } else { 227 return path.replace("StructureDefinition/", "StructureDefinition-")+".html"; 228 } 229 } else { 230 return Utilities.pathURL("http://hl7.org/fhir/uv/sdc", path); // for now? 231 } 232 } 233 234 private boolean renderTreeItem(HierarchicalTableGenerator gen, List<Row> rows, Questionnaire q, QuestionnaireItemComponent i, boolean hasFlags) throws IOException { 235 Row r = gen.new Row(); 236 rows.add(r); 237 boolean hasExt = false; 238 239 r.setIcon("icon-q-"+i.getType().toCode().toLowerCase()+".png", i.getType().getDisplay()); 240 Cell c1 = gen.new Cell(null, context.getDefinitionsTarget() == null ? "" : context.getDefinitionsTarget()+"#item."+i.getLinkId(), i.getLinkId(), null, null); 241 c1.setId("item."+i.getLinkId()); 242 r.getCells().add(c1); 243 String txt = (i.hasPrefix() ? i.getPrefix() + ". " : "") + i.getText(); 244 r.getCells().add(gen.new Cell(null, null, txt, null, null)); 245 r.getCells().add(gen.new Cell(null, null, (i.getRequired() ? "1" : "0")+".."+(i.getRepeats() ? "*" : "1"), null, null)); 246 if (i.getTypeElement().hasExtension(EXT_QUESTIONNAIRE_ITEM_TYPE_ORIGINAL)) { 247 String t = i.getTypeElement().getExtensionString(EXT_QUESTIONNAIRE_ITEM_TYPE_ORIGINAL); 248 r.getCells().add(gen.new Cell(null, context.getLink(KnownLinkType.SPEC)+"codesystem-item-type.html#item-type-"+t, t, null, null)); 249 } else { 250 r.getCells().add(gen.new Cell(null, context.getLink(KnownLinkType.SPEC)+"codesystem-item-type.html#item-type-"+i.getType().toCode(), i.getType().toCode(), null, null)); 251 } 252 253 if (hasFlags) { 254 // flags: 255 Cell flags = gen.new Cell(); 256 r.getCells().add(flags); 257 if (i.getReadOnly()) { 258 flags.addPiece(gen.new Piece(Utilities.pathURL(context.getLink(KnownLinkType.SPEC), "questionnaire-definitions.html#Questionnaire.item.readOnly"), null, "Is Readonly").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", Utilities.path(context.getLocalPrefix(), "icon-qi-readonly.png")))); 259 } 260 if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject")) { 261 flags.addPiece(gen.new Piece(getSDCLink("StructureDefinition-sdc-questionnaire-isSubject.html"), null, "Can change the subject of the questionnaire").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", Utilities.path(context.getLocalPrefix(), "icon-qi-subject.png")))); 262 } 263 if (ToolingExtensions.readBoolExtension(i, ToolingExtensions.EXT_Q_HIDDEN)) { 264 flags.addPiece(gen.new Piece(getSpecLink("extension-questionnaire-hidden.html"), null, "Is a hidden item").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", Utilities.path(context.getLocalPrefix(), "icon-qi-hidden.png")))); 265 } 266 if (ToolingExtensions.readBoolExtension(i, ToolingExtensions.EXT_Q_OTP_DISP)) { 267 flags.addPiece(gen.new Piece(getSDCLink("StructureDefinition-sdc-questionnaire-optionalDisplay.html"), null, "Is optional to display").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", Utilities.path(context.getLocalPrefix(), "icon-qi-optional.png")))); 268 } 269 if (i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod")) { 270 flags.addPiece(gen.new Piece(getSDCLink("StructureDefinition-sdc-questionnaire-observationLinkPeriod.html"), null, "Is linked to an observation").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", Utilities.path(context.getLocalPrefix(), "icon-qi-observation.png")))); 271 } 272 if (i.hasExtension(ToolingExtensions.EXT_Q_CHOICE_ORIENT)) { 273 String code = ToolingExtensions.readStringExtension(i, ToolingExtensions.EXT_Q_CHOICE_ORIENT); 274 flags.addPiece(gen.new Piece(getSpecLink("extension-questionnaire-choiceorientation.html"), null, "Orientation: "+code).addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", Utilities.path(context.getLocalPrefix(), "icon-qi-"+code+".png")))); 275 } 276 if (i.hasExtension(ToolingExtensions.EXT_Q_DISPLAY_CAT)) { 277 CodeableConcept cc = i.getExtensionByUrl(ToolingExtensions.EXT_Q_DISPLAY_CAT).getValueCodeableConcept(); 278 String code = cc.getCode("http://hl7.org/fhir/questionnaire-display-category"); 279 flags.addPiece(gen.new Piece(getSDCLink("StructureDefinition-sdc-questionnaire-displayCategory.html"), null, "Category: "+code).addHtml(new XhtmlNode(NodeType.Element, "img").attribute("alt", "icon").attribute("src", Utilities.path(context.getLocalPrefix(), "icon-qi-"+code+".png")))); 280 } 281 } 282 Cell defn = gen.new Cell(); 283 r.getCells().add(defn); 284 285 if (i.hasMaxLength()) { 286 defn.getPieces().add(gen.new Piece(null, "Max Length: ", null)); 287 defn.getPieces().add(gen.new Piece(null, Integer.toString(i.getMaxLength()), null)); 288 } 289 if (i.hasDefinition()) { 290 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 291 defn.getPieces().add(gen.new Piece(null, "Definition: ", null)); 292 genDefinitionLink(gen, i, defn, q); 293 } 294 if (i.hasEnableWhen()) { 295 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 296 Piece p = gen.new Piece(null, "Enable When: ", null); 297 defn.getPieces().add(p); 298 if (i.getEnableWhen().size() == 1) { 299 XhtmlNode x = new XhtmlNode(NodeType.Element, "span"); 300 p.getChildren().add(x); 301 renderEnableWhen(x, i.getEnableWhenFirstRep()); 302 } else { 303 XhtmlNode x = new XhtmlNode(NodeType.Element, "ul"); 304 p.getChildren().add(x); 305 for (QuestionnaireItemEnableWhenComponent qi : i.getEnableWhen()) { 306 renderEnableWhen(x.li(), qi); 307 } 308 } 309 } 310 if (i.hasAnswerValueSet()) { 311 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 312 defn.getPieces().add(gen.new Piece(null, "Value Set: ", null)); 313 if (!Utilities.noString(i.getAnswerValueSet()) && i.getAnswerValueSet().startsWith("#")) { 314 ValueSet vs = (ValueSet) q.getContained(i.getAnswerValueSet().substring(1)); 315 if (vs == null) { 316 defn.getPieces().add(gen.new Piece(null, i.getAnswerValueSet(), null)); 317 } else { 318 defn.getPieces().add(gen.new Piece(vs.getUserString("path"), vs.present(), null)); 319 } 320 } else { 321 ValueSet vs = context.getWorker().fetchResource(ValueSet.class, i.getAnswerValueSet(), q); 322 if (vs == null || !vs.hasUserData("path")) { 323 defn.getPieces().add(gen.new Piece(null, i.getAnswerValueSet(), null)); 324 } else { 325 defn.getPieces().add(gen.new Piece(vs.getUserString("path"), vs.present(), null)); 326 } 327 } 328 } 329 if (i.hasAnswerOption()) { 330 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 331 defn.getPieces().add(gen.new Piece(null, "Options: ", null)); 332 if (context.getDefinitionsTarget() == null) { 333 // if we don't have a definitions target, we'll add them below. 334 defn.getPieces().add(gen.new Piece("#opt-item."+i.getLinkId(), Integer.toString(i.getAnswerOption().size())+" "+Utilities.pluralize("option", i.getAnswerOption().size()), null)); 335 } else { 336 defn.getPieces().add(gen.new Piece(context.getDefinitionsTarget()+"#item."+i.getLinkId(), Integer.toString(i.getAnswerOption().size())+" "+Utilities.pluralize("option", i.getAnswerOption().size()), null)); 337 } 338 } 339 if (i.hasInitial()) { 340 for (QuestionnaireItemInitialComponent v : i.getInitial()) { 341 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 342 defn.getPieces().add(gen.new Piece(null, "Initial Value: ", null)); 343 defn.getPieces().add(gen.new Piece(null, v.getValue().fhirType(), null)); 344 defn.getPieces().add(gen.new Piece(null, " = ", null)); 345 if (v.getValue().isPrimitive()) { 346 defn.getPieces().add(gen.new Piece(null, v.getValue().primitiveValue(), null)); 347 } else { 348 renderCoding(gen, defn.getPieces(), v.getValueCoding()); 349 } 350 } 351 } 352 // still todo 353 354// 355//http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-choiceColumn 356// 357//http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-width 358//http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod 359//http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl 360//http://hl7.org/fhir/StructureDefinition/questionnaire-sliderStepValue 361 362 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")) { 363 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 364 defn.getPieces().add(gen.new Piece(null, "Expressions: ", null)); 365 Piece p = gen.new Piece("ul"); 366 defn.getPieces().add(p); 367 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression")) { 368 addExpression(p, e.getValueExpression(), "Initial Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression"); 369 } 370 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression")) { 371 addExpression(p, e.getValueExpression(), "Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression"); 372 } 373 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext")) { 374 addExpression(p, e.getValueExpression(), "Item Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext"); 375 } 376 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression")) { 377 addExpression(p, e.getValueExpression(), "Enable When", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression"); 378 } 379 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression")) { 380 addExpression(p, e.getValueExpression(), "Calculated Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression"); 381 } 382 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression")) { 383 addExpression(p, e.getValueExpression(), "Candidates", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression"); 384 } 385 } 386 387 for (QuestionnaireItemComponent c : i.getItem()) { 388 hasExt = renderTreeItem(gen, r.getSubRows(), q, c, hasFlags) || hasExt; 389 } 390 return hasExt; 391 } 392 393 public void genDefinitionLink(HierarchicalTableGenerator gen, QuestionnaireItemComponent i, Cell defn, Questionnaire q) { 394 // can we resolve the definition? 395 String path = null; 396 String d = i.getDefinition(); 397 if (d.contains("#")) { 398 path = d.substring(d.indexOf("#")+1); 399 d = d.substring(0, d.indexOf("#")); 400 } 401 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, d, q); 402 if (sd != null) { 403 String url = sd.getUserString("path"); 404 if (url != null) { 405 defn.getPieces().add(gen.new Piece(url+"#"+path, path, null)); 406 } else { 407 defn.getPieces().add(gen.new Piece(null, i.getDefinition(), null)); 408 } 409 } else { 410 defn.getPieces().add(gen.new Piece(null, i.getDefinition(), null)); 411 } 412 } 413 414 public void genDefinitionLink(XhtmlNode x, QuestionnaireItemComponent i, Questionnaire q) { 415 // can we resolve the definition? 416 String path = null; 417 String d = i.getDefinition(); 418 if (d.contains("#")) { 419 path = d.substring(d.indexOf("#")+1); 420 d = d.substring(0, d.indexOf("#")); 421 } 422 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, d, q); 423 if (sd != null) { 424 String url = sd.getUserString("path"); 425 if (url != null) { 426 x.ah(url+"#"+path).tx(path); 427 } else { 428 x.tx(i.getDefinition()); 429 } 430 } else { 431 x.tx(i.getDefinition()); 432 } 433 } 434 435 private void addExpression(Piece p, Expression exp, String label, String url) { 436 XhtmlNode x = new XhtmlNode(NodeType.Element, "li").style("font-size: 11px"); 437 p.addHtml(x); 438 x.ah(url).tx(label); 439 x.tx(": "); 440 x.code(exp.getExpression()); 441 } 442 443 private boolean renderLogic(XhtmlNode x, Questionnaire q) throws FHIRException, IOException { 444 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context.getDestDir(), context.isInlineGraphics(), true); 445 TableModel model = gen.new TableModel("qtree="+q.getId(), true); 446 model.setAlternating(true); 447 model.setDocoImg(context.getLink(KnownLinkType.SPEC) +"help16.png"); 448 model.setDocoRef(context.getLink(KnownLinkType.SPEC)+"formats.html#table"); 449 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "LinkId"), translate("sd.hint", "The linkId for the item"), null, 0)); 450 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Description & Constraints"), translate("sd.hint", "Additional information about the item"), null, 0)); 451 452 boolean hasExt = false; 453 if (!q.hasItem()) { 454 gen.emptyRow(model, 2); 455 } else { 456 for (QuestionnaireItemComponent i : q.getItem()) { 457 hasExt = renderLogicItem(gen, model.getRows(), q, i) || hasExt; 458 } 459 } 460 XhtmlNode xn = gen.generate(model, context.getLocalPrefix(), 1, null); 461 x.getChildNodes().add(xn); 462 return hasExt; 463 } 464 465 private boolean renderLogicItem(HierarchicalTableGenerator gen, List<Row> rows, Questionnaire q, QuestionnaireItemComponent i) throws IOException { 466 Row r = gen.new Row(); 467 rows.add(r); 468 boolean hasExt = false; 469 470 r.setIcon("icon-q-"+i.getType().toCode().toLowerCase()+".png", i.getType().getDisplay()); 471 r.getCells().add(gen.new Cell(null, context.getDefinitionsTarget() == null ? "" : context.getDefinitionsTarget()+"#item."+i.getLinkId(), i.getLinkId(), null, null)); 472 Cell defn = gen.new Cell(); 473 r.getCells().add(defn); 474 475 if (i.hasMaxLength()) { 476 defn.getPieces().add(gen.new Piece(null, "Max Length: ", null)); 477 defn.getPieces().add(gen.new Piece(null, Integer.toString(i.getMaxLength()), null)); 478 } 479 if (i.hasDefinition()) { 480 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 481 defn.getPieces().add(gen.new Piece(null, "Definition: ", null)); 482 genDefinitionLink(gen, i, defn, q); 483 } 484 if (i.hasEnableWhen()) { 485 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 486 defn.getPieces().add(gen.new Piece(null, "Enable When: ", null)); 487 defn.getPieces().add(gen.new Piece(null, "todo", null)); 488 } 489 if (i.hasAnswerValueSet()) { 490 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 491 defn.getPieces().add(gen.new Piece(null, "Value Set: ", null)); 492 if (Utilities.noString(i.getAnswerValueSet()) && i.getAnswerValueSet().startsWith("#")) { 493 ValueSet vs = (ValueSet) q.getContained(i.getAnswerValueSet().substring(1)); 494 if (vs == null) { 495 defn.getPieces().add(gen.new Piece(null, i.getAnswerValueSet(), null)); 496 } else { 497 defn.getPieces().add(gen.new Piece(vs.getUserString("path"), vs.present(), null)); 498 } 499 } else { 500 ValueSet vs = context.getWorker().fetchResource(ValueSet.class, i.getAnswerValueSet(), q); 501 if (vs == null || !vs.hasUserData("path")) { 502 defn.getPieces().add(gen.new Piece(null, i.getAnswerValueSet(), null)); 503 } else { 504 defn.getPieces().add(gen.new Piece(vs.getUserString("path"), vs.present(), null)); 505 } 506 } 507 } 508 if (i.hasAnswerOption()) { 509 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 510 defn.getPieces().add(gen.new Piece(null, "Options: ", null)); 511 defn.getPieces().add(gen.new Piece(context.getDefinitionsTarget()+"#item."+i.getLinkId(), Integer.toString(i.getAnswerOption().size())+" "+Utilities.pluralize("option", i.getAnswerOption().size()), null)); 512 } 513 if (i.hasInitial()) { 514 for (QuestionnaireItemInitialComponent v : i.getInitial()) { 515 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 516 defn.getPieces().add(gen.new Piece(null, "Initial Value: ", null)); 517 defn.getPieces().add(gen.new Piece(null, v.getValue().fhirType(), null)); 518 defn.getPieces().add(gen.new Piece(null, " = ", null)); 519 if (v.getValue().isPrimitive()) { 520 defn.getPieces().add(gen.new Piece(null, v.getValue().primitiveValue(), null)); 521 } else { 522 renderCoding(gen, defn.getPieces(), v.getValueCoding()); 523 } 524 } 525 } 526 // still todo 527 528// 529//http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-choiceColumn 530// 531//http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-width 532//http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod 533//http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl 534//http://hl7.org/fhir/StructureDefinition/questionnaire-sliderStepValue 535 536 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")) { 537 if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 538 defn.getPieces().add(gen.new Piece(null, "Expressions: ", null)); 539 Piece p = gen.new Piece("ul"); 540 defn.getPieces().add(p); 541 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression")) { 542 addExpression(p, e.getValueExpression(), "Initial Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression"); 543 } 544 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression")) { 545 addExpression(p, e.getValueExpression(), "Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression"); 546 } 547 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext")) { 548 addExpression(p, e.getValueExpression(), "Item Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext"); 549 } 550 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression")) { 551 addExpression(p, e.getValueExpression(), "Enable When", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression"); 552 } 553 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression")) { 554 addExpression(p, e.getValueExpression(), "Calculated Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression"); 555 } 556 for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression")) { 557 addExpression(p, e.getValueExpression(), "Candidates", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression"); 558 } 559 } 560 561 for (QuestionnaireItemComponent c : i.getItem()) { 562 hasExt = renderLogicItem(gen, r.getSubRows(), q, c) || hasExt; 563 } 564 return hasExt; 565 566 } 567 568 569 public boolean renderForm(XhtmlNode x, Questionnaire q) throws UnsupportedEncodingException, IOException { 570 boolean hasExt = false; 571 XhtmlNode d = x.div(); 572 boolean hasPrefix = false; 573 for (QuestionnaireItemComponent c : q.getItem()) { 574 hasPrefix = hasPrefix || doesItemHavePrefix(c); 575 } 576 int i = 1; 577 for (QuestionnaireItemComponent c : q.getItem()) { 578 hasExt = renderFormItem(d, q, c, hasPrefix ? null : Integer.toString(i), 0) || hasExt; 579 i++; 580 } 581 return hasExt; 582 } 583 584 private boolean doesItemHavePrefix(QuestionnaireItemComponent i) { 585 if (i.hasPrefix()) { 586 return true; 587 } 588 for (QuestionnaireItemComponent c : i.getItem()) { 589 if (doesItemHavePrefix(c)) { 590 return true; 591 } 592 } 593 return false; 594 } 595 596 private boolean renderFormItem(XhtmlNode x, Questionnaire q, QuestionnaireItemComponent i, String pfx, int indent) throws IOException { 597 boolean hasExt = false; 598 XhtmlNode d = x.div().style("width: "+Integer.toString(900-indent*10)+"px; border-top: 1px #eeeeee solid"); 599 if (indent > 0) { 600 d.style("margin-left: "+Integer.toString(10*indent)+"px"); 601 } 602 XhtmlNode display = d.div().style("display: inline-block; width: "+Integer.toString(500-indent*10)+"px"); 603 XhtmlNode details = d.div().style("border: 1px #ccccff solid; padding: 2px; display: inline-block; background-color: #fefce7; width: 380px"); 604 XhtmlNode p = display.para(); 605 if (i.getType() == QuestionnaireItemType.GROUP) { 606 p = p.b(); 607 } 608 if (i.hasPrefix()) { 609 p.tx(i.getPrefix()); 610 p.tx(": "); 611 } 612 p.span(null, "linkId: "+i.getLinkId()).tx(i.getText()); 613 if (i.getRequired()) { 614 p.span("color: red", "Mandatory").tx("*"); 615 } 616 617 XhtmlNode input = null; 618 switch (i.getType()) { 619 case STRING: 620 p.tx(" "); 621 input = p.input(i.getLinkId(), "text", i.getType().getDisplay(), 60); 622 break; 623 case ATTACHMENT: 624 break; 625 case BOOLEAN: 626 p.tx(" "); 627 input = p.input(i.getLinkId(), "checkbox", i.getType().getDisplay(), 1); 628 break; 629 case CODING: 630 input = p.select(i.getLinkId()); 631 listOptions(q, i, input); 632 break; 633 case DATE: 634 p.tx(" "); 635 input = p.input(i.getLinkId(), "date", i.getType().getDisplay(), 10); 636 break; 637 case DATETIME: 638 p.tx(" "); 639 input = p.input(i.getLinkId(), "datetime-local", i.getType().getDisplay(), 25); 640 break; 641 case DECIMAL: 642 p.tx(" "); 643 input = p.input(i.getLinkId(), "number", i.getType().getDisplay(), 15); 644 break; 645 case DISPLAY: 646 break; 647 case GROUP: 648 649 break; 650 case INTEGER: 651 p.tx(" "); 652 input = p.input(i.getLinkId(), "number", i.getType().getDisplay(), 10); 653 break; 654 case QUANTITY: 655 p.tx(" "); 656 input = p.input(i.getLinkId(), "number", "value", 15); 657 p.tx(" "); 658 input = p.input(i.getLinkId(), "unit", "unit", 10); 659 break; 660 case QUESTION: 661 break; 662 case REFERENCE: 663 break; 664 case TEXT: 665 break; 666 case TIME: 667 break; 668 case URL: 669 break; 670 default: 671 break; 672 } 673 if (input != null) { 674 if (i.getReadOnly()) { 675 input.attribute("readonly", "1"); 676 input.style("background-color: #eeeeee"); 677 } 678 } 679 680// if (i.hasExtension(ToolingExtensions.EXT_Q_CHOICE_ORIENT)) { 681// String code = ToolingExtensions.readStringExtension(i, ToolingExtensions.EXT_Q_CHOICE_ORIENT); 682// 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")))); 683//} 684 685 686 XhtmlNode ul = details.ul(); 687 boolean hasFlag = false; 688 XhtmlNode flags = item(ul, "Flags"); 689 item(ul, "linkId", i.getLinkId()); 690 691 if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject")) { 692 hasFlag = true; 693 flags.ah(getSDCLink("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject"), "Can change the subject of the questionnaire").img(Utilities.path(context.getLocalPrefix(), "icon-qi-subject.png"), "icon"); 694 } 695 if (ToolingExtensions.readBoolExtension(i, ToolingExtensions.EXT_Q_HIDDEN)) { 696 hasFlag = true; 697 flags.ah(Utilities.pathURL(context.getLink(KnownLinkType.SPEC), "extension-questionnaire-hidden.html"), "Is a hidden item").img(Utilities.path(context.getLocalPrefix(), "icon-qi-hidden.png"), "icon"); 698 d.style("background-color: #eeeeee"); 699 } 700 if (ToolingExtensions.readBoolExtension(i, ToolingExtensions.EXT_Q_OTP_DISP)) { 701 hasFlag = true; 702 flags.ah(getSDCLink(ToolingExtensions.EXT_Q_OTP_DISP), "Is optional to display").img(Utilities.path(context.getLocalPrefix(), "icon-qi-optional.png"), "icon"); 703 } 704 if (i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod")) { 705 hasFlag = true; 706 flags.ah(getSDCLink("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod"), "Is linked to an observation").img(Utilities.path(context.getLocalPrefix(), "icon-qi-observation.png"), "icon"); 707 } 708 if (i.hasExtension(ToolingExtensions.EXT_Q_DISPLAY_CAT)) { 709 CodeableConcept cc = i.getExtensionByUrl(ToolingExtensions.EXT_Q_DISPLAY_CAT).getValueCodeableConcept(); 710 String code = cc.getCode("http://hl7.org/fhir/questionnaire-display-category"); 711 hasFlag = true; 712 flags.ah(getSDCLink("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-displayCategory"), "Category: "+code).img(Utilities.path(context.getLocalPrefix(), "icon-qi-"+code+".png"), "icon"); 713 } 714 715 if (i.hasMaxLength()) { 716 item(ul, "Max Length", Integer.toString(i.getMaxLength())); 717 } 718 if (i.hasDefinition()) { 719 genDefinitionLink(item(ul, "Definition"), i, q); 720 } 721 if (i.hasEnableWhen()) { 722 item(ul, "Enable When", "todo"); 723 } 724 if (i.hasAnswerValueSet()) { 725 XhtmlNode ans = item(ul, "Answers"); 726 if (!Utilities.noString(i.getAnswerValueSet()) && i.getAnswerValueSet().startsWith("#")) { 727 ValueSet vs = (ValueSet) q.getContained(i.getAnswerValueSet().substring(1)); 728 if (vs == null || !vs.hasUserData("path")) { 729 ans.tx(i.getAnswerValueSet()); 730 } else { 731 ans.ah(vs.getUserString("path")).tx(vs.present()); 732 } 733 } else { 734 ValueSet vs = context.getWorker().fetchResource(ValueSet.class, i.getAnswerValueSet(), q); 735 if (vs == null || !vs.hasUserData("path")) { 736 ans.tx(i.getAnswerValueSet()); 737 } else { 738 ans.ah(vs.getUserString("path")).tx(vs.present()); 739 } 740 } 741 } 742 if (i.hasAnswerOption()) { 743 item(ul, "Answers", Integer.toString(i.getAnswerOption().size())+" "+Utilities.pluralize("option", i.getAnswerOption().size()), context.getDefinitionsTarget()+"#item."+i.getLinkId()); 744 } 745 if (i.hasInitial()) { 746 XhtmlNode vi = item(ul, "Initial Values"); 747 boolean first = true; 748 for (QuestionnaireItemInitialComponent v : i.getInitial()) { 749 if (first) first = false; else vi.tx(", "); 750 if (v.getValue().isPrimitive()) { 751 vi.tx(v.getValue().primitiveValue()); 752 } else { 753 renderCoding(vi, v.getValueCoding(), true); 754 } 755 } 756 } 757 if (!hasFlag) { 758 ul.remove(flags); 759 } 760// 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")) { 761// if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 762// defn.getPieces().add(gen.new Piece(null, "Expressions: ", null)); 763// Piece p = gen.new Piece("ul"); 764// defn.getPieces().add(p); 765// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression")) { 766// addExpression(p, e.getValueExpression(), "Initial Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression"); 767// } 768// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression")) { 769// addExpression(p, e.getValueExpression(), "Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression"); 770// } 771// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext")) { 772// addExpression(p, e.getValueExpression(), "Item Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext"); 773// } 774// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression")) { 775// addExpression(p, e.getValueExpression(), "Enable When", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression"); 776// } 777// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression")) { 778// addExpression(p, e.getValueExpression(), "Calculated Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression"); 779// } 780// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression")) { 781// addExpression(p, e.getValueExpression(), "Candidates", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression"); 782// } 783// } 784// 785 786 int t = 1; 787 for (QuestionnaireItemComponent c : i.getItem()) { 788 hasExt = renderFormItem(x, q, c, pfx == null ? null : pfx+"."+Integer.toString(t), indent+1) || hasExt; 789 t++; 790 } 791 return hasExt; 792 } 793 794 private void item(XhtmlNode ul, String name, String value, String valueLink) { 795 if (!Utilities.noString(value)) { 796 ul.li().style("font-size: 10px").ah(valueLink).tx(name+": "+value); 797 } 798 } 799 800 private void item(XhtmlNode ul, String name, String value) { 801 if (!Utilities.noString(value)) { 802 ul.li().style("font-size: 10px").tx(name+": "+value); 803 } 804 } 805 private XhtmlNode item(XhtmlNode ul, String name) { 806 XhtmlNode li = ul.li(); 807 li.style("font-size: 10px").tx(name+": "); 808 return li; 809 } 810 811 812 private void listOptions(Questionnaire q, QuestionnaireItemComponent i, XhtmlNode select) { 813 if (i.hasAnswerValueSet()) { 814 ValueSet vs = null; 815 if (!Utilities.noString(i.getAnswerValueSet()) && i.getAnswerValueSet().startsWith("#")) { 816 vs = (ValueSet) q.getContained(i.getAnswerValueSet().substring(1)); 817 if (vs != null && !vs.hasUrl()) { 818 vs = vs.copy(); 819 vs.setUrl(q.getUrl()+"--"+q.getContained(i.getAnswerValueSet().substring(1))); 820 } 821 } else { 822 vs = context.getContext().fetchResource(ValueSet.class, i.getAnswerValueSet(), q); 823 } 824 if (vs != null) { 825 ValueSetExpansionOutcome exp = context.getContext().expandVS(vs, true, false); 826 if (exp.getValueset() != null) { 827 for (ValueSetExpansionContainsComponent cc : exp.getValueset().getExpansion().getContains()) { 828 select.option(cc.getCode(), cc.hasDisplay() ? cc.getDisplay() : cc.getCode(), false); 829 } 830 return; 831 } 832 } 833 } else if (i.hasAnswerOption()) { 834 renderItemOptions(select, i); 835 } 836 select.option("a", "??", false); 837 } 838 839 public String display(Resource dr) throws UnsupportedEncodingException, IOException { 840 return display((Questionnaire) dr); 841 } 842 843 public String display(Questionnaire q) throws UnsupportedEncodingException, IOException { 844 return "Questionnaire "+q.present(); 845 } 846 847 private boolean renderLinks(XhtmlNode x, Questionnaire q) { 848 x.para().tx("Try this questionnaire out:"); 849 XhtmlNode ul = x.ul(); 850 ul.li().ah("http://todo.nlm.gov/path?mode=ig&src="+Utilities.pathURL(context.getLink(KnownLinkType.SELF), "package.tgz")+"&q="+q.getId()+".json").tx("NLM Forms Library"); 851 return false; 852 } 853 854 private boolean renderDefns(XhtmlNode x, Questionnaire q) throws IOException { 855 XhtmlNode tbl = x.table("dict"); 856 boolean ext = false; 857 ext = renderRootDefinition(tbl, q, new ArrayList<>()) || ext; 858 for (QuestionnaireItemComponent qi : q.getItem()) { 859 ext = renderDefinition(tbl, q, qi, new ArrayList<>()) || ext; 860 } 861 return ext; 862 } 863 864 private boolean renderRootDefinition(XhtmlNode tbl, Questionnaire q, List<QuestionnaireItemComponent> parents) throws IOException { 865 boolean ext = false; 866 XhtmlNode td = tbl.tr().td("structure").colspan("2").span(null, null).attribute("class", "self-link-parent"); 867 td.an(q.getId()); 868 td.img(Utilities.path(context.getLocalPrefix(), "icon_q_root.gif"), "icon"); 869 td.tx(" Questionnaire "); 870 td.b().tx(q.getId()); 871 872 // general information 873 defn(tbl, "URL", q.getUrl()); 874 defn(tbl, "Version", q.getVersion()); 875 defn(tbl, "Name", q.getName()); 876 defn(tbl, "Title", q.getTitle()); 877 if (q.hasDerivedFrom()) { 878 td = defn(tbl, "Derived From"); 879 boolean first = true; 880 for (CanonicalType c : q.getDerivedFrom()) { 881 if (first) first = false; else td.tx(", "); 882 td.tx(c.asStringValue()); // todo: make these a reference 883 } 884 } 885 defn(tbl, "Status", q.getStatus().getDisplay()); 886 defn(tbl, "Experimental", q.getExperimental()); 887 defn(tbl, "Publication Date", q.getDateElement().primitiveValue()); 888 defn(tbl, "Approval Date", q.getApprovalDateElement().primitiveValue()); 889 defn(tbl, "Last Review Date", q.getLastReviewDateElement().primitiveValue()); 890 if (q.hasEffectivePeriod()) { 891 renderPeriod(defn(tbl, "Effective Period"), q.getEffectivePeriod()); 892 } 893 894 if (q.hasSubjectType()) { 895 td = defn(tbl, "Subject Type"); 896 boolean first = true; 897 for (CodeType c : q.getSubjectType()) { 898 if (first) first = false; else td.tx(", "); 899 td.tx(c.asStringValue()); 900 } 901 } 902 defn(tbl, "Description", q.getDescription()); 903 defn(tbl, "Purpose", q.getPurpose()); 904 defn(tbl, "Copyright", q.getCopyright()); 905 if (q.hasCode()) { 906 td = defn(tbl, Utilities.pluralize("Code", q.getCode().size())); 907 boolean first = true; 908 for (Coding c : q.getCode()) { 909 if (first) first = false; else td.tx(", "); 910 renderCodingWithDetails(td, c); 911 } 912 } 913 return false; 914 } 915 916 private boolean renderDefinition(XhtmlNode tbl, Questionnaire q, QuestionnaireItemComponent qi, List<QuestionnaireItemComponent> parents) throws IOException { 917 boolean ext = false; 918 XhtmlNode td = tbl.tr().td("structure").colspan("2").span(null, null).attribute("class", "self-link-parent"); 919 td.an("item."+qi.getLinkId()); 920 for (QuestionnaireItemComponent p : parents) { 921 td.ah("#item."+p.getLinkId()).img(Utilities.path(context.getLocalPrefix(), "icon_q_item.png"), "icon"); 922 td.tx(" > "); 923 } 924 td.img(Utilities.path(context.getLocalPrefix(), "icon_q_item.png"), "icon"); 925 td.tx(" Item "); 926 td.b().tx(qi.getLinkId()); 927 928 // general information 929 defn(tbl, "Link Id", qi.getLinkId()); 930 defn(tbl, "Prefix", qi.getPrefix()); 931 defn(tbl, "Text", qi.getText()); 932 defn(tbl, "Type", qi.getType().getDisplay()); 933 defn(tbl, "Required", qi.getRequired(), true); 934 defn(tbl, "Repeats", qi.getRepeats(), true); 935 defn(tbl, "Read Only", qi.getReadOnly(), false); 936 if (ToolingExtensions.readBoolExtension(qi, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject")) { 937 defn(tbl, "Subject", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject", "This element changes who the subject of the question is", null); 938 } 939 940 // content control 941 defn(tbl, "Max Length", qi.getMaxLength()); 942 if (qi.hasAnswerValueSet()) { 943 defn(tbl, "Value Set", qi.getDefinition(), context.getWorker().fetchResource(ValueSet.class, qi.getAnswerValueSet(), q)); 944 } 945 if (qi.hasAnswerOption()) { 946 XhtmlNode tr = tbl.tr(); 947 tr.td().tx("Allowed Answers"); 948 XhtmlNode ul = tr.td().ul(); 949 for (QuestionnaireItemAnswerOptionComponent ans : qi.getAnswerOption()) { 950 XhtmlNode li = ul.li(); 951 render(li, ans.getValue()); 952 if (ans.getInitialSelected()) { 953 li.tx(" (initially selected)"); 954 } 955 } 956 } 957 if (qi.hasInitial()) { 958 XhtmlNode tr = tbl.tr(); 959 tr.td().tx(Utilities.pluralize("Initial Answer", qi.getInitial().size())); 960 if (qi.getInitial().size() == 1) { 961 render(tr.td(), qi.getInitialFirstRep().getValue()); 962 } else { 963 XhtmlNode ul = tr.td().ul(); 964 for (QuestionnaireItemInitialComponent ans : qi.getInitial()) { 965 XhtmlNode li = ul.li(); 966 render(li, ans.getValue()); 967 } 968 } 969 } 970 971 // appearance 972 if (qi.hasExtension(ToolingExtensions.EXT_Q_DISPLAY_CAT)) { 973 XhtmlNode tr = tbl.tr(); 974 tr.td().ah(ToolingExtensions.EXT_Q_DISPLAY_CAT).tx("Display Category"); 975 render(tr.td(), qi.getExtensionByUrl(ToolingExtensions.EXT_Q_DISPLAY_CAT).getValue()); 976 } 977 if (ToolingExtensions.readBoolExtension(qi, ToolingExtensions.EXT_Q_HIDDEN)) { 978 defn(tbl, "Hidden Item", ToolingExtensions.EXT_Q_DISPLAY_CAT, "This item is a hidden question", null); 979 } 980 if (ToolingExtensions.readBoolExtension(qi, ToolingExtensions.EXT_Q_OTP_DISP)) { 981 defn(tbl, "Hidden Item", ToolingExtensions.EXT_Q_OTP_DISP, "This item is optional to display", null); 982 } 983 984 // formal definitions 985 if (qi.hasDefinition()) { 986 genDefinitionLink(defn(tbl, "Definition"), qi, q); 987 } 988 989 if (qi.hasCode()) { 990 XhtmlNode tr = tbl.tr(); 991 tr.td().tx(Utilities.pluralize("Code", qi.getCode().size())); 992 XhtmlNode ul = tr.td().ul(); 993 for (Coding c : qi.getCode()) { 994 renderCodingWithDetails(ul.li(), c); 995 } 996 } 997 if (qi.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod")) { 998 XhtmlNode tr = tbl.tr(); 999 tr.td().ah("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod").tx("Observation Link Period"); 1000 render(tr.td(), qi.getExtensionByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod").getValue()); 1001 } 1002 1003 // dynamic management 1004 if (qi.hasEnableWhen()) { 1005 XhtmlNode tr = tbl.tr(); 1006 tr.td().tx("Enable When"); 1007 td = tr.td(); 1008 if (qi.getEnableWhen().size() == 1) { 1009 renderEnableWhen(td, qi.getEnableWhen().get(0)); 1010 } else { 1011 td.tx(qi.getEnableBehavior().getDisplay()+" are true:"); 1012 XhtmlNode ul = td.ul(); 1013 for (QuestionnaireItemEnableWhenComponent ew : qi.getEnableWhen()) { 1014 renderEnableWhen(ul.li(), ew); 1015 } 1016 } 1017 } 1018 1019 1020 // other stuff 1021 1022 1023 1024 List<QuestionnaireItemComponent> curr = new ArrayList<>(); 1025 curr.addAll(parents); 1026 curr.add(qi); 1027 for (QuestionnaireItemComponent qic : qi.getItem()) { 1028 ext = renderDefinition(tbl, q, qic, curr) || ext; 1029 } 1030 return ext; 1031 } 1032 1033 private void defn(XhtmlNode tbl, String name, String url, Resource res) throws UnsupportedEncodingException, IOException { 1034 if (res != null && res.hasUserData("path")) { 1035 defn(tbl, "Definition", RendererFactory.factory(res, context).display(res), res.getUserString("path")); 1036 } else if (Utilities.isAbsoluteUrlLinkable(url)) { 1037 defn(tbl, "Definition", url, url); 1038 } { 1039 defn(tbl, "Definition", url); 1040 } 1041 1042 } 1043 1044 private void renderEnableWhen(XhtmlNode x, QuestionnaireItemEnableWhenComponent ew) { 1045 x.ah("#item."+ew.getQuestion()).tx(ew.getQuestion()); 1046 x.tx(" "); 1047 x.tx(ew.getOperator().toCode()); 1048 x.tx(" "); 1049 x.tx(display(ew.getAnswer())); 1050 } 1051 1052 private XhtmlNode defn(XhtmlNode tbl, String name) { 1053 XhtmlNode tr = tbl.tr(); 1054 tr.td().tx(name); 1055 return tr.td(); 1056 } 1057 1058 private void defn(XhtmlNode tbl, String name, int value) { 1059 if (value > 0) { 1060 XhtmlNode tr = tbl.tr(); 1061 tr.td().tx(name); 1062 tr.td().tx(value); 1063 } 1064 } 1065 1066 1067 private void defn(XhtmlNode tbl, String name, boolean value) { 1068 XhtmlNode tr = tbl.tr(); 1069 tr.td().tx(name); 1070 tr.td().tx(Boolean.toString(value)); 1071 } 1072 1073 private void defn(XhtmlNode tbl, String name, String value) { 1074 if (!Utilities.noString(value)) { 1075 XhtmlNode tr = tbl.tr(); 1076 tr.td().tx(name); 1077 tr.td().tx(value); 1078 } 1079 } 1080 1081 private void defn(XhtmlNode tbl, String name, String value, String url) { 1082 if (!Utilities.noString(value)) { 1083 XhtmlNode tr = tbl.tr(); 1084 tr.td().tx(name); 1085 tr.td().ah(url).tx(value); 1086 } 1087 } 1088 1089 private void defn(XhtmlNode tbl, String name, String nurl, String value, String url) { 1090 if (!Utilities.noString(value)) { 1091 XhtmlNode tr = tbl.tr(); 1092 tr.td().ah(nurl).tx(name); 1093 if (url != null) { 1094 tr.td().ah(url).tx(value); 1095 } else { 1096 tr.td().tx(value); 1097 } 1098 } 1099 } 1100 1101 private void defn(XhtmlNode tbl, String name, boolean value, boolean ifFalse) { 1102 if (ifFalse || value) { 1103 XhtmlNode tr = tbl.tr(); 1104 tr.td().tx(name); 1105 tr.td().tx(Boolean.toString(value)); 1106 } 1107 } 1108 1109}