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}