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