001package org.hl7.fhir.r5.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.r5.formats.FormatUtilities; 018import org.hl7.fhir.r5.model.Address; 019import org.hl7.fhir.r5.model.Annotation; 020import org.hl7.fhir.r5.model.Attachment; 021import org.hl7.fhir.r5.model.Base; 022import org.hl7.fhir.r5.model.Base64BinaryType; 023import org.hl7.fhir.r5.model.BooleanType; 024import org.hl7.fhir.r5.model.CodeType; 025import org.hl7.fhir.r5.model.CodeableConcept; 026import org.hl7.fhir.r5.model.CodeableReference; 027import org.hl7.fhir.r5.model.Coding; 028import org.hl7.fhir.r5.model.ContactDetail; 029import org.hl7.fhir.r5.model.ContactPoint; 030import org.hl7.fhir.r5.model.DataRequirement; 031import org.hl7.fhir.r5.model.DateTimeType; 032import org.hl7.fhir.r5.model.DomainResource; 033import org.hl7.fhir.r5.model.Dosage; 034import org.hl7.fhir.r5.model.ElementDefinition; 035import org.hl7.fhir.r5.model.Enumeration; 036import org.hl7.fhir.r5.model.Expression; 037import org.hl7.fhir.r5.model.Extension; 038import org.hl7.fhir.r5.model.HumanName; 039import org.hl7.fhir.r5.model.IdType; 040import org.hl7.fhir.r5.model.Identifier; 041import org.hl7.fhir.r5.model.InstantType; 042import org.hl7.fhir.r5.model.Meta; 043import org.hl7.fhir.r5.model.Money; 044import org.hl7.fhir.r5.model.Narrative; 045import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; 046import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 047import org.hl7.fhir.r5.model.Period; 048import org.hl7.fhir.r5.model.PrimitiveType; 049import org.hl7.fhir.r5.model.Property; 050import org.hl7.fhir.r5.model.Quantity; 051import org.hl7.fhir.r5.model.Range; 052import org.hl7.fhir.r5.model.Ratio; 053import org.hl7.fhir.r5.model.Reference; 054import org.hl7.fhir.r5.model.RelatedArtifact; 055import org.hl7.fhir.r5.model.Resource; 056import org.hl7.fhir.r5.model.SampledData; 057import org.hl7.fhir.r5.model.Signature; 058import org.hl7.fhir.r5.model.StringType; 059import org.hl7.fhir.r5.model.StructureDefinition; 060import org.hl7.fhir.r5.model.Timing; 061import org.hl7.fhir.r5.model.UriType; 062import org.hl7.fhir.r5.model.UsageContext; 063import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; 064import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper; 065import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; 066import org.hl7.fhir.r5.renderers.utils.DOMWrappers.BaseWrapperElement; 067import org.hl7.fhir.r5.renderers.utils.DOMWrappers.ResourceWrapperElement; 068import org.hl7.fhir.r5.renderers.utils.DirectWrappers; 069import org.hl7.fhir.r5.renderers.utils.DirectWrappers.BaseWrapperDirect; 070import org.hl7.fhir.r5.renderers.utils.DirectWrappers.PropertyWrapperDirect; 071import org.hl7.fhir.r5.renderers.utils.DirectWrappers.ResourceWrapperDirect; 072import org.hl7.fhir.r5.renderers.utils.RenderingContext; 073import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; 074import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference; 075import org.hl7.fhir.r5.utils.EOperationOutcome; 076import org.hl7.fhir.r5.utils.ToolingExtensions; 077import org.hl7.fhir.r5.utils.XVerExtensionManager; 078import org.hl7.fhir.r5.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.r5.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.r5.elementmodel.Element txt = er.getNamedChild("text"); 194// if (txt == null) { 195// txt = new org.hl7.fhir.r5.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.r5.elementmodel.Element st = txt.getNamedChild("status"); 205// if (st == null) { 206// st = new org.hl7.fhir.r5.elementmodel.Element("status", txt.getProperty().getChild(null, "status")); 207// txt.getChildren().add(0, st); 208// } 209// st.setValue(status.toCode()); 210// org.hl7.fhir.r5.elementmodel.Element div = txt.getNamedChild("div"); 211// if (div == null) { 212// div = new org.hl7.fhir.r5.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.r5.model.DateType) { 314 org.hl7.fhir.r5.model.DateType dt = ((org.hl7.fhir.r5.model.DateType) e); 315 renderDate(x, dt); 316 } else if (e instanceof Enumeration) { 317 Object ev = ((Enumeration<?>) e).getValue(); 318 x.addText(ev == null ? "" : ev.toString()); // todo: look up a display name if there is one 319 } else if (e instanceof BooleanType) { 320 x.addText(((BooleanType) e).getValue().toString()); 321 } else if (e instanceof CodeableConcept) { 322 renderCodeableConcept(x, (CodeableConcept) e, showCodeDetails); 323 } else if (e instanceof Coding) { 324 renderCoding(x, (Coding) e, showCodeDetails); 325 } else if (e instanceof CodeableReference) { 326 renderCodeableReference(x, (CodeableReference) e, showCodeDetails); 327 } else if (e instanceof Annotation) { 328 renderAnnotation(x, (Annotation) e); 329 } else if (e instanceof Identifier) { 330 renderIdentifier(x, (Identifier) e); 331 } else if (e instanceof org.hl7.fhir.r5.model.IntegerType) { 332 if (((org.hl7.fhir.r5.model.IntegerType) e).hasValue()) { 333 x.addText(Integer.toString(((org.hl7.fhir.r5.model.IntegerType) e).getValue())); 334 } else { 335 x.addText("??"); 336 } 337 } else if (e instanceof org.hl7.fhir.r5.model.Integer64Type) { 338 if (((org.hl7.fhir.r5.model.Integer64Type) e).hasValue()) { 339 x.addText(Long.toString(((org.hl7.fhir.r5.model.Integer64Type) e).getValue())); 340 } else { 341 x.addText("??"); 342 } 343 } else if (e instanceof org.hl7.fhir.r5.model.DecimalType) { 344 x.addText(((org.hl7.fhir.r5.model.DecimalType) e).getValue().toString()); 345 } else if (e instanceof HumanName) { 346 renderHumanName(x, (HumanName) e); 347 } else if (e instanceof SampledData) { 348 renderSampledData(x, (SampledData) e); 349 } else if (e instanceof Address) { 350 renderAddress(x, (Address) e); 351 } else if (e instanceof ContactPoint) { 352 renderContactPoint(x, (ContactPoint) e); 353 } else if (e instanceof Expression) { 354 renderExpression(x, (Expression) e); 355 } else if (e instanceof Money) { 356 renderMoney(x, (Money) e); 357 } else if (e instanceof ContactDetail) { 358 ContactDetail cd = (ContactDetail) e; 359 if (cd.hasName()) { 360 x.tx(cd.getName()+": "); 361 } 362 boolean first = true; 363 for (ContactPoint c : cd.getTelecom()) { 364 if (first) first = false; else x.tx(","); 365 renderContactPoint(x, c); 366 } 367 } else if (e instanceof UriType) { 368 renderUri(x, (UriType) e, defn.getPath(), rcontext != null && rcontext.getResourceResource() != null ? rcontext.getResourceResource().getId() : null); 369 } else if (e instanceof Timing) { 370 renderTiming(x, (Timing) e); 371 } else if (e instanceof Range) { 372 renderRange(x, (Range) e); 373 } else if (e instanceof Quantity) { 374 renderQuantity(x, (Quantity) e, showCodeDetails); 375 } else if (e instanceof Ratio) { 376 renderQuantity(x, ((Ratio) e).getNumerator(), showCodeDetails); 377 x.tx("/"); 378 renderQuantity(x, ((Ratio) e).getDenominator(), showCodeDetails); 379 } else if (e instanceof Period) { 380 Period p = (Period) e; 381 renderPeriod(x, p); 382 } else if (e instanceof Reference) { 383 Reference r = (Reference) e; 384 if (r.getReference() != null && r.getReference().contains("#")) { 385 if (containedIds.contains(r.getReference().substring(1))) { 386 x.ah(r.getReference()).tx("See "+r.getReference()); 387 } else { 388 // in this case, we render the resource in line 389 ResourceWrapper rw = null; 390 for (ResourceWrapper t : res.getContained()) { 391 if (r.getReference().substring(1).equals(t.getId())) { 392 rw = t; 393 } 394 } 395 if (rw == null) { 396 renderReference(res, x, r); 397 } else { 398 x.an(rw.getId()); 399 ResourceRenderer rr = RendererFactory.factory(rw, context.copy().setAddGeneratedNarrativeHeader(false)); 400 rr.render(parent.blockquote(), rw); 401 } 402 } 403 } else { 404 renderReference(res, x, r); 405 } 406 } else if (e instanceof Resource) { 407 return; 408 } else if (e instanceof DataRequirement) { 409 DataRequirement p = (DataRequirement) e; 410 renderDataRequirement(x, p); 411 } else if (e instanceof PrimitiveType) { 412 x.tx(((PrimitiveType) e).primitiveValue()); 413 } else if (e instanceof ElementDefinition) { 414 x.tx("todo-bundle"); 415 } else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) { 416 throw new NotImplementedException("type "+e.getClass().getName()+" not handled - should not be here"); 417 } 418 } 419 420 private boolean displayLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException { 421 return displayLeaf(res, ew, defn, x, name, showCodeDetails, true); 422 } 423 424 private boolean displayLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails, boolean allowLinks) throws FHIRException, UnsupportedEncodingException, IOException { 425 if (ew == null) 426 return false; 427 Base e = ew.getBase(); 428 if (e == null) 429 return false; 430 431 Map<String, String> displayHints = readDisplayHints(defn); 432 433 if (name.endsWith("[x]")) 434 name = name.substring(0, name.length() - 3); 435 436 if (!showCodeDetails && e instanceof PrimitiveType && isDefault(displayHints, ((PrimitiveType) e))) 437 return false; 438 439 if (e instanceof StringType) { 440 x.addText(name+": "+((StringType) e).getValue()); 441 return true; 442 } else if (e instanceof CodeType) { 443 x.addText(name+": "+((CodeType) e).getValue()); 444 return true; 445 } else if (e instanceof IdType) { 446 x.addText(name+": "+((IdType) e).getValue()); 447 return true; 448 } else if (e instanceof UriType) { 449 if (Utilities.isAbsoluteUrlLinkable(((UriType) e).getValue()) && allowLinks) { 450 x.tx(name+": "); 451 x.ah(((UriType) e).getValue()).addText(((UriType) e).getValue()); 452 } else { 453 x.addText(name+": "+((UriType) e).getValue()); 454 } 455 return true; 456 } else if (e instanceof DateTimeType) { 457 x.addText(name+": "+((DateTimeType) e).toHumanDisplay()); 458 return true; 459 } else if (e instanceof InstantType) { 460 x.addText(name+": "+((InstantType) e).toHumanDisplay()); 461 return true; 462 } else if (e instanceof Extension) { 463 // x.tx("Extensions: todo"); 464 return false; 465 } else if (e instanceof org.hl7.fhir.r5.model.DateType) { 466 x.addText(name+": "+((org.hl7.fhir.r5.model.DateType) e).toHumanDisplay()); 467 return true; 468 } else if (e instanceof Enumeration) { 469 x.addText(((Enumeration<?>) e).getValue().toString()); // todo: look up a display name if there is one 470 return true; 471 } else if (e instanceof BooleanType) { 472 if (((BooleanType) e).hasValue()) { 473 x.addText(name); 474 x.addText(": "); 475 x.addText(((BooleanType) e).getValueAsString()); 476 return true; 477 } 478 } else if (e instanceof CodeableReference) { 479 if (((CodeableReference) e).hasReference()) { 480 Reference r = ((CodeableReference) e).getReference(); 481 renderReference(res, x, r, allowLinks); 482 } else { 483 renderCodeableConcept(x, ((CodeableReference) e).getConcept(), showCodeDetails); 484 } 485 return true; 486 } else if (e instanceof CodeableConcept) { 487 renderCodeableConcept(x, (CodeableConcept) e, showCodeDetails); 488 return true; 489 } else if (e instanceof Coding) { 490 renderCoding(x, (Coding) e, showCodeDetails); 491 return true; 492 } else if (e instanceof Annotation) { 493 renderAnnotation(x, (Annotation) e, showCodeDetails); 494 return true; 495 } else if (e instanceof org.hl7.fhir.r5.model.IntegerType) { 496 x.addText(Integer.toString(((org.hl7.fhir.r5.model.IntegerType) e).getValue())); 497 return true; 498 } else if (e instanceof org.hl7.fhir.r5.model.DecimalType) { 499 x.addText(((org.hl7.fhir.r5.model.DecimalType) e).getValue().toString()); 500 return true; 501 } else if (e instanceof Identifier) { 502 renderIdentifier(x, (Identifier) e); 503 return true; 504 } else if (e instanceof HumanName) { 505 renderHumanName(x, (HumanName) e); 506 return true; 507 } else if (e instanceof SampledData) { 508 renderSampledData(x, (SampledData) e); 509 return true; 510 } else if (e instanceof Address) { 511 renderAddress(x, (Address) e); 512 return true; 513 } else if (e instanceof ContactPoint) { 514 if (allowLinks) { 515 renderContactPoint(x, (ContactPoint) e); 516 } else { 517 displayContactPoint(x, (ContactPoint) e); 518 } 519 return true; 520 } else if (e instanceof Timing) { 521 renderTiming(x, (Timing) e); 522 return true; 523 } else if (e instanceof Quantity) { 524 renderQuantity(x, (Quantity) e, showCodeDetails); 525 return true; 526 } else if (e instanceof Ratio) { 527 renderQuantity(x, ((Ratio) e).getNumerator(), showCodeDetails); 528 x.tx("/"); 529 renderQuantity(x, ((Ratio) e).getDenominator(), showCodeDetails); 530 return true; 531 } else if (e instanceof Period) { 532 Period p = (Period) e; 533 x.addText(name+": "); 534 x.addText(!p.hasStart() ? "?ngen-2?" : p.getStartElement().toHumanDisplay()); 535 x.tx(" --> "); 536 x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay()); 537 return true; 538 } else if (e instanceof Reference) { 539 Reference r = (Reference) e; 540 if (r.hasDisplayElement()) 541 x.addText(r.getDisplay()); 542 else if (r.hasReferenceElement()) { 543 ResourceWithReference tr = resolveReference(res, r.getReference()); 544 x.addText(tr == null ? r.getReference() : "?ngen-3"); // getDisplayForReference(tr.getReference())); 545 } else 546 x.tx("?ngen-4?"); 547 return true; 548 } else if (e instanceof Narrative) { 549 return false; 550 } else if (e instanceof Resource) { 551 return false; 552 } else if (e instanceof ContactDetail) { 553 ContactDetail cd = (ContactDetail) e; 554 if (cd.hasName()) { 555 x.tx(cd.getName()+": "); 556 } 557 boolean first = true; 558 for (ContactPoint c : cd.getTelecom()) { 559 if (first) first = false; else x.tx(","); 560 if (allowLinks) { 561 renderContactPoint(x, c); 562 } else { 563 displayContactPoint(x, c); 564 } 565 } 566 return true; 567 } else if (e instanceof Range) { 568 return false; 569 } else if (e instanceof Meta) { 570 return false; 571 } else if (e instanceof Dosage) { 572 return false; 573 } else if (e instanceof Signature) { 574 return false; 575 } else if (e instanceof UsageContext) { 576 return false; 577 } else if (e instanceof RelatedArtifact) { 578 return false; 579 } else if (e instanceof ElementDefinition) { 580 return false; 581 } else if (e instanceof Base64BinaryType) { 582 return false; 583 } else if (!(e instanceof Attachment)) 584 throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet"); 585 return false; 586 } 587 588 589 590 private boolean isPrimitive(ElementDefinition e) { 591 //we can tell if e is a primitive because it has types 592 if (e.getType().isEmpty()) { 593 return false; 594 } 595 if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode())) { 596 return false; 597 } 598 if (e.getType().size() > 1) { 599 return true; 600 } 601 StructureDefinition sd = context.getWorker().fetchTypeDefinition(e.getTypeFirstRep().getCode()); 602 if (sd != null) { 603 if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 604 return true; 605 } 606 if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) { 607 if (Utilities.existsInList(e.getTypeFirstRep().getCode(), "Extension", "CodeableConcept", "Coding", "Annotation", "Identifier", "HumanName", "SampledData", 608 "Address", "ContactPoint", "ContactDetail", "Timing", "Range", "Quantity", "Ratio", "Period", "Reference")) { 609 return true; 610 } 611 } 612 } 613 return false; 614 } 615 616 private boolean isBase(String code) { 617 return code.equals("Element") || code.equals("BackboneElement"); 618 } 619 620 private List<ElementDefinition> getChildrenForPath(List<ElementDefinition> elements, String path) throws DefinitionException { 621 // do we need to do a name reference substitution? 622 for (ElementDefinition e : elements) { 623 if (e.getPath().equals(path) && e.hasContentReference()) { 624 String ref = e.getContentReference(); 625 ElementDefinition t = null; 626 // now, resolve the name 627 for (ElementDefinition e1 : elements) { 628 if (ref.equals("#"+e1.getId())) 629 t = e1; 630 } 631 if (t == null) 632 throw new DefinitionException("Unable to resolve content reference "+ref+" trying to resolve "+path); 633 path = t.getPath(); 634 break; 635 } 636 } 637 638 List<ElementDefinition> results = new ArrayList<ElementDefinition>(); 639 for (ElementDefinition e : elements) { 640 if (e.getPath().startsWith(path+".") && !e.getPath().substring(path.length()+1).contains(".")) 641 results.add(e); 642 } 643 return results; 644 } 645 646 647 private boolean generateByProfile(StructureDefinition profile, boolean showCodeDetails) { 648 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 649 if(context.isAddGeneratedNarrativeHeader()) { 650 x.para().b().tx("Generated Narrative"+(showCodeDetails ? " with Details" : "")); 651 } 652 try { 653 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); 654 } catch (Exception e) { 655 e.printStackTrace(); 656 x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); 657 } 658 inject(rcontext.getResourceResource(), x, NarrativeStatus.GENERATED); 659 return true; 660 } 661 662 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 { 663 generateByProfile(new ResourceWrapperDirect(this.context, res), profile, new BaseWrapperDirect(this.context, e), allElements, defn, children, x, path, showCodeDetails, 0); 664 } 665 666 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 { 667 if (children.isEmpty()) { 668 renderLeaf(res, e, defn, x, x, false, showCodeDetails, readDisplayHints(defn), path, indent); 669 } else { 670 for (PropertyWrapper p : splitExtensions(profile, e.children())) { 671 if (p.hasValues()) { 672 ElementDefinition child = getElementDefinition(children, path+"."+p.getName(), p); 673 if (child == null) { 674 child = p.getElementDefinition(); 675 } 676 if (child != null) { 677 if (!child.getBase().hasPath() || !child.getBase().getPath().startsWith("Resource.")) { 678 generateElementByProfile(res, profile, allElements, x, path, showCodeDetails, indent, p, child); 679 } 680 } 681 } 682 } 683 } 684 } 685 686 public void generateElementByProfile(ResourceWrapper res, StructureDefinition profile, List<ElementDefinition> allElements, XhtmlNode x, String path, 687 boolean showCodeDetails, int indent, PropertyWrapper p, ElementDefinition child) throws UnsupportedEncodingException, IOException, EOperationOutcome { 688 Map<String, String> displayHints = readDisplayHints(child); 689 if ("DomainResource.contained".equals(child.getBase().getPath())) { 690// if (p.getValues().size() > 0 && child != null) { 691// for (BaseWrapper v : p.getValues()) { 692// x.an(v.get("id").primitiveValue()); 693// } 694// } 695 } else if (!exemptFromRendering(child)) { 696 if (isExtension(p)) { 697 hasExtensions = true; 698 } 699 List<ElementDefinition> grandChildren = getChildrenForPath(allElements, path+"."+p.getName()); 700 filterGrandChildren(grandChildren, path+"."+p.getName(), p); 701 if (p.getValues().size() > 0) { 702 if (isPrimitive(child)) { 703 XhtmlNode para = x.isPara() ? para = x : x.para(); 704 String name = p.getName(); 705 if (name.endsWith("[x]")) 706 name = name.substring(0, name.length() - 3); 707 if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { 708 para.b().addText(name); 709 para.tx(": "); 710 if (renderAsList(child) && p.getValues().size() > 1) { 711 XhtmlNode list = x.ul(); 712 for (BaseWrapper v : p.getValues()) 713 renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent); 714 } else { 715 boolean first = true; 716 for (BaseWrapper v : p.getValues()) { 717 if (first) { 718 first = false; 719 } else { 720 para.tx(", "); 721 } 722 renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent); 723 } 724 } 725 } 726 } else if (canDoTable(path, p, grandChildren, x)) { 727 XhtmlNode xn = new XhtmlNode(NodeType.Element, getHeader()); 728 xn.addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); 729 XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table"); 730 tbl.setAttribute("class", "grid"); 731 XhtmlNode tr = tbl.tr(); 732 tr.td().tx("-"); // work around problem with empty table rows 733 boolean add = addColumnHeadings(tr, grandChildren); 734 for (BaseWrapper v : p.getValues()) { 735 if (v != null) { 736 tr = tbl.tr(); 737 tr.td().tx("*"); // work around problem with empty table rows 738 add = addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path, indent) || add; 739 } 740 } 741 if (add) { 742 x.add(xn); 743 x.add(tbl); 744 } 745 } else if (isExtension(p)) { 746 for (BaseWrapper v : p.getValues()) { 747 if (v != null) { 748 PropertyWrapper vp = v.getChildByName("value"); 749 PropertyWrapper ev = v.getChildByName("extension"); 750 if (vp.hasValues()) { 751 BaseWrapper vv = vp.value(); 752 XhtmlNode para = x.para(); 753 para.b().addText(p.getStructure().present()); 754 para.tx(": "); 755 renderLeaf(res, vv, child, x, para, false, showCodeDetails, displayHints, path, indent); 756 } else if (ev.hasValues()) { 757 XhtmlNode bq = x.addTag("blockquote"); 758 bq.para().b().addText(isExtension(p) ? p.getStructure().present() : p.getName()); 759 for (BaseWrapper vv : ev.getValues()) { 760 StructureDefinition ex = context.getWorker().fetchTypeDefinition("Extension"); 761 List<ElementDefinition> children = getChildrenForPath(ex.getSnapshot().getElement(), "Extension"); 762 generateByProfile(res, ex, vv, allElements, child, children, bq, "Extension", showCodeDetails, indent+1); 763 } 764 } 765 } 766 } 767 } else { 768 for (BaseWrapper v : p.getValues()) { 769 if (v != null) { 770 XhtmlNode bq = x.addTag("blockquote"); 771 bq.para().b().addText(isExtension(p) ? p.getStructure().present() : p.getName()); 772 generateByProfile(res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails, indent+1); 773 } 774 } 775 } 776 } 777 } 778 } 779 780 781 private String getHeader() { 782 int i = 3; 783 while (i <= context.getHeaderLevelContext()) 784 i++; 785 if (i > 6) 786 i = 6; 787 return "h"+Integer.toString(i); 788 } 789 790 private List<PropertyWrapper> getValues(String path, PropertyWrapper p, ElementDefinition e) { 791 List<PropertyWrapper> res = new ArrayList<PropertyWrapper>(); 792 for (BaseWrapper v : p.getValues()) { 793 for (PropertyWrapper g : v.children()) { 794 if ((path+"."+p.getName()+"."+g.getName()).equals(e.getPath())) 795 res.add(p); 796 } 797 } 798 return res; 799 } 800 801 private boolean canDoTable(String path, PropertyWrapper p, List<ElementDefinition> grandChildren, XhtmlNode x) { 802 if (isExtension(p)) { 803 return false; 804 } 805 if (x.getName().equals("p")) { 806 return false; 807 } 808 809 for (ElementDefinition e : grandChildren) { 810 List<PropertyWrapper> values = getValues(path, p, e); 811 if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e)) 812 return false; 813 } 814 return true; 815 } 816 817 public boolean isExtension(PropertyWrapper p) { 818 return p.getName().contains("extension["); 819 } 820 821 822 private boolean canCollapse(ElementDefinition e) { 823 // we can collapse any data type 824 return !e.getType().isEmpty(); 825 } 826 private boolean exemptFromRendering(ElementDefinition child) { 827 if (child == null) 828 return false; 829 if ("Composition.subject".equals(child.getPath())) 830 return true; 831 if ("Composition.section".equals(child.getPath())) 832 return true; 833 return false; 834 } 835 836 private boolean renderAsList(ElementDefinition child) { 837 if (child.getType().size() == 1) { 838 String t = child.getType().get(0).getWorkingCode(); 839 if (t.equals("Address") || t.equals("Reference")) 840 return true; 841 } 842 return false; 843 } 844 845 private boolean addColumnHeadings(XhtmlNode tr, List<ElementDefinition> grandChildren) { 846 boolean b = false; 847 for (ElementDefinition e : grandChildren) { 848 b = true; 849 tr.td().b().addText(Utilities.capitalize(tail(e.getPath()))); 850 } 851 return b; 852 } 853 854 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 { 855 boolean b = false; 856 for (ElementDefinition e : grandChildren) { 857 PropertyWrapper p = v.getChildByName(e.getPath().substring(e.getPath().lastIndexOf(".")+1)); 858 XhtmlNode td = tr.td(); 859 if (p == null || p.getValues().size() == 0 || p.getValues().get(0) == null) { 860 b = true; 861 td.tx(" "); 862 } else { 863 for (BaseWrapper vv : p.getValues()) { 864 b = true; 865 td.sep(", "); 866 renderLeaf(res, vv, e, td, td, false, showCodeDetails, displayHints, path, indent); 867 } 868 } 869 } 870 return b; 871 } 872 873 private void filterGrandChildren(List<ElementDefinition> grandChildren, String string, PropertyWrapper prop) { 874 List<ElementDefinition> toRemove = new ArrayList<ElementDefinition>(); 875 toRemove.addAll(grandChildren); 876 for (BaseWrapper b : prop.getValues()) { 877 List<ElementDefinition> list = new ArrayList<ElementDefinition>(); 878 for (ElementDefinition ed : toRemove) { 879 PropertyWrapper p = b.getChildByName(tail(ed.getPath())); 880 if (p != null && p.hasValues()) 881 list.add(ed); 882 } 883 toRemove.removeAll(list); 884 } 885 grandChildren.removeAll(toRemove); 886 } 887 888 private List<PropertyWrapper> splitExtensions(StructureDefinition profile, List<PropertyWrapper> children) throws UnsupportedEncodingException, IOException, FHIRException { 889 List<PropertyWrapper> results = new ArrayList<PropertyWrapper>(); 890 Map<String, PropertyWrapper> map = new HashMap<String, PropertyWrapper>(); 891 for (PropertyWrapper p : children) 892 if (p.getName().equals("extension") || p.getName().equals("modifierExtension")) { 893 // we're going to split these up, and create a property for each url 894 if (p.hasValues()) { 895 for (BaseWrapper v : p.getValues()) { 896 Extension ex = (Extension) v.getBase(); 897 String url = ex.getUrl(); 898 StructureDefinition ed = getContext().getWorker().fetchResource(StructureDefinition.class, url); 899 if (ed == null) { 900 if (xverManager == null) { 901 xverManager = new XVerExtensionManager(context.getWorker()); 902 } 903 if (xverManager.matchingUrl(url) && xverManager.status(url) == XVerExtensionStatus.Valid) { 904 ed = xverManager.makeDefinition(url); 905 getContext().getWorker().generateSnapshot(ed); 906 getContext().getWorker().cacheResource(ed); 907 } 908 } 909 if (p.getName().equals("modifierExtension") && ed == null) { 910 throw new DefinitionException("Unknown modifier extension "+url); 911 } 912 PropertyWrapper pe = map.get(p.getName()+"["+url+"]"); 913 if (pe == null) { 914 if (ed == null) { 915 if (url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us")) { 916 throw new DefinitionException("unknown extension "+url); 917 } 918 // System.out.println("unknown extension "+url); 919 pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex), null); 920 } else { 921 ElementDefinition def = ed.getSnapshot().getElement().get(0); 922 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()); 923 ((PropertyWrapperDirect) pe).getWrapped().setStructure(ed); 924 } 925 results.add(pe); 926 } else 927 pe.getValues().add(v); 928 } 929 } 930 } else 931 results.add(p); 932 return results; 933 } 934 935 936 private Map<String, String> readDisplayHints(ElementDefinition defn) throws DefinitionException { 937 Map<String, String> hints = new HashMap<String, String>(); 938 if (defn != null) { 939 String displayHint = ToolingExtensions.getDisplayHint(defn); 940 if (!Utilities.noString(displayHint)) { 941 String[] list = displayHint.split(";"); 942 for (String item : list) { 943 String[] parts = item.split(":"); 944 if (parts.length == 1) { 945 hints.put("value", parts[0].trim()); 946 } else { 947 if (parts.length != 2) { 948 throw new DefinitionException("error reading display hint: '"+displayHint+"'"); 949 } 950 hints.put(parts[0].trim(), parts[1].trim()); 951 } 952 } 953 } 954 } 955 return hints; 956 } 957 958 @SuppressWarnings("rawtypes") 959 private boolean isDefaultValue(Map<String, String> displayHints, List<BaseWrapper> list) throws UnsupportedEncodingException, IOException, FHIRException { 960 if (list.size() != 1) 961 return false; 962 if (list.get(0).getBase() instanceof PrimitiveType) 963 return isDefault(displayHints, (PrimitiveType) list.get(0).getBase()); 964 else 965 return false; 966 } 967 968 private boolean isDefault(Map<String, String> displayHints, PrimitiveType primitiveType) { 969 String v = primitiveType.asStringValue(); 970 if (!Utilities.noString(v) && displayHints.containsKey("default") && v.equals(displayHints.get("default"))) 971 return true; 972 return false; 973 } 974 975 976 protected String tail(String path) { 977 return path.substring(path.lastIndexOf(".")+1); 978 } 979 980 public boolean canRender(Resource resource) { 981 return context.getWorker().getResourceNames().contains(resource.fhirType()); 982 } 983 984}