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}