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.ResourceRenderer;
023import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper;
024import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper;
025import org.hl7.fhir.r5.renderers.utils.BaseWrappers.RendererWrapperImpl;
026import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
027import org.hl7.fhir.r5.renderers.utils.BaseWrappers.WrapperBaseImpl;
028import org.hl7.fhir.utilities.Utilities;
029import org.hl7.fhir.utilities.xhtml.NodeType;
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          throw new FHIRException(e.getMessage(), e);
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    @Override
115    public ResourceWrapper getResource() throws UnsupportedEncodingException, IOException, FHIRException {
116      return new ElementWrappers.ResourceWrapperMetaElement(getContext(), element);
117    }
118
119  }
120
121  public static class ResourceWrapperMetaElement extends WrapperBaseImpl implements ResourceWrapper {
122    private Element wrapped;
123    private List<ResourceWrapper> list;
124    private List<PropertyWrapper> list2;
125    private StructureDefinition definition;
126    public ResourceWrapperMetaElement(RenderingContext context, Element wrapped) {
127      super(context);
128      this.wrapped = wrapped;
129      this.definition = wrapped.getProperty().getStructure();
130    }
131
132    @Override
133    public List<ResourceWrapper> getContained() {
134      if (list == null) {
135        List<Element> children = wrapped.getChildrenByName("contained");
136        list = new ArrayList<ResourceWrapper>();
137        for (Element e : children) {
138          list.add(new ResourceWrapperMetaElement(context, e));
139        }
140      }
141      return list;
142    }
143
144    @Override
145    public String getId() {
146      return wrapped.getNamedChildValue("id");
147    }
148
149    @Override
150    public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException {
151      Element txt = wrapped.getNamedChild("text");
152      if (txt == null)
153        return null;
154      Element div = txt.getNamedChild("div");
155      if (div == null)
156        return null;
157      else
158        return div.getXhtml();
159    }
160
161    @Override
162    public String getName() {
163      return wrapped.getName();
164    }
165
166    @Override
167    public String getNameFromResource() {
168      Property name = wrapped.getChildByName("name");
169      if (name != null && name.hasValues()) {
170        Base b = name.getValues().get(0);
171        if (b.isPrimitive()) {
172          return b.primitiveValue();          
173        } else if (b.fhirType().equals("HumanName")) {
174          Property family = b.getChildByName("family");
175          Property given = wrapped.getChildByName("given");
176          String s = given != null && given.hasValues() ? given.getValues().get(0).primitiveValue() : "";
177          if (family != null && family.hasValues() && family.getValues().get(0).primitiveValue() != null)
178            s = s + " " + family.getValues().get(0).primitiveValue().toUpperCase();
179          return s;
180        } else {
181          // well, we couldn't get a name from that
182          return null;
183        }
184      }
185      return null;
186    }
187    
188    @Override
189    public List<PropertyWrapper> children() {
190      if (list2 == null) {
191        List<ElementDefinition> children = context.getProfileUtilities().getChildList(definition, definition.getSnapshot().getElement().get(0));
192        list2 = new ArrayList<PropertyWrapper>();
193        for (ElementDefinition child : children) {
194          List<Element> elements = new ArrayList<Element>();
195          if (child.getPath().endsWith("[x]"))
196            wrapped.getNamedChildrenWithWildcard(tail(child.getPath()), elements);
197          else
198            wrapped.getNamedChildren(tail(child.getPath()), elements);
199          list2.add(new PropertyWrapperMetaElement(context, definition, child, elements));
200        }
201      }
202      return list2;
203    }
204
205    @Override
206    public void describe(XhtmlNode x) {
207      if (wrapped.hasChild("title") && wrapped.getChildValue("title") != null) {
208        x.tx(wrapped.getChildValue("title"));
209      } else if (wrapped.hasChild("name") && wrapped.getChildValue("name") != null) {
210        x.tx(wrapped.getChildValue("name"));       
211      } else {
212        x.tx("?ngen-1?");
213      }
214    }
215
216    @Override
217    public void injectNarrative(ResourceRenderer renderer, XhtmlNode x, NarrativeStatus status) throws IOException {
218      org.hl7.fhir.r5.elementmodel.Element txt = wrapped.getNamedChild("text");
219      if (txt == null) {
220        txt = new org.hl7.fhir.r5.elementmodel.Element("text", wrapped.getProperty().getChild(null, "text"));
221        int i = 0;
222        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"))) {
223          i++;
224        }
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      // now process the xhtml
242      if (renderer.isMultiLangMode()) {
243        XhtmlNode xd = div.getXhtml();
244        if (xd == null) { 
245          xd = new XhtmlNode(NodeType.Element, "div");
246          xd.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
247          div.setXhtml(xd);
248        } else {
249          xd.getChildNodes().removeIf(c -> !"div".equals(c.getName()) || !c.hasAttribute("xml:lang"));
250        }
251        renderer.markLanguage(x);
252        xd.getChildNodes().add(x);
253      } else {
254        if (!x.hasAttribute("xmlns")) {
255          x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
256        }
257        String l = wrapped.getChildValue("language");
258        if (!Utilities.noString(l)) {
259          // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
260          x.setAttribute("lang", l);
261          x.setAttribute("xml:lang", l);
262        }
263        div.setXhtml(x);
264      }
265      div.setValue(new XhtmlComposer(XhtmlComposer.XML, context.isPretty()).compose(div.getXhtml()));
266    }
267
268    @Override
269    public BaseWrapper root() {
270      return new BaseWrapperMetaElement(context, wrapped, getName(), definition, definition.getSnapshot().getElementFirstRep());
271    }
272
273    @Override
274    public StructureDefinition getDefinition() {
275      return definition;
276    }
277
278    @Override
279    public Base getBase() {
280      return wrapped;
281    }
282
283    @Override
284    public boolean hasNarrative() {
285      StructureDefinition sd = definition;
286      while (sd != null) {
287        if ("DomainResource".equals(sd.getType())) {
288          return true;
289        }
290        sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
291      }
292      return false;
293    }
294
295    @Override
296    public String fhirType() {
297      return wrapped.fhirType();
298    }
299
300    @Override
301    public PropertyWrapper getChildByName(String name) {
302      for (PropertyWrapper p : children())
303        if (p.getName().equals(name))
304          return p;
305      return null;
306    }
307
308    public Element getElement() {
309      return wrapped;
310    }
311
312    @Override
313    public Resource getResource() {
314      return null;
315    }
316
317}
318
319  public static class PropertyWrapperMetaElement extends RendererWrapperImpl implements PropertyWrapper {
320
321    private StructureDefinition structure;
322    private ElementDefinition definition;
323    private List<Element> values;
324    private List<BaseWrapper> list;
325
326    public PropertyWrapperMetaElement(RenderingContext context, StructureDefinition structure, ElementDefinition definition, List<Element> values) {
327      super(context);
328      this.structure = structure;
329      this.definition = definition;
330      this.values = values;
331    }
332
333    @Override
334    public String getName() {
335      return tail(definition.getPath());
336    }
337
338    @Override
339    public boolean hasValues() {
340      return values.size() > 0;
341    }
342
343    @Override
344    public List<BaseWrapper> getValues() {
345      if (list == null) {
346        list = new ArrayList<BaseWrapper>();
347        for (Element e : values) {
348           list.add(new BaseWrapperMetaElement(context, e, e.fhirType(), structure, definition));
349        }
350      }
351      return list;
352    }
353
354    @Override
355    public String getTypeCode() {
356      return definition.typeSummary();
357    }
358
359    @Override
360    public String getDefinition() {
361      return definition.getDefinition();
362    }
363
364    @Override
365    public int getMinCardinality() {
366      return definition.getMin();
367    }
368
369    @Override
370    public int getMaxCardinality() {
371      return "*".equals(definition.getMax()) ? Integer.MAX_VALUE : Integer.valueOf(definition.getMax());
372    }
373
374    @Override
375    public StructureDefinition getStructure() {
376      return structure;
377    }
378
379    @Override
380    public BaseWrapper value() {
381      if (getValues().size() != 1)
382        throw new Error("Access single value, but value count is "+getValues().size());
383      return getValues().get(0);
384    }
385
386    @Override
387    public ResourceWrapper getAsResource() {
388      return new ElementWrappers.ResourceWrapperMetaElement(context, values.get(0));
389    }
390
391    @Override
392    public String fhirType() {
393      return getTypeCode();
394    }
395
396    @Override
397    public ElementDefinition getElementDefinition() {
398      return definition;
399    }
400
401  }
402
403}