
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 } 106 107 public static class PropertyWrapperElement extends RendererWrapperImpl implements PropertyWrapper { 108 109 private StructureDefinition structure; 110 private ElementDefinition definition; 111 private List<Element> values; 112 private List<BaseWrapper> list; 113 114 public PropertyWrapperElement(RenderingContext context, StructureDefinition structure, ElementDefinition definition, List<Element> values) { 115 super(context); 116 this.structure = structure; 117 this.definition = definition; 118 this.values = values; 119 } 120 121 @Override 122 public String getName() { 123 return tail(definition.getPath()); 124 } 125 126 @Override 127 public boolean hasValues() { 128 return values.size() > 0; 129 } 130 131 @Override 132 public List<BaseWrapper> getValues() { 133 if (list == null) { 134 list = new ArrayList<BaseWrapper>(); 135 for (Element e : values) 136 list.add(new BaseWrapperElement(context, e, determineType(e), structure, definition)); 137 } 138 return list; 139 } 140 private String determineType(Element e) { 141 if (definition.getType().isEmpty()) 142 return null; 143 if (definition.getType().size() == 1) { 144 if (definition.getType().get(0).getWorkingCode().equals("Element") || definition.getType().get(0).getWorkingCode().equals("BackboneElement")) 145 return null; 146 return definition.getType().get(0).getWorkingCode(); 147 } 148 String t = e.getNodeName().substring(tail(definition.getPath()).length()-3); 149 150 if (isPrimitive(Utilities.uncapitalize(t))) 151 return Utilities.uncapitalize(t); 152 else 153 return t; 154 } 155 156 private boolean isPrimitive(String code) { 157 StructureDefinition sd = context.getWorker().fetchTypeDefinition(code); 158 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 159 } 160 161 @Override 162 public String getTypeCode() { 163 if (definition == null || definition.getType().size() != 1) { 164 if (values.size() != 1) { 165 throw new Error("not handled"); 166 } 167 String tn = values.get(0).getLocalName().substring(tail(definition.getPath()).replace("[x]", "").length()); 168 if (isPrimitive(Utilities.uncapitalize(tn))) { 169 return Utilities.uncapitalize(tn); 170 } else { 171 return tn; 172 } 173 } 174 return definition.getType().get(0).getWorkingCode(); 175 } 176 177 @Override 178 public String getDefinition() { 179 if (definition == null) 180 throw new Error("not handled"); 181 return definition.getDefinition(); 182 } 183 184 @Override 185 public int getMinCardinality() { 186 if (definition == null) 187 throw new Error("not handled"); 188 return definition.getMin(); 189 } 190 191 @Override 192 public int getMaxCardinality() { 193 if (definition == null) 194 throw new Error("not handled"); 195 return definition.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(definition.getMax()); 196 } 197 198 @Override 199 public StructureDefinition getStructure() { 200 return structure; 201 } 202 203 @Override 204 public BaseWrapper value() { 205 if (getValues().size() != 1) 206 throw new Error("Access single value, but value count is "+getValues().size()); 207 return getValues().get(0); 208 } 209 210 @Override 211 public ResourceWrapper getAsResource() { 212 throw new Error("Not implemented yet"); 213 } 214 215 @Override 216 public String fhirType() { 217 return getTypeCode(); 218 } 219 220 @Override 221 public ElementDefinition getElementDefinition() { 222 return definition; 223 } 224 225 } 226 227 public static class ResourceWrapperElement extends WrapperBaseImpl implements ResourceWrapper { 228 229 private Element wrapped; 230 private StructureDefinition definition; 231 private List<ResourceWrapper> list; 232 private List<PropertyWrapper> list2; 233 234 public ResourceWrapperElement(RenderingContext context, Element wrapped, StructureDefinition definition) { 235 super(context); 236 this.wrapped = wrapped; 237 this.definition = definition; 238 } 239 240 @Override 241 public List<ResourceWrapper> getContained() { 242 if (list == null) { 243 List<Element> children = new ArrayList<Element>(); 244 XMLUtil.getNamedChildren(wrapped, "contained", children); 245 list = new ArrayList<ResourceWrapper>(); 246 for (Element e : children) { 247 Element c = XMLUtil.getFirstChild(e); 248 list.add(new ResourceWrapperElement(context, c, context.getWorker().fetchTypeDefinition(c.getNodeName()))); 249 } 250 } 251 return list; 252 } 253 254 @Override 255 public String getId() { 256 return XMLUtil.getNamedChildValue(wrapped, "id"); 257 } 258 259 @Override 260 public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException { 261 Element txt = XMLUtil.getNamedChild(wrapped, "text"); 262 if (txt == null) 263 return null; 264 Element div = XMLUtil.getNamedChild(txt, "div"); 265 if (div == null) 266 return null; 267 try { 268 return new XhtmlParser().parse(new XmlGenerator().generate(div), "div"); 269 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 270 throw new FHIRFormatError(e.getMessage(), e); 271 } catch (org.hl7.fhir.exceptions.FHIRException e) { 272 throw new FHIRException(e.getMessage(), e); 273 } 274 } 275 276 @Override 277 public String getName() { 278 return wrapped.getNodeName(); 279 } 280 281 @Override 282 public String getNameFromResource() { 283 Element e = XMLUtil.getNamedChild(wrapped, "name"); 284 if (e != null) { 285 if (e.hasAttribute("value")) { 286 return e.getAttribute("value"); 287 } 288 if (XMLUtil.hasNamedChild(e, "text")) { 289 return XMLUtil.getNamedChildValue(e, "text"); 290 } 291 if (XMLUtil.hasNamedChild(e, "family") || XMLUtil.hasNamedChild(e, "given")) { 292 Element family = XMLUtil.getNamedChild(e, "family"); 293 Element given = XMLUtil.getNamedChild(e, "given"); 294 String s = given != null && given.hasAttribute("value") ? given.getAttribute("value") : ""; 295 if (family != null && family.hasAttribute("value")) 296 s = s + " " + family.getAttribute("value").toUpperCase(); 297 return s; 298 } 299 return null; 300 } 301 return null; 302 } 303 304 @Override 305 public List<PropertyWrapper> children() { 306 if (list2 == null) { 307 List<ElementDefinition> children = context.getProfileUtilities().getChildList(definition, definition.getSnapshot().getElement().get(0)); 308 list2 = new ArrayList<PropertyWrapper>(); 309 for (ElementDefinition child : children) { 310 List<Element> elements = new ArrayList<Element>(); 311 XMLUtil.getNamedChildrenWithWildcard(wrapped, tail(child.getPath()), elements); 312 list2.add(new PropertyWrapperElement(context, definition, child, elements)); 313 } 314 } 315 return list2; 316 } 317 318 319 320 @Override 321 public void describe(XhtmlNode x) { 322 throw new Error("Not done yet"); 323 } 324 325 @Override 326 public void injectNarrative(XhtmlNode x, NarrativeStatus status) { 327 if (!x.hasAttribute("xmlns")) 328 x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 329 Element le = XMLUtil.getNamedChild(wrapped, "language"); 330 String l = le == null ? null : le.getAttribute("value"); 331 if (!Utilities.noString(l)) { 332 // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues 333 x.setAttribute("lang", l); 334 x.setAttribute("xml:lang", l); 335 } 336 Element txt = XMLUtil.getNamedChild(wrapped, "text"); 337 if (txt == null) { 338 txt = wrapped.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "text"); 339 Element n = XMLUtil.getFirstChild(wrapped); 340 while (n != null && (n.getNodeName().equals("id") || n.getNodeName().equals("meta") || n.getNodeName().equals("implicitRules") || n.getNodeName().equals("language"))) 341 n = XMLUtil.getNextSibling(n); 342 if (n == null) 343 wrapped.appendChild(txt); 344 else 345 wrapped.insertBefore(txt, n); 346 } 347 Element st = XMLUtil.getNamedChild(txt, "status"); 348 if (st == null) { 349 st = wrapped.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "status"); 350 Element n = XMLUtil.getFirstChild(txt); 351 if (n == null) 352 txt.appendChild(st); 353 else 354 txt.insertBefore(st, n); 355 } 356 st.setAttribute("value", status.toCode()); 357 Element div = XMLUtil.getNamedChild(txt, "div"); 358 if (div == null) { 359 div = wrapped.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "div"); 360 div.setAttribute("xmlns", FormatUtilities.XHTML_NS); 361 txt.appendChild(div); 362 } 363 if (div.hasChildNodes()) 364 div.appendChild(wrapped.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "hr")); 365 new XhtmlComposer(XhtmlComposer.XML, context.isPretty()).compose(div, x); 366 } 367 368 @Override 369 public BaseWrapper root() { 370 return new BaseWrapperElement(context, wrapped, getName(), definition, definition.getSnapshot().getElementFirstRep()); 371 } 372 373 @Override 374 public StructureDefinition getDefinition() { 375 return definition; 376 } 377 378 @Override 379 public Base getBase() { 380 throw new Error("Not Implemented yet"); 381 } 382 383 @Override 384 public boolean hasNarrative() { 385 StructureDefinition sd = definition; 386 while (sd != null) { 387 if ("DomainResource".equals(sd.getType())) { 388 return true; 389 } 390 sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 391 } 392 return false; 393 } 394 395 @Override 396 public String fhirType() { 397 return wrapped.getNodeName(); 398 } 399 400 @Override 401 public PropertyWrapper getChildByName(String name) { 402 for (PropertyWrapper p : children()) 403 if (p.getName().equals(name)) 404 return p; 405 return null; 406 } 407 408 @Override 409 public Resource getResource() { 410 return null; 411 } 412 413 414 } 415 416}