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