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