001package org.hl7.fhir.r5.renderers.utils; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.util.ArrayList; 006import java.util.List; 007 008import org.hl7.fhir.exceptions.FHIRException; 009import org.hl7.fhir.exceptions.FHIRFormatError; 010import org.hl7.fhir.r5.formats.FormatUtilities; 011import org.hl7.fhir.r5.model.Base; 012import org.hl7.fhir.r5.model.ElementDefinition; 013import org.hl7.fhir.r5.model.Property; 014import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; 015import org.hl7.fhir.r5.model.StructureDefinition; 016import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 017import org.hl7.fhir.r5.renderers.ResourceRenderer; 018import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; 019import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper; 020import org.hl7.fhir.r5.renderers.utils.BaseWrappers.RendererWrapperImpl; 021import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; 022import org.hl7.fhir.r5.renderers.utils.BaseWrappers.WrapperBaseImpl; 023import org.hl7.fhir.utilities.Utilities; 024import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 025import org.hl7.fhir.utilities.xhtml.XhtmlNode; 026import org.hl7.fhir.utilities.xhtml.XhtmlParser; 027import org.hl7.fhir.utilities.xml.XMLUtil; 028import org.hl7.fhir.utilities.xml.XmlGenerator; 029import org.w3c.dom.Element; 030 031public class DOMWrappers { 032 033 034 public static class BaseWrapperElement extends WrapperBaseImpl implements BaseWrapper { 035 private Element element; 036 private String type; 037 private StructureDefinition structure; 038 private ElementDefinition definition; 039 private List<ElementDefinition> children; 040 private List<PropertyWrapper> list; 041 042 public BaseWrapperElement(RenderingContext context, Element element, String type, StructureDefinition structure, ElementDefinition definition) { 043 super(context); 044 this.element = element; 045 this.type = type; 046 this.structure = structure; 047 this.definition = definition; 048 } 049 050 @Override 051 public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException { 052 if (Utilities.noString(type) || type.equals("Resource") || type.equals("BackboneElement") || type.equals("Element")) 053 return null; 054 055 String xml; 056 try { 057 xml = new XmlGenerator().generate(element); 058 } catch (org.hl7.fhir.exceptions.FHIRException e) { 059 throw new FHIRException(e.getMessage(), e); 060 } 061 return context.getParser().parseType(xml, type); 062 } 063 064 @Override 065 public List<PropertyWrapper> children() { 066 if (list == null) { 067 children = context.getProfileUtilities().getChildList(structure, definition); 068 if (children.isEmpty() && type != null) { 069 StructureDefinition sdt = context.getWorker().fetchTypeDefinition(type); 070 children = context.getProfileUtilities().getChildList(sdt, sdt.getSnapshot().getElementFirstRep()); 071 } 072 list = new ArrayList<PropertyWrapper>(); 073 for (ElementDefinition child : children) { 074 List<Element> elements = new ArrayList<Element>(); 075 XMLUtil.getNamedChildrenWithWildcard(element, tail(child.getPath()), elements); 076 list.add(new PropertyWrapperElement(context, structure, child, elements)); 077 } 078 } 079 return list; 080 } 081 082 @Override 083 public PropertyWrapper getChildByName(String name) { 084 for (PropertyWrapper p : children()) 085 if (p.getName().equals(name)) 086 return p; 087 return null; 088 } 089 090 @Override 091 public String fhirType() { 092 return type; 093 } 094 095 } 096 097 public static class PropertyWrapperElement extends RendererWrapperImpl implements PropertyWrapper { 098 099 private StructureDefinition structure; 100 private ElementDefinition definition; 101 private List<Element> values; 102 private List<BaseWrapper> list; 103 104 public PropertyWrapperElement(RenderingContext context, StructureDefinition structure, ElementDefinition definition, List<Element> values) { 105 super(context); 106 this.structure = structure; 107 this.definition = definition; 108 this.values = values; 109 } 110 111 @Override 112 public String getName() { 113 return tail(definition.getPath()); 114 } 115 116 @Override 117 public boolean hasValues() { 118 return values.size() > 0; 119 } 120 121 @Override 122 public List<BaseWrapper> getValues() { 123 if (list == null) { 124 list = new ArrayList<BaseWrapper>(); 125 for (Element e : values) 126 list.add(new BaseWrapperElement(context, e, determineType(e), structure, definition)); 127 } 128 return list; 129 } 130 private String determineType(Element e) { 131 if (definition.getType().isEmpty()) 132 return null; 133 if (definition.getType().size() == 1) { 134 if (definition.getType().get(0).getWorkingCode().equals("Element") || definition.getType().get(0).getWorkingCode().equals("BackboneElement")) 135 return null; 136 return definition.getType().get(0).getWorkingCode(); 137 } 138 String t = e.getNodeName().substring(tail(definition.getPath()).length()-3); 139 140 if (isPrimitive(Utilities.uncapitalize(t))) 141 return Utilities.uncapitalize(t); 142 else 143 return t; 144 } 145 146 private boolean isPrimitive(String code) { 147 StructureDefinition sd = context.getWorker().fetchTypeDefinition(code); 148 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 149 } 150 151 @Override 152 public String getTypeCode() { 153 if (definition == null || definition.getType().size() != 1) { 154 if (values.size() != 1) { 155 throw new Error("not handled"); 156 } 157 String tn = values.get(0).getLocalName().substring(tail(definition.getPath()).replace("[x]", "").length()); 158 if (isPrimitive(Utilities.uncapitalize(tn))) { 159 return Utilities.uncapitalize(tn); 160 } else { 161 return tn; 162 } 163 } 164 return definition.getType().get(0).getWorkingCode(); 165 } 166 167 @Override 168 public String getDefinition() { 169 if (definition == null) 170 throw new Error("not handled"); 171 return definition.getDefinition(); 172 } 173 174 @Override 175 public int getMinCardinality() { 176 if (definition == null) 177 throw new Error("not handled"); 178 return definition.getMin(); 179 } 180 181 @Override 182 public int getMaxCardinality() { 183 if (definition == null) 184 throw new Error("not handled"); 185 return definition.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(definition.getMax()); 186 } 187 188 @Override 189 public StructureDefinition getStructure() { 190 return structure; 191 } 192 193 @Override 194 public BaseWrapper value() { 195 if (getValues().size() != 1) 196 throw new Error("Access single value, but value count is "+getValues().size()); 197 return getValues().get(0); 198 } 199 200 @Override 201 public ResourceWrapper getAsResource() { 202 throw new Error("Not implemented yet"); 203 } 204 205 @Override 206 public String fhirType() { 207 return getTypeCode(); 208 } 209 210 @Override 211 public ElementDefinition getElementDefinition() { 212 return definition; 213 } 214 215 } 216 217 public static class ResourceWrapperElement extends WrapperBaseImpl implements ResourceWrapper { 218 219 private Element wrapped; 220 private StructureDefinition definition; 221 private List<ResourceWrapper> list; 222 private List<PropertyWrapper> list2; 223 224 public ResourceWrapperElement(RenderingContext context, Element wrapped, StructureDefinition definition) { 225 super(context); 226 this.wrapped = wrapped; 227 this.definition = definition; 228 } 229 230 @Override 231 public List<ResourceWrapper> getContained() { 232 if (list == null) { 233 List<Element> children = new ArrayList<Element>(); 234 XMLUtil.getNamedChildren(wrapped, "contained", children); 235 list = new ArrayList<ResourceWrapper>(); 236 for (Element e : children) { 237 Element c = XMLUtil.getFirstChild(e); 238 list.add(new ResourceWrapperElement(context, c, context.getWorker().fetchTypeDefinition(c.getNodeName()))); 239 } 240 } 241 return list; 242 } 243 244 @Override 245 public String getId() { 246 return XMLUtil.getNamedChildValue(wrapped, "id"); 247 } 248 249 @Override 250 public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException { 251 Element txt = XMLUtil.getNamedChild(wrapped, "text"); 252 if (txt == null) 253 return null; 254 Element div = XMLUtil.getNamedChild(txt, "div"); 255 if (div == null) 256 return null; 257 try { 258 return new XhtmlParser().parse(new XmlGenerator().generate(div), "div"); 259 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 260 throw new FHIRFormatError(e.getMessage(), e); 261 } catch (org.hl7.fhir.exceptions.FHIRException e) { 262 throw new FHIRException(e.getMessage(), e); 263 } 264 } 265 266 @Override 267 public String getName() { 268 return wrapped.getNodeName(); 269 } 270 271 @Override 272 public String getNameFromResource() { 273 Element e = XMLUtil.getNamedChild(wrapped, "name"); 274 if (e != null) { 275 if (e.hasAttribute("value")) { 276 return e.getAttribute("value"); 277 } 278 if (XMLUtil.hasNamedChild(e, "text")) { 279 return XMLUtil.getNamedChildValue(e, "text"); 280 } 281 if (XMLUtil.hasNamedChild(e, "family") || XMLUtil.hasNamedChild(e, "given")) { 282 Element family = XMLUtil.getNamedChild(e, "family"); 283 Element given = XMLUtil.getNamedChild(e, "given"); 284 String s = given != null && given.hasAttribute("value") ? given.getAttribute("value") : ""; 285 if (family != null && family.hasAttribute("value")) 286 s = s + " " + family.getAttribute("value").toUpperCase(); 287 return s; 288 } 289 return null; 290 } 291 return null; 292 } 293 294 @Override 295 public List<PropertyWrapper> children() { 296 if (list2 == null) { 297 List<ElementDefinition> children = context.getProfileUtilities().getChildList(definition, definition.getSnapshot().getElement().get(0)); 298 list2 = new ArrayList<PropertyWrapper>(); 299 for (ElementDefinition child : children) { 300 List<Element> elements = new ArrayList<Element>(); 301 XMLUtil.getNamedChildrenWithWildcard(wrapped, tail(child.getPath()), elements); 302 list2.add(new PropertyWrapperElement(context, definition, child, elements)); 303 } 304 } 305 return list2; 306 } 307 308 309 310 @Override 311 public void describe(XhtmlNode x) { 312 throw new Error("Not done yet"); 313 } 314 315 @Override 316 public void injectNarrative(XhtmlNode x, NarrativeStatus status) { 317 if (!x.hasAttribute("xmlns")) 318 x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 319 Element le = XMLUtil.getNamedChild(wrapped, "language"); 320 String l = le == null ? null : le.getAttribute("value"); 321 if (!Utilities.noString(l)) { 322 // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues 323 x.setAttribute("lang", l); 324 x.setAttribute("xml:lang", l); 325 } 326 Element txt = XMLUtil.getNamedChild(wrapped, "text"); 327 if (txt == null) { 328 txt = wrapped.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "text"); 329 Element n = XMLUtil.getFirstChild(wrapped); 330 while (n != null && (n.getNodeName().equals("id") || n.getNodeName().equals("meta") || n.getNodeName().equals("implicitRules") || n.getNodeName().equals("language"))) 331 n = XMLUtil.getNextSibling(n); 332 if (n == null) 333 wrapped.appendChild(txt); 334 else 335 wrapped.insertBefore(txt, n); 336 } 337 Element st = XMLUtil.getNamedChild(txt, "status"); 338 if (st == null) { 339 st = wrapped.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "status"); 340 Element n = XMLUtil.getFirstChild(txt); 341 if (n == null) 342 txt.appendChild(st); 343 else 344 txt.insertBefore(st, n); 345 } 346 st.setAttribute("value", status.toCode()); 347 Element div = XMLUtil.getNamedChild(txt, "div"); 348 if (div == null) { 349 div = wrapped.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "div"); 350 div.setAttribute("xmlns", FormatUtilities.XHTML_NS); 351 txt.appendChild(div); 352 } 353 if (div.hasChildNodes()) 354 div.appendChild(wrapped.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "hr")); 355 new XhtmlComposer(XhtmlComposer.XML, context.isPretty()).compose(div, x); 356 } 357 358 @Override 359 public BaseWrapper root() { 360 return new BaseWrapperElement(context, wrapped, getName(), definition, definition.getSnapshot().getElementFirstRep()); 361 } 362 363 @Override 364 public StructureDefinition getDefinition() { 365 return definition; 366 } 367 368 @Override 369 public Base getBase() { 370 throw new Error("Not Implemented yet"); 371 } 372 373 @Override 374 public boolean hasNarrative() { 375 StructureDefinition sd = definition; 376 while (sd != null) { 377 if ("DomainResource".equals(sd.getType())) { 378 return true; 379 } 380 sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 381 } 382 return false; 383 } 384 385 @Override 386 public String fhirType() { 387 return wrapped.getNodeName(); 388 } 389 390 @Override 391 public PropertyWrapper getChildByName(String name) { 392 for (PropertyWrapper p : children()) 393 if (p.getName().equals(name)) 394 return p; 395 return null; 396 } 397 398 399 } 400 401}