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