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        div.setValue(new XhtmlComposer(XhtmlComposer.XML, context.isPretty()).compose(x));
243      }
244      div.setValue(x.toString());
245      div.setXhtml(x);
246
247    }
248
249    @Override
250    public BaseWrapper root() {
251      return new BaseWrapperMetaElement(context, wrapped, getName(), definition, definition.getSnapshot().getElementFirstRep());
252    }
253
254    @Override
255    public StructureDefinition getDefinition() {
256      return definition;
257    }
258
259    @Override
260    public Base getBase() {
261      return wrapped;
262    }
263
264    @Override
265    public boolean hasNarrative() {
266      StructureDefinition sd = definition;
267      while (sd != null) {
268        if ("DomainResource".equals(sd.getType())) {
269          return true;
270        }
271        sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
272      }
273      return false;
274    }
275
276    @Override
277    public String fhirType() {
278      return wrapped.fhirType();
279    }
280
281    @Override
282    public PropertyWrapper getChildByName(String name) {
283      for (PropertyWrapper p : children())
284        if (p.getName().equals(name))
285          return p;
286      return null;
287    }
288
289}
290
291  public static class PropertyWrapperMetaElement extends RendererWrapperImpl implements PropertyWrapper {
292
293    private StructureDefinition structure;
294    private ElementDefinition definition;
295    private List<Element> values;
296    private List<BaseWrapper> list;
297
298    public PropertyWrapperMetaElement(RenderingContext context, StructureDefinition structure, ElementDefinition definition, List<Element> values) {
299      super(context);
300      this.structure = structure;
301      this.definition = definition;
302      this.values = values;
303    }
304
305    @Override
306    public String getName() {
307      return tail(definition.getPath());
308    }
309
310    @Override
311    public boolean hasValues() {
312      return values.size() > 0;
313    }
314
315    @Override
316    public List<BaseWrapper> getValues() {
317      if (list == null) {
318        list = new ArrayList<BaseWrapper>();
319        for (Element e : values) {
320           list.add(new BaseWrapperMetaElement(context, e, e.fhirType(), structure, definition));
321        }
322      }
323      return list;
324    }
325
326    @Override
327    public String getTypeCode() {
328      return definition.typeSummary();
329    }
330
331    @Override
332    public String getDefinition() {
333      return definition.getDefinition();
334    }
335
336    @Override
337    public int getMinCardinality() {
338      return definition.getMin();
339    }
340
341    @Override
342    public int getMaxCardinality() {
343      return "*".equals(definition.getMax()) ? Integer.MAX_VALUE : Integer.valueOf(definition.getMax());
344    }
345
346    @Override
347    public StructureDefinition getStructure() {
348      return structure;
349    }
350
351    @Override
352    public BaseWrapper value() {
353      if (getValues().size() != 1)
354        throw new Error("Access single value, but value count is "+getValues().size());
355      return getValues().get(0);
356    }
357
358    @Override
359    public ResourceWrapper getAsResource() {
360      return new ElementWrappers.ResourceWrapperMetaElement(context, values.get(0));
361    }
362
363    @Override
364    public String fhirType() {
365      return getTypeCode();
366    }
367
368    @Override
369    public ElementDefinition getElementDefinition() {
370      return definition;
371    }
372
373  }
374
375}