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