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