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.DefinitionException;
011import org.hl7.fhir.exceptions.FHIRException;
012import org.hl7.fhir.exceptions.FHIRFormatError;
013import org.hl7.fhir.r5.conformance.ProfileUtilities.ElementDefinitionResolution;
014import org.hl7.fhir.r5.elementmodel.Element;
015import org.hl7.fhir.r5.elementmodel.XmlParser;
016import org.hl7.fhir.r5.formats.IParser.OutputStyle;
017import org.hl7.fhir.r5.model.Base;
018import org.hl7.fhir.r5.model.ElementDefinition;
019import org.hl7.fhir.r5.model.Property;
020import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
021import org.hl7.fhir.r5.model.StringType;
022import org.hl7.fhir.r5.model.StructureDefinition;
023import org.hl7.fhir.r5.renderers.ResourceRenderer;
024import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper;
025import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper;
026import org.hl7.fhir.r5.renderers.utils.BaseWrappers.RendererWrapperImpl;
027import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
028import org.hl7.fhir.r5.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          // well, we couldn't get a name from that
177          return null;
178        }
179      }
180      return null;
181    }
182    
183    @Override
184    public List<PropertyWrapper> children() {
185      if (list2 == null) {
186        List<ElementDefinition> children = context.getProfileUtilities().getChildList(definition, definition.getSnapshot().getElement().get(0));
187        list2 = new ArrayList<PropertyWrapper>();
188        for (ElementDefinition child : children) {
189          List<Element> elements = new ArrayList<Element>();
190          if (child.getPath().endsWith("[x]"))
191            wrapped.getNamedChildrenWithWildcard(tail(child.getPath()), elements);
192          else
193            wrapped.getNamedChildren(tail(child.getPath()), elements);
194          list2.add(new PropertyWrapperMetaElement(context, definition, child, elements));
195        }
196      }
197      return list2;
198    }
199
200    @Override
201    public void describe(XhtmlNode x) {
202      if (wrapped.hasChild("title") && wrapped.getChildValue("title") != null) {
203        x.tx(wrapped.getChildValue("title"));
204      } else if (wrapped.hasChild("name") && wrapped.getChildValue("name") != null) {
205        x.tx(wrapped.getChildValue("name"));       
206      } else {
207        x.tx("?ngen-1?");
208      }
209    }
210
211    @Override
212    public void injectNarrative(XhtmlNode x, NarrativeStatus status) throws IOException {
213      if (!x.hasAttribute("xmlns"))
214        x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
215      String l = wrapped.getChildValue("language");
216      if (!Utilities.noString(l)) {
217        // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
218        x.setAttribute("lang", l);
219        x.setAttribute("xml:lang", l);
220      }
221      org.hl7.fhir.r5.elementmodel.Element txt = wrapped.getNamedChild("text");
222      if (txt == null) {
223        txt = new org.hl7.fhir.r5.elementmodel.Element("text", wrapped.getProperty().getChild(null, "text"));
224        int i = 0;
225        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")))
226          i++;
227        if (i >= wrapped.getChildren().size())
228          wrapped.getChildren().add(txt);
229        else
230          wrapped.getChildren().add(i, txt);
231      }
232      org.hl7.fhir.r5.elementmodel.Element st = txt.getNamedChild("status");
233      if (st == null) {
234        st = new org.hl7.fhir.r5.elementmodel.Element("status", txt.getProperty().getChild(null, "status"));
235        txt.getChildren().add(0, st);
236      }
237      st.setValue(status.toCode());
238      org.hl7.fhir.r5.elementmodel.Element div = txt.getNamedChild("div");
239      if (div == null) {
240        div = new org.hl7.fhir.r5.elementmodel.Element("div", txt.getProperty().getChild(null, "div"));
241        txt.getChildren().add(div);
242      } 
243      div.setValue(new XhtmlComposer(XhtmlComposer.XML, context.isPretty()).compose(x));
244      div.setXhtml(x);
245    }
246
247    @Override
248    public BaseWrapper root() {
249      return new BaseWrapperMetaElement(context, wrapped, getName(), definition, definition.getSnapshot().getElementFirstRep());
250    }
251
252    @Override
253    public StructureDefinition getDefinition() {
254      return definition;
255    }
256
257    @Override
258    public Base getBase() {
259      return wrapped;
260    }
261
262    @Override
263    public boolean hasNarrative() {
264      StructureDefinition sd = definition;
265      while (sd != null) {
266        if ("DomainResource".equals(sd.getType())) {
267          return true;
268        }
269        sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
270      }
271      return false;
272    }
273
274    @Override
275    public String fhirType() {
276      return wrapped.fhirType();
277    }
278
279    @Override
280    public PropertyWrapper getChildByName(String name) {
281      for (PropertyWrapper p : children())
282        if (p.getName().equals(name))
283          return p;
284      return null;
285    }
286
287    public Element getElement() {
288      return wrapped;
289    }
290
291}
292
293  public static class PropertyWrapperMetaElement extends RendererWrapperImpl implements PropertyWrapper {
294
295    private StructureDefinition structure;
296    private ElementDefinition definition;
297    private List<Element> values;
298    private List<BaseWrapper> list;
299
300    public PropertyWrapperMetaElement(RenderingContext context, StructureDefinition structure, ElementDefinition definition, List<Element> values) {
301      super(context);
302      this.structure = structure;
303      this.definition = definition;
304      this.values = values;
305    }
306
307    @Override
308    public String getName() {
309      return tail(definition.getPath());
310    }
311
312    @Override
313    public boolean hasValues() {
314      return values.size() > 0;
315    }
316
317    @Override
318    public List<BaseWrapper> getValues() {
319      if (list == null) {
320        list = new ArrayList<BaseWrapper>();
321        for (Element e : values) {
322           list.add(new BaseWrapperMetaElement(context, e, e.fhirType(), structure, definition));
323        }
324      }
325      return list;
326    }
327
328    @Override
329    public String getTypeCode() {
330      return definition.typeSummary();
331    }
332
333    @Override
334    public String getDefinition() {
335      return definition.getDefinition();
336    }
337
338    @Override
339    public int getMinCardinality() {
340      return definition.getMin();
341    }
342
343    @Override
344    public int getMaxCardinality() {
345      return "*".equals(definition.getMax()) ? Integer.MAX_VALUE : Integer.valueOf(definition.getMax());
346    }
347
348    @Override
349    public StructureDefinition getStructure() {
350      return structure;
351    }
352
353    @Override
354    public BaseWrapper value() {
355      if (getValues().size() != 1)
356        throw new Error("Access single value, but value count is "+getValues().size());
357      return getValues().get(0);
358    }
359
360    @Override
361    public ResourceWrapper getAsResource() {
362      return new ElementWrappers.ResourceWrapperMetaElement(context, values.get(0));
363    }
364
365    @Override
366    public String fhirType() {
367      return getTypeCode();
368    }
369
370    @Override
371    public ElementDefinition getElementDefinition() {
372      return definition;
373    }
374
375  }
376
377}