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