
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.hl7.fhir.exceptions.DefinitionException; 013import org.hl7.fhir.exceptions.FHIRException; 014import org.hl7.fhir.exceptions.FHIRFormatError; 015import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions; 016import org.hl7.fhir.r5.context.ContextUtilities; 017import org.hl7.fhir.r5.model.ElementDefinition; 018import org.hl7.fhir.r5.model.Resource; 019import org.hl7.fhir.r5.model.StructureDefinition; 020import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 021import org.hl7.fhir.r5.renderers.utils.RenderingContext; 022import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 023import org.hl7.fhir.r5.renderers.utils.ResourceWrapper.NamedResourceWrapperList; 024import org.hl7.fhir.r5.utils.EOperationOutcome; 025import org.hl7.fhir.r5.utils.ToolingExtensions; 026import org.hl7.fhir.r5.utils.XVerExtensionManager; 027import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus; 028import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 029import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 030import org.hl7.fhir.utilities.Utilities; 031import org.hl7.fhir.utilities.xhtml.NodeType; 032import org.hl7.fhir.utilities.xhtml.XhtmlNode; 033 034@MarkedToMoveToAdjunctPackage 035public class ProfileDrivenRenderer extends ResourceRenderer { 036 037 private Set<String> containedIds = new HashSet<>(); 038 039 public ProfileDrivenRenderer(RenderingContext context) { 040 super(context); 041 } 042 043 @Override 044 public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException { 045 renderResourceTechDetails(r, x); 046 try { 047 StructureDefinition sd = context.getContext().fetchTypeDefinition(r.fhirType()); 048 if (sd == null) { 049 throw new FHIRException(context.formatPhrase(RenderingContext.PROF_DRIV_FEXCP, r.fhirType())+" "); 050 } else { 051 ElementDefinition ed = sd.getSnapshot().getElement().get(0); 052 containedIds.clear(); 053 generateByProfile(status, r, sd, r, ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), context.isTechnicalMode(), 0); 054 } 055 } catch (Exception e) { 056 if (DEBUG) { 057 System.out.println(context.formatPhrase(RenderingContext.PROF_DRIV_ERR_GEN_NARR) +r.fhirType()+"/"+r.getId()+": "+e.getMessage()); 058 e.printStackTrace(); 059 } 060 x.para().b().style("color: maroon").tx(context.formatPhrase(RenderingContext.PROF_DRIV_EXCP, e.getMessage())+" "); 061 } 062 } 063 064 065 @Override 066 public String buildSummary(ResourceWrapper res) throws UnsupportedEncodingException, IOException { 067 StructureDefinition profile = getContext().getWorker().fetchTypeDefinition(res.fhirType()); 068 if (profile == null) 069 return "unknown resource type " +res.fhirType(); 070 else { 071 List<ResourceWrapper> children = res.children(); 072 ContextUtilities cu = res.getContextUtilities(); 073 for (ResourceWrapper p : children) { 074 if (p.name().equals("title") && cu.isDatatype(p.fhirType()) && !p.isEmpty()) { 075 return res.fhirType()+" "+ displayDataType(p); 076 } 077 } 078 for (ResourceWrapper p : children) { 079 if (p.name().equals("name") && cu.isDatatype(p.fhirType()) && !p.isEmpty()) { 080 return res.fhirType()+" "+ displayDataType(p); 081 } 082 } 083 for (ResourceWrapper p : children) { 084 if (p.name().equals("code") && cu.isDatatype(p.fhirType()) && !p.isEmpty()) { 085 return res.fhirType()+" "+ displayDataType(p); 086 } 087 } 088 switch (res.fhirType()) { 089 case "Binary" : return res.fhirType()+": "+res.primitiveValue("contentType")+" ("+res.primitiveValue("data").length()+" bytes base64)"; 090 } 091 return generateResourceSummary(res, profile, profile.getSnapshot().getElementFirstRep(), false, false); 092 } 093 } 094 095 public String generateResourceSummary(ResourceWrapper res, StructureDefinition sd, ElementDefinition ed, boolean showCodeDetails, boolean canLink) throws FHIRException, UnsupportedEncodingException, IOException { 096 if (sd == null) 097 return "unknown resource " +res.fhirType(); 098 else { 099 SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, ed, true); 100 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("; "); 101 for (NamedResourceWrapperList p : res.childrenInGroups()) { 102 ElementDefinition pDefn = getElementDefinition(childDefs, p); 103 if (pDefn != null && !ignoreProperty(p) && !pDefn.getBase().getPath().startsWith("Resource.")) { 104 if (p.getValues().size() > 0 && p.getValues().get(0) != null && pDefn != null && isSimple(pDefn) && includeInSummary(pDefn, p)) { 105 CommaSeparatedStringBuilder b2 = new CommaSeparatedStringBuilder(","); 106 for (ResourceWrapper v : p.getValues()) { 107 b2.append(displayDataType(v)); 108 } 109 b.append(formatPhrase(RenderingContext.PROF_DRIV_SUMM_PROP, labelForElement(pDefn), b2.toString())); 110 } 111 } 112 } 113 if (b.length() == 0) { 114 return formatPhrase(RenderingContext.PROF_DRIV_SUMM_NONE, res.fhirType()); 115 } else { 116 return formatPhrase(RenderingContext.PROF_DRIV_SUMM, res.fhirType(), b.toString()); 117 } 118 } 119 } 120 121 private String labelForElement(ElementDefinition pDefn) { 122 return pDefn.getName(); 123 } 124 125 private ElementDefinition getElementDefinition(SourcedChildDefinitions childDefs, NamedResourceWrapperList p) { 126 for (ElementDefinition ed : childDefs.getList()) { 127 if (ed.getName().equals(p.getName())) { 128 return ed; 129 } 130 } 131 return null; 132 } 133 134 private boolean ignoreProperty(NamedResourceWrapperList p) { 135 return Utilities.existsInList(p.getName(), "contained"); 136 } 137 138 private boolean includeInSummary(ElementDefinition child, NamedResourceWrapperList list) throws UnsupportedEncodingException, FHIRException, IOException { 139 if (child.getName().endsWith("active") && list != null && list.getValues().size() > 0 && "true".equals(list.getValues().get(0).primitiveValue())) { 140 return false; 141 } 142 if (child.getIsModifier()) 143 return true; 144 if (child.getMustSupport()) 145 return true; 146 if (child.getType().size() == 1) { 147 String t = child.getType().get(0).getWorkingCode(); 148 if (t.equals("Address") || t.equals("Contact") || t.equals("Reference") || t.equals("Uri") || t.equals("Url") || t.equals("Canonical")) 149 return false; 150 } 151 return true; 152 } 153 154 private ElementDefinition getElementDefinition(List<ElementDefinition> elements, String path) { 155 for (ElementDefinition element : elements) 156 if (element.getPath().equals(path)) 157 return element; 158 return null; 159 } 160 161 private void renderLeaf(RenderingStatus status, ResourceWrapper res, ResourceWrapper ew, StructureDefinition sd, ElementDefinition defn, XhtmlNode parent, XhtmlNode x, boolean title, boolean showCodeDetails, Map<String, String> displayHints, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { 162 if (ew == null) 163 return; 164 165 if (context.isShowComments()) { 166 x = renderCommentsSpan(x, ew); 167 } 168 if (Utilities.existsInList(ew.fhirType(), "Extension") || ew.isResource()) { 169 return; 170 } else if (ew.fhirType().equals("ElementDefinition")) { 171 x.tx("todo-bundle"); 172 } else if (!renderDataType(status, parent, x, ew)) { 173 // well, we have a cell (x) to render this thing, whatever it is 174 // it's not a data type for which we have a built rendering, so we're going to get a list of it's renderable datatype properties, and render them in a list 175 // SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, defn); 176 boolean first = true; 177 x.tx(" ("); 178 for (ResourceWrapper child : ew.children()) { 179// ElementDefinition childDefn = getElementDefinition(childDefs.getList(), child.name()); 180 if (child != null && !"Extension".equals(child.fhirType()) && canRenderDataType(child.fhirType())) { 181 if (first) { 182 first = false; 183 } else { 184 x.tx("; "); 185 } 186 x.tx(context.formatMessage(RenderingContext.GENERAL_DATA_DISPLAY_PROPERTY, child.name(), displayDataType(child))); 187 } 188 } 189 x.tx(")"); 190 } 191 } 192 193 private XhtmlNode renderCommentsSpan(XhtmlNode x, ResourceWrapper e) { 194 if (e.hasFormatComment()) { 195 return x.span(null, CommaSeparatedStringBuilder.join(" ", e.getFormatCommentsPre())); 196 } else { 197 return x; 198 } 199 } 200 201// private boolean displayLeaf(ResourceWrapper res, ResourceWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails, boolean allowLinks) throws FHIRException, UnsupportedEncodingException, IOException { 202// if (ew == null) 203// return false; 204// 205// Map<String, String> displayHints = readDisplayHints(defn); 206// 207// if (name.endsWith("[x]")) 208// name = name.substring(0, name.length() - 3); 209// 210// if (!showCodeDetails && ew.isPrimitive() && isDefault(displayHints, ew)) { 211// return false; 212// } else if (Utilities.existsInList(ew.fhirType(), "Extension")) { 213// return false; 214// } else { 215// x.addText(name+": "+ displayDataType(ew)); 216// return true; 217// } 218// } 219 220 221 222 private boolean isSimple(ElementDefinition e) { 223 //we can tell if e is a primitive because it has types 224 if (e.getType().isEmpty()) { 225 return false; 226 } 227 if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode())) { 228 return false; 229 } 230 if (e.getType().size() > 1) { 231 return true; 232 } 233 StructureDefinition sd = context.getWorker().fetchTypeDefinition(e.getTypeFirstRep().getCode()); 234 if (sd != null) { 235 if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 236 return true; 237 } 238 if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) { 239 if (Utilities.existsInList(e.getTypeFirstRep().getCode(), "Extension", "CodeableConcept", "Coding", "Annotation", "Identifier", "HumanName", "SampledData", 240 "Address", "ContactPoint", "ContactDetail", "Timing", "Range", "Quantity", "Ratio", "Period", "Reference")) { 241 return true; 242 } 243 } 244 } 245 return false; 246 } 247 248 private boolean isBase(String code) { 249 return code != null && (code.equals("Element") || code.equals("BackboneElement")); 250 } 251 252 private SourcedChildDefinitions getChildrenForPath(StructureDefinition profile, String path) throws DefinitionException { 253 var elements = profile.getSnapshot().getElement(); 254 // do we need to do a name reference substitution? 255 for (ElementDefinition e : elements) { 256 if (e.getPath().equals(path) && e.hasContentReference()) { 257 String ref = e.getContentReference(); 258 ElementDefinition t = null; 259 // now, resolve the name 260 for (ElementDefinition e1 : elements) { 261 if (ref.equals("#"+e1.getId())) 262 t = e1; 263 } 264 if (t == null) 265 throw new DefinitionException("Unable to resolve content reference "+ref+" trying to resolve "+path); 266 path = t.getPath(); 267 break; 268 } 269 } 270 271 ElementDefinition t = null; 272 List<ElementDefinition> results = new ArrayList<ElementDefinition>(); 273 for (ElementDefinition e : elements) { 274 if (e.getPath().equals(path)) { 275 t = e; 276 } 277 if (e.getPath().startsWith(path+".") && !e.getPath().substring(path.length()+1).contains(".")) 278 results.add(e); 279 } 280 if (results.isEmpty() && t != null && t.getType().size() == 1) { 281 StructureDefinition tsd = context.getWorker().fetchTypeDefinition(t.getTypeFirstRep().getWorkingCode()); 282 return getChildrenForPath(tsd, tsd.getType()); 283 } 284 return new SourcedChildDefinitions(profile, results, path); 285 } 286 287 private void generateByProfile(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, ResourceWrapper e, ElementDefinition defn, List<ElementDefinition> children, XhtmlNode x, String path, boolean showCodeDetails, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { 288 if (children.isEmpty()) { 289 StructureDefinition sdt = context.getWorker().fetchTypeDefinition(e.fhirType()); 290 if (sdt != null && (sdt.getKind() == StructureDefinitionKind.COMPLEXTYPE || sdt.getKind() == StructureDefinitionKind.PRIMITIVETYPE)) { 291 renderLeaf(status, res, e, profile, defn, x, x, false, showCodeDetails, readDisplayHints(defn), indent); 292 } else { 293 // we don't have anything to render? 294 } 295 } else { 296 List<NamedResourceWrapperList> pl = splitExtensions(profile, e.childrenInGroups()); 297 for (NamedResourceWrapperList p : pl) { 298 generateForProperty(status, res, profile, children, x, path, showCodeDetails, indent, false, p); 299 } 300 for (NamedResourceWrapperList p : pl) { 301 generateForProperty(status, res, profile, children, x, path, showCodeDetails, indent, true, p); 302 } 303 } 304 } 305 306 private void generateForProperty(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, 307 List<ElementDefinition> children, XhtmlNode x, String path, 308 boolean showCodeDetails, int indent, boolean round2, NamedResourceWrapperList p) 309 throws UnsupportedEncodingException, IOException, EOperationOutcome { 310 if (!p.getValues().isEmpty()) { 311 ElementDefinition child = getElementDefinition(children, path+"."+p.getName()); 312 if (child != null) { 313 if (!child.getBase().hasPath() || !child.getBase().getPath().startsWith("Resource.")) { 314 generateElementByProfile(status, res, profile, x, path, showCodeDetails, indent, p, child, round2); 315 } 316 } 317 } 318 } 319 320 public void generateElementByProfile(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, XhtmlNode x, String path, 321 boolean showCodeDetails, int indent, NamedResourceWrapperList p, ElementDefinition child, boolean round2) throws UnsupportedEncodingException, IOException, EOperationOutcome { 322 Map<String, String> displayHints = readDisplayHints(child); 323 if ("DomainResource.contained".equals(child.getBase().getPath())) { 324 if (round2) { 325 for (ResourceWrapper v : p.getValues()) { 326 RenderingContext ctxt = context.forContained(); 327 if (v.getResourceWrapper() != null && !RendererFactory.hasSpecificRenderer(v.fhirType())) { 328 x.hr(); 329 ResourceRenderer rnd = RendererFactory.factory(v.fhirType(), ctxt); 330 rnd.buildNarrative(status, x.blockquote(), v); 331 } 332 } 333 } 334 } else if (!round2 && !exemptFromRendering(child)) { 335 boolean isExt = isExtension(p); 336 if (isExt) { 337 status.setExtensions(true); 338 } 339 SourcedChildDefinitions grandChildren = getChildrenForPath(profile, path+"."+p.getName()); 340 filterGrandChildren(grandChildren.getList(), path+"."+p.getName(), p); 341 if (p.getValues().size() > 0) { 342 if (isSimple(child) && !isExt) { 343 XhtmlNode para = x.isPara() ? para = x : x.para(); 344 String name = p.getName(); 345 if (name.endsWith("[x]")) 346 name = name.substring(0, name.length() - 3); 347 if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { 348 349 para.b().addText(name); 350 para.tx(": "); 351 if (renderAsList(child) && p.getValues().size() > 1) { 352 XhtmlNode list = x.ul(); 353 for (ResourceWrapper v : p.getValues()) 354 renderLeaf(status, res, v, profile, child, x, list.li(), false, showCodeDetails, displayHints, indent); 355 } else { 356 boolean first = true; 357 for (ResourceWrapper v : p.getValues()) { 358 if (first) { 359 first = false; 360 } else { 361 para.tx(", "); 362 } 363 renderLeaf(status, res, v, profile, child, x, para, false, showCodeDetails, displayHints, indent); 364 } 365 } 366 } 367 } else if (canDoTable(path, p, grandChildren.getList(), x)) { 368 XhtmlNode xn = new XhtmlNode(NodeType.Element, getHeader()); 369 xn.addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); 370 XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table"); 371 tbl.setAttribute("class", "grid"); 372 XhtmlNode tr = tbl.tr(); 373 tr.td().style("display: none").tx("-"); // work around problem with empty table rows 374 boolean add = addColumnHeadings(tr, grandChildren.getList()); 375 for (ResourceWrapper v : p.getValues()) { 376 if (v != null) { 377 tr = tbl.tr(); 378 tr.td().style("display: none").tx("*"); // work around problem with empty table rows 379 add = addColumnValues(status, res, tr, profile, grandChildren.getList(), v, showCodeDetails, displayHints, indent) || add; 380 } 381 } 382 if (add) { 383 x.add(xn); 384 x.add(tbl); 385 } 386 } else if (isExtension(p)) { 387 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, p.getUrl()); 388 for (ResourceWrapper v : p.getValues()) { 389 if (v != null) { 390 ResourceWrapper vp = v.child("value"); 391 List<ResourceWrapper> ev = v.children("extension"); 392 if (vp != null) { 393 XhtmlNode para = x.para(); 394 para.b().addText(labelforExtension(sd, p.getUrl())); 395 para.tx(": "); 396 renderLeaf(status, res, vp, profile, child, x, para, false, showCodeDetails, displayHints, indent); 397 } else if (!ev.isEmpty()) { 398 XhtmlNode bq = x.addTag("blockquote"); 399 bq.para().b().addText(labelforExtension(sd, p.getUrl())); 400 // what happens now depends. If all the children are simple extensions, they'll be rendered as properties 401 boolean allSimple = true; 402 for (ResourceWrapper vv : ev) { 403 if (!vv.has("value")) { 404 allSimple = false; 405 } 406 } 407 if (allSimple) { 408 XhtmlNode ul = bq.ul(); 409 for (ResourceWrapper vv : ev) { 410 XhtmlNode li = ul.li(); 411 li.tx(labelForSubExtension(vv.primitiveValue("url"), sd)); 412 li.tx(": "); 413 renderLeaf(status, res, vv.child("value"), sd, child, x, li, isExt, showCodeDetails, displayHints, indent); 414 } 415 } else { 416 for (ResourceWrapper vv : ev) { 417 StructureDefinition ex = context.getWorker().fetchTypeDefinition("Extension"); 418 SourcedChildDefinitions children = getChildrenForPath(ex, "Extension"); 419 generateByProfile(status, res, ex, vv, child, children.getList(), bq, "Extension", showCodeDetails, indent+1); 420 } 421 } 422 } 423 } 424 } 425 } else { 426 for (ResourceWrapper v : p.getValues()) { 427 if (v != null) { 428 XhtmlNode bq = x.addTag("blockquote"); 429 bq.para().b().addText(p.getName()); 430 generateByProfile(status, res, grandChildren.getSource(), v, child, grandChildren.getList(), bq, grandChildren.getPath(), showCodeDetails, indent+1); 431 } 432 } 433 } 434 } 435 } 436 } 437 438// 439// private String getGrandChildBase(List<ElementDefinition> grandChildren) { 440// if (grandChildren == null || grandChildren.isEmpty()) { 441// return null; 442// } 443// String[] path = grandChildren.get(0).getPath().split("\\."); 444// for (int i = 1; i < grandChildren.size(); i++) { 445// path = getSharedString(path, grandChildren.get(1).getPath().split("\\.")); 446// } 447// return CommaSeparatedStringBuilder.join(".", path); 448// } 449// 450// private String[] getSharedString(String[] path, String[] path2) { 451// int m = -1; 452// for (int i = 0; i < Integer.min(path.length, path2.length); i++) { 453// if (path[i].equals(path2[i])) { 454// m = i; 455// } else { 456// break; 457// } 458// } 459// return m == -1 ? new String[0] : Arrays.copyOfRange(path, 0, m+1); 460// } 461 462 private String labelForSubExtension(String url, StructureDefinition sd) { 463 return url; 464 } 465 466 private String labelforExtension(StructureDefinition sd, String url) { 467 if (sd == null) { 468 return tail(url); 469 } else { 470 return sd.present(); 471 } 472 } 473 474 private String getHeader() { 475 int i = 3; 476 while (i <= context.getHeaderLevelContext()) 477 i++; 478 if (i > 6) 479 i = 6; 480 return "h"+Integer.toString(i); 481 } 482 483 private List<ResourceWrapper> getValues(String path, NamedResourceWrapperList p, ElementDefinition e) { 484 List<ResourceWrapper> res = new ArrayList<ResourceWrapper>(); 485 for (ResourceWrapper v : p.getValues()) { 486 for (ResourceWrapper g : v.children()) { 487 if ((path+"."+p.getName()+"."+g.name()).equals(e.getPath())) 488 res.add(v); 489 } 490 } 491 return res; 492 } 493 494 private boolean canDoTable(String path, NamedResourceWrapperList p, List<ElementDefinition> grandChildren, XhtmlNode x) { 495 if (isExtension(p)) { 496 return false; 497 } 498 if (x.getName().equals("p")) { 499 return false; 500 } 501 502 if (grandChildren.size() == 0) { 503 return false; 504 } 505 506 for (ElementDefinition e : grandChildren) { 507 List<ResourceWrapper> values = getValues(path, p, e); 508 if (values.size() > 1 || !isSimple(e) || !canCollapse(e)) 509 return false; 510 } 511 return true; 512 } 513 514 public boolean isExtension(NamedResourceWrapperList p) { 515 return p.getUrl() != null; 516 } 517 518 519 private boolean canCollapse(ElementDefinition e) { 520 // we can collapse any data type 521 return !e.getType().isEmpty(); 522 } 523 private boolean exemptFromRendering(ElementDefinition child) { 524 if (child == null) 525 return false; 526 if ("DomainResource.text".equals(child.getBase().getPath())) { 527 return true; 528 } 529 if ("Composition.subject".equals(child.getPath())) { 530 return true; 531 } 532 if ("Composition.section".equals(child.getPath())) { 533 return true; 534 } 535 return false; 536 } 537 538 private boolean renderAsList(ElementDefinition child) { 539 if (child.getType().size() == 1) { 540 String t = child.getType().get(0).getWorkingCode(); 541 if (t.equals("Address") || t.equals("Reference")) 542 return true; 543 } 544 return false; 545 } 546 547 private boolean addColumnHeadings(XhtmlNode tr, List<ElementDefinition> grandChildren) { 548 boolean b = false; 549 for (ElementDefinition e : grandChildren) { 550 b = true; 551 tr.td().b().addText(Utilities.capitalize(tail(e.getPath()))); 552 } 553 return b; 554 } 555 556 private boolean addColumnValues(RenderingStatus status, ResourceWrapper res, XhtmlNode tr, StructureDefinition profile, List<ElementDefinition> grandChildren, ResourceWrapper v, boolean showCodeDetails, Map<String, String> displayHints, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { 557 boolean b = false; 558 for (ElementDefinition e : grandChildren) { 559 List<ResourceWrapper> p = v.children(e.getPath().substring(e.getPath().lastIndexOf(".")+1)); 560 XhtmlNode td = tr.td(); 561 if (p == null || p.size() == 0) { 562 b = true; 563 td.tx(" "); 564 } else { 565 for (ResourceWrapper vv : p) { 566 b = true; 567 td.sep(", "); 568 renderLeaf(status, res, vv, profile, e, td, td, false, showCodeDetails, displayHints, indent); 569 } 570 } 571 } 572 return b; 573 } 574 575 private void filterGrandChildren(List<ElementDefinition> grandChildren, String string, NamedResourceWrapperList prop) { 576 List<ElementDefinition> toRemove = new ArrayList<ElementDefinition>(); 577 toRemove.addAll(grandChildren); 578 for (ResourceWrapper b : prop.getValues()) { 579 List<ElementDefinition> list = new ArrayList<ElementDefinition>(); 580 for (ElementDefinition ed : toRemove) { 581 List<ResourceWrapper> p = b.children(tail(ed.getPath())); 582 if (p != null && !p.isEmpty()) 583 list.add(ed); 584 } 585 toRemove.removeAll(list); 586 } 587 grandChildren.removeAll(toRemove); 588 } 589 590 private List<NamedResourceWrapperList> splitExtensions(StructureDefinition profile, List<NamedResourceWrapperList> children) throws UnsupportedEncodingException, IOException, FHIRException { 591 List<NamedResourceWrapperList> results = new ArrayList<NamedResourceWrapperList>(); 592 for (NamedResourceWrapperList p : children) { 593 if (p.getName().equals("extension") || p.getName().equals("modifierExtension")) { 594 // we're going to split these up, and create a property for each url 595 for (ResourceWrapper v : p.getValues()) { 596 String url = v.primitiveValue("url"); 597 if (url != null) { 598 // 1. check extension is valid 599 StructureDefinition ed = getContext().getWorker().fetchResource(StructureDefinition.class, url); 600 if (ed == null) { 601 if (xverManager == null) { 602 xverManager = new XVerExtensionManager(context.getWorker()); 603 } 604 if (xverManager.matchingUrl(url) && xverManager.status(url) == XVerExtensionStatus.Valid) { 605 ed = xverManager.makeDefinition(url); 606 new ContextUtilities(getContext().getWorker()).generateSnapshot(ed); 607 getContext().getWorker().cacheResource(ed); 608 } 609 } 610 if (p.getName().equals("modifierExtension") && ed == null) { 611 throw new DefinitionException("Unknown modifier extension "+url); 612 } else { 613 // nothing 614 } 615 616 // 2. Park it 617 NamedResourceWrapperList nl = null; 618 for (NamedResourceWrapperList t : results) { 619 if (t.getUrl() != null && t.getUrl().equals(url)) { 620 nl = t; 621 } 622 } 623 if (nl == null) { 624 nl = new NamedResourceWrapperList(p.getName(), url); 625 results.add(nl); 626 } 627 nl.getValues().add(v); 628 } 629 } 630 } else { 631 results.add(p); 632 } 633 } 634 return results; 635 } 636 637 638 private Map<String, String> readDisplayHints(ElementDefinition defn) throws DefinitionException { 639 Map<String, String> hints = new HashMap<String, String>(); 640 if (defn != null) { 641 String displayHint = ToolingExtensions.getDisplayHint(defn); 642 if (!Utilities.noString(displayHint)) { 643 String[] list = displayHint.split(";"); 644 for (String item : list) { 645 String[] parts = item.split(":"); 646 if (parts.length == 1) { 647 hints.put("value", parts[0].trim()); 648 } else { 649 if (parts.length != 2) { 650 throw new DefinitionException("error reading display hint: '"+displayHint+"'"); 651 } 652 hints.put(parts[0].trim(), parts[1].trim()); 653 } 654 } 655 } 656 } 657 return hints; 658 } 659 660 @SuppressWarnings("rawtypes") 661 private boolean isDefaultValue(Map<String, String> displayHints, List<ResourceWrapper> list) throws UnsupportedEncodingException, IOException, FHIRException { 662 if (list.size() != 1) 663 return false; 664 if (list.get(0).isPrimitive()) 665 return isDefault(displayHints, list.get(0)); 666 else 667 return false; 668 } 669 670 private boolean isDefault(Map<String, String> displayHints, ResourceWrapper primitiveType) { 671 String v = primitiveType.primitiveValue(); 672 if (!Utilities.noString(v) && displayHints.containsKey("default") && v.equals(displayHints.get("default"))) 673 return true; 674 return false; 675 } 676 677 678 protected String tail(String path) { 679 return path.substring(path.lastIndexOf(".")+1); 680 } 681 682 protected String utail(String path) { 683 return path.contains("/") ? path.substring(path.lastIndexOf("/")+1) : path; 684 } 685 686 public boolean canRender(Resource resource) { 687 return context.getWorker().getResourceNames().contains(resource.fhirType()); 688 } 689 690 public RendererType getRendererType() { 691 return RendererType.PROFILE; 692 } 693 694}