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