001package org.hl7.fhir.r5.renderers;
002
003import java.io.IOException;
004import java.io.UnsupportedEncodingException;
005
006import java.util.ArrayList;
007import java.util.HashMap;
008import java.util.List;
009import java.util.Map;
010
011import javax.annotation.Nullable;
012
013import org.hl7.fhir.exceptions.DefinitionException;
014import org.hl7.fhir.exceptions.FHIRFormatError;
015import org.hl7.fhir.exceptions.FHIRException;
016import org.hl7.fhir.r5.model.CanonicalResource;
017import org.hl7.fhir.r5.model.CanonicalType;
018import org.hl7.fhir.r5.model.CapabilityStatement;
019import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
020import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
021import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceOperationComponent;
022import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
023import org.hl7.fhir.r5.model.CapabilityStatement.ReferenceHandlingPolicy;
024import org.hl7.fhir.r5.model.CapabilityStatement.ResourceInteractionComponent;
025import org.hl7.fhir.r5.model.CapabilityStatement.SystemInteractionComponent;
026import org.hl7.fhir.r5.model.CapabilityStatement.SystemRestfulInteraction;
027import org.hl7.fhir.r5.model.CapabilityStatement.TypeRestfulInteraction;
028import org.hl7.fhir.r5.model.CodeType;
029import org.hl7.fhir.r5.model.CodeableConcept;
030import org.hl7.fhir.r5.model.Element;
031import org.hl7.fhir.r5.model.Enumeration;
032import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
033import org.hl7.fhir.r5.model.Extension;
034import org.hl7.fhir.r5.model.OperationDefinition;
035import org.hl7.fhir.r5.model.Resource;
036import org.hl7.fhir.r5.model.StringType;
037import org.hl7.fhir.r5.model.StructureDefinition;
038import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
039import org.hl7.fhir.r5.renderers.utils.RenderingContext;
040import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
041import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
042import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext;
043import org.hl7.fhir.r5.utils.ToolingExtensions;
044import org.hl7.fhir.utilities.Utilities;
045import org.hl7.fhir.utilities.xhtml.NodeType;
046import org.hl7.fhir.utilities.xhtml.XhtmlNode;
047
048
049public class CapabilityStatementRenderer extends ResourceRenderer {
050  
051  private static final String EXPECTATION = "http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation";
052  private static final String COMBINED = "http://hl7.org/fhir/StructureDefinition/capabilitystatement-search-parameter-combination";
053  private static final String SP_BASE = "http://hl7.org/fhir/searchparameter/";
054  private static final String FHIR_BASE = "http://hl7.org/fhir/";
055  private static final String VERS_DEF_PREFIX = "FHIR Release ";
056
057  private String currentFhirBase = "";
058  private String collapseClass = "panel-collapse in";
059
060  private boolean multExpectationsPresent = false;
061  
062  //Private classes for driving the rendering
063
064  private class CombinedSearchParamSet {
065    private Map<Boolean, List<SingleParam>> params;
066    String expectation = "";
067
068    CombinedSearchParamSet(String expectation) {
069      params = new HashMap<Boolean, List<SingleParam>>();
070      params.put(true, new ArrayList<SingleParam>());
071      params.put(false, new ArrayList<SingleParam>());
072      if (!Utilities.noString(expectation)) this.expectation = expectation;
073    }
074 
075    public Map<Boolean, List<SingleParam>> getParams() {
076        return params;
077    }
078
079    public String getExpectation() {
080        return expectation;
081    }
082
083    public void addParam(boolean required, SingleParam param) {
084      params.get(required).add(param);
085    }
086  }
087  private class SingleParam {
088    private String name = "";
089    private String definition = "";
090    private String type = "";
091    private String documentation = "";
092    private String expectation = "";
093    private String hostResource = "";
094
095    public SingleParam(String name) {
096      if (!Utilities.noString(name)) this.name=name;
097    }
098 
099    public SingleParam(String name, String definition) {
100      if (!Utilities.noString(name)) this.name=name;
101      if (!Utilities.noString(definition)) this.definition=definition;
102    }
103
104    public SingleParam(String name, String definition, String type) {
105      if (!Utilities.noString(name)) this.name=name;
106      if (!Utilities.noString(definition)) this.definition=definition;
107      if (!Utilities.noString(type)) this.type=type;
108    }
109
110    public SingleParam(String name, String definition, String type, String documentation) {
111      if (!Utilities.noString(name)) this.name=name;
112      if (!Utilities.noString(definition)) this.definition=definition;
113      if (!Utilities.noString(type)) this.type=type;
114      if (!Utilities.noString(documentation)) this.documentation=documentation;
115    }
116 
117    public SingleParam(String name, String definition, String type, String documentation, String expectation, String hostResource) {
118      if (!Utilities.noString(name)) this.name=name;
119      if (!Utilities.noString(definition)) this.definition=definition;
120      if (!Utilities.noString(type)) this.type=type;
121      if (!Utilities.noString(documentation)) this.documentation=documentation;
122      if (!Utilities.noString(expectation)) this.expectation=expectation;
123      if (!Utilities.noString(hostResource)) this.hostResource = hostResource;
124    }
125 
126    public String getName() {
127        return name;
128    }
129
130    public String getDefinition() {
131        return definition;
132    }
133
134    public String getDocumentation() {
135        return documentation;
136    }
137 
138    public String getType() {
139        return type;
140    }
141
142    public String getExpectation() {
143        return expectation;
144    }
145
146    public String getHostResource() {
147      return hostResource;
148    }
149  }
150  private class ResourceSearchParams {
151    private Map<String, List<CombinedSearchParamSet>> combinedParams;
152    private Map<String, SingleParam> individualParamsByName;
153    private Map<String, List<SingleParam>> individualParamsByExp;
154
155    public ResourceSearchParams() {
156      combinedParams = new HashMap<String, List<CombinedSearchParamSet>>();
157      combinedParams.put("SHALL", new ArrayList<CombinedSearchParamSet>());
158      combinedParams.put("SHOULD", new ArrayList<CombinedSearchParamSet>());
159      combinedParams.put("SHOULD-NOT", new ArrayList<CombinedSearchParamSet>());
160      combinedParams.put("MAY", new ArrayList<CombinedSearchParamSet>());
161      combinedParams.put("supported", new ArrayList<CombinedSearchParamSet>());
162      individualParamsByName  = new HashMap<String, SingleParam>();
163      individualParamsByExp = new HashMap<String, List<SingleParam>>();
164      individualParamsByExp.put("SHALL", new ArrayList<SingleParam>());
165      individualParamsByExp.put("SHOULD", new ArrayList<SingleParam>());
166      individualParamsByExp.put("MAY", new ArrayList<SingleParam>());
167      individualParamsByExp.put("SHOULD-NOT", new ArrayList<SingleParam>());
168      individualParamsByExp.put("supported", new ArrayList<SingleParam>());
169    }
170
171    public Map<String, List<CombinedSearchParamSet>> getCombined() {
172      return combinedParams;
173    }
174
175    public Map<String, SingleParam> getInd() {
176        return individualParamsByName;
177    }
178
179    public Map<String, SingleParam> getIndbyName() {
180      return individualParamsByName;
181    }
182    
183    public Map<String, List<SingleParam>> getIndbyExp() {
184      return individualParamsByExp;
185    }
186
187    public void addCombinedParamSet(String exp, CombinedSearchParamSet params) {
188      combinedParams.get(exp).add(params);
189    }
190 
191    public void addIndividualbyName(String name, SingleParam param) {
192      individualParamsByName.put(name, param);
193    }
194 
195    public void addIndividualbyExp(String exp, SingleParam param) {
196      individualParamsByExp.get(exp).add(param);
197    }
198 
199  }
200 
201  private class SingleOperation {
202    private String name = "";
203    private String definition = "";
204    private String documentation = "";
205    private String expectation = "";
206
207    public SingleOperation(String name) {
208      if (!Utilities.noString(name)) this.name=name;
209    }
210
211    public SingleOperation(String name, String definition) {
212      if (!Utilities.noString(name)) this.name=name;
213      if (!Utilities.noString(definition)) this.definition=definition;
214    }
215
216    public SingleOperation(String name, String definition, String documentation) {
217      if (!Utilities.noString(name)) this.name=name;
218      if (!Utilities.noString(definition)) this.definition=definition;
219
220      if (!Utilities.noString(documentation)) this.documentation=documentation;
221    }
222
223    public SingleOperation(String name, String definition, String documentation, String expectation) {
224      if (!Utilities.noString(name)) this.name=name;
225      if (!Utilities.noString(definition)) this.definition=definition;
226      if (!Utilities.noString(documentation)) this.documentation=documentation;
227      if (!Utilities.noString(expectation)) this.expectation=expectation;
228    }
229 
230    public String getName() {
231        return name;
232    }
233
234    public String getDefinition() {
235        return definition;
236    }
237
238    public String getDocumentation() {
239        return documentation;
240     }
241
242    public String getExpectation() {
243      return expectation;
244    }
245  }
246 
247  private class ResourceOperations {
248    private Map<String, List<SingleOperation>> operationsByExp;
249
250    public ResourceOperations() {
251
252      operationsByExp = new HashMap<String, List<SingleOperation>>();
253      operationsByExp.put("SHALL", new ArrayList<SingleOperation>());
254      operationsByExp.put("SHOULD", new ArrayList<SingleOperation>());
255      operationsByExp.put("MAY", new ArrayList<SingleOperation>());
256      operationsByExp.put("SHOULD-NOT", new ArrayList<SingleOperation>());
257      operationsByExp.put("supported", new ArrayList<SingleOperation>());
258    }
259 
260    public Map<String, List<SingleOperation>> getOperations() {
261      return operationsByExp;
262    }
263    public void addOperation(String exp, SingleOperation op) {
264      operationsByExp.get(exp).add(op);
265    }
266
267  }
268
269  //Constructors
270  public CapabilityStatementRenderer(RenderingContext context) {
271    super(context);
272  }
273
274  public CapabilityStatementRenderer(RenderingContext context, ResourceContext rcontext) {
275    super(context, rcontext);
276  }
277  
278  public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException {
279    return render(x, (CapabilityStatement) dr);
280  }
281
282  public boolean render(XhtmlNode x, CapabilityStatement conf) throws FHIRFormatError, DefinitionException, IOException {
283    boolean igRenderingMode = (context.getRules() == GenerationRules.IG_PUBLISHER);
284    FHIRVersion currentVersion = conf.getFhirVersion();
285    String versionPathComponent = getVersionPathComponent(currentVersion.getDefinition());
286    if (!Utilities.noString(versionPathComponent)) {
287      currentFhirBase = FHIR_BASE + versionPathComponent + "/";
288    }
289    else {
290      currentFhirBase = FHIR_BASE;
291    }
292    
293    String igVersion = conf.getVersion();
294
295    x.h(2,"title").addText(conf.getTitle());
296    XhtmlNode uList = x.ul();
297    uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_IMP_VER, igVersion) + " ");
298    uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_FHIR_VER, currentVersion.toCode()) + " ");
299    addSupportedFormats(uList, conf);
300    
301    uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_PUB_ON, displayDateTime(conf.getDateElement()) + " "));
302    uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_PUB_BY, conf.getPublisherElement().asStringValue()) + " ");
303
304
305    XhtmlNode block = x.addTag("blockquote").attribute("class","impl-note");
306    block.addTag("p").addTag("strong").addText(context.formatPhrase(RenderingContext.CAPABILITY_NOTE_CAP));
307    block.addTag("p").addText(context.formatPhrase(RenderingContext.CAPABILTY_ALLOW_CAP));
308
309
310    addSupportedCSs(x, conf);
311    addSupportedIGs(x, conf);
312
313    int restNum = conf.getRest().size();
314    int nextLevel = 3;
315    if (restNum > 0) {
316      x.h(2,"rest").addText((context.formatPhrase(RenderingContext.CAPABILITY_REST_CAPS)));
317      int count=1;
318      for (CapabilityStatementRestComponent rest : conf.getRest()) {
319        if (restNum > 1) {
320          x.h(3,"rest"+Integer.toString(count)).addText(context.formatPhrase(RenderingContext.CAPABILITY_REST_CONFIG, Integer.toString(count)) + " ");
321          nextLevel = 4;
322        }
323        addRestConfigPanel(x, rest, nextLevel, count);
324        
325        boolean hasVRead = false;
326        boolean hasPatch = false;
327        boolean hasDelete = false;
328        boolean hasHistory = false;
329        boolean hasUpdates = false;
330        for (CapabilityStatementRestResourceComponent r : rest.getResource()) {
331          hasVRead = hasVRead || hasOp(r, TypeRestfulInteraction.VREAD);
332          hasPatch = hasPatch || hasOp(r, TypeRestfulInteraction.PATCH);
333          hasDelete = hasDelete || hasOp(r, TypeRestfulInteraction.DELETE);
334          hasHistory = hasHistory || hasOp(r, TypeRestfulInteraction.HISTORYTYPE);
335          hasUpdates = hasUpdates || hasOp(r, TypeRestfulInteraction.HISTORYINSTANCE);
336        }
337        if (rest.getResource().size() >0) {
338          x.h(nextLevel,"resourcesCap" + Integer.toString(count)).addText(context.formatPhrase(RenderingContext.CAPABILITY_RES_PRO));
339          x.h(nextLevel+1,"resourcesSummary" + Integer.toString(count)).addText(context.formatPhrase(RenderingContext.GENERAL_SUMM));
340          addSummaryIntro(x);
341          addSummaryTable(x, rest, hasVRead, hasPatch, hasDelete, hasHistory, hasUpdates, count);
342          x.addTag("hr");
343          //Third time for individual resources
344          int resCount = 1;
345          for (CapabilityStatementRestResourceComponent r : rest.getResource()) {
346            addResourceConfigPanel(x, r, nextLevel+1, count, resCount, igRenderingMode);
347            resCount++;
348          }
349        }
350        count++;
351      }
352    }
353
354    if (multExpectationsPresent) {
355      addWarningPanel(x,"⹋⹋ - this mark indicates that there are more than one expectation extensions present");
356    }
357
358    return true;
359  }
360
361  private String getVersionPathComponent(String definition) {
362    if (Utilities.noString(definition)) return "";
363    if (!definition.startsWith(VERS_DEF_PREFIX)) return "";
364    String restOfDef[] = definition.substring(VERS_DEF_PREFIX.length()).split(" ");
365    if (restOfDef[1].startsWith("(")) return "R"+restOfDef[0];
366    return "";
367  }
368
369  public void describe(XhtmlNode x, CapabilityStatement cs) {
370    x.tx(display(cs));
371  }
372
373  public String display(CapabilityStatement cs) {
374    return cs.present();
375  }
376
377  @Override
378  public String display(Resource r) throws UnsupportedEncodingException, IOException {
379    return ((CapabilityStatement) r).present();
380  }
381
382
383  private boolean hasOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction on) {
384    for (ResourceInteractionComponent op : r.getInteraction()) {
385      if (op.getCode() == on)
386        return true;
387    }
388    return false;
389  }
390
391  private String showOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction on) {
392    for (ResourceInteractionComponent op : r.getInteraction()) {
393      if (op.getCode() == on)
394        return "y";
395    }
396    return "";
397  }
398
399  private String showOp(CapabilityStatementRestComponent r, SystemRestfulInteraction on) {
400    for (SystemInteractionComponent op : r.getInteraction()) {
401      if (op.getCode() == on)
402        return "y";
403    }
404    return "";
405  }
406
407  private XhtmlNode addTableRow(XhtmlNode t, String name) {
408    XhtmlNode tr = t.tr();
409    tr.td().addText(name);
410    return tr.td();
411  }
412  
413  private void addTableRow(XhtmlNode t, String name, String value) {
414    XhtmlNode tr = t.tr();
415    tr.td().addText(name);
416    tr.td().addText(value);
417  }
418
419  @Nullable
420  private String getExtValueCode(Extension ext) {
421    if (ext != null) {
422      return ext.getValueCodeType().getCode();
423    }
424    return null;
425  }
426
427  private void addSupportedCSs(XhtmlNode x, CapabilityStatement cap) {
428    if (cap.hasInstantiates()) {
429      XhtmlNode p = x.para();
430      p.tx(cap.getInstantiates().size() > 1 ? "This CapabilityStatement instantiates these CapabilityStatements" : "This CapabilityStatement instantiates the CapabilityStatement");
431      boolean first = true;
432      for (CanonicalType ct : cap.getInstantiates()) {
433        CapabilityStatement cs = context.getContext().fetchResource(CapabilityStatement.class, ct.getValue(), cap);
434        if (first) {first = false;} else {p.tx(", ");};
435        if (cs == null) {
436          p.code().tx(ct.getValue());
437        } else {
438          p.ah(cs.getWebPath()).tx(cs.present());
439        }
440      }
441    }
442    if (cap.hasImports()) {
443      XhtmlNode p = x.para();
444      p.tx(cap.getImports().size() > 1 ? "This CapabilityStatement imports these CapabilityStatements" : "This CapabilityStatement imports the CapabilityStatement");
445      boolean first = true;
446      for (CanonicalType ct : cap.getImports()) {
447        CapabilityStatement cs = context.getContext().fetchResource(CapabilityStatement.class, ct.getValue(), cap);
448        if (first) {first = false;} else {p.tx(", ");};
449        if (cs == null) {
450          p.code().tx(ct.getValue());
451        } else {
452          p.ah(cs.getWebPath()).tx(cs.present());
453        }
454      }      
455    }
456  }
457  
458  private void addSupportedIGs(XhtmlNode x, CapabilityStatement cap) {
459    String capExpectation=null;
460    if (cap.hasImplementationGuide()) {
461      ArrayList<String> igShoulds = new ArrayList<String>();
462      ArrayList<String> igShalls = new ArrayList<String>();
463      ArrayList<String> igMays = new ArrayList<String>();
464      int i=0;
465      for (CanonicalType c : cap.getImplementationGuide())
466      {
467        capExpectation=getExtValueCode(c.getExtensionByUrl(EXPECTATION));
468        if (!Utilities.noString(capExpectation)) {
469          if (capExpectation.equals("SHALL")) {
470            igShalls.add(c.asStringValue());
471          }
472          else if (capExpectation.equals("SHOULD")) {
473            igShoulds.add(c.asStringValue());
474          }
475          else if (capExpectation.equals("MAY")) {
476            igMays.add(c.asStringValue());
477          }
478        }
479        else {
480          igShalls.add(c.asStringValue());   //default to SHALL
481        }
482      }
483      XhtmlNode ul = null;
484      if (igShalls.size() > 0) {
485        x.h(3,"shallIGs").addText(context.formatPhrase(RenderingContext.CAPABILTY_SHALL_SUPP));
486        ul = x.ul();
487        for (String url : igShalls) {
488          addResourceLink(ul.li(), url, url);
489          //ul.li().ah(url).addText(url);
490        }
491      }
492      if (igShoulds.size() > 0) {
493        x.h(3,"shouldIGs").addText(context.formatPhrase(RenderingContext.CAPABILITY_SHOULD_SUPP));
494        ul = x.ul();
495        for (String url : igShoulds) {
496          addResourceLink(ul.li(), url, url);
497          //ul.li().ah(url).addText(url);
498        }
499      }
500      if (igMays.size() > 0) {
501        x.h(3,"shouldIGs").addText(context.formatPhrase(RenderingContext.CAPABILITY_SHOULD_SUPP));
502        ul = x.ul();
503        for (String url : igMays) {
504          addResourceLink(ul.li(), url, url);
505          //ul.li().ah(url).addText(url);
506        }
507      }
508
509    }
510  }
511
512  private void addSupportedFormats(XhtmlNode uList, CapabilityStatement conf) {
513    XhtmlNode lItem = uList.li();
514    lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP_FORM) + " ");
515    Boolean first = true;
516    String capExpectation = null;
517    for (CodeType c : conf.getFormat()) {
518      if (!first) {
519        lItem.addText(", ");
520      }
521      capExpectation = getExtValueCode(c.getExtensionByUrl(EXPECTATION));
522      if (!Utilities.noString(capExpectation)) {
523        lItem.addTag("strong").addText(capExpectation);
524        lItem.addText(" "+ (context.formatPhrase(RenderingContext.CAPABILITY_SUPP) + " "));
525      }
526      lItem.code().addText(c.getCode());
527      first = false;
528    }
529    lItem = uList.li();
530    lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP_PATCH_FORM) + " ");
531    first=true;
532    for (CodeType c : conf.getPatchFormat()) {
533      if (!first) {
534        lItem.addText(", ");
535      }
536      capExpectation = getExtValueCode(c.getExtensionByUrl(EXPECTATION));
537      if (!Utilities.noString(capExpectation)) {
538        lItem.addTag("strong").addText(capExpectation);
539        lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP) + " ");
540      }
541      lItem.code().addText(c.getCode());
542      first = false;
543    }
544  }
545
546  private void addRestConfigPanel(XhtmlNode x, CapabilityStatementRestComponent rest, int nextLevel, int count) throws FHIRFormatError, DefinitionException, IOException {
547    XhtmlNode panel= null;
548    XhtmlNode body = null;
549    XhtmlNode row = null;
550    XhtmlNode cell = null;
551    XhtmlNode heading = null;
552    panel = x.div().attribute("class", "panel panel-default");
553    heading = panel.div().attribute("class", "panel-heading").h(nextLevel,"mode" + Integer.toString(count)).attribute("class", "panel-title");
554    heading.addText("Mode: ");
555    heading.code().addText(rest.getMode().toCode());
556    body = panel.div().attribute("class", "panel-body");
557    addMarkdown(body, rest.getDocumentation());
558    //Security info
559    if (rest.hasSecurity()) {
560      body.div().attribute("class","lead").addTag("em").addText("Security");
561      String mdText = rest.getSecurity().getDescription();
562      boolean cors = rest.getSecurity().getCors();
563      List<CodeableConcept> services = rest.getSecurity().getService();
564      if (cors || services.size() >0) {
565        row = body.div().attribute("class","row");
566        row.div().attribute("class","col-lg-6").addText(getCorsText(cors));
567        cell = row.div().attribute("class","col-lg-6");
568        cell.addText("Security services supported: ");
569        addSeparatedListOfCodes(cell, getSecServices(services), ",");
570      } 
571    
572      if (!Utilities.noString(mdText)) {
573        addMarkdown(body.blockquote(),mdText);
574      }
575    }  
576    body.div().attribute("class","lead").addTag("em").addText(context.formatPhrase(RenderingContext.CAPABILITY_SUMM_SYS_INT));
577    addSystemInteractions(body, rest.getInteraction());
578        
579  }
580
581  private String getCorsText(boolean on) {
582    if (on) {
583      return context.formatPhrase(RenderingContext.CAPABILITY_CORS_YES);
584    }
585    return context.formatPhrase(RenderingContext.CAPABILITY_CORS_NO);
586  }
587
588  private List<String> getSecServices(List<CodeableConcept> services)
589  {
590    List<String> serviceStrings = new ArrayList<String>();
591    for (CodeableConcept c: services) {
592      if (c.hasCoding()){
593        serviceStrings.add(c.getCodingFirstRep().getCode());
594      }
595      else {
596        serviceStrings.add(c.getText());
597      }
598    }
599    return serviceStrings;
600  }
601
602
603  private void addSystemInteractions(XhtmlNode body, List<SystemInteractionComponent> interactions) {
604    if (interactions.size()==0) return;
605    XhtmlNode uList = body.ul();
606    String capExpectation = null;
607    String expName = null;
608    String documentation = null;
609    Map<String,String> expression = null;
610    ArrayList<Map<String,String>> shalls = new ArrayList<Map<String,String>>();
611    ArrayList<Map<String,String>> shoulds = new ArrayList<Map<String,String>>();
612    ArrayList<Map<String,String>> mays = new ArrayList<Map<String,String>>();
613    ArrayList<Map<String,String>> shouldnots = new ArrayList<Map<String,String>>();
614    ArrayList<Map<String,String>> supports = new ArrayList<Map<String,String>>();
615    for (SystemInteractionComponent comp : interactions) {
616      capExpectation=getExtValueCode(comp.getExtensionByUrl(EXPECTATION));
617      expName = comp.getCode().toCode();
618      documentation = comp.getDocumentation();
619      if (Utilities.noString(documentation)) {
620        documentation="";
621      }
622      expression = new HashMap<String,String>();
623      expression.put(expName,documentation);
624      if (!Utilities.noString(capExpectation)) {
625        if (capExpectation.equals("SHALL")) {
626          shalls.add(expression);
627        }
628        else if (capExpectation.equals("SHOULD")) {
629          shoulds.add(expression);
630        }
631        else if (capExpectation.equals("MAY")) {
632          mays.add(expression);
633        }
634        else if (capExpectation.equals("SHOULD-NOT")) {
635          shouldnots.add(expression);
636        }
637      }
638      else {
639        supports.add(expression);
640      }
641    }
642    addInteractionListItems(uList, "SHALL", shalls);
643    addInteractionListItems(uList, "SHOULD", shoulds);
644    addInteractionListItems(uList, "MAY", mays);
645    addInteractionListItems(uList, "SHOULD NOT", shouldnots);
646    addInteractionListItems(uList, null, supports);
647  }
648
649  private void addInteractionListItems(XhtmlNode uList, String verb, List<Map<String,String>> interactions) {
650    XhtmlNode item = null;
651    String interaction = null;
652    String documentation = null;
653    for (Map<String,String> interactionMap : interactions) {
654      item = uList.li();
655      if (Utilities.noString(verb)) {
656        item.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP_THE) + " ");
657      }
658      else {
659        item.addTag("strong").addText(verb);
660        item.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP_THE) + " ");
661      }
662      interaction = interactionMap.keySet().toArray()[0].toString();
663      item.code(interaction);
664      documentation = interactionMap.get(interaction);
665      if (Utilities.noString(documentation)) {
666        item.addText(context.formatPhrase(RenderingContext.CAPABILITY_INT));
667      }
668      else {
669        item.addText(context.formatPhrase(RenderingContext.CAPABILITY_INT_DESC));
670        try {
671          addMarkdown(item, documentation);
672        } catch (FHIRFormatError e) {
673          // TODO Auto-generated catch block
674          e.printStackTrace();
675        } catch (DefinitionException e) {
676          // TODO Auto-generated catch block
677          e.printStackTrace();
678        } catch (IOException e) {
679          // TODO Auto-generated catch block
680          e.printStackTrace();
681        }
682      }
683    }
684  }
685
686  private void addInteractionSummaryList(XhtmlNode uList, String verb, List<String> interactions) {
687    if (interactions.size() == 0) return;
688    XhtmlNode item = uList.li();
689    if (Utilities.noString(verb)) {
690      item.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPPS) + " ");
691    }
692    else {
693      item.addTag("strong").addText(verb);
694      item.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP) + " ");
695    }
696    addSeparatedListOfCodes(item, interactions, ",");
697    item.addText(".");
698  }
699
700  private void addSummaryIntro(XhtmlNode x) {
701    XhtmlNode uList = null;
702    XhtmlNode lItem = null;
703    x.para().addText(context.formatPhrase(RenderingContext.CAPABILITY_SUMM_RES));
704    uList=x.ul();
705    uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_REV_PROF));
706    lItem = uList.li();
707    lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_INTER_SUPP));
708    lItem.b().addTag("span").attribute("class","bg-info").addText("R");
709    lItem.addText("ead, ");
710    lItem.b().addTag("span").attribute("class","bg-info").addText("S");
711    lItem.addText("earch, ");
712    lItem.b().addTag("span").attribute("class","bg-info").addText("U");
713    lItem.addText("pdate, and ");
714    lItem.b().addTag("span").attribute("class","bg-info").addText("C");
715    lItem.addText("reate, are always shown, while ");
716    lItem.b().addTag("span").attribute("class","bg-info").addText("VR");
717    lItem.addText("ead, ");
718    lItem.b().addTag("span").attribute("class","bg-info").addText("P");
719    lItem.addText("atch, ");
720    lItem.b().addTag("span").attribute("class","bg-info").addText("D");
721    lItem.addText("elete, ");
722    lItem.b().addTag("span").attribute("class","bg-info").addText("H");
723    lItem.addText("istory on ");
724    lItem.b().addTag("span").attribute("class","bg-info").addText("I");
725    lItem.addText("nstance, or ");
726    lItem.b().addTag("span").attribute("class","bg-info").addText("H");
727    lItem.addText("istory on ");
728    lItem.b().addTag("span").attribute("class","bg-info").addText("T");
729    lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_TYP_PRES));
730    uList.li().addTag("span").addText(context.formatPhrase(RenderingContext.CAPABILITY_SEARCH_PAR) + " ");
731    lItem = uList.li();
732    lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_RES_ENB) + " ");
733    lItem.code().addText("_include");
734    lItem = uList.li();
735    lItem.addText(context.formatPhrase(RenderingContext.CAPABILITY_OTH_RES_ENB) + " ");
736    lItem.code().addText("_revinclude");
737    uList.li().addText(context.formatPhrase(RenderingContext.CAPABILITY_RES_OPER));
738  }
739
740  private void addSummaryTable(XhtmlNode x, CapabilityStatement.CapabilityStatementRestComponent rest, boolean hasVRead, boolean hasPatch, boolean hasDelete, boolean hasHistory, boolean hasUpdates, int count) throws IOException {
741    XhtmlNode t = x.div().attribute("class","table-responsive").table("table table-condensed table-hover");
742    XhtmlNode tr = t.addTag("thead").tr();
743    tr.th().b().tx(context.formatPhrase(RenderingContext.CAPABILITY_RES_TYP));
744    tr.th().b().tx(context.formatPhrase(RenderingContext.GENERAL_PROF));
745    tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_READ_INT)).tx("R");
746    if (hasVRead)
747      tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_VREAD_INT)).tx("V-R");
748    tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_SEARCH_INT)).tx("S");
749    tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_UPDATE_INT)).tx("U");
750    if (hasPatch)
751      tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_PATCH_INT)).tx("P");
752    tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_CREATE_INT)).tx("C");
753    if (hasDelete)
754      tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_DELETE_INT)).tx("D");
755    if (hasUpdates)
756      tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_HISTORY_INT)).tx("H-I");
757    if (hasHistory)
758      tr.th().attribute("class", "text-center").b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_HISTORY_TYPE)).tx("H-T");
759    tr.th().b().attribute("title", context.formatPhrase(RenderingContext.CAPABILITY_REQ_RECOM)).tx(context.formatPhrase(RenderingContext.CAPABILITY_SEARCHES));
760    tr.th().code().b().tx("_include");
761    tr.th().code().b().tx("_revinclude");
762    tr.th().b().tx(context.formatPhrase(RenderingContext.CAPABILITY_OP));
763
764    XhtmlNode tbody = t.addTag("tbody");
765    XhtmlNode profCell = null;
766    boolean hasProf = false;
767    boolean hasSupProf = false;
768    int resCount = 1;
769    String countString = "";
770    for (CapabilityStatementRestResourceComponent r : rest.getResource()) {
771      tr = tbody.tr();
772      countString = Integer.toString(count) + "-" + Integer.toString(resCount);
773      tr.td().ah("#" + r.getType() + countString).addText(r.getType());
774      resCount++;
775      //Show profiles
776      profCell = tr.td();
777      hasProf = r.hasProfile();
778      hasSupProf = r.hasSupportedProfile();
779      if ((!hasProf) && (!hasSupProf)) {
780        profCell.nbsp();
781      }
782      else if (hasProf) {
783        addResourceLink(profCell, r.getProfile(), r.getProfile());
784        //profCell.ah(r.getProfile()).addText(r.getProfile());
785        if (hasSupProf) {
786          profCell.br();
787          profCell.addTag("em").addText(context.formatPhrase(RenderingContext.CAPABILITY_ADD_SUPP_PROF));
788          renderSupportedProfiles(profCell, r);
789        }
790      }
791      else {    //Case of only supported profiles
792        profCell.addText(context.formatPhrase(RenderingContext.CAPABILITY_SUPP_PROFS));
793        renderSupportedProfiles(profCell, r);
794      }
795      //Show capabilities
796      tr.td().addText(showOp(r, TypeRestfulInteraction.READ));
797      if (hasVRead)
798        tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.VREAD));
799      tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.SEARCHTYPE));
800      tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.UPDATE));
801      if (hasPatch)
802        tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.PATCH));
803      tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.CREATE));
804      if (hasDelete)
805        tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.DELETE));
806      if (hasUpdates)
807        tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.HISTORYINSTANCE));
808      if (hasHistory)
809        tr.td().attribute("class", "text-center").addText(showOp(r, TypeRestfulInteraction.HISTORYTYPE));
810      //Show search parameters
811      List<String> stringList = new ArrayList<String>();
812      getParams(stringList,r.getSearchParam());
813      getCombinedParams(stringList,r.getExtensionsByUrl(COMBINED));
814      tr.td().addText(getSeparatedList(stringList,","));
815      //Show supported includes
816      stringList = getStringListFromStringTypeList(r.getSearchInclude());
817      addSeparatedListOfCodes(tr.td(), stringList, ",");
818      //Show supported revIncludes
819      stringList = getStringListFromStringTypeList(r.getSearchRevInclude());
820      addSeparatedListOfCodes(tr.td(), stringList, ",");
821      //Show supported operations
822      stringList = getStringListFromOperations(r.getOperation());
823      addSeparatedListOfCodes(tr.td(), stringList, ",");
824    }
825  }
826
827  private List<String> getCombinedParams(List<String> paramNames, List<Extension> paramExtensions) {
828    for (Extension e : paramExtensions) {
829      String capExpectation = expectationForDisplay(e,EXPECTATION);
830      if (!Utilities.noString(capExpectation)) {
831        if (capExpectation.equals("SHALL") || capExpectation.equals("SHOULD") || capExpectation.equals("MAY")) {
832          paramNames.add(printCombinedParams(e));
833        }
834      }
835    }
836    return paramNames;
837  }
838
839  private void renderSupportedProfiles(XhtmlNode profCell, CapabilityStatementRestResourceComponent r) throws IOException {
840    for (CanonicalType sp: r.getSupportedProfile()) { 
841      profCell.br();
842      profCell.nbsp().nbsp();
843      StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, sp.getValue());
844      if (sd != null) {
845        profCell.ah(sd.getWebPath()).addText(sd.present());
846      } else {
847        profCell.ah(sp.getValue()).addText(sp.getValue());        
848      }
849    }
850    if (r.hasExtension(ToolingExtensions.EXT_PROFILE_MAPPING)) {
851      profCell.br();
852      profCell.b().tx(context.formatPhrase(RenderingContext.CAPABILITY_PROF_MAP));
853      XhtmlNode tbl = profCell.table("grid");
854      boolean doco = false;
855      for (Extension ext : r.getExtensionsByUrl(ToolingExtensions.EXT_PROFILE_MAPPING)) {
856        doco = doco || ext.hasExtension("documentation");
857      }
858      XhtmlNode tr = tbl.tr();
859      tr.th().tx(context.formatPhrase(RenderingContext.GENERAL_CRIT));
860      tr.th().tx(context.formatPhrase(RenderingContext.GENERAL_PROF));
861      if (doco) {
862        tr.th().tx(context.formatPhrase(RenderingContext.GENERAL_CRIT));
863      }
864      for (Extension ext : r.getExtensionsByUrl(ToolingExtensions.EXT_PROFILE_MAPPING)) {
865        tr = tbl.tr();
866        tr.td().code().tx(ToolingExtensions.readStringExtension(ext, "search"));
867        String url = ToolingExtensions.readStringExtension(ext, "profile");
868        StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, url);
869        if (sd != null) {
870          tr.td().code().ah(sd.getWebPath()).tx(sd.present());
871        } else {
872          tr.td().code().tx(url);
873        }
874        if (doco) {
875          tr.td().code().markdown(ToolingExtensions.readStringExtension(ext, "documentation"), "documentation"); 
876        }
877      }      
878    }
879  }
880
881  private String printCombinedParams(Extension e) {
882    StringBuilder params = new StringBuilder();
883    List<Extension> requiredParams = e.getExtensionsByUrl("required");
884    List<Extension> optionalParams = e.getExtensionsByUrl("optional");
885    boolean first = true;
886    for (Extension param : requiredParams) {
887      if (!first) {
888        params.append("+");
889      }
890      first = false;
891      params.append(param.getValueStringType());
892    }
893    for (Extension param : optionalParams) {
894      params.append("+");
895      params.append(param.getValueStringType());
896    }
897    return params.toString();
898  }
899
900  private List<String> getParams(List<String> paramNames, List<CapabilityStatementRestResourceSearchParamComponent> params) {
901    for (CapabilityStatementRestResourceSearchParamComponent p : params) {
902      String capExpectation = expectationForDisplay(p,EXPECTATION);
903      if (!Utilities.noString(capExpectation)) {
904        if (capExpectation.equals("SHALL") || capExpectation.equals("SHOULD") || capExpectation.equals("MAY")) {
905          paramNames.add(p.getName());
906        }
907      }
908      else {  //When there is not extension, assume parameters are required
909        paramNames.add(p.getName()); 
910      }
911    }
912    return paramNames;
913  }
914
915  private String getSeparatedList(List<String> list, String separator) {
916    StringBuilder result = new StringBuilder();
917    boolean first = true;
918    for (String s : list) {
919      if (!first) {
920        result.append(separator + " ");
921      }
922      first = false;
923      result.append(s);
924    }
925    return result.toString();
926  }
927
928  private List<String> getStringListFromStringTypeList(List<StringType> list) {
929    List<String> stringList = new ArrayList<String>();
930    for (StringType st : list) {
931      stringList.add(st.asStringValue());
932    }
933    return stringList;
934  }
935
936  private void addSeparatedListOfCodes(XhtmlNode parent, List<String> list, String separator) {
937    boolean first = true;
938    for (String s : list) {
939      if (!first) {
940        parent.addText(separator + " ");
941      }
942      first = false;
943      parent.code().addText(s);
944    }
945  }
946
947  private List<String> getStringListFromOperations(List<CapabilityStatementRestResourceOperationComponent> list) {
948    List<String> result = new ArrayList<String>();
949    for (CapabilityStatementRestResourceOperationComponent op : list) {
950      String capExpectation = expectationForDisplay(op,EXPECTATION);
951      if (!Utilities.noString(capExpectation)) {
952        if (capExpectation.equals("SHALL") || capExpectation.equals("SHOULD") || capExpectation.equals("MAY")) {
953          result.add("$"+op.getName());
954        }
955      }
956    }
957    return result;
958  }
959
960  private void addResourceConfigPanel(XhtmlNode x, CapabilityStatementRestResourceComponent r, int nextLevel, int count, int resCount, boolean igRenderingMode) throws FHIRFormatError, DefinitionException, IOException {
961    XhtmlNode panel= null;
962    XhtmlNode body = null;
963    XhtmlNode panelHead = null;
964    XhtmlNode panelRef = null;
965    
966    String countString = Integer.toString(count) + "-" + Integer.toString(resCount);
967    panel = x.div().attribute("class", "panel panel-default");
968    if (igRenderingMode) {
969      panelHead = panel.div().attribute("class", "panel-heading").attribute("role", "tab").attribute("id","heading" + countString).h(nextLevel,r.getType() + countString).attribute("class", "panel-title");
970      panelRef = panelHead.ah("#collapse" + countString).attribute("role","button").attribute("data-toggle", "collapse").attribute("aria-expanded","true").attribute("aria-controls","collapse" + countString);
971      //panelRef = panelHead.addTag("button").attribute("href","#collapse" + countString).attribute("role","button").attribute("data-toggle", "collapse").attribute("aria-expanded","false").attribute("aria-controls","collapse" + countString);
972      //panelRef.span("float: right;","").attribute("class", "lead").addText("Resource Conformance: " + getResourceExpectation(r));
973      panelRef.span("float: right;","").addText("Resource Conformance: " + getResourceExpectation(r));
974      panelRef.addText(r.getType());
975      body = panel.div().attribute("class", collapseClass).attribute("id","collapse" + countString).attribute("role","tabpanel").attribute("aria-labelledby","heading" + countString).div().attribute("class", "panel-body").div().attribute("class", "container");
976    }
977    else {
978      panelHead = panel.div().attribute("class", "panel-heading").h(nextLevel,r.getType() + countString).attribute("class", "panel-title");
979      panelHead.span("float: right;","").addText(context.formatPhrase(RenderingContext.CAPABILITY_RES_CONF, getResourceExpectation(r)) + " ");
980      panelHead.addText(r.getType());
981      body = panel.div().attribute("class", "panel-body").div().attribute("class", "container");
982    }
983    
984    
985    //Top part of panel
986    XhtmlNode cell = null;
987    XhtmlNode row = body.div().attribute("class", "row");   
988    String text = r.getProfile();
989    boolean pullInteraction = false;
990    String refPolicyWidth = "col-lg-3";
991    if (!Utilities.noString(text)) {
992      cell = row.div().attribute("class", "col-lg-6");
993      addLead(cell,context.formatPhrase(RenderingContext.CAPABILITY_BASE_SYS));
994      cell.br();
995      addResourceLink(cell, text, text);
996      cell=row.div().attribute("class", "col-lg-3");
997      addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_PROF_CONF));
998      cell.br();
999      cell.b().addText(getProfileExpectation(r.getProfileElement()));
1000    }
1001    else {   //No profile, use FHIR Core Resource
1002      cell = row.div().attribute("class", "col-lg-4");
1003      addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_FHIR));
1004      cell.br();
1005      cell.ah(currentFhirBase + r.getType().toLowerCase() + ".html").addText(r.getType());
1006      pullInteraction = true;
1007      refPolicyWidth = "col-lg-4";
1008    }
1009    
1010    cell = row.div().attribute("class", refPolicyWidth);
1011    addLead(cell,context.formatPhrase(RenderingContext.CAPABILITY_REF_PROF));
1012    cell.br();
1013    addSeparatedListOfCodes(cell, getReferencePolicyStrings(r.getReferencePolicy()) , ",");
1014    if (pullInteraction) {
1015      addInteractions(row, r, 4);
1016    }
1017    body.para();
1018    List<CanonicalType> supportedProfiles = r.getSupportedProfile();
1019    if (supportedProfiles.size() > 0) {
1020      row = body.div().attribute("class", "row");
1021      cell = row.div().attribute("class", "col-6");
1022      addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_SUPP_PROFS));
1023      XhtmlNode para = cell.para();
1024      boolean first = true;
1025      for (CanonicalType c : supportedProfiles) {
1026        if (!first) {
1027          para.br();
1028        }
1029        first=false;
1030        addResourceLink(para, c.asStringValue(), c.asStringValue());
1031        //para.ah(c.asStringValue()).addText(c.asStringValue());
1032      }  
1033    }
1034    if (!pullInteraction) {
1035      if (supportedProfiles.size() < 1) {
1036        row = body.div().attribute("class", "row");
1037      }
1038      addInteractions(row, r, 6);
1039    }
1040
1041    //Resource Documentation
1042    body.para();
1043    String mdText = r.getDocumentation();
1044    if (!Utilities.noString(mdText)) {
1045      row = body.div().attribute("class", "row");
1046      cell = row.div().attribute("class", "col-12");
1047      addLead(cell, context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION));
1048      addMarkdown(cell.blockquote(), mdText);
1049    }
1050
1051    //Resource search parameters
1052    ResourceSearchParams sParams = collectParams(r);
1053    addSearchParams(body, sParams);
1054    //addSearchParamsDocumentation(body, sParams);
1055    //Resource operations
1056    ResourceOperations ops = collectOperations(r);
1057    addExtendedOperations(body, ops);
1058  }
1059
1060  private void addExtendedOperations(XhtmlNode body, ResourceOperations ops) {
1061    if (ops == null) return;
1062    Map<String, List<SingleOperation>> map = ops.getOperations();
1063    if (!hasOperations(map)) return;
1064    XhtmlNode row;
1065    XhtmlNode cell;
1066    XhtmlNode table;
1067    XhtmlNode tbody;
1068    XhtmlNode tr;
1069    row = body.div().attribute("class", "row");
1070    cell = row.div().attribute("class", "col-12");
1071    addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_EXT_OP));
1072    table = cell.table("table table-condensed table-hover");
1073    tr = table.addTag("thead").tr();
1074    tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_CONFORMANCE));
1075    tr.th().addText(context.formatPhrase(RenderingContext.CAPABILITY_OPER));
1076    tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION));
1077    tbody = table.addTag("tbody");
1078    addOps(tbody, map, "supported");
1079    addOps(tbody, map, "SHALL");
1080    addOps(tbody, map, "SHOULD");
1081    addOps(tbody, map, "MAY");
1082    addOps(tbody, map, "SHOULD-NOT");
1083    return;
1084  }
1085
1086  private ResourceOperations collectOperations(CapabilityStatementRestResourceComponent r) {
1087    List <CapabilityStatementRestResourceOperationComponent> opList = r.getOperation();
1088    if (opList.size()==0) return null;
1089    String capExpectation;
1090    SingleOperation operation;
1091    ResourceOperations ops = new ResourceOperations();
1092    
1093    for ( CapabilityStatementRestResourceOperationComponent op : opList) {
1094      capExpectation = expectationForDisplay(op,EXPECTATION);
1095      if (Utilities.noString(capExpectation)) {
1096        capExpectation = "supported";
1097      }
1098      operation = new SingleOperation(op.getName(),op.getDefinition(),op.getDocumentation(),capExpectation);
1099      ops.addOperation(capExpectation, operation);
1100    }
1101    return ops;
1102  }
1103
1104  private void addInteractions(XhtmlNode row, CapabilityStatementRestResourceComponent r, int width) {
1105    String capExpectation;
1106    String widthString = "col-lg-" + Integer.toString(width);
1107    List<String> shalls = new ArrayList<String>();
1108    List<String> shoulds = new ArrayList<String>();
1109    List<String> mays = new ArrayList<String>();
1110    List<String> shouldnots = new ArrayList<String>();
1111    List<String> supporteds = new ArrayList<String>();
1112
1113    for (ResourceInteractionComponent op : r.getInteraction()) {
1114      capExpectation = expectationForDisplay(op,EXPECTATION);
1115      if (!Utilities.noString(capExpectation)) {
1116        switch(capExpectation) {
1117          case "SHALL"      : shalls.add(op.getCode().toCode());
1118                              break;
1119          case "SHOULD"     : shoulds.add(op.getCode().toCode());
1120                              break;
1121          case "MAY"        : mays.add(op.getCode().toCode());
1122                              break;
1123          case "SHOULD-NOT" : shouldnots.add(op.getCode().toCode());
1124                              break;
1125        }
1126      }
1127      else {
1128        supporteds.add(op.getCode().toCode());
1129      }
1130    }
1131    XhtmlNode cell = row.div().attribute("class", widthString);
1132    addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_INT_SUMM));
1133    cell.br();
1134    XhtmlNode ul = cell.ul();
1135    addInteractionSummaryList(ul, "SHALL", shalls);
1136    addInteractionSummaryList(ul, "SHOULD", shoulds);
1137    addInteractionSummaryList(ul, "MAY", mays);
1138    addInteractionSummaryList(ul, "SHOULD-NOT", shouldnots);
1139    addInteractionSummaryList(ul, "", supporteds);
1140  }
1141
1142  private ResourceSearchParams collectParams(CapabilityStatementRestResourceComponent r) {
1143    ResourceSearchParams sParams = new ResourceSearchParams();
1144    String capExpectation;
1145    SingleParam param;
1146    for ( CapabilityStatementRestResourceSearchParamComponent sp : r.getSearchParam()) {
1147      capExpectation = expectationForDisplay(sp,EXPECTATION);
1148      if (Utilities.noString(capExpectation)) {
1149        capExpectation = "supported";
1150      }
1151      param = new SingleParam(sp.getName(),sp.getDefinition(),sp.getType().toCode(),sp.getDocumentation(),capExpectation, r.getType().toLowerCase());
1152      sParams.addIndividualbyName(param.getName(), param);
1153      sParams.addIndividualbyExp(capExpectation,param);
1154    }
1155    //CombinedSearchParam component;
1156    CombinedSearchParamSet combinedParams;
1157    String paramName;
1158    for (Extension e : r.getExtensionsByUrl(COMBINED)) {
1159      capExpectation = expectationForDisplay(e,EXPECTATION);
1160      if (Utilities.noString(capExpectation)) {
1161        capExpectation = "supported";
1162      }
1163      combinedParams = new CombinedSearchParamSet(capExpectation);
1164      for (Extension cmpnt : e.getExtensionsByUrl("required")) {
1165        paramName = cmpnt.getValueStringType().asStringValue();
1166        param = sParams.getIndbyName().get(paramName);
1167        if (param == null) {
1168          param = new SingleParam(paramName,"","<unknown>");
1169        }
1170        //component = new CombinedSearchParam(param, true);
1171        combinedParams.addParam(true, param);
1172      }
1173      for (Extension cmpnt : e.getExtensionsByUrl("optional")) {
1174        paramName = cmpnt.getValueStringType().asStringValue();
1175        param = sParams.getIndbyName().get(paramName);
1176        if (param == null) {
1177          param = new SingleParam(paramName);
1178        }
1179        //component = new CombinedSearchParam(param);
1180        combinedParams.addParam(false, param);
1181      }
1182      sParams.addCombinedParamSet(capExpectation, combinedParams);
1183    }
1184    return sParams;
1185  }
1186
1187  private void addSearchParams(XhtmlNode body, ResourceSearchParams sParams) {
1188    Map<String, List<CombinedSearchParamSet>> comboMap = sParams.getCombined();
1189    if (isCombinedEmpty(comboMap) && sParams.getIndbyName().size()==0) return;
1190    XhtmlNode row;
1191    XhtmlNode cell;
1192    XhtmlNode table;
1193    XhtmlNode tbody;
1194    XhtmlNode tr;
1195    row = body.div().attribute("class", "row");
1196    cell = row.div().attribute("class", "col-lg-7");
1197    addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_SEARCH_PARS));
1198    table = cell.table("table table-condensed table-hover");
1199    tr = table.addTag("thead").tr();
1200    tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_CONFORMANCE));
1201    tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_PAR));
1202    tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_TYPE));
1203    tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION));
1204    tbody = table.addTag("tbody");
1205    Map<String,List<SingleParam>> map = sParams.getIndbyExp();
1206    addIndRows(tbody, map, "supported");
1207    addIndRows(tbody, map, "SHALL");
1208    addIndRows(tbody, map, "SHOULD");
1209    addIndRows(tbody, map, "MAY");
1210    addIndRows(tbody, map, "SHOULD-NOT");
1211    cell = row.div().attribute("class", "col-lg-5");
1212    if (!isCombinedEmpty(comboMap)) {
1213      addLead(cell, context.formatPhrase(RenderingContext.CAPABILITY_COMB_SEARCH_PAR));
1214      table = cell.table("table table-condensed table-hover");
1215      tr = table.addTag("thead").tr();
1216      tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_CONFORMANCE));
1217      tr.th().addText(context.formatPhrase(RenderingContext.GENERAL_PARS));
1218      tr.th().addText(context.formatPhrase(RenderingContext.CAPABILITY_TYPS));
1219      tbody = table.addTag("tbody");
1220      addComboRows(tbody, comboMap, "supported");
1221      addComboRows(tbody, comboMap, "SHALL");
1222      addComboRows(tbody, comboMap, "SHOULD");
1223      addComboRows(tbody, comboMap, "MAY");
1224      addComboRows(tbody, comboMap, "SHOULD-NOT");
1225    }
1226    else {
1227      cell.nbsp();
1228    }
1229  }
1230
1231  private void addIndRows(XhtmlNode tbody, Map<String, List<SingleParam>> map, String exp) {
1232    XhtmlNode tr;
1233    for (SingleParam param: map.get(exp)) {
1234      tr=tbody.tr();
1235      if (!exp.equals("supported")) {
1236        tr.td().b().addText(exp);
1237      }
1238      else {
1239        tr.td().b().addText("SHALL");
1240      }
1241      addResourceLink(tr.td(), param.getName(), param.getDefinition(),true, param.getHostResource());
1242      /*if (Utilities.noString(param.getDefinition())) {
1243        tr.td().addText(param.getName());
1244      }
1245      else {
1246        tr.td().ah(param.getDefinition()).addText(param.getName());
1247      }*/
1248      tr.td().code().addText(param.getType());
1249      try {
1250        addMarkdown(tr.td(), param.getDocumentation());
1251      } catch (FHIRFormatError e) {
1252        // TODO Auto-generated catch block
1253        e.printStackTrace();
1254      } catch (DefinitionException e) {
1255        // TODO Auto-generated catch block
1256        e.printStackTrace();
1257      } catch (IOException e) {
1258        // TODO Auto-generated catch block
1259        e.printStackTrace();
1260      }
1261    }
1262  }
1263
1264  private void addOps(XhtmlNode tbody, Map<String, List<SingleOperation>> map, String exp) {
1265    XhtmlNode tr;
1266    String name;
1267    String canonicalUri;
1268    for (SingleOperation operation: map.get(exp)) {
1269      tr = tbody.tr();
1270      name = "$" + operation.getName();
1271      if (!exp.equals("supported")) {
1272        tr.td().b().addText(exp);
1273      }
1274      else {
1275        tr.td().b().addText("SHALL");
1276      }
1277      canonicalUri = operation.getDefinition();
1278      addResourceLink(tr.td(), name, canonicalUri);
1279      /*
1280      if (Utilities.noString(operation.getDefinition())) {
1281        tr.td().addText(name);
1282      }
1283      else {
1284        tr.td().ah(operation.getDefinition()).addText(name);
1285        Resource cr = context.getContext().fetchResource(Resource.class, operation.getDefinition());
1286      } */
1287      try {
1288        addMarkdown(tr.td(), operation.getDocumentation());
1289      } catch (FHIRFormatError e) {
1290        // TODO Auto-generated catch block
1291        e.printStackTrace();
1292      } catch (DefinitionException e) {
1293        // TODO Auto-generated catch block
1294        e.printStackTrace();
1295      } catch (IOException e) {
1296        // TODO Auto-generated catch block
1297        e.printStackTrace();
1298      }
1299    }
1300  }
1301
1302  private void addComboRows(XhtmlNode tbody, Map<String, List<CombinedSearchParamSet>> map, String exp) {
1303    XhtmlNode tr,td;
1304    boolean first;
1305    for (CombinedSearchParamSet paramSet: map.get(exp)) {
1306      tr=tbody.tr();
1307      if (!exp.equals("supported")) {
1308        tr.td().b().addText(exp);
1309      }
1310      else {
1311        tr.td().nbsp();
1312      }
1313      //Parameter combination
1314      td = tr.td();
1315      first = true;
1316      for (SingleParam p : paramSet.getParams().get(true)) {
1317        if (!first) {
1318          td.addText("+");
1319        }
1320        first=false;
1321        if (Utilities.noString(p.getDefinition())) {
1322          td.addText(p.getName());
1323        }
1324        else {
1325          addResourceLink(td, p.getName(), p.getDefinition(), true, p.getHostResource());
1326          //td.ah(p.param.getDefinition()).addText(p.param.getName());
1327        }
1328      }
1329      if (paramSet.getParams().get(false).size() > 0) {
1330        td.addText("(");
1331        for (SingleParam p : paramSet.getParams().get(false)) {
1332          td.addText("+");
1333          if (Utilities.noString(p.getDefinition())) {
1334            td.addText(p.getName());
1335          }
1336          else {
1337            addResourceLink(td, p.getName(), p.getDefinition(), true, p.getHostResource());
1338            //td.ah(p.param.getDefinition()).addText(p.param.getName());
1339          }
1340        }
1341        td.addText(")");
1342      }
1343      //types combination
1344      td = tr.td();
1345      first = true;
1346      for (SingleParam p : paramSet.getParams().get(true)) {
1347        if (!first) {
1348          td.addText("+");
1349        }
1350        first=false;
1351        td.code().addText(p.getType());
1352      }
1353      if (paramSet.getParams().get(false).size() > 0) {
1354        td.addText("(");
1355        for (SingleParam p : paramSet.getParams().get(false)) {
1356          td.addText("+");
1357          if (!p.getType().equals("<unknown>")) {
1358            td.code().addText(p.getType());
1359          }
1360          else {
1361            td.addText(p.getType());
1362          }
1363        }
1364        td.addText(")");
1365      }
1366    }
1367  }
1368
1369  boolean isCombinedEmpty(Map<String, List<CombinedSearchParamSet>> combinedMap) {
1370    boolean result = true;
1371    for (String s : combinedMap.keySet()) {
1372      if (combinedMap.get(s).size() > 0) {
1373        result=false;
1374        break;
1375      }
1376    }
1377    return result;
1378  }
1379
1380  boolean hasOperations(Map<String, List<SingleOperation>> operationsMap) {
1381    boolean result = false;
1382    for (String s : operationsMap.keySet()) {
1383      if (operationsMap.get(s).size() > 0) {
1384        result=true;
1385        break;
1386      }
1387    }
1388    return result;
1389  }
1390
1391  private List<String> getReferencePolicyStrings(List<Enumeration<ReferenceHandlingPolicy>> list) {
1392    List<String> stringList = new ArrayList<String>();
1393    for (Enumeration<ReferenceHandlingPolicy> p : list) {
1394      stringList.add(p.getCode());
1395    }
1396    return stringList;
1397  }
1398  private String getResourceExpectation(CapabilityStatementRestResourceComponent r) {
1399    String capExpectation = expectationForDisplay(r,EXPECTATION);
1400    if (!Utilities.noString(capExpectation)) return capExpectation;
1401    boolean shalls = false;
1402    boolean shoulds = false;
1403    boolean mays = false;
1404    boolean shouldnots = false;
1405    for (ResourceInteractionComponent ric : r.getInteraction()) {
1406      capExpectation = expectationForDisplay(ric,EXPECTATION);
1407      if (!Utilities.noString(capExpectation)) {
1408        switch(capExpectation) {
1409          case "SHALL" :  shalls = true;
1410                          break;
1411          case "SHOULD" : shoulds = true;
1412                          break;
1413          case "MAY" :    mays = true;
1414                          break;
1415          case "SHOULD-NOT" : shouldnots = true;
1416                              break;
1417        }
1418      }
1419    }
1420    if (shalls) return "SHALL";
1421    //Check search parameters requirements
1422    for ( CapabilityStatementRestResourceSearchParamComponent sp : r.getSearchParam()) {
1423      capExpectation = expectationForDisplay(sp,EXPECTATION);
1424      if (!Utilities.noString(capExpectation)) {
1425        switch(capExpectation) {
1426          case "SHALL" :  shalls = true;
1427                          break;
1428          case "SHOULD" : shoulds = true;
1429                          break;
1430          case "MAY" :    mays = true;
1431                          break;
1432          case "SHOULD-NOT" : shouldnots = true;
1433                              break;
1434        }
1435      }
1436    }
1437    if (shalls) return "SHALL";
1438    if (shoulds) return "SHOULD";
1439    if (mays) return "MAY";
1440    if (shouldnots) return "SHOULD NOT";
1441    return "supported";
1442  }
1443
1444  private String getProfileExpectation(CanonicalType r) {
1445    String capExpectation = expectationForDisplay(r,EXPECTATION);
1446    if (!Utilities.noString(capExpectation)) return capExpectation;
1447    return "SHALL";
1448  }
1449
1450  private void addLead(XhtmlNode node, String text) {
1451    node.addTag("span").attribute("class", "lead").addText(text);
1452  }
1453
1454  public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
1455    if (r.has("title")) {
1456      return r.children("title").get(0).getBase().primitiveValue();
1457    }
1458    if (r.has("name")) {
1459      return r.children("name").get(0).getBase().primitiveValue();
1460    }
1461    return "??";
1462  }
1463
1464  private void addResourceLink(XhtmlNode node, String name, String canonicalUri) {
1465    addResourceLink(node, name, canonicalUri, false, "");
1466  }
1467
1468  private void addResourceLink(XhtmlNode node, String name, String canonicalUri, boolean isParam, String hostResource) {
1469    if (Utilities.noString(canonicalUri)) {
1470      node.addText(name);
1471      return;
1472    }
1473
1474    Resource cr = context.getContext().fetchResource(Resource.class, canonicalUri);
1475    if (cr == null) {
1476      node.addText(name);
1477    }
1478    else {
1479      //String path = cr.getUserString("path");
1480      String path = cr.getWebPath();
1481      if (Utilities.noString(path)) {
1482        if (isParam && (canonicalUri.toLowerCase().startsWith(SP_BASE)) && (!Utilities.noString(currentFhirBase)) && (!Utilities.noString(hostResource))) {
1483          String resourceName = "";
1484          if (canonicalUri.substring(SP_BASE.length()).split("-")[0].toLowerCase().equals("resource")) {
1485            resourceName = "resource";
1486          }
1487          else if (canonicalUri.substring(SP_BASE.length()).split("-")[0].toLowerCase().equals("domainresource")) {
1488            resourceName = "domainresource";
1489          }
1490          else {
1491            resourceName = hostResource;
1492          }
1493
1494          node.ah(currentFhirBase + resourceName + ".html#search").addText(name);
1495        }
1496        else {
1497          node.addText(name);
1498        }
1499      }
1500      else {
1501        node.ah(path).addText(name);
1502      }
1503    }
1504  }
1505
1506  private String expectationForDisplay(Element e, String url) {
1507    String result;
1508    try {
1509      result = e.getExtensionString(url);
1510      return result;
1511    }
1512    catch (FHIRException fex) {
1513      List<Extension> ext = e.getExtensionsByUrl(url); 
1514      if (ext.isEmpty()) 
1515        return null; 
1516      if (!ext.get(0).hasValue())
1517        return null;
1518      multExpectationsPresent = true;
1519      return ext.get(0).getValue().primitiveValue() + "-⹋⹋";
1520    }
1521
1522  }
1523
1524  private void addWarningPanel(XhtmlNode node, String text) {
1525    XhtmlNode panel = node.addTag("div").attribute("class","panel panel-danger").addTag("div").attribute("class","panel-body");
1526    panel.addTag("span").attribute("class","label label-danger").addText("Error detected");
1527    panel.addText(" " + text);
1528  }
1529}