001package org.hl7.fhir.r4b.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.util.ArrayList; 006import java.util.HashMap; 007import java.util.HashSet; 008import java.util.List; 009import java.util.Map; 010import java.util.Set; 011 012import org.apache.commons.codec.binary.Base64; 013import org.apache.commons.lang3.NotImplementedException; 014import org.hl7.fhir.exceptions.DefinitionException; 015import org.hl7.fhir.exceptions.FHIRException; 016import org.hl7.fhir.exceptions.FHIRFormatError; 017import org.hl7.fhir.r4b.formats.FormatUtilities; 018import org.hl7.fhir.r4b.model.Address; 019import org.hl7.fhir.r4b.model.Annotation; 020import org.hl7.fhir.r4b.model.Attachment; 021import org.hl7.fhir.r4b.model.Base; 022import org.hl7.fhir.r4b.model.Base64BinaryType; 023import org.hl7.fhir.r4b.model.BooleanType; 024import org.hl7.fhir.r4b.model.CodeType; 025import org.hl7.fhir.r4b.model.CodeableConcept; 026import org.hl7.fhir.r4b.model.CodeableReference; 027import org.hl7.fhir.r4b.model.Coding; 028import org.hl7.fhir.r4b.model.ContactDetail; 029import org.hl7.fhir.r4b.model.ContactPoint; 030import org.hl7.fhir.r4b.model.DataRequirement; 031import org.hl7.fhir.r4b.model.DateTimeType; 032import org.hl7.fhir.r4b.model.DomainResource; 033import org.hl7.fhir.r4b.model.Dosage; 034import org.hl7.fhir.r4b.model.ElementDefinition; 035import org.hl7.fhir.r4b.model.Enumeration; 036import org.hl7.fhir.r4b.model.Expression; 037import org.hl7.fhir.r4b.model.Extension; 038import org.hl7.fhir.r4b.model.HumanName; 039import org.hl7.fhir.r4b.model.IdType; 040import org.hl7.fhir.r4b.model.Identifier; 041import org.hl7.fhir.r4b.model.InstantType; 042import org.hl7.fhir.r4b.model.Meta; 043import org.hl7.fhir.r4b.model.Money; 044import org.hl7.fhir.r4b.model.Narrative; 045import org.hl7.fhir.r4b.model.Narrative.NarrativeStatus; 046import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind; 047import org.hl7.fhir.r4b.model.Period; 048import org.hl7.fhir.r4b.model.PrimitiveType; 049import org.hl7.fhir.r4b.model.Property; 050import org.hl7.fhir.r4b.model.Quantity; 051import org.hl7.fhir.r4b.model.Range; 052import org.hl7.fhir.r4b.model.Ratio; 053import org.hl7.fhir.r4b.model.Reference; 054import org.hl7.fhir.r4b.model.RelatedArtifact; 055import org.hl7.fhir.r4b.model.Resource; 056import org.hl7.fhir.r4b.model.SampledData; 057import org.hl7.fhir.r4b.model.Signature; 058import org.hl7.fhir.r4b.model.StringType; 059import org.hl7.fhir.r4b.model.StructureDefinition; 060import org.hl7.fhir.r4b.model.Timing; 061import org.hl7.fhir.r4b.model.UriType; 062import org.hl7.fhir.r4b.model.UsageContext; 063import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.BaseWrapper; 064import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.PropertyWrapper; 065import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.ResourceWrapper; 066import org.hl7.fhir.r4b.renderers.utils.DOMWrappers.BaseWrapperElement; 067import org.hl7.fhir.r4b.renderers.utils.DOMWrappers.ResourceWrapperElement; 068import org.hl7.fhir.r4b.renderers.utils.DirectWrappers; 069import org.hl7.fhir.r4b.renderers.utils.DirectWrappers.BaseWrapperDirect; 070import org.hl7.fhir.r4b.renderers.utils.DirectWrappers.PropertyWrapperDirect; 071import org.hl7.fhir.r4b.renderers.utils.DirectWrappers.ResourceWrapperDirect; 072import org.hl7.fhir.r4b.renderers.utils.RenderingContext; 073import org.hl7.fhir.r4b.renderers.utils.Resolver.ResourceContext; 074import org.hl7.fhir.r4b.renderers.utils.Resolver.ResourceWithReference; 075import org.hl7.fhir.r4b.utils.EOperationOutcome; 076import org.hl7.fhir.r4b.utils.ToolingExtensions; 077import org.hl7.fhir.r4b.utils.XVerExtensionManager; 078import org.hl7.fhir.r4b.utils.XVerExtensionManager.XVerExtensionStatus; 079import org.hl7.fhir.utilities.Utilities; 080import org.hl7.fhir.utilities.xhtml.NodeType; 081import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 082import org.hl7.fhir.utilities.xhtml.XhtmlNode; 083import org.hl7.fhir.utilities.xml.XMLUtil; 084import org.w3c.dom.Element; 085 086public class ProfileDrivenRenderer extends ResourceRenderer { 087 088 private Set<String> containedIds = new HashSet<>(); 089 private boolean hasExtensions; 090 091 public ProfileDrivenRenderer(RenderingContext context, ResourceContext rcontext) { 092 super(context, rcontext); 093 } 094 095 public ProfileDrivenRenderer(RenderingContext context) { 096 super(context); 097 } 098 099 @Override 100 public boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException { 101 return render(x, new DirectWrappers.ResourceWrapperDirect(context, r)); 102 } 103 104 @Override 105 public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException { 106 if (context.isAddGeneratedNarrativeHeader()) { 107 x.para().b().tx("Generated Narrative"); 108 } 109 if (context.isTechnicalMode()) { 110 renderResourceHeader(r, x); 111 } 112 try { 113 StructureDefinition sd = r.getDefinition(); 114 if (sd == null) { 115 throw new FHIRException("Cannot find definition for "+r.fhirType()); 116 } else { 117 ElementDefinition ed = sd.getSnapshot().getElement().get(0); 118 containedIds.clear(); 119 hasExtensions = false; 120 generateByProfile(r, sd, r.root(), sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), context.isTechnicalMode(), 0); 121 } 122 } catch (Exception e) { 123 System.out.println("Error Generating Narrative for "+r.fhirType()+"/"+r.getId()+": "+e.getMessage()); 124 e.printStackTrace(); 125 x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); 126 } 127 return hasExtensions; 128 } 129 130 131 @Override 132 public String display(Resource r) throws UnsupportedEncodingException, IOException { 133 return "todo"; 134 } 135 136 @Override 137 public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 138 return "Not done yet"; 139 } 140 141// 142// public void inject(Element er, XhtmlNode x, NarrativeStatus status, boolean pretty) { 143// if (!x.hasAttribute("xmlns")) 144// x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 145// Element le = XMLUtil.getNamedChild(er, "language"); 146// String l = le == null ? null : le.getAttribute("value"); 147// if (!Utilities.noString(l)) { 148// // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues 149// x.setAttribute("lang", l); 150// x.setAttribute("xml:lang", l); 151// } 152// Element txt = XMLUtil.getNamedChild(er, "text"); 153// if (txt == null) { 154// txt = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "text"); 155// Element n = XMLUtil.getFirstChild(er); 156// while (n != null && (n.getNodeName().equals("id") || n.getNodeName().equals("meta") || n.getNodeName().equals("implicitRules") || n.getNodeName().equals("language"))) 157// n = XMLUtil.getNextSibling(n); 158// if (n == null) 159// er.appendChild(txt); 160// else 161// er.insertBefore(txt, n); 162// } 163// Element st = XMLUtil.getNamedChild(txt, "status"); 164// if (st == null) { 165// st = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "status"); 166// Element n = XMLUtil.getFirstChild(txt); 167// if (n == null) 168// txt.appendChild(st); 169// else 170// txt.insertBefore(st, n); 171// } 172// st.setAttribute("value", status.toCode()); 173// Element div = XMLUtil.getNamedChild(txt, "div"); 174// if (div == null) { 175// div = er.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "div"); 176// div.setAttribute("xmlns", FormatUtilities.XHTML_NS); 177// txt.appendChild(div); 178// } 179// if (div.hasChildNodes()) 180// div.appendChild(er.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "hr")); 181// new XhtmlComposer(XhtmlComposer.XML, pretty).compose(div, x); 182// } 183// 184// public void inject(org.hl7.fhir.r4b.elementmodel.Element er, XhtmlNode x, NarrativeStatus status, boolean pretty) throws IOException, FHIRException { 185// if (!x.hasAttribute("xmlns")) 186// x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 187// String l = er.getChildValue("language"); 188// if (!Utilities.noString(l)) { 189// // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues 190// x.setAttribute("lang", l); 191// x.setAttribute("xml:lang", l); 192// } 193// org.hl7.fhir.r4b.elementmodel.Element txt = er.getNamedChild("text"); 194// if (txt == null) { 195// txt = new org.hl7.fhir.r4b.elementmodel.Element("text", er.getProperty().getChild(null, "text")); 196// int i = 0; 197// while (i < er.getChildren().size() && (er.getChildren().get(i).getName().equals("id") || er.getChildren().get(i).getName().equals("meta") || er.getChildren().get(i).getName().equals("implicitRules") || er.getChildren().get(i).getName().equals("language"))) 198// i++; 199// if (i >= er.getChildren().size()) 200// er.getChildren().add(txt); 201// else 202// er.getChildren().add(i, txt); 203// } 204// org.hl7.fhir.r4b.elementmodel.Element st = txt.getNamedChild("status"); 205// if (st == null) { 206// st = new org.hl7.fhir.r4b.elementmodel.Element("status", txt.getProperty().getChild(null, "status")); 207// txt.getChildren().add(0, st); 208// } 209// st.setValue(status.toCode()); 210// org.hl7.fhir.r4b.elementmodel.Element div = txt.getNamedChild("div"); 211// if (div == null) { 212// div = new org.hl7.fhir.r4b.elementmodel.Element("div", txt.getProperty().getChild(null, "div")); 213// txt.getChildren().add(div); 214// div.setValue(new XhtmlComposer(XhtmlComposer.XML, pretty).compose(x)); 215// } 216// div.setValue(x.toString()); 217// div.setXhtml(x); 218// } 219// 220 221 222 public void generateResourceSummary(XhtmlNode x, ResourceWrapper res, boolean textAlready, boolean showCodeDetails, boolean canLink) throws FHIRException, UnsupportedEncodingException, IOException { 223 if (!textAlready) { 224 XhtmlNode div = res.getNarrative(); 225 if (div != null) { 226 if (div.allChildrenAreText()) 227 x.getChildNodes().addAll(div.getChildNodes()); 228 if (div.getChildNodes().size() == 1 && div.getChildNodes().get(0).allChildrenAreText()) 229 x.getChildNodes().addAll(div.getChildNodes().get(0).getChildNodes()); 230 } 231 x.tx("Generated Summary: "); 232 } 233 String path = res.fhirType(); 234 StructureDefinition profile = getContext().getWorker().fetchResource(StructureDefinition.class, path); 235 if (profile == null) 236 x.tx("unknown resource " +path); 237 else { 238 boolean firstElement = true; 239 boolean last = false; 240 for (PropertyWrapper p : res.children()) { 241 if (!ignoreProperty(p) && !p.getElementDefinition().getBase().getPath().startsWith("Resource.")) { 242 ElementDefinition child = getElementDefinition(profile.getSnapshot().getElement(), path+"."+p.getName(), p); 243 if (p.getValues().size() > 0 && p.getValues().get(0) != null && child != null && isPrimitive(child) && includeInSummary(child, p.getValues())) { 244 if (firstElement) 245 firstElement = false; 246 else if (last) 247 x.tx("; "); 248 boolean first = true; 249 last = false; 250 for (BaseWrapper v : p.getValues()) { 251 if (first) 252 first = false; 253 else if (last) 254 x.tx(", "); 255 last = displayLeaf(res, v, child, x, p.getName(), showCodeDetails, canLink) || last; 256 } 257 } 258 } 259 } 260 } 261 } 262 263 264 private boolean ignoreProperty(PropertyWrapper p) { 265 return Utilities.existsInList(p.getName(), "contained"); 266 } 267 268 private boolean includeInSummary(ElementDefinition child, List<BaseWrapper> list) throws UnsupportedEncodingException, FHIRException, IOException { 269 if (child.getName().endsWith("active") && list != null && list.size() > 0 && "true".equals(list.get(0).getBase().primitiveValue())) { 270 return false; 271 } 272 if (child.getIsModifier()) 273 return true; 274 if (child.getMustSupport()) 275 return true; 276 if (child.getType().size() == 1) { 277 String t = child.getType().get(0).getWorkingCode(); 278 if (t.equals("Address") || t.equals("Contact") || t.equals("Reference") || t.equals("Uri") || t.equals("Url") || t.equals("Canonical")) 279 return false; 280 } 281 return true; 282 } 283 284 private ElementDefinition getElementDefinition(List<ElementDefinition> elements, String path, PropertyWrapper p) { 285 for (ElementDefinition element : elements) 286 if (element.getPath().equals(path)) 287 return element; 288 if (path.endsWith("\"]") && p.getStructure() != null) 289 return p.getStructure().getSnapshot().getElement().get(0); 290 return null; 291 } 292 293 private void renderLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode parent, XhtmlNode x, boolean title, boolean showCodeDetails, Map<String, String> displayHints, String path, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { 294 if (ew == null) 295 return; 296 297 Base e = ew.getBase(); 298 299 if (e instanceof StringType) 300 x.addText(((StringType) e).getValue()); 301 else if (e instanceof CodeType) 302 x.addText(((CodeType) e).getValue()); 303 else if (e instanceof IdType) 304 x.addText(((IdType) e).getValue()); 305 else if (e instanceof Extension) 306 return; 307 else if (e instanceof InstantType) 308 x.addText(((InstantType) e).toHumanDisplay()); 309 else if (e instanceof DateTimeType) { 310 renderDateTime(x, e); 311 } else if (e instanceof Base64BinaryType) 312 x.addText(new Base64().encodeAsString(((Base64BinaryType) e).getValue())); 313 else if (e instanceof org.hl7.fhir.r4b.model.DateType) { 314 org.hl7.fhir.r4b.model.DateType dt = ((org.hl7.fhir.r4b.model.DateType) e); 315 if (((org.hl7.fhir.r4b.model.DateType) e).hasValue()) { 316 x.addText(((org.hl7.fhir.r4b.model.DateType) e).toHumanDisplay()); 317 } 318 } else if (e instanceof Enumeration) { 319 Object ev = ((Enumeration<?>) e).getValue(); 320 x.addText(ev == null ? "" : ev.toString()); // todo: look up a display name if there is one 321 } else if (e instanceof BooleanType) { 322 x.addText(((BooleanType) e).getValue().toString()); 323 } else if (e instanceof CodeableConcept) { 324 renderCodeableConcept(x, (CodeableConcept) e, showCodeDetails); 325 } else if (e instanceof Coding) { 326 renderCoding(x, (Coding) e, showCodeDetails); 327 } else if (e instanceof CodeableReference) { 328 renderCodeableReference(x, (CodeableReference) e, showCodeDetails); 329 } else if (e instanceof Annotation) { 330 renderAnnotation(x, (Annotation) e); 331 } else if (e instanceof Identifier) { 332 renderIdentifier(x, (Identifier) e); 333 } else if (e instanceof org.hl7.fhir.r4b.model.IntegerType) { 334 if (((org.hl7.fhir.r4b.model.IntegerType) e).hasValue()) { 335 x.addText(Integer.toString(((org.hl7.fhir.r4b.model.IntegerType) e).getValue())); 336 } else { 337 x.addText("??"); 338 } 339 } else if (e instanceof org.hl7.fhir.r4b.model.Integer64Type) { 340 if (((org.hl7.fhir.r4b.model.Integer64Type) e).hasValue()) { 341 x.addText(Long.toString(((org.hl7.fhir.r4b.model.Integer64Type) e).getValue())); 342 } else { 343 x.addText("??"); 344 } 345 } else if (e instanceof org.hl7.fhir.r4b.model.DecimalType) { 346 x.addText(((org.hl7.fhir.r4b.model.DecimalType) e).getValue().toString()); 347 } else if (e instanceof HumanName) { 348 renderHumanName(x, (HumanName) e); 349 } else if (e instanceof SampledData) { 350 renderSampledData(x, (SampledData) e); 351 } else if (e instanceof Address) { 352 renderAddress(x, (Address) e); 353 } else if (e instanceof ContactPoint) { 354 renderContactPoint(x, (ContactPoint) e); 355 } else if (e instanceof Expression) { 356 renderExpression(x, (Expression) e); 357 } else if (e instanceof Money) { 358 renderMoney(x, (Money) e); 359 } else if (e instanceof ContactDetail) { 360 ContactDetail cd = (ContactDetail) e; 361 if (cd.hasName()) { 362 x.tx(cd.getName()+": "); 363 } 364 boolean first = true; 365 for (ContactPoint c : cd.getTelecom()) { 366 if (first) first = false; else x.tx(","); 367 renderContactPoint(x, c); 368 } 369 } else if (e instanceof UriType) { 370 renderUri(x, (UriType) e, defn.getPath(), rcontext != null && rcontext.getResourceResource() != null ? rcontext.getResourceResource().getId() : null); 371 } else if (e instanceof Timing) { 372 renderTiming(x, (Timing) e); 373 } else if (e instanceof Range) { 374 renderRange(x, (Range) e); 375 } else if (e instanceof Quantity) { 376 renderQuantity(x, (Quantity) e, showCodeDetails); 377 } else if (e instanceof Ratio) { 378 renderQuantity(x, ((Ratio) e).getNumerator(), showCodeDetails); 379 x.tx("/"); 380 renderQuantity(x, ((Ratio) e).getDenominator(), showCodeDetails); 381 } else if (e instanceof Period) { 382 Period p = (Period) e; 383 renderPeriod(x, p); 384 } else if (e instanceof Reference) { 385 Reference r = (Reference) e; 386 if (r.getReference() != null && r.getReference().contains("#")) { 387 if (containedIds.contains(r.getReference().substring(1))) { 388 x.ah(r.getReference()).tx("See "+r.getReference()); 389 } else { 390 // in this case, we render the resource in line 391 ResourceWrapper rw = null; 392 for (ResourceWrapper t : res.getContained()) { 393 if (r.getReference().substring(1).equals(t.getId())) { 394 rw = t; 395 } 396 } 397 if (rw == null) { 398 renderReference(res, x, r); 399 } else { 400 x.an(rw.getId()); 401 ResourceRenderer rr = RendererFactory.factory(rw, context.copy().setAddGeneratedNarrativeHeader(false)); 402 rr.render(parent.blockquote(), rw); 403 } 404 } 405 } else { 406 renderReference(res, x, r); 407 } 408 } else if (e instanceof Resource) { 409 return; 410 } else if (e instanceof DataRequirement) { 411 DataRequirement p = (DataRequirement) e; 412 renderDataRequirement(x, p); 413 } else if (e instanceof PrimitiveType) { 414 x.tx(((PrimitiveType) e).primitiveValue()); 415 } else if (e instanceof ElementDefinition) { 416 x.tx("todo-bundle"); 417 } else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) { 418 throw new NotImplementedException("type "+e.getClass().getName()+" not handled - should not be here"); 419 } 420 } 421 422 private boolean displayLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException { 423 return displayLeaf(res, ew, defn, x, name, showCodeDetails, true); 424 } 425 426 private boolean displayLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails, boolean allowLinks) throws FHIRException, UnsupportedEncodingException, IOException { 427 if (ew == null) 428 return false; 429 Base e = ew.getBase(); 430 if (e == null) 431 return false; 432 433 Map<String, String> displayHints = readDisplayHints(defn); 434 435 if (name.endsWith("[x]")) 436 name = name.substring(0, name.length() - 3); 437 438 if (!showCodeDetails && e instanceof PrimitiveType && isDefault(displayHints, ((PrimitiveType) e))) 439 return false; 440 441 if (e instanceof StringType) { 442 x.addText(name+": "+((StringType) e).getValue()); 443 return true; 444 } else if (e instanceof CodeType) { 445 x.addText(name+": "+((CodeType) e).getValue()); 446 return true; 447 } else if (e instanceof IdType) { 448 x.addText(name+": "+((IdType) e).getValue()); 449 return true; 450 } else if (e instanceof UriType) { 451 if (Utilities.isAbsoluteUrlLinkable(((UriType) e).getValue()) && allowLinks) { 452 x.tx(name+": "); 453 x.ah(((UriType) e).getValue()).addText(((UriType) e).getValue()); 454 } else { 455 x.addText(name+": "+((UriType) e).getValue()); 456 } 457 return true; 458 } else if (e instanceof DateTimeType) { 459 x.addText(name+": "+((DateTimeType) e).toHumanDisplay()); 460 return true; 461 } else if (e instanceof InstantType) { 462 x.addText(name+": "+((InstantType) e).toHumanDisplay()); 463 return true; 464 } else if (e instanceof Extension) { 465 // x.tx("Extensions: todo"); 466 return false; 467 } else if (e instanceof org.hl7.fhir.r4b.model.DateType) { 468 x.addText(name+": "+((org.hl7.fhir.r4b.model.DateType) e).toHumanDisplay()); 469 return true; 470 } else if (e instanceof Enumeration) { 471 x.addText(((Enumeration<?>) e).getValue().toString()); // todo: look up a display name if there is one 472 return true; 473 } else if (e instanceof BooleanType) { 474 if (((BooleanType) e).hasValue()) { 475 x.addText(name); 476 x.addText(": "); 477 x.addText(((BooleanType) e).getValueAsString()); 478 return true; 479 } 480 } else if (e instanceof CodeableReference) { 481 if (((CodeableReference) e).hasReference()) { 482 Reference r = ((CodeableReference) e).getReference(); 483 renderReference(res, x, r, allowLinks); 484 } else { 485 renderCodeableConcept(x, ((CodeableReference) e).getConcept(), showCodeDetails); 486 } 487 return true; 488 } else if (e instanceof CodeableConcept) { 489 renderCodeableConcept(x, (CodeableConcept) e, showCodeDetails); 490 return true; 491 } else if (e instanceof Coding) { 492 renderCoding(x, (Coding) e, showCodeDetails); 493 return true; 494 } else if (e instanceof Annotation) { 495 renderAnnotation(x, (Annotation) e, showCodeDetails); 496 return true; 497 } else if (e instanceof org.hl7.fhir.r4b.model.IntegerType) { 498 x.addText(Integer.toString(((org.hl7.fhir.r4b.model.IntegerType) e).getValue())); 499 return true; 500 } else if (e instanceof org.hl7.fhir.r4b.model.DecimalType) { 501 x.addText(((org.hl7.fhir.r4b.model.DecimalType) e).getValue().toString()); 502 return true; 503 } else if (e instanceof Identifier) { 504 renderIdentifier(x, (Identifier) e); 505 return true; 506 } else if (e instanceof HumanName) { 507 renderHumanName(x, (HumanName) e); 508 return true; 509 } else if (e instanceof SampledData) { 510 renderSampledData(x, (SampledData) e); 511 return true; 512 } else if (e instanceof Address) { 513 renderAddress(x, (Address) e); 514 return true; 515 } else if (e instanceof ContactPoint) { 516 if (allowLinks) { 517 renderContactPoint(x, (ContactPoint) e); 518 } else { 519 displayContactPoint(x, (ContactPoint) e); 520 } 521 return true; 522 } else if (e instanceof Timing) { 523 renderTiming(x, (Timing) e); 524 return true; 525 } else if (e instanceof Quantity) { 526 renderQuantity(x, (Quantity) e, showCodeDetails); 527 return true; 528 } else if (e instanceof Ratio) { 529 renderQuantity(x, ((Ratio) e).getNumerator(), showCodeDetails); 530 x.tx("/"); 531 renderQuantity(x, ((Ratio) e).getDenominator(), showCodeDetails); 532 return true; 533 } else if (e instanceof Period) { 534 Period p = (Period) e; 535 x.addText(name+": "); 536 x.addText(!p.hasStart() ? "?ngen-2?" : p.getStartElement().toHumanDisplay()); 537 x.tx(" --> "); 538 x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay()); 539 return true; 540 } else if (e instanceof Reference) { 541 Reference r = (Reference) e; 542 if (r.hasDisplayElement()) 543 x.addText(r.getDisplay()); 544 else if (r.hasReferenceElement()) { 545 ResourceWithReference tr = resolveReference(res, r.getReference()); 546 x.addText(tr == null ? r.getReference() : "?ngen-3"); // getDisplayForReference(tr.getReference())); 547 } else 548 x.tx("?ngen-4?"); 549 return true; 550 } else if (e instanceof Narrative) { 551 return false; 552 } else if (e instanceof Resource) { 553 return false; 554 } else if (e instanceof ContactDetail) { 555 ContactDetail cd = (ContactDetail) e; 556 if (cd.hasName()) { 557 x.tx(cd.getName()+": "); 558 } 559 boolean first = true; 560 for (ContactPoint c : cd.getTelecom()) { 561 if (first) first = false; else x.tx(","); 562 if (allowLinks) { 563 renderContactPoint(x, c); 564 } else { 565 displayContactPoint(x, c); 566 } 567 } 568 return true; 569 } else if (e instanceof Range) { 570 return false; 571 } else if (e instanceof Meta) { 572 return false; 573 } else if (e instanceof Dosage) { 574 return false; 575 } else if (e instanceof Signature) { 576 return false; 577 } else if (e instanceof UsageContext) { 578 return false; 579 } else if (e instanceof RelatedArtifact) { 580 return false; 581 } else if (e instanceof ElementDefinition) { 582 return false; 583 } else if (e instanceof Base64BinaryType) { 584 return false; 585 } else if (!(e instanceof Attachment)) 586 throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet"); 587 return false; 588 } 589 590 591 592 private boolean isPrimitive(ElementDefinition e) { 593 //we can tell if e is a primitive because it has types 594 if (e.getType().isEmpty()) { 595 return false; 596 } 597 if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode())) { 598 return false; 599 } 600 if (e.getType().size() > 1) { 601 return true; 602 } 603 StructureDefinition sd = context.getWorker().fetchTypeDefinition(e.getTypeFirstRep().getCode()); 604 if (sd != null) { 605 if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 606 return true; 607 } 608 if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) { 609 if (Utilities.existsInList(e.getTypeFirstRep().getCode(), "Extension", "CodeableConcept", "Coding", "Annotation", "Identifier", "HumanName", "SampledData", 610 "Address", "ContactPoint", "ContactDetail", "Timing", "Range", "Quantity", "Ratio", "Period", "Reference")) { 611 return true; 612 } 613 } 614 } 615 return false; 616 } 617 618 private boolean isBase(String code) { 619 return code.equals("Element") || code.equals("BackboneElement"); 620 } 621 622 private List<ElementDefinition> getChildrenForPath(List<ElementDefinition> elements, String path) throws DefinitionException { 623 // do we need to do a name reference substitution? 624 for (ElementDefinition e : elements) { 625 if (e.getPath().equals(path) && e.hasContentReference()) { 626 String ref = e.getContentReference(); 627 ElementDefinition t = null; 628 // now, resolve the name 629 for (ElementDefinition e1 : elements) { 630 if (ref.equals("#"+e1.getId())) 631 t = e1; 632 } 633 if (t == null) 634 throw new DefinitionException("Unable to resolve content reference "+ref+" trying to resolve "+path); 635 path = t.getPath(); 636 break; 637 } 638 } 639 640 List<ElementDefinition> results = new ArrayList<ElementDefinition>(); 641 for (ElementDefinition e : elements) { 642 if (e.getPath().startsWith(path+".") && !e.getPath().substring(path.length()+1).contains(".")) 643 results.add(e); 644 } 645 return results; 646 } 647 648 649 private boolean generateByProfile(StructureDefinition profile, boolean showCodeDetails) { 650 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 651 if(context.isAddGeneratedNarrativeHeader()) { 652 x.para().b().tx("Generated Narrative"+(showCodeDetails ? " with Details" : "")); 653 } 654 try { 655 generateByProfile(rcontext.getResourceResource(), profile, rcontext.getResourceResource(), profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile.getSnapshot().getElement(), rcontext.getResourceResource().getResourceType().toString()), x, rcontext.getResourceResource().getResourceType().toString(), showCodeDetails); 656 } catch (Exception e) { 657 e.printStackTrace(); 658 x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); 659 } 660 inject(rcontext.getResourceResource(), x, NarrativeStatus.GENERATED); 661 return true; 662 } 663 664 private void generateByProfile(Resource res, StructureDefinition profile, Base e, List<ElementDefinition> allElements, ElementDefinition defn, List<ElementDefinition> children, XhtmlNode x, String path, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { 665 generateByProfile(new ResourceWrapperDirect(this.context, res), profile, new BaseWrapperDirect(this.context, e), allElements, defn, children, x, path, showCodeDetails, 0); 666 } 667 668 private void generateByProfile(ResourceWrapper res, StructureDefinition profile, BaseWrapper e, List<ElementDefinition> allElements, ElementDefinition defn, List<ElementDefinition> children, XhtmlNode x, String path, boolean showCodeDetails, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { 669 if (children.isEmpty()) { 670 renderLeaf(res, e, defn, x, x, false, showCodeDetails, readDisplayHints(defn), path, indent); 671 } else { 672 for (PropertyWrapper p : splitExtensions(profile, e.children())) { 673 if (p.hasValues()) { 674 ElementDefinition child = getElementDefinition(children, path+"."+p.getName(), p); 675 if (child == null) { 676 child = p.getElementDefinition(); 677 } 678 if (child != null) { 679 if (!child.getBase().hasPath() || !child.getBase().getPath().startsWith("Resource.")) { 680 generateElementByProfile(res, profile, allElements, x, path, showCodeDetails, indent, p, child); 681 } 682 } 683 } 684 } 685 } 686 } 687 688 public void generateElementByProfile(ResourceWrapper res, StructureDefinition profile, List<ElementDefinition> allElements, XhtmlNode x, String path, 689 boolean showCodeDetails, int indent, PropertyWrapper p, ElementDefinition child) throws UnsupportedEncodingException, IOException, EOperationOutcome { 690 Map<String, String> displayHints = readDisplayHints(child); 691 if ("DomainResource.contained".equals(child.getBase().getPath())) { 692// if (p.getValues().size() > 0 && child != null) { 693// for (BaseWrapper v : p.getValues()) { 694// x.an(v.get("id").primitiveValue()); 695// } 696// } 697 } else if (!exemptFromRendering(child)) { 698 if (isExtension(p)) { 699 hasExtensions = true; 700 } 701 List<ElementDefinition> grandChildren = getChildrenForPath(allElements, path+"."+p.getName()); 702 filterGrandChildren(grandChildren, path+"."+p.getName(), p); 703 if (p.getValues().size() > 0) { 704 if (isPrimitive(child)) { 705 XhtmlNode para = x.isPara() ? para = x : x.para(); 706 String name = p.getName(); 707 if (name.endsWith("[x]")) 708 name = name.substring(0, name.length() - 3); 709 if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { 710 para.b().addText(name); 711 para.tx(": "); 712 if (renderAsList(child) && p.getValues().size() > 1) { 713 XhtmlNode list = x.ul(); 714 for (BaseWrapper v : p.getValues()) 715 renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent); 716 } else { 717 boolean first = true; 718 for (BaseWrapper v : p.getValues()) { 719 if (first) { 720 first = false; 721 } else { 722 para.tx(", "); 723 } 724 renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent); 725 } 726 } 727 } 728 } else if (canDoTable(path, p, grandChildren, x)) { 729 XhtmlNode xn = new XhtmlNode(NodeType.Element, getHeader()); 730 xn.addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); 731 XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table"); 732 tbl.setAttribute("class", "grid"); 733 XhtmlNode tr = tbl.tr(); 734 tr.td().tx("-"); // work around problem with empty table rows 735 boolean add = addColumnHeadings(tr, grandChildren); 736 for (BaseWrapper v : p.getValues()) { 737 if (v != null) { 738 tr = tbl.tr(); 739 tr.td().tx("*"); // work around problem with empty table rows 740 add = addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path, indent) || add; 741 } 742 } 743 if (add) { 744 x.add(xn); 745 x.add(tbl); 746 } 747 } else if (isExtension(p)) { 748 for (BaseWrapper v : p.getValues()) { 749 if (v != null) { 750 PropertyWrapper vp = v.getChildByName("value"); 751 PropertyWrapper ev = v.getChildByName("extension"); 752 if (vp.hasValues()) { 753 BaseWrapper vv = vp.value(); 754 XhtmlNode para = x.para(); 755 para.b().addText(p.getStructure().present()); 756 para.tx(": "); 757 renderLeaf(res, vv, child, x, para, false, showCodeDetails, displayHints, path, indent); 758 } else if (ev.hasValues()) { 759 XhtmlNode bq = x.addTag("blockquote"); 760 bq.para().b().addText(isExtension(p) ? p.getStructure().present() : p.getName()); 761 for (BaseWrapper vv : ev.getValues()) { 762 StructureDefinition ex = context.getWorker().fetchTypeDefinition("Extension"); 763 List<ElementDefinition> children = getChildrenForPath(ex.getSnapshot().getElement(), "Extension"); 764 generateByProfile(res, ex, vv, allElements, child, children, bq, "Extension", showCodeDetails, indent+1); 765 } 766 } 767 } 768 } 769 } else { 770 for (BaseWrapper v : p.getValues()) { 771 if (v != null) { 772 XhtmlNode bq = x.addTag("blockquote"); 773 bq.para().b().addText(isExtension(p) ? p.getStructure().present() : p.getName()); 774 generateByProfile(res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails, indent+1); 775 } 776 } 777 } 778 } 779 } 780 } 781 782 783 private String getHeader() { 784 int i = 3; 785 while (i <= context.getHeaderLevelContext()) 786 i++; 787 if (i > 6) 788 i = 6; 789 return "h"+Integer.toString(i); 790 } 791 792 private List<PropertyWrapper> getValues(String path, PropertyWrapper p, ElementDefinition e) { 793 List<PropertyWrapper> res = new ArrayList<PropertyWrapper>(); 794 for (BaseWrapper v : p.getValues()) { 795 for (PropertyWrapper g : v.children()) { 796 if ((path+"."+p.getName()+"."+g.getName()).equals(e.getPath())) 797 res.add(p); 798 } 799 } 800 return res; 801 } 802 803 private boolean canDoTable(String path, PropertyWrapper p, List<ElementDefinition> grandChildren, XhtmlNode x) { 804 if (isExtension(p)) { 805 return false; 806 } 807 if (x.getName().equals("p")) { 808 return false; 809 } 810 811 for (ElementDefinition e : grandChildren) { 812 List<PropertyWrapper> values = getValues(path, p, e); 813 if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e)) 814 return false; 815 } 816 return true; 817 } 818 819 public boolean isExtension(PropertyWrapper p) { 820 return p.getName().contains("extension["); 821 } 822 823 824 private boolean canCollapse(ElementDefinition e) { 825 // we can collapse any data type 826 return !e.getType().isEmpty(); 827 } 828 private boolean exemptFromRendering(ElementDefinition child) { 829 if (child == null) 830 return false; 831 if ("Composition.subject".equals(child.getPath())) 832 return true; 833 if ("Composition.section".equals(child.getPath())) 834 return true; 835 return false; 836 } 837 838 private boolean renderAsList(ElementDefinition child) { 839 if (child.getType().size() == 1) { 840 String t = child.getType().get(0).getWorkingCode(); 841 if (t.equals("Address") || t.equals("Reference")) 842 return true; 843 } 844 return false; 845 } 846 847 private boolean addColumnHeadings(XhtmlNode tr, List<ElementDefinition> grandChildren) { 848 boolean b = false; 849 for (ElementDefinition e : grandChildren) { 850 b = true; 851 tr.td().b().addText(Utilities.capitalize(tail(e.getPath()))); 852 } 853 return b; 854 } 855 856 private boolean addColumnValues(ResourceWrapper res, XhtmlNode tr, List<ElementDefinition> grandChildren, BaseWrapper v, boolean showCodeDetails, Map<String, String> displayHints, String path, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { 857 boolean b = false; 858 for (ElementDefinition e : grandChildren) { 859 PropertyWrapper p = v.getChildByName(e.getPath().substring(e.getPath().lastIndexOf(".")+1)); 860 XhtmlNode td = tr.td(); 861 if (p == null || p.getValues().size() == 0 || p.getValues().get(0) == null) { 862 b = true; 863 td.tx(" "); 864 } else { 865 for (BaseWrapper vv : p.getValues()) { 866 b = true; 867 td.sep(", "); 868 renderLeaf(res, vv, e, td, td, false, showCodeDetails, displayHints, path, indent); 869 } 870 } 871 } 872 return b; 873 } 874 875 private void filterGrandChildren(List<ElementDefinition> grandChildren, String string, PropertyWrapper prop) { 876 List<ElementDefinition> toRemove = new ArrayList<ElementDefinition>(); 877 toRemove.addAll(grandChildren); 878 for (BaseWrapper b : prop.getValues()) { 879 List<ElementDefinition> list = new ArrayList<ElementDefinition>(); 880 for (ElementDefinition ed : toRemove) { 881 PropertyWrapper p = b.getChildByName(tail(ed.getPath())); 882 if (p != null && p.hasValues()) 883 list.add(ed); 884 } 885 toRemove.removeAll(list); 886 } 887 grandChildren.removeAll(toRemove); 888 } 889 890 private List<PropertyWrapper> splitExtensions(StructureDefinition profile, List<PropertyWrapper> children) throws UnsupportedEncodingException, IOException, FHIRException { 891 List<PropertyWrapper> results = new ArrayList<PropertyWrapper>(); 892 Map<String, PropertyWrapper> map = new HashMap<String, PropertyWrapper>(); 893 for (PropertyWrapper p : children) 894 if (p.getName().equals("extension") || p.getName().equals("modifierExtension")) { 895 // we're going to split these up, and create a property for each url 896 if (p.hasValues()) { 897 for (BaseWrapper v : p.getValues()) { 898 Extension ex = (Extension) v.getBase(); 899 String url = ex.getUrl(); 900 StructureDefinition ed = getContext().getWorker().fetchResource(StructureDefinition.class, url); 901 if (ed == null) { 902 if (xverManager == null) { 903 xverManager = new XVerExtensionManager(context.getWorker()); 904 } 905 if (xverManager.matchingUrl(url) && xverManager.status(url) == XVerExtensionStatus.Valid) { 906 ed = xverManager.makeDefinition(url); 907 getContext().getWorker().generateSnapshot(ed); 908 getContext().getWorker().cacheResource(ed); 909 } 910 } 911 if (p.getName().equals("modifierExtension") && ed == null) { 912 throw new DefinitionException("Unknown modifier extension "+url); 913 } 914 PropertyWrapper pe = map.get(p.getName()+"["+url+"]"); 915 if (pe == null) { 916 if (ed == null) { 917 if (url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us")) { 918 throw new DefinitionException("unknown extension "+url); 919 } 920 // System.out.println("unknown extension "+url); 921 pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex), null); 922 } else { 923 ElementDefinition def = ed.getSnapshot().getElement().get(0); 924 pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex), ed.getSnapshot().getElementFirstRep()); 925 ((PropertyWrapperDirect) pe).getWrapped().setStructure(ed); 926 } 927 results.add(pe); 928 } else 929 pe.getValues().add(v); 930 } 931 } 932 } else 933 results.add(p); 934 return results; 935 } 936 937 938 private Map<String, String> readDisplayHints(ElementDefinition defn) throws DefinitionException { 939 Map<String, String> hints = new HashMap<String, String>(); 940 if (defn != null) { 941 String displayHint = ToolingExtensions.getDisplayHint(defn); 942 if (!Utilities.noString(displayHint)) { 943 String[] list = displayHint.split(";"); 944 for (String item : list) { 945 String[] parts = item.split(":"); 946 if (parts.length == 1) { 947 hints.put("value", parts[0].trim()); 948 } else { 949 if (parts.length != 2) { 950 throw new DefinitionException("error reading display hint: '"+displayHint+"'"); 951 } 952 hints.put(parts[0].trim(), parts[1].trim()); 953 } 954 } 955 } 956 } 957 return hints; 958 } 959 960 @SuppressWarnings("rawtypes") 961 private boolean isDefaultValue(Map<String, String> displayHints, List<BaseWrapper> list) throws UnsupportedEncodingException, IOException, FHIRException { 962 if (list.size() != 1) 963 return false; 964 if (list.get(0).getBase() instanceof PrimitiveType) 965 return isDefault(displayHints, (PrimitiveType) list.get(0).getBase()); 966 else 967 return false; 968 } 969 970 private boolean isDefault(Map<String, String> displayHints, PrimitiveType primitiveType) { 971 String v = primitiveType.asStringValue(); 972 if (!Utilities.noString(v) && displayHints.containsKey("default") && v.equals(displayHints.get("default"))) 973 return true; 974 return false; 975 } 976 977 978 protected String tail(String path) { 979 return path.substring(path.lastIndexOf(".")+1); 980 } 981 982 public boolean canRender(Resource resource) { 983 return context.getWorker().getResourceNames().contains(resource.fhirType()); 984 } 985 986}