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