001package org.hl7.fhir.r5.renderers.utils;
002
003import java.io.IOException;
004import java.io.UnsupportedEncodingException;
005import java.util.ArrayList;
006import java.util.List;
007
008import org.hl7.fhir.exceptions.FHIRException;
009import org.hl7.fhir.exceptions.FHIRFormatError;
010import org.hl7.fhir.r5.formats.FormatUtilities;
011import org.hl7.fhir.r5.model.Base;
012import org.hl7.fhir.r5.model.ElementDefinition;
013import org.hl7.fhir.r5.model.Property;
014import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
015import org.hl7.fhir.r5.model.StructureDefinition;
016import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
017import org.hl7.fhir.r5.renderers.ResourceRenderer;
018import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper;
019import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper;
020import org.hl7.fhir.r5.renderers.utils.BaseWrappers.RendererWrapperImpl;
021import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
022import org.hl7.fhir.r5.renderers.utils.BaseWrappers.WrapperBaseImpl;
023import org.hl7.fhir.utilities.Utilities;
024import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
025import org.hl7.fhir.utilities.xhtml.XhtmlNode;
026import org.hl7.fhir.utilities.xhtml.XhtmlParser;
027import org.hl7.fhir.utilities.xml.XMLUtil;
028import org.hl7.fhir.utilities.xml.XmlGenerator;
029import org.w3c.dom.Element;
030import org.w3c.dom.Node;
031
032public class DOMWrappers {
033
034
035  public static class BaseWrapperElement extends WrapperBaseImpl implements BaseWrapper {
036    private Element element;
037    private String type;
038    private StructureDefinition structure;
039    private ElementDefinition definition;
040    private List<ElementDefinition> children;
041    private List<PropertyWrapper> list;
042
043    public BaseWrapperElement(RenderingContext context, Element element, String type, StructureDefinition structure, ElementDefinition definition) {
044      super(context);
045      this.element = element;
046      this.type = type;
047      this.structure = structure;
048      this.definition = definition;
049    }
050
051    @Override
052    public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException {
053      if (Utilities.noString(type) || type.equals("Resource") || type.equals("BackboneElement") || type.equals("Element"))
054        return null;
055
056      String xml;
057      try {
058        xml = new XmlGenerator().generate(element);
059      } catch (org.hl7.fhir.exceptions.FHIRException e) {
060        throw new FHIRException(e.getMessage(), e);
061      }
062      Node n = element.getPreviousSibling();
063      Base ret = context.getParser().parseType(xml, type);
064      while (n != null && (n.getNodeType() == Node.COMMENT_NODE || n.getNodeType() == Node.TEXT_NODE)) {
065        if (n.getNodeType() == Node.COMMENT_NODE) {
066          ret.getFormatCommentsPre().add(0, n.getTextContent());
067        }
068        n = n.getPreviousSibling();
069      }
070      return ret;
071    }
072
073    @Override
074    public List<PropertyWrapper> children() {
075      if (list == null) {
076        children = context.getProfileUtilities().getChildList(structure, definition);
077        if (children.isEmpty() && type != null) {
078          StructureDefinition sdt = context.getWorker().fetchTypeDefinition(type);
079          children = context.getProfileUtilities().getChildList(sdt, sdt.getSnapshot().getElementFirstRep());          
080        }
081        list = new ArrayList<PropertyWrapper>();
082        for (ElementDefinition child : children) {
083          List<Element> elements = new ArrayList<Element>();
084          XMLUtil.getNamedChildrenWithWildcard(element, tail(child.getPath()), elements);
085          list.add(new PropertyWrapperElement(context, structure, child, elements));
086        }
087      }
088      return list;
089    }
090
091    @Override
092    public PropertyWrapper getChildByName(String name) {
093      for (PropertyWrapper p : children())
094        if (p.getName().equals(name))
095          return p;
096      return null;
097    }
098
099    @Override
100    public String fhirType() {
101      return type;
102    }
103
104  }
105
106  public static class PropertyWrapperElement extends RendererWrapperImpl implements PropertyWrapper {
107
108    private StructureDefinition structure;
109    private ElementDefinition definition;
110    private List<Element> values;
111    private List<BaseWrapper> list;
112
113    public PropertyWrapperElement(RenderingContext context, StructureDefinition structure, ElementDefinition definition, List<Element> values) {
114      super(context);
115      this.structure = structure;
116      this.definition = definition;
117      this.values = values;
118    }
119
120    @Override
121    public String getName() {
122      return tail(definition.getPath());
123    }
124
125    @Override
126    public boolean hasValues() {
127      return values.size() > 0;
128    }
129
130    @Override
131    public List<BaseWrapper> getValues() {
132      if (list == null) {
133        list = new ArrayList<BaseWrapper>();
134        for (Element e : values)
135          list.add(new BaseWrapperElement(context, e, determineType(e), structure, definition));
136      }
137      return list;
138    }
139    private String determineType(Element e) {
140      if (definition.getType().isEmpty())
141        return null;
142      if (definition.getType().size() == 1) {
143        if (definition.getType().get(0).getWorkingCode().equals("Element") || definition.getType().get(0).getWorkingCode().equals("BackboneElement"))
144          return null;
145        return definition.getType().get(0).getWorkingCode();
146      }
147      String t = e.getNodeName().substring(tail(definition.getPath()).length()-3);
148
149      if (isPrimitive(Utilities.uncapitalize(t)))
150        return Utilities.uncapitalize(t);
151      else
152        return t;
153    }
154
155    private boolean isPrimitive(String code) {
156      StructureDefinition sd = context.getWorker().fetchTypeDefinition(code);
157      return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
158    }
159
160    @Override
161    public String getTypeCode() {
162      if (definition == null || definition.getType().size() != 1) {
163        if (values.size() != 1) {
164          throw new Error("not handled");
165        }
166        String tn = values.get(0).getLocalName().substring(tail(definition.getPath()).replace("[x]", "").length());
167        if (isPrimitive(Utilities.uncapitalize(tn))) {
168          return Utilities.uncapitalize(tn);
169        } else {
170          return tn;
171        }
172      }
173      return definition.getType().get(0).getWorkingCode();
174    }
175
176    @Override
177    public String getDefinition() {
178      if (definition == null)
179        throw new Error("not handled");
180      return definition.getDefinition();
181    }
182
183    @Override
184    public int getMinCardinality() {
185      if (definition == null)
186        throw new Error("not handled");
187      return definition.getMin();
188    }
189
190    @Override
191    public int getMaxCardinality() {
192      if (definition == null)
193        throw new Error("not handled");
194      return definition.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(definition.getMax());
195    }
196
197    @Override
198    public StructureDefinition getStructure() {
199      return structure;
200    }
201
202    @Override
203    public BaseWrapper value() {
204      if (getValues().size() != 1)
205        throw new Error("Access single value, but value count is "+getValues().size());
206      return getValues().get(0);
207    }
208
209    @Override
210    public ResourceWrapper getAsResource() {
211     throw new Error("Not implemented yet");
212    }
213
214    @Override
215    public String fhirType() {
216      return getTypeCode();
217    }
218
219    @Override
220    public ElementDefinition getElementDefinition() {
221      return definition;
222    }
223
224  }
225
226  public static class ResourceWrapperElement extends WrapperBaseImpl implements ResourceWrapper {
227
228    private Element wrapped;
229    private StructureDefinition definition;
230    private List<ResourceWrapper> list;
231    private List<PropertyWrapper> list2;
232
233    public ResourceWrapperElement(RenderingContext context, Element wrapped, StructureDefinition definition) {
234      super(context);
235      this.wrapped = wrapped;
236      this.definition = definition;
237    }
238
239    @Override
240    public List<ResourceWrapper> getContained() {
241      if (list == null) {
242        List<Element> children = new ArrayList<Element>();
243        XMLUtil.getNamedChildren(wrapped, "contained", children);
244        list = new ArrayList<ResourceWrapper>();
245        for (Element e : children) {
246          Element c = XMLUtil.getFirstChild(e);
247          list.add(new ResourceWrapperElement(context, c, context.getWorker().fetchTypeDefinition(c.getNodeName())));
248        }
249      }
250      return list;
251    }
252
253    @Override
254    public String getId() {
255      return XMLUtil.getNamedChildValue(wrapped, "id");
256    }
257
258    @Override
259    public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException {
260      Element txt = XMLUtil.getNamedChild(wrapped, "text");
261      if (txt == null)
262        return null;
263      Element div = XMLUtil.getNamedChild(txt, "div");
264      if (div == null)
265        return null;
266      try {
267        return new XhtmlParser().parse(new XmlGenerator().generate(div), "div");
268      } catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
269        throw new FHIRFormatError(e.getMessage(), e);
270      } catch (org.hl7.fhir.exceptions.FHIRException e) {
271        throw new FHIRException(e.getMessage(), e);
272      }
273    }
274
275    @Override
276    public String getName() {
277      return wrapped.getNodeName();
278    }
279
280    @Override
281    public String getNameFromResource() {
282      Element e = XMLUtil.getNamedChild(wrapped, "name");
283      if (e != null) {
284        if (e.hasAttribute("value")) {
285          return e.getAttribute("value");
286        }
287        if (XMLUtil.hasNamedChild(e, "text")) {
288          return XMLUtil.getNamedChildValue(e, "text");
289        }
290        if (XMLUtil.hasNamedChild(e, "family") || XMLUtil.hasNamedChild(e, "given")) {
291          Element family = XMLUtil.getNamedChild(e, "family");
292          Element given = XMLUtil.getNamedChild(e, "given");
293          String s = given != null && given.hasAttribute("value") ? given.getAttribute("value") : "";
294          if (family != null && family.hasAttribute("value"))
295            s = s + " " + family.getAttribute("value").toUpperCase();
296          return s;
297        }
298        return null;
299      }
300      return null;
301    }
302
303    @Override
304    public List<PropertyWrapper> children() {
305      if (list2 == null) {
306        List<ElementDefinition> children = context.getProfileUtilities().getChildList(definition, definition.getSnapshot().getElement().get(0));
307        list2 = new ArrayList<PropertyWrapper>();
308        for (ElementDefinition child : children) {
309          List<Element> elements = new ArrayList<Element>();
310          XMLUtil.getNamedChildrenWithWildcard(wrapped, tail(child.getPath()), elements);
311          list2.add(new PropertyWrapperElement(context, definition, child, elements));
312        }
313      }
314      return list2;
315    }
316
317
318
319    @Override
320    public void describe(XhtmlNode x) {
321      throw new Error("Not done yet");      
322    }
323
324    @Override
325    public void injectNarrative(XhtmlNode x, NarrativeStatus status) {
326      if (!x.hasAttribute("xmlns"))
327        x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
328      Element le = XMLUtil.getNamedChild(wrapped, "language");
329      String l = le == null ? null : le.getAttribute("value");
330      if (!Utilities.noString(l)) {
331        // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
332        x.setAttribute("lang", l);
333        x.setAttribute("xml:lang", l);
334      }
335      Element txt = XMLUtil.getNamedChild(wrapped, "text");
336      if (txt == null) {
337        txt = wrapped.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "text");
338        Element n = XMLUtil.getFirstChild(wrapped);
339        while (n != null && (n.getNodeName().equals("id") || n.getNodeName().equals("meta") || n.getNodeName().equals("implicitRules") || n.getNodeName().equals("language")))
340          n = XMLUtil.getNextSibling(n);
341        if (n == null)
342          wrapped.appendChild(txt);
343        else
344          wrapped.insertBefore(txt, n);
345      }
346      Element st = XMLUtil.getNamedChild(txt, "status");
347      if (st == null) {
348        st = wrapped.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "status");
349        Element n = XMLUtil.getFirstChild(txt);
350        if (n == null)
351          txt.appendChild(st);
352        else
353          txt.insertBefore(st, n);
354      }
355      st.setAttribute("value", status.toCode());
356      Element div = XMLUtil.getNamedChild(txt, "div");
357      if (div == null) {
358        div = wrapped.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "div");
359        div.setAttribute("xmlns", FormatUtilities.XHTML_NS);
360        txt.appendChild(div);
361      }
362      if (div.hasChildNodes())
363        div.appendChild(wrapped.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "hr"));
364      new XhtmlComposer(XhtmlComposer.XML, context.isPretty()).compose(div, x);
365    }
366
367    @Override
368    public BaseWrapper root() {
369      return new BaseWrapperElement(context, wrapped, getName(), definition, definition.getSnapshot().getElementFirstRep());
370    }
371
372    @Override
373    public StructureDefinition getDefinition() {
374      return definition;
375    }
376
377    @Override
378    public Base getBase() {
379      throw new Error("Not Implemented yet");
380    }
381
382    @Override
383    public boolean hasNarrative() {
384      StructureDefinition sd = definition;
385      while (sd != null) {
386        if ("DomainResource".equals(sd.getType())) {
387          return true;
388        }
389        sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
390      }
391      return false;
392    }
393
394    @Override
395    public String fhirType() {
396      return wrapped.getNodeName();
397    }
398    
399    @Override
400    public PropertyWrapper getChildByName(String name) {
401      for (PropertyWrapper p : children())
402        if (p.getName().equals(name))
403          return p;
404      return null;
405    }
406
407
408  }
409
410}