001package org.hl7.fhir.r5.renderers.utils;
002
003import java.io.IOException;
004import java.util.Collection;
005import java.util.Locale;
006
007import org.hl7.fhir.r5.elementmodel.Element;
008import org.hl7.fhir.r5.model.Base;
009import org.hl7.fhir.r5.model.ElementDefinition;
010import org.hl7.fhir.r5.model.Resource;
011import org.hl7.fhir.r5.model.ValueSet;
012import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
013import org.hl7.fhir.utilities.Utilities;
014import org.hl7.fhir.utilities.xhtml.NodeType;
015import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
016import org.hl7.fhir.utilities.xhtml.XhtmlNode;
017
018/** 
019 * This class is used to walk through the resources when rendering, whether
020 * the resource is a native resource or loaded by the element model
021 */
022@MarkedToMoveToAdjunctPackage
023public class ResourceWrapperModel extends ResourceWrapper {
024
025  protected Element model;
026
027  ResourceWrapperModel() {
028    super();
029  }
030  
031  private ResourceWrapperModel makeChild(String name, int index, ElementKind kind, Element em) {
032    ResourceWrapperModel self = new ResourceWrapperModel();
033    self.contextUtils = this.contextUtils;
034    self.parent = this;
035    self.name = name;
036    self.index = index;
037    self.kind = kind;
038    self.model = em;
039    return self;
040  }
041  
042  public String fhirVersion() {
043    return model.getFHIRPublicationVersion().toCode();
044  }
045  
046  public String fhirType() {
047    if (kind == ElementKind.BackboneElement) {
048      return basePath();
049    } else {
050      return model.fhirType();
051    }
052  }
053
054  public boolean isPrimitive() {
055    return model.isPrimitive();
056  }
057
058  public boolean hasPrimitiveValue() {
059    return model.hasPrimitiveValue();        
060  }
061
062  public String primitiveValue() {
063    return model.primitiveValue();
064  }
065
066  protected void loadTheChildren() {
067    for (Element child : model.getChildren()) {
068      String name = child.getProperty().isChoice() ? child.getProperty().getName() : child.getName();
069      int index = child.isList() ? child.getIndex() : -1;
070      ElementKind kind = determineModelKind(child);
071      children.add(makeChild(name, index, kind, child));
072    }
073  }
074
075  private ElementKind determineModelKind(Element child) {
076    if (child.isPrimitive()) {
077      return ElementKind.PrimitiveType;
078    } else if (child.fhirType().contains("Backbone")) {
079      return ElementKind.BackboneElement;
080    } else if (child.getProperty().getContextUtils().isDatatype(child.fhirType())) {
081      return ElementKind.DataType;
082    } else if (!child.isResource()) {
083      return ElementKind.BackboneElement;
084    } else {
085      switch (child.getSpecial()) {
086      case BUNDLE_ENTRY:
087        return ElementKind.BundleEntry;
088      case BUNDLE_ISSUES:
089        return ElementKind.InlineResource;
090      case BUNDLE_OUTCOME:
091        return ElementKind.InlineResource;
092      case CONTAINED:
093        return ElementKind.ContainedResource;
094      case PARAMETER:
095        return ElementKind.InlineResource;
096      default:
097        return ElementKind.IndependentResource;
098      }
099    }
100  }
101
102
103  public boolean isResource() {
104    return model.isResource();
105  }
106
107  public boolean canHaveNarrative() {
108    if (!isResource()) {
109      return false;
110    }
111    return contextUtils.isDomainResource(fhirType()); 
112  }
113  
114  public XhtmlNode getNarrative() {
115    if (!canHaveNarrative()) {
116      return null;
117    }
118    ResourceWrapper text = child("text");
119    if (text == null) {
120      return null;
121    }
122    ResourceWrapper div = text.child("div");
123    if (div == null) {
124      return null;
125    }
126      return ((ResourceWrapperModel) div).model.getXhtml(); 
127  }
128  
129  public boolean hasNarrative() {
130    if (!canHaveNarrative()) {
131      return false;
132    }
133    ResourceWrapper text = child("text");
134    if (text == null) {
135      return false;
136    }
137    ResourceWrapper div = text.child("div");
138    if (div == null) {
139      return false;
140    }
141    return ((ResourceWrapperModel) div).model.getXhtml() != null; 
142  }
143  
144  @Override
145  public void setNarrative(XhtmlNode x, String status, boolean multiLangMode, Locale locale, boolean isPretty) throws IOException {
146    org.hl7.fhir.r5.elementmodel.Element txt = model.getNamedChild("text");
147    if (txt == null) {
148      txt = new org.hl7.fhir.r5.elementmodel.Element("text", model.getProperty().getChild(null, "text"));
149      int i = 0;
150      while (i < model.getChildren().size() && (model.getChildren().get(i).getName().equals("id") || model.getChildren().get(i).getName().equals("meta") || model.getChildren().get(i).getName().equals("implicitRules") || model.getChildren().get(i).getName().equals("language"))) {
151        i++;
152      }
153      if (i >= model.getChildren().size())
154        model.getChildren().add(txt);
155      else
156        model.getChildren().add(i, txt);
157    }
158    org.hl7.fhir.r5.elementmodel.Element st = txt.getNamedChild("status");
159    if (st == null) {
160      st = new org.hl7.fhir.r5.elementmodel.Element("status", txt.getProperty().getChild(null, "status"));
161      txt.getChildren().add(0, st);
162    }
163    st.setValue(status);
164    org.hl7.fhir.r5.elementmodel.Element div = txt.getNamedChild("div");
165    if (div == null) {
166      div = new org.hl7.fhir.r5.elementmodel.Element("div", txt.getProperty().getChild(null, "div"));
167      txt.getChildren().add(div);
168    } 
169    // now process the xhtml
170    if (multiLangMode) {
171      XhtmlNode xd = div.getXhtml();
172      if (xd == null) { 
173        xd = new XhtmlNode(NodeType.Element, "div");
174        xd.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
175        div.setXhtml(xd);
176      } else {
177        xd.getChildNodes().removeIf(c -> !"div".equals(c.getName()) || !c.hasAttribute("xml:lang"));
178      }
179      markLanguage(x, locale);
180      xd.addChildNode(x);
181    } else {
182      if (!x.hasAttribute("xmlns")) {
183        x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
184      }
185      String l = model.getChildValue("language");
186      if (!Utilities.noString(l)) {
187        // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
188        x.setAttribute("lang", l);
189        x.setAttribute("xml:lang", l);
190      }
191      div.setXhtml(x);
192    }
193    div.setValue(new XhtmlComposer(XhtmlComposer.XML, isPretty).compose(div.getXhtml()));
194  }
195
196  public void markLanguage(XhtmlNode x, Locale locale) {
197    x.setAttribute("lang", locale.toLanguageTag());
198    x.setAttribute("xml:lang", locale.toLanguageTag());
199    x.addTag(0, "hr");
200    x.addTag(0, "p").b().tx(locale.getDisplayName());
201    x.addTag(0, "hr");
202  }
203  
204
205  public String getId() {
206    return model.getIdBase(); 
207  }
208  
209  @Override
210  public String toString() {
211    return name + (index == -1 ? "" : "["+index+"]")+": "+fhirType()+" ("+kind+"/"+path()+"): element = "+model.fhirType()+" -> "+model.toString();
212  }
213
214  public boolean matches(ResourceWrapperModel b) {
215    if (isEmpty() || b.isEmpty()) {
216      return isEmpty() && b.isEmpty();
217    } else {
218      if (hasPrimitiveValue() || b.hasPrimitiveValue()) {
219        if (!hasPrimitiveValue() || !b.hasPrimitiveValue() || !primitiveValue().equals(b.primitiveValue())) {
220          return false;
221        }
222      }
223      if (children().size() != b.children().size()) {
224        return false;
225      } else {
226        for (int i = 0; i < children().size(); i++) {
227          if (!children().get(i).matches(b.children().get(i))) {
228            return false;
229          }
230        }
231        return true;
232      }
233    }
234  }
235
236  public Resource getResourceNative() {
237    return null;
238  }
239
240  public boolean hasFormatComment() {
241   return model.hasFormatComment();
242  }
243
244  public Collection<String> getFormatCommentsPre() {
245   return model.getFormatCommentsPre();
246  }
247
248  public XhtmlNode getXhtml() {
249   return model.getXhtml();
250  }
251
252  public Base getBase() {
253   return model;
254  }
255
256  public boolean isDirect() {
257    return false;
258  }
259
260  public String getWebPath() {
261    return model.getWebPath();
262  }
263
264  public String getCodeSystemUri() {
265    ElementDefinition pd = model.getProperty().getDefinition(); 
266    if (pd != null && pd.hasBinding() && pd.getBinding().hasValueSet()) { 
267      ValueSet vs = contextUtils.getWorker().fetchResource(ValueSet.class, pd.getBinding().getValueSet()); 
268      if (vs != null && vs.hasCompose() && !vs.getCompose().hasExclude() && vs.getCompose().getInclude().size() == 1) { 
269        return vs.getCompose().getIncludeFirstRep().getSystem(); 
270      } 
271    }
272    return null;
273  }
274
275  @Override
276  public boolean hasUserData(String name) {
277    return model.hasUserData(name);
278  }
279  
280  @Override
281  public Object getUserData(String name) {
282    return model.getUserData(name);
283  }
284
285
286}