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