
001package org.hl7.fhir.convertors.wrapper; 002 003import java.util.Collection; 004import java.util.Locale; 005 006import org.hl7.fhir.r4.model.Base; 007import org.hl7.fhir.r4.model.DomainResource; 008import org.hl7.fhir.r4.model.ElementDefinition; 009import org.hl7.fhir.r4.model.Enumeration; 010import org.hl7.fhir.r4.model.Narrative; 011import org.hl7.fhir.r4.model.Property; 012import org.hl7.fhir.r4.model.Resource; 013import org.hl7.fhir.r5.context.ContextUtilities; 014import org.hl7.fhir.r5.renderers.utils.RenderingContext; 015import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 016import org.hl7.fhir.utilities.xhtml.NodeType; 017import org.hl7.fhir.utilities.xhtml.XhtmlNode; 018 019/** 020 * An R4 wrapper for the R5 rendering framework - use this to feed R4 resources directly 021 * into the R5 framework. 022 * 023 * The R5 framework is fine to render R4 resources, and has R4 (etc) specific code where 024 * appropriate (or will be modified to do so). 025 * 026 * Note that in order to use this, you need an R5 IWorkerContext. You can create a 027 * R5 SimpleWorkerContext and load it with all the definitions from R4 (that's how the 028 * validator works internally, so this is well tested code). But you only need to set 029 * up the R5 context once; then you can create instances of these to wrap the objects you 030 * want rendered on the fly. (is thread safe) 031 * 032 * See test case for how to use this: @org.hl7.fhir.convertors.rendering.R4RenderingTestCases (testR4 method) 033 * 034 */ 035public class ResourceWrapperR4 extends ResourceWrapper { 036 037 protected Base element; 038 039 ResourceWrapperR4() { 040 super(); 041 } 042 043 private ResourceWrapper makeChild(String name, int index, ElementKind kind, Base element) { 044 ResourceWrapperR4 self = new ResourceWrapperR4(); 045 self.contextUtils = this.contextUtils; 046 self.parent = this; 047 self.name = name; 048 self.index = index; 049 self.kind = kind; 050 self.element = element; 051 return self; 052 } 053 054 public String fhirVersion() { 055 return "4.0.1"; 056 } 057 058 public String fhirType() { 059 if (kind == ElementKind.BackboneElement) { 060 return basePath(); 061 } else { 062 return element.fhirType(); 063 } 064 } 065 066 public boolean isPrimitive() { 067 return element.isPrimitive(); 068 } 069 070 public boolean hasPrimitiveValue() { 071 return element.hasPrimitiveValue(); 072 } 073 074 public String primitiveValue() { 075 return element.primitiveValue(); 076 } 077 078 protected void loadTheChildren() { 079 for (Property p : element.children()) { 080 String name = p.getName(); 081 int i = 0; 082 for (Base v : p.getValues()) { 083 loadElementChild(p, name, i, v); 084 i++; 085 } 086 } 087 } 088 089 private void loadElementChild(Property p, String name, int i, Base v) { 090 ElementKind kind = determineModelKind(p, v); 091 int index = p.isList() ? i : -1; 092 ElementDefinition ed = null; 093 children.add(makeChild(name, index, kind, v)); 094 } 095 096 private ElementKind determineModelKind(Property p, Base v) { 097 if (v.isPrimitive()) { 098 return ElementKind.PrimitiveType; 099 } else if (contextUtils.isDatatype(v.fhirType())) { 100 return ElementKind.DataType; 101 } else if (!v.isResource()) { 102 return ElementKind.BackboneElement; 103 } else if (parent == null) { 104 return ElementKind.IndependentResource; 105 } else if ("Bundle.entry".equals(fhirType()) && "resource".equals(p.getName())) { 106 return ElementKind.BundleEntry; 107 } else if ("Bundle".equals(fhirType()) && "outcome".equals(p.getName())) { 108 return ElementKind.InlineResource; 109 } else if ("Bundle".equals(fhirType()) && "issues".equals(p.getName())) { 110 return ElementKind.InlineResource; 111 } else if (isResource() && "contained".equals(p.getName())) { 112 return ElementKind.ContainedResource; 113 } else { 114 return ElementKind.InlineResource; 115 } 116 } 117 118 public boolean isResource() { 119 return element.isResource(); 120 } 121 122 public boolean canHaveNarrative() { 123 if (!isResource()) { 124 return false; 125 } 126 return element instanceof DomainResource; 127 } 128 129 public XhtmlNode getNarrative() { 130 if (!canHaveNarrative()) { 131 return null; 132 } 133 ResourceWrapper text = child("text"); 134 if (text == null) { 135 return null; 136 } 137 ResourceWrapper div = text.child("div"); 138 if (div == null) { 139 return null; 140 } 141 return ((ResourceWrapperR4) div).element.getXhtml(); 142 } 143 144 public boolean hasNarrative() { 145 if (!canHaveNarrative()) { 146 return false; 147 } 148 ResourceWrapper text = child("text"); 149 if (text == null) { 150 return false; 151 } 152 ResourceWrapper div = text.child("div"); 153 if (div == null) { 154 return false; 155 } 156 return ((ResourceWrapperR4) div).element.getXhtml() != null; 157 } 158 159 public void setNarrative(XhtmlNode x, String status, boolean multiLangMode, Locale locale, boolean isPretty) { 160 if (element instanceof DomainResource) { 161 DomainResource r = (DomainResource) element; 162 r.getText().setUserData("renderer.generated", true); 163 if (!r.hasText() || !r.getText().hasDiv()) { 164 r.setText(new Narrative()); 165 r.getText().setStatusAsString(status); 166 } 167 if (multiLangMode) { 168 if (!r.getText().hasDiv()) { 169 XhtmlNode div = new XhtmlNode(NodeType.Element, "div"); 170 div.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 171 r.getText().setDiv(div); 172 } else { 173 r.getText().getDiv().getChildNodes().removeIf(c -> !"div".equals(c.getName()) || !c.hasAttribute("xml:lang")); 174 } 175 markLanguage(x, locale); 176 r.getText().getDiv().addChildNode(x); 177 } else { 178 if (!x.hasAttribute("xmlns")) 179 x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 180 if (r.hasLanguage()) { 181 // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues 182 x.setAttribute("lang", r.getLanguage()); 183 x.setAttribute("xml:lang", r.getLanguage()); 184 } 185 r.getText().setDiv(x); 186 } 187 } else { 188 throw new Error("Cannot call setNarrative on a "+element.fhirType()); 189 } 190 } 191 192 public void markLanguage(XhtmlNode x, Locale locale) { 193 x.setAttribute("lang", locale.toLanguageTag()); 194 x.setAttribute("xml:lang", locale.toLanguageTag()); 195 x.addTag(0, "hr"); 196 x.addTag(0, "p").b().tx(locale.getDisplayName()); 197 x.addTag(0, "hr"); 198 } 199 200 201 public String getId() { 202 return element.getIdBase(); 203 } 204 205 @Override 206 public String toString() { 207 return name + (index == -1 ? "" : "["+index+"]")+": "+fhirType()+" ("+kind+"/"+path()+"): native = "+element.fhirType()+" -> "+element.toString(); 208 } 209 210 public org.hl7.fhir.r5.model.Resource getResourceNative() { 211 return null; 212 } 213 214 public boolean hasFormatComment() { 215 return element.hasFormatComment(); 216 } 217 218 public Collection<String> getFormatCommentsPre() { 219 return element.getFormatCommentsPre(); 220 } 221 222 public XhtmlNode getXhtml() { 223 return element.getXhtml(); 224 } 225 226 public org.hl7.fhir.r5.model.Base getBase() { 227 return null; 228 } 229 230 public boolean isDirect() { 231 return true; 232 } 233 234 public String getWebPath() { 235 if (isResource()) { 236 return ((Resource) element).getUserString("path"); 237 } else { 238 return null; 239 } 240 } 241 242 public String getCodeSystemUri() { 243 if (element instanceof Enumeration<?>) { 244 return ((Enumeration<?>) element).getSystem(); 245 } 246 return null; 247 } 248 249 @Override 250 public boolean hasUserData(String name) { 251 return element.hasUserData(name); 252 } 253 254 @Override 255 public Object getUserData(String name) { 256 return element.getUserData(name); 257 } 258 259 public static ResourceWrapper forResource(RenderingContext rc, Resource resource) { 260 return forResource(rc.getContextUtilities(), resource); 261 } 262 263 public static ResourceWrapper forResource(ContextUtilities contextUtils, Resource resource) { 264 265 ResourceWrapperR4 self = new ResourceWrapperR4(); 266 self.contextUtils = contextUtils; 267 self.parent = null; 268 self.name = null; 269 self.index = -1; 270 self.kind = ElementKind.IndependentResource; 271 self.element = resource; 272 return self; 273 } 274 275}