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}