001package org.hl7.fhir.r4b.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.r4b.model.CanonicalType; 011import org.hl7.fhir.r4b.model.CodeType; 012import org.hl7.fhir.r4b.model.CodeableConcept; 013import org.hl7.fhir.r4b.model.Coding; 014import org.hl7.fhir.r4b.model.DomainResource; 015import org.hl7.fhir.r4b.model.Expression; 016import org.hl7.fhir.r4b.model.Extension; 017import org.hl7.fhir.r4b.model.PrimitiveType; 018import org.hl7.fhir.r4b.model.Questionnaire; 019import org.hl7.fhir.r4b.model.ValueSet; 020import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent; 021import org.hl7.fhir.r4b.model.Questionnaire.QuestionnaireItemAnswerOptionComponent; 022import org.hl7.fhir.r4b.model.Questionnaire.QuestionnaireItemComponent; 023import org.hl7.fhir.r4b.model.Questionnaire.QuestionnaireItemEnableWhenComponent; 024import org.hl7.fhir.r4b.model.Questionnaire.QuestionnaireItemInitialComponent; 025import org.hl7.fhir.r4b.model.Questionnaire.QuestionnaireItemType; 026import org.hl7.fhir.r4b.model.Resource; 027import org.hl7.fhir.r4b.model.StructureDefinition; 028import org.hl7.fhir.r4b.renderers.utils.RenderingContext; 029import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 030import org.hl7.fhir.r4b.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 CHOICE: 617 case OPENCHOICE: 618 input = p.select(i.getLinkId()); 619 listOptions(q, i, input); 620 break; 621 case DATE: 622 p.tx(" "); 623 input = p.input(i.getLinkId(), "date", i.getType().getDisplay(), 10); 624 break; 625 case DATETIME: 626 p.tx(" "); 627 input = p.input(i.getLinkId(), "datetime-local", i.getType().getDisplay(), 25); 628 break; 629 case DECIMAL: 630 p.tx(" "); 631 input = p.input(i.getLinkId(), "number", i.getType().getDisplay(), 15); 632 break; 633 case DISPLAY: 634 break; 635 case GROUP: 636 637 break; 638 case INTEGER: 639 p.tx(" "); 640 input = p.input(i.getLinkId(), "number", i.getType().getDisplay(), 10); 641 break; 642 case QUANTITY: 643 p.tx(" "); 644 input = p.input(i.getLinkId(), "number", "value", 15); 645 p.tx(" "); 646 input = p.input(i.getLinkId(), "unit", "unit", 10); 647 break; 648 case QUESTION: 649 break; 650 case REFERENCE: 651 break; 652 case TEXT: 653 break; 654 case TIME: 655 break; 656 case URL: 657 break; 658 default: 659 break; 660 } 661 if (input != null) { 662 if (i.getReadOnly()) { 663 input.attribute("readonly", "1"); 664 input.style("background-color: #eeeeee"); 665 } 666 } 667 668// if (i.hasExtension("http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation")) { 669// String code = ToolingExtensions.readStringExtension(i, "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation"); 670// 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")))); 671//} 672 673 674 XhtmlNode ul = details.ul(); 675 boolean hasFlag = false; 676 XhtmlNode flags = item(ul, "Flags"); 677 item(ul, "linkId", i.getLinkId()); 678 679 if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject")) { 680 hasFlag = true; 681 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")); 682 } 683 if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden")) { 684 hasFlag = true; 685 flags.ah(Utilities.pathURL(context.getSpecificationLink(), "extension-questionnaire-hidden.html"), "Is a hidden item").img(Utilities.path(context.getLocalPrefix(), "icon-qi-hidden.png")); 686 d.style("background-color: #eeeeee"); 687 } 688 if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-optionalDisplay")) { 689 hasFlag = true; 690 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")); 691 } 692 if (i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod")) { 693 hasFlag = true; 694 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")); 695 } 696 if (i.hasExtension("http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory")) { 697 CodeableConcept cc = i.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory").getValueCodeableConcept(); 698 String code = cc.getCode("http://hl7.org/fhir/questionnaire-display-category"); 699 hasFlag = true; 700 flags.ah("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-displayCategory", "Category: "+code).img(Utilities.path(context.getLocalPrefix(), "icon-qi-"+code+".png")); 701 } 702 703 if (i.hasMaxLength()) { 704 item(ul, "Max Length", Integer.toString(i.getMaxLength())); 705 } 706 if (i.hasDefinition()) { 707 genDefinitionLink(item(ul, "Definition"), i); 708 } 709 if (i.hasEnableWhen()) { 710 item(ul, "Enable When", "todo"); 711 } 712 if (i.hasAnswerValueSet()) { 713 XhtmlNode ans = item(ul, "Answers"); 714 if (!Utilities.noString(i.getAnswerValueSet()) && i.getAnswerValueSet().startsWith("#")) { 715 ValueSet vs = (ValueSet) q.getContained(i.getAnswerValueSet().substring(1)); 716 if (vs == null) { 717 ans.tx(i.getAnswerValueSet()); 718 } else { 719 ans.ah(vs.getUserString("path")).tx(vs.present()); 720 } 721 } else { 722 ValueSet vs = context.getWorker().fetchResource(ValueSet.class, i.getAnswerValueSet()); 723 if (vs == null || !vs.hasUserData("path")) { 724 ans.tx(i.getAnswerValueSet()); 725 } else { 726 ans.ah(vs.getUserString("path")).tx(vs.present()); 727 } 728 } 729 } 730 if (i.hasAnswerOption()) { 731 item(ul, "Answers", Integer.toString(i.getAnswerOption().size())+" "+Utilities.pluralize("option", i.getAnswerOption().size()), context.getDefinitionsTarget()+"#item."+i.getLinkId()); 732 } 733 if (i.hasInitial()) { 734 XhtmlNode vi = item(ul, "Initial Values"); 735 boolean first = true; 736 for (QuestionnaireItemInitialComponent v : i.getInitial()) { 737 if (first) first = false; else vi.tx(", "); 738 if (v.getValue().isPrimitive()) { 739 vi.tx(v.getValue().primitiveValue()); 740 } else { 741 renderCoding(vi, v.getValueCoding(), true); 742 } 743 } 744 } 745 if (!hasFlag) { 746 ul.remove(flags); 747 } 748// 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")) { 749// if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); 750// defn.getPieces().add(gen.new Piece(null, "Expressions: ", null)); 751// Piece p = gen.new Piece("ul"); 752// defn.getPieces().add(p); 753// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression")) { 754// addExpression(p, e.getValueExpression(), "Initial Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression"); 755// } 756// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression")) { 757// addExpression(p, e.getValueExpression(), "Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression"); 758// } 759// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext")) { 760// addExpression(p, e.getValueExpression(), "Item Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext"); 761// } 762// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression")) { 763// addExpression(p, e.getValueExpression(), "Enable When", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression"); 764// } 765// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression")) { 766// addExpression(p, e.getValueExpression(), "Calculated Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression"); 767// } 768// for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression")) { 769// addExpression(p, e.getValueExpression(), "Candidates", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression"); 770// } 771// } 772// 773 774 int t = 1; 775 for (QuestionnaireItemComponent c : i.getItem()) { 776 hasExt = renderFormItem(x, q, c, pfx == null ? null : pfx+"."+Integer.toString(t), indent+1) || hasExt; 777 t++; 778 } 779 return hasExt; 780 } 781 782 private void item(XhtmlNode ul, String name, String value, String valueLink) { 783 if (!Utilities.noString(value)) { 784 ul.li().style("font-size: 10px").ah(valueLink).tx(name+": "+value); 785 } 786 } 787 788 private void item(XhtmlNode ul, String name, String value) { 789 if (!Utilities.noString(value)) { 790 ul.li().style("font-size: 10px").tx(name+": "+value); 791 } 792 } 793 private XhtmlNode item(XhtmlNode ul, String name) { 794 XhtmlNode li = ul.li(); 795 li.style("font-size: 10px").tx(name+": "); 796 return li; 797 } 798 799 800 private void listOptions(Questionnaire q, QuestionnaireItemComponent i, XhtmlNode select) { 801 if (i.hasAnswerValueSet()) { 802 ValueSet vs = null; 803 if (!Utilities.noString(i.getAnswerValueSet()) && i.getAnswerValueSet().startsWith("#")) { 804 vs = (ValueSet) q.getContained(i.getAnswerValueSet().substring(1)); 805 if (vs != null && !vs.hasUrl()) { 806 vs = vs.copy(); 807 vs.setUrl(q.getUrl()+"--"+q.getContained(i.getAnswerValueSet().substring(1))); 808 } 809 } else { 810 vs = context.getContext().fetchResource(ValueSet.class, i.getAnswerValueSet()); 811 } 812 if (vs != null) { 813 ValueSetExpansionOutcome exp = context.getContext().expandVS(vs, true, false); 814 if (exp.getValueset() != null) { 815 for (ValueSetExpansionContainsComponent cc : exp.getValueset().getExpansion().getContains()) { 816 select.option(cc.getCode(), cc.hasDisplay() ? cc.getDisplay() : cc.getCode(), false); 817 } 818 return; 819 } 820 } 821 } else if (i.hasAnswerOption()) { 822 renderItemOptions(select, i); 823 } 824 select.option("a", "??", false); 825 } 826 827 public String display(Resource dr) throws UnsupportedEncodingException, IOException { 828 return display((Questionnaire) dr); 829 } 830 831 public String display(Questionnaire q) throws UnsupportedEncodingException, IOException { 832 return "Questionnaire "+q.present(); 833 } 834 835 private boolean renderLinks(XhtmlNode x, Questionnaire q) { 836 x.para().tx("Try this questionnaire out:"); 837 XhtmlNode ul = x.ul(); 838 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"); 839 return false; 840 } 841 842 private boolean renderDefns(XhtmlNode x, Questionnaire q) throws IOException { 843 XhtmlNode tbl = x.table("dict"); 844 boolean ext = false; 845 ext = renderRootDefinition(tbl, q, new ArrayList<>()) || ext; 846 for (QuestionnaireItemComponent qi : q.getItem()) { 847 ext = renderDefinition(tbl, q, qi, new ArrayList<>()) || ext; 848 } 849 return ext; 850 } 851 852 private boolean renderRootDefinition(XhtmlNode tbl, Questionnaire q, List<QuestionnaireItemComponent> parents) throws IOException { 853 boolean ext = false; 854 XhtmlNode td = tbl.tr().td("structure").colspan("2").span(null, null).attribute("class", "self-link-parent"); 855 td.an(q.getId()); 856 td.img(Utilities.path(context.getLocalPrefix(), "icon_q_root.gif")); 857 td.tx(" Questionnaire "); 858 td.b().tx(q.getId()); 859 860 // general information 861 defn(tbl, "URL", q.getUrl()); 862 defn(tbl, "Version", q.getVersion()); 863 defn(tbl, "Name", q.getName()); 864 defn(tbl, "Title", q.getTitle()); 865 if (q.hasDerivedFrom()) { 866 td = defn(tbl, "Derived From"); 867 boolean first = true; 868 for (CanonicalType c : q.getDerivedFrom()) { 869 if (first) first = false; else td.tx(", "); 870 td.tx(c.asStringValue()); // todo: make these a reference 871 } 872 } 873 defn(tbl, "Status", q.getStatus().getDisplay()); 874 defn(tbl, "Experimental", q.getExperimental()); 875 defn(tbl, "Publication Date", q.getDateElement().primitiveValue()); 876 defn(tbl, "Approval Date", q.getApprovalDateElement().primitiveValue()); 877 defn(tbl, "Last Review Date", q.getLastReviewDateElement().primitiveValue()); 878 if (q.hasEffectivePeriod()) { 879 renderPeriod(defn(tbl, "Effective Period"), q.getEffectivePeriod()); 880 } 881 882 if (q.hasSubjectType()) { 883 td = defn(tbl, "Subject Type"); 884 boolean first = true; 885 for (CodeType c : q.getSubjectType()) { 886 if (first) first = false; else td.tx(", "); 887 td.tx(c.asStringValue()); 888 } 889 } 890 defn(tbl, "Description", q.getDescription()); 891 defn(tbl, "Purpose", q.getPurpose()); 892 defn(tbl, "Copyright", q.getCopyright()); 893 if (q.hasCode()) { 894 td = defn(tbl, Utilities.pluralize("Code", q.getCode().size())); 895 boolean first = true; 896 for (Coding c : q.getCode()) { 897 if (first) first = false; else td.tx(", "); 898 renderCodingWithDetails(td, c); 899 } 900 } 901 return false; 902 } 903 904 private boolean renderDefinition(XhtmlNode tbl, Questionnaire q, QuestionnaireItemComponent qi, List<QuestionnaireItemComponent> parents) throws IOException { 905 boolean ext = false; 906 XhtmlNode td = tbl.tr().td("structure").colspan("2").span(null, null).attribute("class", "self-link-parent"); 907 td.an("item."+qi.getLinkId()); 908 for (QuestionnaireItemComponent p : parents) { 909 td.ah("#item."+p.getLinkId()).img(Utilities.path(context.getLocalPrefix(), "icon_q_item.png")); 910 td.tx(" > "); 911 } 912 td.img(Utilities.path(context.getLocalPrefix(), "icon_q_item.png")); 913 td.tx(" Item "); 914 td.b().tx(qi.getLinkId()); 915 916 // general information 917 defn(tbl, "Link Id", qi.getLinkId()); 918 defn(tbl, "Prefix", qi.getPrefix()); 919 defn(tbl, "Text", qi.getText()); 920 defn(tbl, "Type", qi.getType().getDisplay()); 921 defn(tbl, "Required", qi.getRequired(), true); 922 defn(tbl, "Repeats", qi.getRepeats(), true); 923 defn(tbl, "Read Only", qi.getReadOnly(), false); 924 if (ToolingExtensions.readBoolExtension(qi, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject")) { 925 defn(tbl, "Subject", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject", "This element changes who the subject of the question is", null); 926 } 927 928 // content control 929 defn(tbl, "Max Length", qi.getMaxLength()); 930 if (qi.hasAnswerValueSet()) { 931 defn(tbl, "Value Set", qi.getDefinition(), context.getWorker().fetchResource(ValueSet.class, qi.getAnswerValueSet())); 932 } 933 if (qi.hasAnswerOption()) { 934 XhtmlNode tr = tbl.tr(); 935 tr.td().tx("Allowed Answers"); 936 XhtmlNode ul = tr.td().ul(); 937 for (QuestionnaireItemAnswerOptionComponent ans : qi.getAnswerOption()) { 938 XhtmlNode li = ul.li(); 939 render(li, ans.getValue()); 940 if (ans.getInitialSelected()) { 941 li.tx(" (initially selected)"); 942 } 943 } 944 } 945 if (qi.hasInitial()) { 946 XhtmlNode tr = tbl.tr(); 947 tr.td().tx(Utilities.pluralize("Initial Answer", qi.getInitial().size())); 948 if (qi.getInitial().size() == 1) { 949 render(tr.td(), qi.getInitialFirstRep().getValue()); 950 } else { 951 XhtmlNode ul = tr.td().ul(); 952 for (QuestionnaireItemInitialComponent ans : qi.getInitial()) { 953 XhtmlNode li = ul.li(); 954 render(li, ans.getValue()); 955 } 956 } 957 } 958 959 // appearance 960 if (qi.hasExtension("http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory")) { 961 XhtmlNode tr = tbl.tr(); 962 tr.td().ah("http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory").tx("Display Category"); 963 render(tr.td(), qi.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory").getValue()); 964 } 965 if (ToolingExtensions.readBoolExtension(qi, "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden")) { 966 defn(tbl, "Hidden Item", "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", "This item is a hidden question", null); 967 } 968 if (ToolingExtensions.readBoolExtension(qi, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-optionalDisplay")) { 969 defn(tbl, "Hidden Item", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-optionalDisplay", "This item is optional to display", null); 970 } 971 972 // formal definitions 973 if (qi.hasDefinition()) { 974 genDefinitionLink(defn(tbl, "Definition"), qi); 975 } 976 977 if (qi.hasCode()) { 978 XhtmlNode tr = tbl.tr(); 979 tr.td().tx(Utilities.pluralize("Code", qi.getCode().size())); 980 XhtmlNode ul = tr.td().ul(); 981 for (Coding c : qi.getCode()) { 982 renderCodingWithDetails(ul.li(), c); 983 } 984 } 985 if (qi.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod")) { 986 XhtmlNode tr = tbl.tr(); 987 tr.td().ah("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod").tx("Observation Link Period"); 988 render(tr.td(), qi.getExtensionByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod").getValue()); 989 } 990 991 // dynamic management 992 if (qi.hasEnableWhen()) { 993 XhtmlNode tr = tbl.tr(); 994 tr.td().tx("Enable When"); 995 td = tr.td(); 996 if (qi.getEnableWhen().size() == 1) { 997 renderEnableWhen(td, qi.getEnableWhen().get(0)); 998 } else { 999 td.tx(qi.getEnableBehavior().getDisplay()+" are true:"); 1000 XhtmlNode ul = td.ul(); 1001 for (QuestionnaireItemEnableWhenComponent ew : qi.getEnableWhen()) { 1002 renderEnableWhen(ul.li(), ew); 1003 } 1004 } 1005 } 1006 1007 1008 // other stuff 1009 1010 1011 1012 List<QuestionnaireItemComponent> curr = new ArrayList<>(); 1013 curr.addAll(parents); 1014 curr.add(qi); 1015 for (QuestionnaireItemComponent qic : qi.getItem()) { 1016 ext = renderDefinition(tbl, q, qic, curr) || ext; 1017 } 1018 return ext; 1019 } 1020 1021 private void defn(XhtmlNode tbl, String name, String url, Resource res) throws UnsupportedEncodingException, IOException { 1022 if (res != null && res.hasUserData("path")) { 1023 defn(tbl, "Definition", RendererFactory.factory(res, context).display(res), res.getUserString("path")); 1024 } else if (Utilities.isAbsoluteUrlLinkable(url)) { 1025 defn(tbl, "Definition", url, url); 1026 } { 1027 defn(tbl, "Definition", url); 1028 } 1029 1030 } 1031 1032 private void renderEnableWhen(XhtmlNode x, QuestionnaireItemEnableWhenComponent ew) { 1033 x.ah("#item."+ew.getQuestion()).tx(ew.getQuestion()); 1034 x.tx(" "); 1035 x.tx(ew.getOperator().toCode()); 1036 x.tx(" "); 1037 x.tx(display(ew.getAnswer())); 1038 } 1039 1040 private XhtmlNode defn(XhtmlNode tbl, String name) { 1041 XhtmlNode tr = tbl.tr(); 1042 tr.td().tx(name); 1043 return tr.td(); 1044 } 1045 1046 private void defn(XhtmlNode tbl, String name, int value) { 1047 if (value > 0) { 1048 XhtmlNode tr = tbl.tr(); 1049 tr.td().tx(name); 1050 tr.td().tx(value); 1051 } 1052 } 1053 1054 1055 private void defn(XhtmlNode tbl, String name, boolean value) { 1056 XhtmlNode tr = tbl.tr(); 1057 tr.td().tx(name); 1058 tr.td().tx(Boolean.toString(value)); 1059 } 1060 1061 private void defn(XhtmlNode tbl, String name, String value) { 1062 if (!Utilities.noString(value)) { 1063 XhtmlNode tr = tbl.tr(); 1064 tr.td().tx(name); 1065 tr.td().tx(value); 1066 } 1067 } 1068 1069 private void defn(XhtmlNode tbl, String name, String value, String url) { 1070 if (!Utilities.noString(value)) { 1071 XhtmlNode tr = tbl.tr(); 1072 tr.td().tx(name); 1073 tr.td().ah(url).tx(value); 1074 } 1075 } 1076 1077 private void defn(XhtmlNode tbl, String name, String nurl, String value, String url) { 1078 if (!Utilities.noString(value)) { 1079 XhtmlNode tr = tbl.tr(); 1080 tr.td().ah(nurl).tx(name); 1081 if (url != null) { 1082 tr.td().ah(url).tx(value); 1083 } else { 1084 tr.td().tx(value); 1085 } 1086 } 1087 } 1088 1089 private void defn(XhtmlNode tbl, String name, boolean value, boolean ifFalse) { 1090 if (ifFalse || value) { 1091 XhtmlNode tr = tbl.tr(); 1092 tr.td().tx(name); 1093 tr.td().tx(Boolean.toString(value)); 1094 } 1095 } 1096 1097}