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