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