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