001package org.hl7.fhir.convertors.wrapper;
002
003import java.util.Collection;
004import java.util.Locale;
005
006import org.hl7.fhir.r4.model.Base;
007import org.hl7.fhir.r4.model.DomainResource;
008import org.hl7.fhir.r4.model.ElementDefinition;
009import org.hl7.fhir.r4.model.Enumeration;
010import org.hl7.fhir.r4.model.Narrative;
011import org.hl7.fhir.r4.model.Property;
012import org.hl7.fhir.r4.model.Resource;
013import org.hl7.fhir.r5.context.ContextUtilities;
014import org.hl7.fhir.r5.renderers.utils.RenderingContext;
015import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
016import org.hl7.fhir.utilities.xhtml.NodeType;
017import org.hl7.fhir.utilities.xhtml.XhtmlNode;
018
019/** 
020 * An R4 wrapper for the R5 rendering framework - use this to feed R4 resources directly
021 * into the R5 framework. 
022 * 
023 * The R5 framework is fine to render R4 resources, and has R4 (etc) specific code where 
024 * appropriate (or will be modified to do so).
025 * 
026 * Note that in order to use this, you need an R5 IWorkerContext. You can create a 
027 * R5 SimpleWorkerContext and load it with all the definitions from R4 (that's how the 
028 * validator works internally, so this is well tested code). But you only need to set 
029 * up the R5 context once; then you can create instances of these to wrap the objects you
030 * want rendered on the fly. (is thread safe)
031 * 
032 * See test case for how to use this: @org.hl7.fhir.convertors.rendering.R4RenderingTestCases (testR4 method)
033 * 
034 */
035public class ResourceWrapperR4 extends ResourceWrapper {
036
037  protected Base element;
038
039  ResourceWrapperR4() {
040    super();
041  }
042  
043  private ResourceWrapper makeChild(String name, int index, ElementKind kind, Base element) {
044    ResourceWrapperR4 self = new ResourceWrapperR4();
045    self.contextUtils = this.contextUtils;
046    self.parent = this;
047    self.name = name;
048    self.index = index;
049    self.kind = kind;
050    self.element = element;
051    return self;
052  }
053
054  public String fhirVersion() {
055    return "4.0.1";
056  }
057
058  public String fhirType() {
059    if (kind == ElementKind.BackboneElement) {
060      return basePath();
061    } else {
062      return element.fhirType();
063    }
064  }
065
066  public boolean isPrimitive() {
067    return element.isPrimitive();
068  }
069
070  public boolean hasPrimitiveValue() {
071    return element.hasPrimitiveValue();
072  }
073
074  public String primitiveValue() {
075    return element.primitiveValue();
076  }
077
078  protected void loadTheChildren() {
079    for (Property p : element.children()) {
080      String name = p.getName();
081      int i = 0;
082      for (Base v : p.getValues()) {
083        loadElementChild(p, name, i, v);
084        i++;
085      }
086    }
087  }
088
089  private void loadElementChild(Property p, String name, int i, Base v) {
090    ElementKind kind = determineModelKind(p, v);      
091    int index = p.isList() ? i : -1;
092    ElementDefinition ed = null;
093    children.add(makeChild(name, index, kind, v));
094  }
095
096  private ElementKind determineModelKind(Property p, Base v) {
097    if (v.isPrimitive()) {
098      return ElementKind.PrimitiveType;
099    } else if (contextUtils.isDatatype(v.fhirType())) {
100      return ElementKind.DataType;
101    } else if (!v.isResource()) {
102      return ElementKind.BackboneElement;
103    } else if (parent == null) {
104      return ElementKind.IndependentResource;
105    } else if ("Bundle.entry".equals(fhirType()) && "resource".equals(p.getName())) {
106      return ElementKind.BundleEntry;
107    } else if ("Bundle".equals(fhirType()) && "outcome".equals(p.getName())) {
108      return ElementKind.InlineResource;
109    } else if ("Bundle".equals(fhirType()) && "issues".equals(p.getName())) {
110      return ElementKind.InlineResource;
111    } else if (isResource() && "contained".equals(p.getName())) {
112      return ElementKind.ContainedResource;
113    } else {
114      return ElementKind.InlineResource;
115    }
116  }
117
118  public boolean isResource() {
119    return element.isResource();
120  }
121
122  public boolean canHaveNarrative() {
123    if (!isResource()) {
124      return false;
125    }
126    return element instanceof DomainResource;
127  }
128
129  public XhtmlNode getNarrative() {
130    if (!canHaveNarrative()) {
131      return null;
132    }
133    ResourceWrapper text = child("text");
134    if (text == null) {
135      return null;
136    }
137    ResourceWrapper div = text.child("div");
138    if (div == null) {
139      return null;
140    }
141    return ((ResourceWrapperR4) div).element.getXhtml();
142  }
143
144  public boolean hasNarrative() {
145    if (!canHaveNarrative()) {
146      return false;
147    }
148    ResourceWrapper text = child("text");
149    if (text == null) {
150      return false;
151    }
152    ResourceWrapper div = text.child("div");
153    if (div == null) {
154      return false;
155    }
156    return ((ResourceWrapperR4) div).element.getXhtml() != null;
157  }
158
159  public void setNarrative(XhtmlNode x, String status, boolean multiLangMode, Locale locale, boolean isPretty) {
160    if (element instanceof DomainResource) {
161      DomainResource r = (DomainResource) element;    
162      r.getText().setUserData("renderer.generated", true);
163      if (!r.hasText() || !r.getText().hasDiv()) {
164        r.setText(new Narrative());
165        r.getText().setStatusAsString(status);      
166      }
167      if (multiLangMode) {
168        if (!r.getText().hasDiv()) { 
169          XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
170          div.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
171          r.getText().setDiv(div);
172        } else {
173          r.getText().getDiv().getChildNodes().removeIf(c -> !"div".equals(c.getName()) || !c.hasAttribute("xml:lang"));
174        }
175        markLanguage(x, locale);
176        r.getText().getDiv().addChildNode(x);
177      } else {
178        if (!x.hasAttribute("xmlns"))
179          x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
180        if (r.hasLanguage()) {
181          // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
182          x.setAttribute("lang", r.getLanguage());
183          x.setAttribute("xml:lang", r.getLanguage());
184        }
185        r.getText().setDiv(x);
186      }
187    } else {
188      throw new Error("Cannot call setNarrative on a "+element.fhirType());
189    }
190  }
191
192  public void markLanguage(XhtmlNode x, Locale locale) {
193    x.setAttribute("lang", locale.toLanguageTag());
194    x.setAttribute("xml:lang", locale.toLanguageTag());
195    x.addTag(0, "hr");
196    x.addTag(0, "p").b().tx(locale.getDisplayName());
197    x.addTag(0, "hr");
198  }
199
200
201  public String getId() {
202    return element.getIdBase();
203  }
204
205  @Override
206  public String toString() {
207    return name + (index == -1 ? "" : "["+index+"]")+": "+fhirType()+" ("+kind+"/"+path()+"): native = "+element.fhirType()+" -> "+element.toString();      
208  }
209
210  public org.hl7.fhir.r5.model.Resource getResourceNative() {
211    return null;
212  }
213
214  public boolean hasFormatComment() {
215    return element.hasFormatComment();
216  }
217
218  public Collection<String> getFormatCommentsPre() {
219    return element.getFormatCommentsPre();
220  }
221
222  public XhtmlNode getXhtml() {
223    return element.getXhtml();
224  }
225
226  public org.hl7.fhir.r5.model.Base getBase() {
227    return null;
228  }
229
230  public boolean isDirect() {
231    return true;
232  }
233
234  public String getWebPath() {
235    if (isResource()) {
236      return ((Resource) element).getUserString("path");
237    } else {
238      return null;
239    }
240  }
241
242  public String getCodeSystemUri() {
243    if (element instanceof Enumeration<?>) {
244      return ((Enumeration<?>) element).getSystem();
245    }
246    return null;
247  }
248
249  @Override
250  public boolean hasUserData(String name) {
251    return element.hasUserData(name);
252  }
253
254  @Override
255  public Object getUserData(String name) {
256    return element.getUserData(name);
257  }
258
259  public static ResourceWrapper forResource(RenderingContext rc, Resource resource) {
260    return forResource(rc.getContextUtilities(), resource);
261  }
262
263    public static ResourceWrapper forResource(ContextUtilities contextUtils, Resource resource) {
264
265    ResourceWrapperR4 self = new ResourceWrapperR4();
266    self.contextUtils = contextUtils;
267    self.parent = null;
268    self.name = null;
269    self.index = -1;
270    self.kind = ElementKind.IndependentResource;
271    self.element = resource;
272    return self;
273  }
274
275}