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