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