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}