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