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