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