001package org.hl7.fhir.r5.renderers;
002
003import java.util.List;
004import java.util.Map;
005import java.util.HashMap;
006import java.util.ArrayList;
007import java.io.IOException;
008import java.io.UnsupportedEncodingException;
009
010import org.hl7.fhir.exceptions.DefinitionException;
011import org.hl7.fhir.exceptions.FHIRException;
012import org.hl7.fhir.exceptions.FHIRFormatError;
013import org.hl7.fhir.r5.elementmodel.Element;
014import org.hl7.fhir.r5.model.Base;
015import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
016import org.hl7.fhir.r5.model.CanonicalResource;
017import org.hl7.fhir.r5.model.CodeSystem;
018import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
019import org.hl7.fhir.r5.model.CodeableReference;
020import org.hl7.fhir.r5.model.Coding;
021import org.hl7.fhir.r5.model.DataType;
022import org.hl7.fhir.r5.model.DomainResource;
023import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
024import org.hl7.fhir.r5.model.Extension;
025import org.hl7.fhir.r5.model.Narrative;
026import org.hl7.fhir.r5.model.PrimitiveType;
027import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
028import org.hl7.fhir.r5.model.Reference;
029import org.hl7.fhir.r5.model.Resource;
030import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper;
031import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper;
032import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
033import org.hl7.fhir.r5.renderers.utils.DirectWrappers.ResourceWrapperDirect;
034import org.hl7.fhir.r5.renderers.utils.ElementWrappers.ResourceWrapperMetaElement;
035import org.hl7.fhir.r5.renderers.utils.RenderingContext;
036import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
037import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext;
038import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceReferenceKind;
039import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference;
040import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
041import org.hl7.fhir.r5.utils.EOperationOutcome;
042import org.hl7.fhir.r5.utils.ToolingExtensions;
043import org.hl7.fhir.r5.utils.XVerExtensionManager;
044import org.hl7.fhir.utilities.Utilities;
045import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
046import org.hl7.fhir.utilities.xhtml.NodeType;
047import org.hl7.fhir.utilities.xhtml.XhtmlNode;
048import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
049
050public abstract class ResourceRenderer extends DataRenderer {
051
052  public enum RendererType {
053    NATIVE, PROFILE, LIQUID
054
055  }
056
057  protected ResourceContext rcontext;
058  protected XVerExtensionManager xverManager;
059  protected boolean multiLangMode;
060  
061  
062  public ResourceRenderer(RenderingContext context) {
063    super(context);
064  }
065
066  public ResourceRenderer(RenderingContext context, ResourceContext rcontext) {
067    super(context);
068    this.rcontext = rcontext;
069  }
070
071  public ResourceContext getRcontext() {
072    return rcontext;
073  }
074
075  public ResourceRenderer setRcontext(ResourceContext rcontext) {
076    this.rcontext = rcontext;
077    return this;
078  }
079
080  
081  public boolean isMultiLangMode() {
082    return multiLangMode;
083  }
084
085  public ResourceRenderer setMultiLangMode(boolean multiLangMode) {
086    this.multiLangMode = multiLangMode;
087    return this;
088  }
089
090  public XhtmlNode build(Resource dr) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome {
091    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
092    render(x, dr);
093    return x;
094  }
095  /**
096   * given a resource, update it's narrative with the best rendering available
097   * 
098   * @param r - the domain resource in question
099   * 
100   * @throws IOException
101   * @throws EOperationOutcome 
102   * @throws FHIRException 
103   */
104  
105  public void render(DomainResource r) throws IOException, FHIRException, EOperationOutcome {  
106    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
107    boolean hasExtensions;
108    hasExtensions = render(x, r);
109    String an = r.fhirType()+"_"+r.getId();
110    if (context.isAddName()) {
111      if (!hasAnchorName(x, an)) {
112        injectAnchorName(x, an);
113      }
114    }
115    inject(r, x, hasExtensions ? NarrativeStatus.EXTENSIONS :  NarrativeStatus.GENERATED);
116  }
117
118  public XhtmlNode render(ResourceWrapper r) throws IOException, FHIRException, EOperationOutcome { 
119    assert r.getContext() == context;
120    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
121    boolean hasExtensions = render(x, r);
122
123    String an = r.fhirType()+"_"+r.getId();
124    if (context.isAddName()) {
125      if (!hasAnchorName(x, an)) {
126        injectAnchorName(x, an);
127      }
128    }
129    if (r.hasNarrative()) {
130      r.injectNarrative(this, x, hasExtensions ? NarrativeStatus.EXTENSIONS :  NarrativeStatus.GENERATED);
131    }
132    return x;
133  }
134
135  public XhtmlNode checkNarrative(ResourceWrapper r) throws IOException, FHIRException, EOperationOutcome { 
136    assert r.getContext() == context;
137    XhtmlNode x = r.getNarrative();
138    String an = r.fhirType()+"_"+r.getId();
139    if (context.isAddName()) {
140      if (!hasAnchorName(x, an)) {
141        injectAnchorName(x, an);
142      }
143    }
144    return x;
145  }
146
147  private void injectAnchorName(XhtmlNode x, String an) {
148    XhtmlNode ip = x;
149    while (ip.hasChildren() && "div".equals(ip.getChildNodes().get(0).getName())) {
150      ip = ip.getChildNodes().get(0);
151    }
152    ip.addTag(0, "a").setAttribute("name", an).tx(" ");    
153  }
154
155  protected boolean hasAnchorName(XhtmlNode x, String an) {
156    if ("a".equals(x.getName()) && an.equals(x.getAttribute("name"))) {
157      return true;
158    }
159    if (x.hasChildren()) {
160      for (XhtmlNode c : x.getChildNodes()) {
161        if (hasAnchorName(c, an)) {
162          return true;
163        }
164      }
165    }
166    return false;
167  }
168
169  public abstract boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome;
170  
171  public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome {
172    ProfileDrivenRenderer pr = new ProfileDrivenRenderer(context);
173    return pr.render(x, r);
174  }
175  
176  public void describe(XhtmlNode x, Resource r) throws UnsupportedEncodingException, IOException {
177    x.tx(display(r));
178  }
179
180  public void describe(XhtmlNode x, ResourceWrapper r) throws UnsupportedEncodingException, IOException {
181    x.tx(display(r));
182  }
183
184  public abstract String display(Resource r) throws UnsupportedEncodingException, IOException;
185  public abstract String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException;
186  
187  public void inject(DomainResource r, XhtmlNode x, NarrativeStatus status) {
188    r.getText().setUserData("renderer.generated", true);
189    if (!r.hasText() || !r.getText().hasDiv()) {
190      r.setText(new Narrative());
191      r.getText().setStatus(status);      
192    }
193    if (multiLangMode) {
194      if (!r.getText().hasDiv()) { 
195        XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
196        div.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
197        r.getText().setDiv(div);
198      } else {
199        r.getText().getDiv().getChildNodes().removeIf(c -> !"div".equals(c.getName()) || !c.hasAttribute("xml:lang"));
200      }
201      markLanguage(x);
202      r.getText().getDiv().getChildNodes().add(x);
203    } else {
204      if (!x.hasAttribute("xmlns"))
205        x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
206      if (r.hasLanguage()) {
207        // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
208        x.setAttribute("lang", r.getLanguage());
209        x.setAttribute("xml:lang", r.getLanguage());
210      }
211      r.getText().setDiv(x);
212    }
213  }
214
215  public void renderCanonical(Resource res, XhtmlNode x, String url) throws UnsupportedEncodingException, IOException {
216    ResourceWrapper rw = new ResourceWrapperDirect(this.context, res);
217    renderCanonical(rw, x, url);
218  }
219
220  public void renderCanonical(ResourceWrapper rw, XhtmlNode x, String url) throws UnsupportedEncodingException, IOException {
221    renderCanonical(rw, x, url, true, rw.getResource()); 
222  }
223  
224  public void renderCanonical(ResourceWrapper rw, XhtmlNode x, String url, boolean allowLinks, Resource src) throws UnsupportedEncodingException, IOException {
225    if (url == null) {
226      return;
227    }
228    Resource target = context.getWorker().fetchResource(Resource.class, url, src);
229    if (target == null || !(target instanceof CanonicalResource)) {
230      x.code().tx(url);
231    } else {
232      CanonicalResource cr = (CanonicalResource) target;
233      if (url.contains("|")) {
234        if (target.hasWebPath()) {
235          x.ah(target.getWebPath()).tx(cr.present()+ context.formatPhrase(RenderingContext.RES_REND_VER) +cr.getVersion()+")");
236        } else {
237          url = url.substring(0, url.indexOf("|"));
238          x.code().tx(url);
239          x.tx(": "+cr.present()+ context.formatPhrase(RenderingContext.RES_REND_VER) +cr.getVersion()+")");          
240        }
241      } else {
242        if (target.hasWebPath()) {
243          x.ah(target.getWebPath()).tx(cr.present());
244        } else {
245          x.code().tx(url);
246          x.tx(" ("+cr.present()+")");          
247        }
248      }
249    }
250  }
251
252  public void render(Resource res, XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException {
253    if (type instanceof Reference) {
254      renderReference(res, x, (Reference) type);
255    } else if (type instanceof CodeableReference) {
256      CodeableReference cr = (CodeableReference) type;
257      if (cr.hasReference()) {
258        renderReference(res, x, cr.getReference());
259      } else {
260        render(x, type);
261      } 
262    } else { 
263      render(x, type);
264    }
265  }
266
267  public void render(ResourceWrapper res, XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException {
268    if (type instanceof Reference) {
269      renderReference(res, x, (Reference) type);
270    } else if (type instanceof CodeableReference) {
271      CodeableReference cr = (CodeableReference) type;
272      if (cr.hasReference()) {
273        renderReference(res, x, cr.getReference());
274      } else {
275        render(x, type);
276      } 
277    } else { 
278      render(x, type);
279    }
280  }
281
282  public void renderReference(Resource res, XhtmlNode x, Reference r) throws UnsupportedEncodingException, IOException {
283    ResourceWrapper rw = new ResourceWrapperDirect(this.context, res);
284    renderReference(rw, x, r);
285  }
286
287  public void renderReference(ResourceWrapper rw, XhtmlNode x, Reference r) throws UnsupportedEncodingException, IOException {
288    renderReference(rw, x, r, true); 
289  }
290
291  public void renderReference(Resource res, HierarchicalTableGenerator gen, List<Piece> pieces, Reference r, boolean allowLinks) throws UnsupportedEncodingException, IOException {
292    if (r == null) { 
293      pieces.add(gen.new Piece(null, "null!", null));
294      return;
295    }
296    ResourceWrapper rw = new ResourceWrapperDirect(this.context, res);
297    ResourceWithReference tr = null;
298    String link = null;
299    StringBuilder text = new StringBuilder();
300    if (r.hasReferenceElement() && allowLinks) {
301      tr = resolveReference(rw, r.getReference());
302
303      if (!r.getReference().startsWith("#")) {
304        if (tr != null && tr.getReference() != null) {
305          link = tr.getReference();
306        } else if (r.getReference().contains("?")) {
307          text.append(context.formatPhrase(RenderingContext.RES_REND_COND_REF)+" ");
308        } else {
309          link = r.getReference();
310        }
311      } 
312    }
313    if (tr != null && tr.getReference() != null && tr.getReference().startsWith("#")) {
314      text.append(context.formatPhrase(RenderingContext.RES_REND_SEE_ON_THIS_PAGE)+" ");
315    }
316    // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative
317    String display = r.hasDisplayElement() ? r.getDisplay() : null;
318    String name = tr != null && tr.getResource() != null ? tr.getResource().getNameFromResource() : null; 
319    
320    if (display == null && (tr == null || tr.getResource() == null)) {
321      if (!Utilities.noString(r.getReference())) {
322        text.append(r.getReference());
323      } else if (r.hasIdentifier()) {
324        text.append(displayIdentifier(r.getIdentifier()));
325      } else {
326        text.append("??");        
327      }
328    } else if (context.isTechnicalMode()) {
329      text.append(r.getReference());
330      if (display != null) {
331        text.append(": "+display);
332      }
333      if ((tr == null || (tr.getReference() != null && !tr.getReference().startsWith("#"))) && name != null) {
334        text.append(" \""+name+"\"");
335      }
336      if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) {
337        text.append("(");
338        for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_ID)) {
339          if (ex.hasValue()) {
340            text.append(", ");
341            text.append("#"+ex.getValue().primitiveValue());
342          }
343        }
344        for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_PATH)) {
345          if (ex.hasValue()) {
346            text.append(", ");
347            text.append("/#"+ex.getValue().primitiveValue());
348          }
349        }
350        text.append(")");
351      }  
352    } else {
353      if (display != null) {
354        text.append(display);
355      } else if (name != null) {
356        text.append(name);
357      } else {
358        text.append(context.formatPhrase(RenderingContext.RES_REND_DESC));
359      }
360    }
361    if (tr != null && tr.getReference() != null && tr.getReference().startsWith("#")) {
362      text.append(")");
363    }      
364    pieces.add(gen.new Piece(link,text.toString(), null));
365  }
366  
367  public void renderReference(ResourceWrapper rw, XhtmlNode x, Reference r, boolean allowLinks) throws UnsupportedEncodingException, IOException {
368    if (r == null) {
369      x.tx("null!");
370      return;
371    }
372    XhtmlNode c = null;
373    ResourceWithReference tr = null;
374    boolean onPage = false;
375    if (r.hasReferenceElement() && allowLinks) {
376      tr = resolveReference(rw, r.getReference());
377
378      if (!r.getReference().startsWith("#")) {
379        if (tr != null && tr.getReference() != null) {
380          if (tr.getReference().startsWith("#")) {
381            onPage = true;
382            if (context.getRules() == GenerationRules.IG_PUBLISHER || (tr != null && tr.getKind() != ResourceReferenceKind.BUNDLE)) {
383              c = x.ah("#hc"+tr.getReference().substring(1));
384            } else {
385              c = x;
386            }
387          } else {
388            c = x.ah(tr.getReference());
389          }
390        } else if (r.getReference().contains("?")) {
391          x.tx(context.formatPhrase(RenderingContext.RES_REND_COND_REF)+" ");
392          c = x.code("");
393        } else {
394          c = x.ah(r.getReference());
395        }
396      } else if ("#".equals(r.getReference())) {
397        c = x.ah("#");
398      } else if (context.getRules() == GenerationRules.IG_PUBLISHER || (tr != null && tr.getKind() != ResourceReferenceKind.BUNDLE)) {
399        c = x.ah("#hc"+r.getReference().substring(1));
400        onPage = true;
401      } else {
402        c = x;
403      }
404    } else {
405      c = x.span(null, null);
406    }
407    if (onPage) {
408      c.tx(context.formatPhrase(RenderingContext.RES_REND_SEE_ON_THIS_PAGE)+" ");
409    }
410    // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative
411    String display = r.hasDisplayElement() ? r.getDisplay() : null;
412    String name = tr != null && tr.getResource() != null ? tr.getResource().getNameFromResource() : null; 
413    
414    if (display == null && (tr == null || tr.getResource() == null)) {
415      if (!Utilities.noString(r.getReference())) {
416        c.addText(r.getReference());
417      } else if (r.hasIdentifier()) {
418        renderIdentifier(c, r.getIdentifier());
419      } else {
420        c.addText("??");        
421      }
422    } else if (context.isTechnicalMode()) {
423      c.addText(r.getReference());
424      if (display != null) {
425        c.addText(": "+display);
426      }
427      if ((tr == null || (tr.getReference() != null && !tr.getReference().startsWith("#"))) && name != null) {
428        x.addText(" \""+name+"\"");
429      }
430      if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) {
431        x.addText("(");
432        for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_ID)) {
433          if (ex.hasValue()) {
434            x.sep(", ");
435            x.addText("#"+ex.getValue().primitiveValue());
436          }
437        }
438        for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_PATH)) {
439          if (ex.hasValue()) {
440            x.sep(", ");
441            x.addText("/#"+ex.getValue().primitiveValue());
442          }
443        }
444        x.addText(")");
445      }  
446    } else {
447      if (display != null) {
448        c.addText(display);
449      } else if (name != null) {
450        c.addText(name);
451      } else {
452        c.tx(context.formatPhrase(RenderingContext.RES_REND_GEN_SUM)+" ");
453        if (tr != null) {
454          new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, r.getReference().startsWith("#"), true);
455        }
456      }
457    }
458  }
459
460  public void renderReference(ResourceWrapper rw, XhtmlNode x, BaseWrapper r) throws UnsupportedEncodingException, IOException {
461    XhtmlNode c = x;
462    ResourceWithReference tr = null;
463    String v;
464    if (r.has("reference")) {
465      v = r.get("reference").primitiveValue();
466      tr = resolveReference(rw, v);
467
468      if (!v.startsWith("#")) {
469        if (tr != null && tr.getReference() != null)
470          c = x.ah(tr.getReference());
471        else
472          c = x.ah(v);
473      }
474    } else {
475      v = "";
476    }
477    // what to display: if text is provided, then that. if the reference was resolved, then show the generated narrative
478    if (r.has("display")) {
479      c.addText(r.get("display").primitiveValue());
480      if (tr != null && tr.getResource() != null) {
481        c.tx(context.formatPhrase(RenderingContext.RES_REND_GEN_SUM)+" ");
482        new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, v.startsWith("#"), false);
483      }
484    } else if (tr != null && tr.getResource() != null) {
485      new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), v.startsWith("#"), v.startsWith("#"), false);
486    } else {
487      c.addText(v);
488    }
489  }
490  
491  protected ResourceWithReference resolveReference(ResourceWrapper res, String url) {
492    if (url == null)
493      return null;
494    if (url.startsWith("#") && res != null) {
495      for (ResourceWrapper r : res.getContained()) {
496        if (r.getId().equals(url.substring(1)))
497          return new ResourceWithReference(ResourceReferenceKind.CONTAINED, url, r);
498      }
499      return null;
500    }
501    String version = null;
502    if (url.contains("/_history/")) {
503      version = url.substring(url.indexOf("/_history/")+10);
504      url = url.substring(0, url.indexOf("/_history/"));
505    }
506
507    if (rcontext != null) {
508      BundleEntryComponent bundleResource = rcontext.resolve(url);
509      if (bundleResource != null) {
510        String id = bundleResource.getResource().getId();
511        if (id == null) {
512          id = makeIdFromBundleEntry(bundleResource.getFullUrl());
513        }
514        String bundleUrl = "#" + bundleResource.getResource().getResourceType().name() + "_" + id; 
515        return new ResourceWithReference(ResourceReferenceKind.BUNDLE, bundleUrl, new ResourceWrapperDirect(this.context, bundleResource.getResource()));
516      }
517      org.hl7.fhir.r5.elementmodel.Element bundleElement = rcontext.resolveElement(url, version);
518      if (bundleElement != null) {
519        String bundleUrl = null;
520        Element br = bundleElement.getNamedChild("resource", false);
521        if (br.getChildValue("id") != null) {
522          if ("Bundle".equals(br.fhirType())) {
523            bundleUrl = "#";
524          } else {
525            bundleUrl = "#" + br.fhirType() + "_" + br.getChildValue("id");
526          }
527        } else {
528          bundleUrl = "#" +fullUrlToAnchor(bundleElement.getChildValue("fullUrl"));          
529        }
530        return new ResourceWithReference(ResourceReferenceKind.BUNDLE, bundleUrl, new ResourceWrapperMetaElement(this.context, br));
531      }
532    }
533
534    Resource ae = getContext().getWorker().fetchResource(null, url, version);
535    if (ae != null)
536      return new ResourceWithReference(ResourceReferenceKind.EXTERNAL, url, new ResourceWrapperDirect(this.context, ae));
537    else if (context.getResolver() != null) {
538      return context.getResolver().resolve(context, url);
539    } else
540      return null;
541  }
542  
543  
544  protected String makeIdFromBundleEntry(String url) {
545    if (url == null) {
546      return null;
547    }
548    if (url.startsWith("urn:uuid:")) {
549      return url.substring(9).toLowerCase();
550    }
551    return fullUrlToAnchor(url);    
552  }
553
554  private String fullUrlToAnchor(String url) {
555    return url.replace(":", "").replace("/", "_");
556  }
557
558  protected void generateCopyright(XhtmlNode x, CanonicalResource cs) {
559    XhtmlNode p = x.para();
560    p.b().tx(getContext().formatPhrase(RenderingContext.RESOURCE_COPYRIGHT));
561    smartAddText(p, " " + cs.getCopyright());
562  }
563
564  public String displayReference(Resource res, Reference r) throws UnsupportedEncodingException, IOException {
565    return (context.formatPhrase(RenderingContext.GENERAL_TODO)); 
566   }
567   
568
569   public Base parseType(String string, String type) {
570     return null;
571   }
572
573   protected PropertyWrapper getProperty(ResourceWrapper res, String name) {
574     for (PropertyWrapper t : res.children()) {
575       if (t.getName().equals(name))
576         return t;
577     }
578     return null;
579   }
580
581   protected PropertyWrapper getProperty(BaseWrapper res, String name) {
582     for (PropertyWrapper t : res.children()) {
583       if (t.getName().equals(name))
584         return t;
585     }
586     return null;
587   }
588
589   protected boolean valued(PropertyWrapper pw) {
590     return pw != null && pw.hasValues();
591   }
592
593
594   protected ResourceWrapper fetchResource(BaseWrapper subject) throws UnsupportedEncodingException, FHIRException, IOException {
595     if (context.getResolver() == null)
596       return null;
597
598     PropertyWrapper ref = subject.getChildByName("reference");
599     if (ref == null || !ref.hasValues()) {
600       return null;
601     }
602     String url = ref.value().getBase().primitiveValue();
603     ResourceWithReference rr = context.getResolver().resolve(context, url);
604     return rr == null ? null : rr.getResource();
605   }
606
607
608   protected String describeStatus(PublicationStatus status, boolean experimental) {
609     switch (status) {
610     case ACTIVE: return experimental ? (context.formatPhrase(RenderingContext.GENERAL_EXPER)) : (context.formatPhrase(RenderingContext.RES_REND_ACT)); 
611     case DRAFT: return (context.formatPhrase(RenderingContext.RES_REND_DRAFT));
612     case RETIRED: return (context.formatPhrase(RenderingContext.RES_REND_RET));
613     default: return (context.formatPhrase(RenderingContext.RES_REND_UNKNOWN));
614     }
615   }
616
617   protected void renderCommitteeLink(XhtmlNode x, CanonicalResource cr) {
618     String code = ToolingExtensions.readStringExtension(cr, ToolingExtensions.EXT_WORKGROUP);
619     CodeSystem cs = context.getWorker().fetchCodeSystem("http://terminology.hl7.org/CodeSystem/hl7-work-group");
620     if (cs == null || !cs.hasWebPath())
621       x.tx(code);
622     else {
623       ConceptDefinitionComponent cd = CodeSystemUtilities.findCode(cs.getConcept(), code);
624       if (cd == null) {
625         x.tx(code);
626       } else {
627         x.ah(cs.getWebPath()+"#"+cs.getId()+"-"+cd.getCode()).tx(cd.getDisplay());
628       }
629     }
630   }
631
632   public static String makeInternalBundleLink(String fullUrl) {
633     return fullUrl.replace(":", "-");
634   }
635
636  public boolean canRender(Resource resource) {
637    return true;
638  }
639
640  protected void renderResourceHeader(ResourceWrapper r, XhtmlNode x, boolean doId) throws UnsupportedEncodingException, FHIRException, IOException {
641    XhtmlNode div = x.div().style("display: inline-block").style("background-color: #d9e0e7").style("padding: 6px")
642         .style("margin: 4px").style("border: 1px solid #8da1b4")
643         .style("border-radius: 5px").style("line-height: 60%");
644
645    String id = getPrimitiveValue(r, "id"); 
646    if (doId) {
647      div.an("hc"+id);
648    }
649
650    String lang = getPrimitiveValue(r, "language"); 
651    String ir = getPrimitiveValue(r, "implicitRules"); 
652    BaseWrapper meta = r.getChildByName("meta").hasValues() ? r.getChildByName("meta").getValues().get(0) : null;
653    String versionId = getPrimitiveValue(meta, "versionId");
654    String lastUpdated = getPrimitiveValue(meta, "lastUpdated");
655    String source = getPrimitiveValue(meta, "source");
656    
657    if (id != null || lang != null || versionId != null || lastUpdated != null) {
658      XhtmlNode p = plateStyle(div.para());
659      p.tx(context.formatPhrase(RenderingContext.GENERAL_RESOURCE));
660      p.tx(r.fhirType());
661      p.tx(" ");
662      if (id != null) {
663        p.tx("\""+id+"\" ");
664      }
665      if (versionId != null) {
666        p.tx(context.formatPhrase(RenderingContext.GENERAL_VER) + "\""+versionId+"\" ");
667      }
668      if (lastUpdated != null) {
669        p.tx(context.formatPhrase(RenderingContext.RES_REND_UPDATED) + "\"");
670        renderDateTime(p, lastUpdated);
671        p.tx("\" ");
672      }
673      if (lang != null) {
674        p.tx(" " + context.formatPhrase(RenderingContext.RES_REND_LANGUAGE) + "\""+lang+"\") ");
675      }
676    }
677    if (ir != null) {
678      plateStyle(div.para()).b().tx(context.formatPhrase(RenderingContext.RES_REND_SPEC_RULES) + " "+ir+"!");     
679    }
680    if (source != null) {
681      plateStyle(div.para()).tx(context.formatPhrase(RenderingContext.RES_REND_INFO_SOURCE) + " "+source+"!");           
682    }
683    if (meta != null) {
684      PropertyWrapper pl = meta.getChildByName("profile");
685      if (pl.hasValues()) {
686        XhtmlNode p = plateStyle(div.para());
687        p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.GENERAL_PROF), pl.getValues().size())+": ");
688        boolean first = true;
689        for (BaseWrapper bw : pl.getValues()) {
690          if (first) first = false; else p.tx(", ");
691          renderCanonical(r, p, bw.getBase().primitiveValue());
692        }
693      }
694      PropertyWrapper tl = meta.getChildByName("tag");
695      if (tl.hasValues()) {
696        XhtmlNode p = plateStyle(div.para());
697        p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.RES_REND_TAG), tl.getValues().size())+": ");
698        boolean first = true;
699        for (BaseWrapper bw : tl.getValues()) {
700          if (first) first = false; else p.tx(", ");
701          String system = getPrimitiveValue(bw, "system");
702          String version = getPrimitiveValue(bw, "version");
703          String code = getPrimitiveValue(bw, "system");
704          String display = getPrimitiveValue(bw, "system");
705          renderCoding(p, new Coding(system, version, code, display));
706        }        
707      }
708      PropertyWrapper sl = meta.getChildByName("security");
709      if (sl.hasValues()) {
710        XhtmlNode p = plateStyle(div.para());
711        p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.GENERAL_SECURITY_LABEL), tl.getValues().size())+": ");
712        boolean first = true;
713        for (BaseWrapper bw : sl.getValues()) {
714          if (first) first = false; else p.tx(", ");
715          String system = getPrimitiveValue(bw, "system");
716          String version = getPrimitiveValue(bw, "version");
717          String code = getPrimitiveValue(bw, "system");
718          String display = getPrimitiveValue(bw, "system");
719          renderCoding(p, new Coding(system, version, code, display));
720        }        
721      }
722    }
723      
724  }
725
726  private XhtmlNode plateStyle(XhtmlNode para) {
727    return para.style("margin-bottom: 0px");
728  }
729
730  private String getPrimitiveValue(BaseWrapper b, String name) throws UnsupportedEncodingException, FHIRException, IOException {
731    return b != null && b.has(name) && b.getChildByName(name).hasValues() ? b.getChildByName(name).getValues().get(0).getBase().primitiveValue() : null;
732  }
733
734  private String getPrimitiveValue(ResourceWrapper r, String name) throws UnsupportedEncodingException, FHIRException, IOException {
735    return r.has(name) && r.getChildByName(name).hasValues() ? r.getChildByName(name).getValues().get(0).getBase().primitiveValue() : null;
736  }
737
738  public void renderOrError(DomainResource dr) {
739    try {
740      render(dr);
741    } catch (Exception e) {
742      XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
743      x.para().tx(context.formatPhrase(RenderingContext.RES_REND_ERROR, e.getMessage())+" ");
744      dr.setText(null);
745      inject(dr, x, NarrativeStatus.GENERATED);   
746    }
747    
748  }
749  
750  public RendererType getRendererType() {
751    return RendererType.NATIVE;
752  }
753  
754  public class TableRowData {
755    private Map<String, List<DataType>> cols = new HashMap<>();
756    private TableData data;
757    
758    public void value(String name, DataType value) {
759      if (!cols.containsKey(name)) {
760        cols.put(name, new ArrayList<>());
761      }
762      if (!data.columns.contains(name)) {
763        data.columns.add(name);
764      }
765      cols.get(name).add(value);
766    }
767
768    public boolean hasCol(String name) {
769      return cols.containsKey(name);
770    }
771
772    public List<DataType> get(String name) {
773      return cols.get(name);
774    }
775    
776  }
777  public class TableData {
778    private String title;
779    private List<String> columns = new ArrayList<>();
780    private List<TableRowData> rows = new ArrayList<>();
781    public TableData(String title) {
782      this.title = title;
783    }
784    public String getTitle() {
785      return title;
786    }
787    public List<String> getColumns() {
788      return columns;
789    }
790    public List<TableRowData> getRows() {
791      return rows;
792    }
793    public void addColumn(String name) {
794      columns.add(name);
795    }
796    public TableRowData addRow() {
797      TableRowData res = new TableRowData();
798      rows.add(res);
799      res.data = this;
800      return res;
801    }
802  }
803
804
805  public void renderTable(TableData provider, XhtmlNode x) throws FHIRFormatError, DefinitionException, IOException {
806    List<String> columns = new ArrayList<>();
807    for (String name : provider.getColumns()) {
808      boolean hasData = false;
809      for (TableRowData row : provider.getRows()) {
810        if (row.hasCol(name)) {
811          hasData = true;
812        }
813      }
814      if (hasData) {
815        columns.add(name);
816      }
817    }
818    if (columns.size() > 0) {
819      XhtmlNode table = x.table("grid");
820      
821      if (provider.getTitle() != null) {
822        table.tr().td().colspan(columns.size()).b().tx(provider.getTitle());
823      }
824      XhtmlNode tr = table.tr();
825      for (String col : columns) {
826        tr.th().b().tx(col);
827      }
828      for (TableRowData row : provider.getRows()) {
829        tr = table.tr();
830        for (String col : columns) {
831          XhtmlNode td = tr.td();
832          boolean first = true;
833          List<DataType> list = row.get(col);
834          if (list != null) {
835            for (DataType value : list) {
836              if (first) first = false; else td.tx(", ");
837              render(td, value);
838            }
839          }
840        }
841      }      
842    }
843  }
844
845  public void markLanguage(XhtmlNode x) {
846    x.setAttribute("lang", context.getLocale().toString());
847    x.setAttribute("xml:lang", context.getLocale().toString());
848    x.addTag(0, "hr");
849    x.addTag(0, "p").b().tx(context.getLocale().getDisplayName());
850    x.addTag(0, "hr");
851  }
852  
853
854}