001package org.hl7.fhir.r5.renderers.utils;
002
003import java.io.IOException;
004import java.io.UnsupportedEncodingException;
005import java.nio.charset.StandardCharsets;
006import java.util.ArrayList;
007import java.util.List;
008
009import org.apache.commons.io.output.ByteArrayOutputStream;
010import org.hl7.fhir.exceptions.FHIRException;
011import org.hl7.fhir.exceptions.FHIRFormatError;
012import org.hl7.fhir.r5.elementmodel.Element;
013import org.hl7.fhir.r5.elementmodel.XmlParser;
014import org.hl7.fhir.r5.formats.IParser.OutputStyle;
015import org.hl7.fhir.r5.model.Base;
016import org.hl7.fhir.r5.model.ElementDefinition;
017import org.hl7.fhir.r5.model.Property;
018import org.hl7.fhir.r5.model.Resource;
019import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
020import org.hl7.fhir.r5.model.StringType;
021import org.hl7.fhir.r5.model.StructureDefinition;
022import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper;
023import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper;
024import org.hl7.fhir.r5.renderers.utils.BaseWrappers.RendererWrapperImpl;
025import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
026import org.hl7.fhir.r5.renderers.utils.BaseWrappers.WrapperBaseImpl;
027import org.hl7.fhir.utilities.Utilities;
028import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
029import org.hl7.fhir.utilities.xhtml.XhtmlNode;
030
031public class ElementWrappers {
032
033  public static class BaseWrapperMetaElement extends WrapperBaseImpl implements BaseWrapper {
034    private Element element;
035    private String type;
036    private StructureDefinition structure;
037    private ElementDefinition definition;
038    private List<ElementDefinition> children;
039    private List<PropertyWrapper> list;
040
041    public BaseWrapperMetaElement(RenderingContext context, Element element, String type, StructureDefinition structure, ElementDefinition definition) {
042      super(context);
043      this.element = element;
044      this.type = type;
045      this.structure = structure;
046      this.definition = definition;
047    }
048
049    @Override
050    public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException {
051      if (type == null || type.equals("Resource") || type.equals("BackboneElement") || type.equals("Element"))
052        return null;
053
054      if (element.hasElementProperty()) {
055        return element;
056      }
057      ByteArrayOutputStream xml = new ByteArrayOutputStream();
058      try {
059        new XmlParser(context.getWorker()).compose(element, xml, OutputStyle.PRETTY, null);
060      } catch (Exception e) {
061        throw new FHIRException(e.getMessage(), e);
062      }
063      if (context.getParser() == null) {
064        System.out.println("No version specific parser provided");
065      } 
066      if (context.getParser() == null) {
067        throw new Error("No type parser provided to renderer context");
068      } else {
069        try {
070          return context.getParser().parseType(xml.toString(StandardCharsets.UTF_8), type);
071        } catch (Exception e) {
072          throw new FHIRException(e.getMessage(), e);
073        }
074      }
075    }
076
077    @Override
078    public List<PropertyWrapper> children() {
079      if (list == null) {
080        children = context.getProfileUtilities().getChildList(structure, definition, false, true);
081        if (children.isEmpty() && !Utilities.noString(type)) {
082          StructureDefinition sd = context.getWorker().fetchTypeDefinition(type);
083          children = context.getProfileUtilities().getChildList(sd, sd.getSnapshot().getElementFirstRep());
084        }
085        list = new ArrayList<PropertyWrapper>();
086        for (ElementDefinition child : children) {
087          List<Element> elements = new ArrayList<Element>();
088          String name = tail(child.getPath());
089          if (name.endsWith("[x]"))
090            element.getNamedChildrenWithWildcard(name, elements);
091          else
092            element.getNamedChildren(name, elements);
093          list.add(new PropertyWrapperMetaElement(context, structure, child, elements));
094        }
095      }
096      return list;
097    }
098
099    @Override
100    public PropertyWrapper getChildByName(String name) {
101      for (PropertyWrapper p : children())
102        if (p.getName().equals(name))
103          return p;
104      return null;
105    }
106
107    @Override
108    public String fhirType() {
109      return element.fhirType();
110    }
111
112  }
113
114  public static class ResourceWrapperMetaElement extends WrapperBaseImpl implements ResourceWrapper {
115    private Element wrapped;
116    private List<ResourceWrapper> list;
117    private List<PropertyWrapper> list2;
118    private StructureDefinition definition;
119    public ResourceWrapperMetaElement(RenderingContext context, Element wrapped) {
120      super(context);
121      this.wrapped = wrapped;
122      this.definition = wrapped.getProperty().getStructure();
123    }
124
125    @Override
126    public List<ResourceWrapper> getContained() {
127      if (list == null) {
128        List<Element> children = wrapped.getChildrenByName("contained");
129        list = new ArrayList<ResourceWrapper>();
130        for (Element e : children) {
131          list.add(new ResourceWrapperMetaElement(context, e));
132        }
133      }
134      return list;
135    }
136
137    @Override
138    public String getId() {
139      return wrapped.getNamedChildValue("id");
140    }
141
142    @Override
143    public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException {
144      Element txt = wrapped.getNamedChild("text");
145      if (txt == null)
146        return null;
147      Element div = txt.getNamedChild("div");
148      if (div == null)
149        return null;
150      else
151        return div.getXhtml();
152    }
153
154    @Override
155    public String getName() {
156      return wrapped.getName();
157    }
158
159    @Override
160    public String getNameFromResource() {
161      Property name = wrapped.getChildByName("name");
162      if (name != null && name.hasValues()) {
163        Base b = name.getValues().get(0);
164        if (b.isPrimitive()) {
165          return b.primitiveValue();          
166        } else if (b.fhirType().equals("HumanName")) {
167          Property family = b.getChildByName("family");
168          Property given = wrapped.getChildByName("given");
169          String s = given != null && given.hasValues() ? given.getValues().get(0).primitiveValue() : "";
170          if (family != null && family.hasValues() && family.getValues().get(0).primitiveValue() != null)
171            s = s + " " + family.getValues().get(0).primitiveValue().toUpperCase();
172          return s;
173        } else {
174          // well, we couldn't get a name from that
175          return null;
176        }
177      }
178      return null;
179    }
180    
181    @Override
182    public List<PropertyWrapper> children() {
183      if (list2 == null) {
184        List<ElementDefinition> children = context.getProfileUtilities().getChildList(definition, definition.getSnapshot().getElement().get(0));
185        list2 = new ArrayList<PropertyWrapper>();
186        for (ElementDefinition child : children) {
187          List<Element> elements = new ArrayList<Element>();
188          if (child.getPath().endsWith("[x]"))
189            wrapped.getNamedChildrenWithWildcard(tail(child.getPath()), elements);
190          else
191            wrapped.getNamedChildren(tail(child.getPath()), elements);
192          list2.add(new PropertyWrapperMetaElement(context, definition, child, elements));
193        }
194      }
195      return list2;
196    }
197
198    @Override
199    public void describe(XhtmlNode x) {
200      if (wrapped.hasChild("title") && wrapped.getChildValue("title") != null) {
201        x.tx(wrapped.getChildValue("title"));
202      } else if (wrapped.hasChild("name") && wrapped.getChildValue("name") != null) {
203        x.tx(wrapped.getChildValue("name"));       
204      } else {
205        x.tx("?ngen-1?");
206      }
207    }
208
209    @Override
210    public void injectNarrative(XhtmlNode x, NarrativeStatus status) throws IOException {
211      if (!x.hasAttribute("xmlns"))
212        x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
213      String l = wrapped.getChildValue("language");
214      if (!Utilities.noString(l)) {
215        // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
216        x.setAttribute("lang", l);
217        x.setAttribute("xml:lang", l);
218      }
219      org.hl7.fhir.r5.elementmodel.Element txt = wrapped.getNamedChild("text");
220      if (txt == null) {
221        txt = new org.hl7.fhir.r5.elementmodel.Element("text", wrapped.getProperty().getChild(null, "text"));
222        int i = 0;
223        while (i < wrapped.getChildren().size() && (wrapped.getChildren().get(i).getName().equals("id") || wrapped.getChildren().get(i).getName().equals("meta") || wrapped.getChildren().get(i).getName().equals("implicitRules") || wrapped.getChildren().get(i).getName().equals("language")))
224          i++;
225        if (i >= wrapped.getChildren().size())
226          wrapped.getChildren().add(txt);
227        else
228          wrapped.getChildren().add(i, txt);
229      }
230      org.hl7.fhir.r5.elementmodel.Element st = txt.getNamedChild("status");
231      if (st == null) {
232        st = new org.hl7.fhir.r5.elementmodel.Element("status", txt.getProperty().getChild(null, "status"));
233        txt.getChildren().add(0, st);
234      }
235      st.setValue(status.toCode());
236      org.hl7.fhir.r5.elementmodel.Element div = txt.getNamedChild("div");
237      if (div == null) {
238        div = new org.hl7.fhir.r5.elementmodel.Element("div", txt.getProperty().getChild(null, "div"));
239        txt.getChildren().add(div);
240      } 
241      div.setValue(new XhtmlComposer(XhtmlComposer.XML, context.isPretty()).compose(x));
242      div.setXhtml(x);
243    }
244
245    @Override
246    public BaseWrapper root() {
247      return new BaseWrapperMetaElement(context, wrapped, getName(), definition, definition.getSnapshot().getElementFirstRep());
248    }
249
250    @Override
251    public StructureDefinition getDefinition() {
252      return definition;
253    }
254
255    @Override
256    public Base getBase() {
257      return wrapped;
258    }
259
260    @Override
261    public boolean hasNarrative() {
262      StructureDefinition sd = definition;
263      while (sd != null) {
264        if ("DomainResource".equals(sd.getType())) {
265          return true;
266        }
267        sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
268      }
269      return false;
270    }
271
272    @Override
273    public String fhirType() {
274      return wrapped.fhirType();
275    }
276
277    @Override
278    public PropertyWrapper getChildByName(String name) {
279      for (PropertyWrapper p : children())
280        if (p.getName().equals(name))
281          return p;
282      return null;
283    }
284
285    public Element getElement() {
286      return wrapped;
287    }
288
289    @Override
290    public Resource getResource() {
291      return null;
292    }
293
294}
295
296  public static class PropertyWrapperMetaElement extends RendererWrapperImpl implements PropertyWrapper {
297
298    private StructureDefinition structure;
299    private ElementDefinition definition;
300    private List<Element> values;
301    private List<BaseWrapper> list;
302
303    public PropertyWrapperMetaElement(RenderingContext context, StructureDefinition structure, ElementDefinition definition, List<Element> values) {
304      super(context);
305      this.structure = structure;
306      this.definition = definition;
307      this.values = values;
308    }
309
310    @Override
311    public String getName() {
312      return tail(definition.getPath());
313    }
314
315    @Override
316    public boolean hasValues() {
317      return values.size() > 0;
318    }
319
320    @Override
321    public List<BaseWrapper> getValues() {
322      if (list == null) {
323        list = new ArrayList<BaseWrapper>();
324        for (Element e : values) {
325           list.add(new BaseWrapperMetaElement(context, e, e.fhirType(), structure, definition));
326        }
327      }
328      return list;
329    }
330
331    @Override
332    public String getTypeCode() {
333      return definition.typeSummary();
334    }
335
336    @Override
337    public String getDefinition() {
338      return definition.getDefinition();
339    }
340
341    @Override
342    public int getMinCardinality() {
343      return definition.getMin();
344    }
345
346    @Override
347    public int getMaxCardinality() {
348      return "*".equals(definition.getMax()) ? Integer.MAX_VALUE : Integer.valueOf(definition.getMax());
349    }
350
351    @Override
352    public StructureDefinition getStructure() {
353      return structure;
354    }
355
356    @Override
357    public BaseWrapper value() {
358      if (getValues().size() != 1)
359        throw new Error("Access single value, but value count is "+getValues().size());
360      return getValues().get(0);
361    }
362
363    @Override
364    public ResourceWrapper getAsResource() {
365      return new ElementWrappers.ResourceWrapperMetaElement(context, values.get(0));
366    }
367
368    @Override
369    public String fhirType() {
370      return getTypeCode();
371    }
372
373    @Override
374    public ElementDefinition getElementDefinition() {
375      return definition;
376    }
377
378  }
379
380}