001package org.hl7.fhir.r4.utils; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import org.apache.commons.codec.binary.Base64; 035import org.apache.commons.io.output.ByteArrayOutputStream; 036import org.apache.commons.lang3.NotImplementedException; 037import org.hl7.fhir.exceptions.DefinitionException; 038import org.hl7.fhir.exceptions.FHIRException; 039import org.hl7.fhir.exceptions.FHIRFormatError; 040import org.hl7.fhir.exceptions.TerminologyServiceException; 041import org.hl7.fhir.r4.conformance.ProfileUtilities; 042import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider; 043import org.hl7.fhir.r4.context.IWorkerContext; 044import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult; 045import org.hl7.fhir.r4.formats.FormatUtilities; 046import org.hl7.fhir.r4.formats.IParser.OutputStyle; 047import org.hl7.fhir.r4.formats.XmlParser; 048import org.hl7.fhir.r4.model.*; 049import org.hl7.fhir.r4.model.Bundle.*; 050import org.hl7.fhir.r4.model.CapabilityStatement.*; 051import org.hl7.fhir.r4.model.CodeSystem.*; 052import org.hl7.fhir.r4.model.CompartmentDefinition.CompartmentDefinitionResourceComponent; 053import org.hl7.fhir.r4.model.Composition.SectionComponent; 054import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent; 055import org.hl7.fhir.r4.model.ConceptMap.OtherElementComponent; 056import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent; 057import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent; 058import org.hl7.fhir.r4.model.Enumeration; 059import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem; 060import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; 061import org.hl7.fhir.r4.model.HumanName.NameUse; 062import org.hl7.fhir.r4.model.Narrative.NarrativeStatus; 063import org.hl7.fhir.r4.model.OperationDefinition.OperationDefinitionParameterComponent; 064import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; 065import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent; 066import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 067import org.hl7.fhir.r4.model.Timing.EventTiming; 068import org.hl7.fhir.r4.model.Timing.TimingRepeatComponent; 069import org.hl7.fhir.r4.model.Timing.UnitsOfTime; 070import org.hl7.fhir.r4.model.ValueSet.FilterOperator; 071import org.hl7.fhir.r4.model.ValueSet.*; 072import org.hl7.fhir.r4.terminologies.CodeSystemUtilities; 073import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 074import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext; 075import org.hl7.fhir.r4.utils.LiquidEngine.LiquidDocument; 076import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 077import org.hl7.fhir.utilities.LoincLinker; 078import org.hl7.fhir.utilities.MarkDownProcessor; 079import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; 080import org.hl7.fhir.utilities.TerminologyServiceOptions; 081import org.hl7.fhir.utilities.Utilities; 082import org.hl7.fhir.utilities.xhtml.NodeType; 083import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 084import org.hl7.fhir.utilities.xhtml.XhtmlNode; 085import org.hl7.fhir.utilities.xhtml.XhtmlParser; 086import org.hl7.fhir.utilities.xml.XMLUtil; 087import org.hl7.fhir.utilities.xml.XmlGenerator; 088import org.w3c.dom.Element; 089 090import java.io.IOException; 091import java.io.UnsupportedEncodingException; 092import java.text.ParseException; 093import java.text.SimpleDateFormat; 094import java.util.*; 095 096/* 097Copyright (c) 2011+, HL7, Inc 098 All rights reserved. 099 100 Redistribution and use in source and binary forms, with or without modification, 101 are permitted provided that the following conditions are met: 102 103 * Redistributions of source code must retain the above copyright notice, this 104 list of conditions and the following disclaimer. 105 * Redistributions in binary form must reproduce the above copyright notice, 106 this list of conditions and the following disclaimer in the documentation 107 and/or other materials provided with the distribution. 108 * Neither the name of HL7 nor the names of its contributors may be used to 109 endorse or promote products derived from this software without specific 110 prior written permission. 111 112 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 113 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 114 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 115 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 116 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 117 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 118 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 119 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 120 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 121 POSSIBILITY OF SUCH DAMAGE. 122 123*/ 124 125public class NarrativeGenerator implements INarrativeGenerator { 126 127 public interface ILiquidTemplateProvider { 128 129 String findTemplate(ResourceContext rcontext, DomainResource r); 130 131 } 132 133 public interface ITypeParser { 134 Base parseType(String xml, String type) throws FHIRFormatError, IOException, FHIRException ; 135 } 136 137 public class ConceptMapRenderInstructions { 138 private String name; 139 private String url; 140 private boolean doDescription; 141 public ConceptMapRenderInstructions(String name, String url, boolean doDescription) { 142 super(); 143 this.name = name; 144 this.url = url; 145 this.doDescription = doDescription; 146 } 147 public String getName() { 148 return name; 149 } 150 public String getUrl() { 151 return url; 152 } 153 public boolean isDoDescription() { 154 return doDescription; 155 } 156 157 } 158 159 public class UsedConceptMap { 160 161 private ConceptMapRenderInstructions details; 162 private String link; 163 private ConceptMap map; 164 public UsedConceptMap(ConceptMapRenderInstructions details, String link, ConceptMap map) { 165 super(); 166 this.details = details; 167 this.link = link; 168 this.map = map; 169 } 170 public ConceptMapRenderInstructions getDetails() { 171 return details; 172 } 173 public ConceptMap getMap() { 174 return map; 175 } 176 public String getLink() { 177 return link; 178 } 179 } 180 181 public static class ResourceContext { 182 Bundle bundleResource; 183 184 DomainResource resourceResource; 185 186 public ResourceContext(Bundle bundle, DomainResource dr) { 187 super(); 188 this.bundleResource = bundle; 189 this.resourceResource = dr; 190 } 191 192 public ResourceContext(Element bundle, Element doc) { 193 } 194 195 public ResourceContext(org.hl7.fhir.r4.elementmodel.Element bundle, org.hl7.fhir.r4.elementmodel.Element er) { 196 } 197 198 public Resource resolve(String value) { 199 if (value.startsWith("#")) { 200 for (Resource r : resourceResource.getContained()) { 201 if (r.getId().equals(value.substring(1))) 202 return r; 203 } 204 return null; 205 } 206 if (bundleResource != null) { 207 for (BundleEntryComponent be : bundleResource.getEntry()) { 208 if (be.getFullUrl().equals(value)) 209 return be.getResource(); 210 if (value.equals(be.getResource().fhirType()+"/"+be.getResource().getId())) 211 return be.getResource(); 212 } 213 } 214 return null; 215 } 216 217 } 218 219 private static final String ABSTRACT_CODE_HINT = "This code is not selectable ('Abstract')"; 220 221 public interface IReferenceResolver { 222 223 ResourceWithReference resolve(String url); 224 225 } 226 227 private Bundle bundle; 228 private String definitionsTarget; 229 private String corePath; 230 private String destDir; 231 private String snomedEdition; 232 private ProfileKnowledgeProvider pkp; 233 private MarkDownProcessor markdown = new MarkDownProcessor(Dialect.COMMON_MARK); 234 private ITypeParser parser; // when generating for an element model 235 private ILiquidTemplateProvider templateProvider; 236 private IEvaluationContext services; 237 238 public boolean generate(Bundle b, boolean evenIfAlreadyHasNarrative, Set<String> outputTracker) throws EOperationOutcome, FHIRException, IOException { 239 boolean res = false; 240 this.bundle = b; 241 for (BundleEntryComponent be : b.getEntry()) { 242 if (be.hasResource() && be.getResource() instanceof DomainResource) { 243 DomainResource dr = (DomainResource) be.getResource(); 244 if (evenIfAlreadyHasNarrative || !dr.getText().hasDiv()) 245 res = generate(new ResourceContext(b, dr), dr, outputTracker) || res; 246 } 247 } 248 return res; 249 } 250 251 public boolean generate(DomainResource r, Set<String> outputTracker) throws EOperationOutcome, FHIRException, IOException { 252 return generate(null, r, outputTracker); 253 } 254 255 public boolean generate(ResourceContext rcontext, DomainResource r, Set<String> outputTracker) throws EOperationOutcome, FHIRException, IOException { 256 if (rcontext == null) 257 rcontext = new ResourceContext(null, r); 258 259 if (templateProvider != null) { 260 String liquidTemplate = templateProvider.findTemplate(rcontext, r); 261 if (liquidTemplate != null) { 262 return generateByLiquid(rcontext, r, liquidTemplate, outputTracker); 263 } 264 } 265 if (r instanceof ConceptMap) { 266 return generate(rcontext, (ConceptMap) r); // Maintainer = Grahame 267 } else if (r instanceof ValueSet) { 268 return generate(rcontext, (ValueSet) r, true); // Maintainer = Grahame 269 } else if (r instanceof CodeSystem) { 270 return generate(rcontext, (CodeSystem) r, true, null); // Maintainer = Grahame 271 } else if (r instanceof OperationOutcome) { 272 return generate(rcontext, (OperationOutcome) r); // Maintainer = Grahame 273 } else if (r instanceof CapabilityStatement) { 274 return generate(rcontext, (CapabilityStatement) r); // Maintainer = Grahame 275 } else if (r instanceof CompartmentDefinition) { 276 return generate(rcontext, (CompartmentDefinition) r); // Maintainer = Grahame 277 } else if (r instanceof OperationDefinition) { 278 return generate(rcontext, (OperationDefinition) r); // Maintainer = Grahame 279 } else if (r instanceof StructureDefinition) { 280 return generate(rcontext, (StructureDefinition) r, outputTracker); // Maintainer = Grahame 281 } else if (r instanceof ImplementationGuide) { 282 return generate(rcontext, (ImplementationGuide) r); // Maintainer = Lloyd (until Grahame wants to take over . . . :)) 283 } else if (r instanceof DiagnosticReport) { 284 inject(r, generateDiagnosticReport(new ResourceWrapperDirect(r)), NarrativeStatus.GENERATED); // Maintainer = Grahame 285 return true; 286 } else { 287 StructureDefinition p = null; 288 if (r.hasMeta()) 289 for (UriType pu : r.getMeta().getProfile()) 290 if (p == null) 291 p = context.fetchResource(StructureDefinition.class, pu.getValue()); 292 if (p == null) 293 p = context.fetchResource(StructureDefinition.class, r.getResourceType().toString()); 294 if (p == null) 295 p = context.fetchTypeDefinition(r.getResourceType().toString().toLowerCase()); 296 if (p != null) 297 return generateByProfile(rcontext, p, true); 298 else 299 return false; 300 } 301 } 302 303 private boolean generateByLiquid(ResourceContext rcontext, DomainResource r, String liquidTemplate, Set<String> outputTracker) { 304 305 LiquidEngine engine = new LiquidEngine(context, services); 306 XhtmlNode x; 307 try { 308 LiquidDocument doc = engine.parse(liquidTemplate, "template"); 309 String html = engine.evaluate(doc, r, rcontext); 310 x = new XhtmlParser().parseFragment(html); 311 if (!x.getName().equals("div")) 312 throw new FHIRException("Error in template: Root element is not 'div'"); 313 } catch (FHIRException | IOException e) { 314 x = new XhtmlNode(NodeType.Element, "div"); 315 x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage()); 316 } 317 inject(r, x, NarrativeStatus.GENERATED); 318 return true; 319 } 320 321 private interface PropertyWrapper { 322 public String getName(); 323 public boolean hasValues(); 324 public List<BaseWrapper> getValues(); 325 public String getTypeCode(); 326 public String getDefinition(); 327 public int getMinCardinality(); 328 public int getMaxCardinality(); 329 public StructureDefinition getStructure(); 330 public BaseWrapper value(); 331 } 332 333 private interface ResourceWrapper { 334 public List<ResourceWrapper> getContained(); 335 public String getId(); 336 public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException; 337 public String getName(); 338 public List<PropertyWrapper> children(); 339 } 340 341 private interface BaseWrapper { 342 public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException; 343 public List<PropertyWrapper> children(); 344 public PropertyWrapper getChildByName(String tail); 345 } 346 347 private class BaseWrapperElement implements BaseWrapper { 348 private Element element; 349 private String type; 350 private StructureDefinition structure; 351 private ElementDefinition definition; 352 private List<ElementDefinition> children; 353 private List<PropertyWrapper> list; 354 355 public BaseWrapperElement(Element element, String type, StructureDefinition structure, ElementDefinition definition) { 356 this.element = element; 357 this.type = type; 358 this.structure = structure; 359 this.definition = definition; 360 } 361 362 @Override 363 public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException { 364 if (type == null || type.equals("Resource") || type.equals("BackboneElement") || type.equals("Element")) 365 return null; 366 367 String xml; 368 try { 369 xml = new XmlGenerator().generate(element); 370 } catch (org.hl7.fhir.exceptions.FHIRException e) { 371 throw new FHIRException(e.getMessage(), e); 372 } 373 return parseType(xml, type); 374 } 375 376 @Override 377 public List<PropertyWrapper> children() { 378 if (list == null) { 379 children = ProfileUtilities.getChildList(structure, definition); 380 list = new ArrayList<NarrativeGenerator.PropertyWrapper>(); 381 for (ElementDefinition child : children) { 382 List<Element> elements = new ArrayList<Element>(); 383 XMLUtil.getNamedChildrenWithWildcard(element, tail(child.getPath()), elements); 384 list.add(new PropertyWrapperElement(structure, child, elements)); 385 } 386 } 387 return list; 388 } 389 390 @Override 391 public PropertyWrapper getChildByName(String name) { 392 for (PropertyWrapper p : children()) 393 if (p.getName().equals(name)) 394 return p; 395 return null; 396 } 397 398 } 399 400 private class PropertyWrapperElement implements PropertyWrapper { 401 402 private StructureDefinition structure; 403 private ElementDefinition definition; 404 private List<Element> values; 405 private List<BaseWrapper> list; 406 407 public PropertyWrapperElement(StructureDefinition structure, ElementDefinition definition, List<Element> values) { 408 this.structure = structure; 409 this.definition = definition; 410 this.values = values; 411 } 412 413 @Override 414 public String getName() { 415 return tail(definition.getPath()); 416 } 417 418 @Override 419 public boolean hasValues() { 420 return values.size() > 0; 421 } 422 423 @Override 424 public List<BaseWrapper> getValues() { 425 if (list == null) { 426 list = new ArrayList<NarrativeGenerator.BaseWrapper>(); 427 for (Element e : values) 428 list.add(new BaseWrapperElement(e, determineType(e), structure, definition)); 429 } 430 return list; 431 } 432 private String determineType(Element e) { 433 if (definition.getType().isEmpty()) 434 return null; 435 if (definition.getType().size() == 1) { 436 if (definition.getType().get(0).getWorkingCode().equals("Element") || definition.getType().get(0).getWorkingCode().equals("BackboneElement")) 437 return null; 438 return definition.getType().get(0).getWorkingCode(); 439 } 440 String t = e.getNodeName().substring(tail(definition.getPath()).length()-3); 441 442 if (isPrimitive(Utilities.uncapitalize(t))) 443 return Utilities.uncapitalize(t); 444 else 445 return t; 446 } 447 448 private boolean isPrimitive(String code) { 449 StructureDefinition sd = context.fetchTypeDefinition(code); 450 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 451 } 452 453 @Override 454 public String getTypeCode() { 455 if (definition == null || definition.getType().size() != 1) 456 throw new Error("not handled"); 457 return definition.getType().get(0).getWorkingCode(); 458 } 459 460 @Override 461 public String getDefinition() { 462 if (definition == null) 463 throw new Error("not handled"); 464 return definition.getDefinition(); 465 } 466 467 @Override 468 public int getMinCardinality() { 469 if (definition == null) 470 throw new Error("not handled"); 471 return definition.getMin(); 472 } 473 474 @Override 475 public int getMaxCardinality() { 476 if (definition == null) 477 throw new Error("not handled"); 478 return definition.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(definition.getMax()); 479 } 480 481 @Override 482 public StructureDefinition getStructure() { 483 return structure; 484 } 485 486 @Override 487 public BaseWrapper value() { 488 if (getValues().size() != 1) 489 throw new Error("Access single value, but value count is "+getValues().size()); 490 return getValues().get(0); 491 } 492 493 } 494 495 private class BaseWrapperMetaElement implements BaseWrapper { 496 private org.hl7.fhir.r4.elementmodel.Element element; 497 private String type; 498 private StructureDefinition structure; 499 private ElementDefinition definition; 500 private List<ElementDefinition> children; 501 private List<PropertyWrapper> list; 502 503 public BaseWrapperMetaElement(org.hl7.fhir.r4.elementmodel.Element element, String type, StructureDefinition structure, ElementDefinition definition) { 504 this.element = element; 505 this.type = type; 506 this.structure = structure; 507 this.definition = definition; 508 } 509 510 @Override 511 public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException { 512 if (type == null || type.equals("Resource") || type.equals("BackboneElement") || type.equals("Element")) 513 return null; 514 515 if (element.hasElementProperty()) 516 return null; 517 ByteArrayOutputStream xml = new ByteArrayOutputStream(); 518 try { 519 new org.hl7.fhir.r4.elementmodel.XmlParser(context).compose(element, xml, OutputStyle.PRETTY, null); 520 } catch (Exception e) { 521 throw new FHIRException(e.getMessage(), e); 522 } 523 return parseType(xml.toString(), type); 524 } 525 526 @Override 527 public List<PropertyWrapper> children() { 528 if (list == null) { 529 children = ProfileUtilities.getChildList(structure, definition); 530 list = new ArrayList<NarrativeGenerator.PropertyWrapper>(); 531 for (ElementDefinition child : children) { 532 List<org.hl7.fhir.r4.elementmodel.Element> elements = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>(); 533 String name = tail(child.getPath()); 534 if (name.endsWith("[x]")) 535 element.getNamedChildrenWithWildcard(name, elements); 536 else 537 element.getNamedChildren(name, elements); 538 list.add(new PropertyWrapperMetaElement(structure, child, elements)); 539 } 540 } 541 return list; 542 } 543 544 @Override 545 public PropertyWrapper getChildByName(String name) { 546 for (PropertyWrapper p : children()) 547 if (p.getName().equals(name)) 548 return p; 549 return null; 550 } 551 552 } 553 public class ResourceWrapperMetaElement implements ResourceWrapper { 554 private org.hl7.fhir.r4.elementmodel.Element wrapped; 555 private List<ResourceWrapper> list; 556 private List<PropertyWrapper> list2; 557 private StructureDefinition definition; 558 public ResourceWrapperMetaElement(org.hl7.fhir.r4.elementmodel.Element wrapped) { 559 this.wrapped = wrapped; 560 this.definition = wrapped.getProperty().getStructure(); 561 } 562 563 @Override 564 public List<ResourceWrapper> getContained() { 565 if (list == null) { 566 List<org.hl7.fhir.r4.elementmodel.Element> children = wrapped.getChildrenByName("contained"); 567 list = new ArrayList<NarrativeGenerator.ResourceWrapper>(); 568 for (org.hl7.fhir.r4.elementmodel.Element e : children) { 569 list.add(new ResourceWrapperMetaElement(e)); 570 } 571 } 572 return list; 573 } 574 575 @Override 576 public String getId() { 577 return wrapped.getNamedChildValue("id"); 578 } 579 580 @Override 581 public XhtmlNode getNarrative() throws IOException, FHIRException { 582 org.hl7.fhir.r4.elementmodel.Element txt = wrapped.getNamedChild("text"); 583 if (txt == null) 584 return null; 585 org.hl7.fhir.r4.elementmodel.Element div = txt.getNamedChild("div"); 586 if (div == null) 587 return null; 588 else 589 return div.getXhtml(); 590 } 591 592 @Override 593 public String getName() { 594 return wrapped.getName(); 595 } 596 597 @Override 598 public List<PropertyWrapper> children() { 599 if (list2 == null) { 600 List<ElementDefinition> children = ProfileUtilities.getChildList(definition, definition.getSnapshot().getElement().get(0)); 601 list2 = new ArrayList<NarrativeGenerator.PropertyWrapper>(); 602 for (ElementDefinition child : children) { 603 List<org.hl7.fhir.r4.elementmodel.Element> elements = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>(); 604 if (child.getPath().endsWith("[x]")) 605 wrapped.getNamedChildrenWithWildcard(tail(child.getPath()), elements); 606 else 607 wrapped.getNamedChildren(tail(child.getPath()), elements); 608 list2.add(new PropertyWrapperMetaElement(definition, child, elements)); 609 } 610 } 611 return list2; 612 } 613 } 614 615 private class PropertyWrapperMetaElement implements PropertyWrapper { 616 617 private StructureDefinition structure; 618 private ElementDefinition definition; 619 private List<org.hl7.fhir.r4.elementmodel.Element> values; 620 private List<BaseWrapper> list; 621 622 public PropertyWrapperMetaElement(StructureDefinition structure, ElementDefinition definition, List<org.hl7.fhir.r4.elementmodel.Element> values) { 623 this.structure = structure; 624 this.definition = definition; 625 this.values = values; 626 } 627 628 @Override 629 public String getName() { 630 return tail(definition.getPath()); 631 } 632 633 @Override 634 public boolean hasValues() { 635 return values.size() > 0; 636 } 637 638 @Override 639 public List<BaseWrapper> getValues() { 640 if (list == null) { 641 list = new ArrayList<NarrativeGenerator.BaseWrapper>(); 642 for (org.hl7.fhir.r4.elementmodel.Element e : values) 643 list.add(new BaseWrapperMetaElement(e, e.fhirType(), structure, definition)); 644 } 645 return list; 646 } 647 648 @Override 649 public String getTypeCode() { 650 return definition.typeSummary(); 651 } 652 653 @Override 654 public String getDefinition() { 655 return definition.getDefinition(); 656 } 657 658 @Override 659 public int getMinCardinality() { 660 return definition.getMin(); 661 } 662 663 @Override 664 public int getMaxCardinality() { 665 return "*".equals(definition.getMax()) ? Integer.MAX_VALUE : Integer.valueOf(definition.getMax()); 666 } 667 668 @Override 669 public StructureDefinition getStructure() { 670 return structure; 671 } 672 673 @Override 674 public BaseWrapper value() { 675 if (getValues().size() != 1) 676 throw new Error("Access single value, but value count is "+getValues().size()); 677 return getValues().get(0); 678 } 679 680 } 681 682 private class ResourceWrapperElement implements ResourceWrapper { 683 684 private Element wrapped; 685 private StructureDefinition definition; 686 private List<ResourceWrapper> list; 687 private List<PropertyWrapper> list2; 688 689 public ResourceWrapperElement(Element wrapped, StructureDefinition definition) { 690 this.wrapped = wrapped; 691 this.definition = definition; 692 } 693 694 @Override 695 public List<ResourceWrapper> getContained() { 696 if (list == null) { 697 List<Element> children = new ArrayList<Element>(); 698 XMLUtil.getNamedChildren(wrapped, "contained", children); 699 list = new ArrayList<NarrativeGenerator.ResourceWrapper>(); 700 for (Element e : children) { 701 Element c = XMLUtil.getFirstChild(e); 702 list.add(new ResourceWrapperElement(c, context.fetchTypeDefinition(c.getNodeName()))); 703 } 704 } 705 return list; 706 } 707 708 @Override 709 public String getId() { 710 return XMLUtil.getNamedChildValue(wrapped, "id"); 711 } 712 713 @Override 714 public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException { 715 Element txt = XMLUtil.getNamedChild(wrapped, "text"); 716 if (txt == null) 717 return null; 718 Element div = XMLUtil.getNamedChild(txt, "div"); 719 if (div == null) 720 return null; 721 try { 722 return new XhtmlParser().parse(new XmlGenerator().generate(div), "div"); 723 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 724 throw new FHIRFormatError(e.getMessage(), e); 725 } catch (org.hl7.fhir.exceptions.FHIRException e) { 726 throw new FHIRException(e.getMessage(), e); 727 } 728 } 729 730 @Override 731 public String getName() { 732 return wrapped.getNodeName(); 733 } 734 735 @Override 736 public List<PropertyWrapper> children() { 737 if (list2 == null) { 738 List<ElementDefinition> children = ProfileUtilities.getChildList(definition, definition.getSnapshot().getElement().get(0)); 739 list2 = new ArrayList<NarrativeGenerator.PropertyWrapper>(); 740 for (ElementDefinition child : children) { 741 List<Element> elements = new ArrayList<Element>(); 742 XMLUtil.getNamedChildrenWithWildcard(wrapped, tail(child.getPath()), elements); 743 list2.add(new PropertyWrapperElement(definition, child, elements)); 744 } 745 } 746 return list2; 747 } 748 } 749 750 private class PropertyWrapperDirect implements PropertyWrapper { 751 private Property wrapped; 752 private List<BaseWrapper> list; 753 754 private PropertyWrapperDirect(Property wrapped) { 755 super(); 756 if (wrapped == null) 757 throw new Error("wrapped == null"); 758 this.wrapped = wrapped; 759 } 760 761 @Override 762 public String getName() { 763 return wrapped.getName(); 764 } 765 766 @Override 767 public boolean hasValues() { 768 return wrapped.hasValues(); 769 } 770 771 @Override 772 public List<BaseWrapper> getValues() { 773 if (list == null) { 774 list = new ArrayList<NarrativeGenerator.BaseWrapper>(); 775 for (Base b : wrapped.getValues()) 776 list.add(b == null ? null : new BaseWrapperDirect(b)); 777 } 778 return list; 779 } 780 781 @Override 782 public String getTypeCode() { 783 return wrapped.getTypeCode(); 784 } 785 786 @Override 787 public String getDefinition() { 788 return wrapped.getDefinition(); 789 } 790 791 @Override 792 public int getMinCardinality() { 793 return wrapped.getMinCardinality(); 794 } 795 796 @Override 797 public int getMaxCardinality() { 798 return wrapped.getMinCardinality(); 799 } 800 801 @Override 802 public StructureDefinition getStructure() { 803 return wrapped.getStructure(); 804 } 805 806 @Override 807 public BaseWrapper value() { 808 if (getValues().size() != 1) 809 throw new Error("Access single value, but value count is "+getValues().size()); 810 return getValues().get(0); 811 } 812 813 public String toString() { 814 return "#."+wrapped.toString(); 815 } 816 } 817 818 private class BaseWrapperDirect implements BaseWrapper { 819 private Base wrapped; 820 private List<PropertyWrapper> list; 821 822 private BaseWrapperDirect(Base wrapped) { 823 super(); 824 if (wrapped == null) 825 throw new Error("wrapped == null"); 826 this.wrapped = wrapped; 827 } 828 829 @Override 830 public Base getBase() { 831 return wrapped; 832 } 833 834 @Override 835 public List<PropertyWrapper> children() { 836 if (list == null) { 837 list = new ArrayList<NarrativeGenerator.PropertyWrapper>(); 838 for (Property p : wrapped.children()) 839 list.add(new PropertyWrapperDirect(p)); 840 } 841 return list; 842 843 } 844 845 @Override 846 public PropertyWrapper getChildByName(String name) { 847 Property p = wrapped.getChildByName(name); 848 if (p == null) 849 return null; 850 else 851 return new PropertyWrapperDirect(p); 852 } 853 854 } 855 856 public class ResourceWrapperDirect implements ResourceWrapper { 857 private Resource wrapped; 858 859 public ResourceWrapperDirect(Resource wrapped) { 860 super(); 861 if (wrapped == null) 862 throw new Error("wrapped == null"); 863 this.wrapped = wrapped; 864 } 865 866 @Override 867 public List<ResourceWrapper> getContained() { 868 List<ResourceWrapper> list = new ArrayList<NarrativeGenerator.ResourceWrapper>(); 869 if (wrapped instanceof DomainResource) { 870 DomainResource dr = (DomainResource) wrapped; 871 for (Resource c : dr.getContained()) { 872 list.add(new ResourceWrapperDirect(c)); 873 } 874 } 875 return list; 876 } 877 878 @Override 879 public String getId() { 880 return wrapped.getId(); 881 } 882 883 @Override 884 public XhtmlNode getNarrative() { 885 if (wrapped instanceof DomainResource) { 886 DomainResource dr = (DomainResource) wrapped; 887 if (dr.hasText() && dr.getText().hasDiv()) 888 return dr.getText().getDiv(); 889 } 890 return null; 891 } 892 893 @Override 894 public String getName() { 895 return wrapped.getResourceType().toString(); 896 } 897 898 @Override 899 public List<PropertyWrapper> children() { 900 List<PropertyWrapper> list = new ArrayList<PropertyWrapper>(); 901 for (Property c : wrapped.children()) 902 list.add(new PropertyWrapperDirect(c)); 903 return list; 904 } 905 } 906 907 public static class ResourceWithReference { 908 909 private String reference; 910 private ResourceWrapper resource; 911 912 public ResourceWithReference(String reference, ResourceWrapper resource) { 913 this.reference = reference; 914 this.resource = resource; 915 } 916 917 public String getReference() { 918 return reference; 919 } 920 921 public ResourceWrapper getResource() { 922 return resource; 923 } 924 } 925 926 private String prefix; 927 private IWorkerContext context; 928 private String basePath; 929 private String tooCostlyNoteEmpty; 930 private String tooCostlyNoteNotEmpty; 931 private IReferenceResolver resolver; 932 private int headerLevelContext; 933 private List<ConceptMapRenderInstructions> renderingMaps = new ArrayList<ConceptMapRenderInstructions>(); 934 private boolean pretty; 935 private boolean canonicalUrlsAsLinks; 936 private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions(); 937 938 public NarrativeGenerator(String prefix, String basePath, IWorkerContext context) { 939 super(); 940 this.prefix = prefix; 941 this.context = context; 942 this.basePath = basePath; 943 init(); 944 } 945 946 public NarrativeGenerator setLiquidServices(ILiquidTemplateProvider templateProvider, IEvaluationContext services) { 947 this.templateProvider = templateProvider; 948 this.services = services; 949 return this; 950 } 951 952 public Base parseType(String xml, String type) throws IOException, FHIRException { 953 if (parser != null) 954 return parser.parseType(xml, type); 955 else 956 return new XmlParser().parseAnyType(xml, type); 957 } 958 959 public NarrativeGenerator(String prefix, String basePath, IWorkerContext context, IReferenceResolver resolver) { 960 super(); 961 this.prefix = prefix; 962 this.context = context; 963 this.basePath = basePath; 964 this.resolver = resolver; 965 init(); 966 } 967 968 969 private void init() { 970 renderingMaps.add(new ConceptMapRenderInstructions("Canonical Status", "http://hl7.org/fhir/ValueSet/resource-status", false)); 971 } 972 973 public List<ConceptMapRenderInstructions> getRenderingMaps() { 974 return renderingMaps; 975 } 976 977 public int getHeaderLevelContext() { 978 return headerLevelContext; 979 } 980 981 public NarrativeGenerator setHeaderLevelContext(int headerLevelContext) { 982 this.headerLevelContext = headerLevelContext; 983 return this; 984 } 985 986 public String getTooCostlyNoteEmpty() { 987 return tooCostlyNoteEmpty; 988 } 989 990 991 public NarrativeGenerator setTooCostlyNoteEmpty(String tooCostlyNoteEmpty) { 992 this.tooCostlyNoteEmpty = tooCostlyNoteEmpty; 993 return this; 994 } 995 996 997 public String getTooCostlyNoteNotEmpty() { 998 return tooCostlyNoteNotEmpty; 999 } 1000 1001 1002 public NarrativeGenerator setTooCostlyNoteNotEmpty(String tooCostlyNoteNotEmpty) { 1003 this.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty; 1004 return this; 1005 } 1006 1007 1008 // dom based version, for build program 1009 public String generate(Element doc) throws IOException, org.hl7.fhir.exceptions.FHIRException { 1010 return generate(null, doc); 1011 } 1012 public String generate(ResourceContext rcontext, Element doc) throws IOException, org.hl7.fhir.exceptions.FHIRException { 1013 if (rcontext == null) 1014 rcontext = new ResourceContext(null, doc); 1015 String rt = "http://hl7.org/fhir/StructureDefinition/"+doc.getNodeName(); 1016 StructureDefinition p = context.fetchResource(StructureDefinition.class, rt); 1017 return generateByProfile(doc, p, true); 1018 } 1019 1020 // dom based version, for build program 1021 public String generate(org.hl7.fhir.r4.elementmodel.Element er, boolean showCodeDetails, ITypeParser parser) throws IOException, FHIRException { 1022 return generate(null, er, showCodeDetails, parser); 1023 } 1024 1025 public String generate(ResourceContext rcontext, org.hl7.fhir.r4.elementmodel.Element er, boolean showCodeDetails, ITypeParser parser) throws IOException, FHIRException { 1026 if (rcontext == null) 1027 rcontext = new ResourceContext(null, er); 1028 this.parser = parser; 1029 1030 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 1031 x.para().b().tx("Generated Narrative"+(showCodeDetails ? " with Details" : "")); 1032 try { 1033 ResourceWrapperMetaElement resw = new ResourceWrapperMetaElement(er); 1034 BaseWrapperMetaElement base = new BaseWrapperMetaElement(er, null, er.getProperty().getStructure(), er.getProperty().getDefinition()); 1035 base.children(); 1036 generateByProfile(resw, er.getProperty().getStructure(), base, er.getProperty().getStructure().getSnapshot().getElement(), er.getProperty().getDefinition(), base.children, x, er.fhirType(), showCodeDetails, 0, rcontext); 1037 1038 } catch (Exception e) { 1039 e.printStackTrace(); 1040 x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage()); 1041 } 1042 inject(er, x, NarrativeStatus.GENERATED); 1043 return new XhtmlComposer(XhtmlComposer.XML, pretty).compose(x); 1044 } 1045 1046 private boolean generateByProfile(ResourceContext rc, StructureDefinition profile, boolean showCodeDetails) { 1047 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 1048 x.para().b().tx("Generated Narrative"+(showCodeDetails ? " with Details" : "")); 1049 try { 1050 generateByProfile(rc.resourceResource, profile, rc.resourceResource, profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile.getSnapshot().getElement(), rc.resourceResource.getResourceType().toString()), x, rc.resourceResource.getResourceType().toString(), showCodeDetails, rc); 1051 } catch (Exception e) { 1052 e.printStackTrace(); 1053 x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage()); 1054 } 1055 inject(rc.resourceResource, x, NarrativeStatus.GENERATED); 1056 return true; 1057 } 1058 1059 private String generateByProfile(Element er, StructureDefinition profile, boolean showCodeDetails) throws IOException, org.hl7.fhir.exceptions.FHIRException { 1060 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 1061 x.para().b().tx("Generated Narrative"+(showCodeDetails ? " with Details" : "")); 1062 try { 1063 generateByProfile(er, profile, er, profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile.getSnapshot().getElement(), er.getLocalName()), x, er.getLocalName(), showCodeDetails); 1064 } catch (Exception e) { 1065 e.printStackTrace(); 1066 x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage()); 1067 } 1068 inject(er, x, NarrativeStatus.GENERATED); 1069 String b = new XhtmlComposer(XhtmlComposer.XML, pretty).compose(x); 1070 return b; 1071 } 1072 1073 private void generateByProfile(Element eres, StructureDefinition profile, Element ee, List<ElementDefinition> allElements, ElementDefinition defn, List<ElementDefinition> children, XhtmlNode x, String path, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException { 1074 1075 ResourceWrapperElement resw = new ResourceWrapperElement(eres, profile); 1076 BaseWrapperElement base = new BaseWrapperElement(ee, null, profile, profile.getSnapshot().getElement().get(0)); 1077 generateByProfile(resw, profile, base, allElements, defn, children, x, path, showCodeDetails, 0, null); 1078 } 1079 1080 1081 private void generateByProfile(Resource res, StructureDefinition profile, Base e, List<ElementDefinition> allElements, ElementDefinition defn, List<ElementDefinition> children, XhtmlNode x, String path, boolean showCodeDetails, ResourceContext rc) throws FHIRException, UnsupportedEncodingException, IOException { 1082 generateByProfile(new ResourceWrapperDirect(res), profile, new BaseWrapperDirect(e), allElements, defn, children, x, path, showCodeDetails, 0, rc); 1083 } 1084 1085 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, ResourceContext rc) throws FHIRException, UnsupportedEncodingException, IOException { 1086 if (children.isEmpty()) { 1087 renderLeaf(res, e, defn, x, false, showCodeDetails, readDisplayHints(defn), path, indent, rc); 1088 } else { 1089 for (PropertyWrapper p : splitExtensions(profile, e.children())) { 1090 if (p.hasValues()) { 1091 ElementDefinition child = getElementDefinition(children, path+"."+p.getName(), p); 1092 if (child != null) { 1093 Map<String, String> displayHints = readDisplayHints(child); 1094 if (!exemptFromRendering(child)) { 1095 List<ElementDefinition> grandChildren = getChildrenForPath(allElements, path+"."+p.getName()); 1096 filterGrandChildren(grandChildren, path+"."+p.getName(), p); 1097 if (p.getValues().size() > 0 && child != null) { 1098 if (isPrimitive(child)) { 1099 XhtmlNode para = x.para(); 1100 String name = p.getName(); 1101 if (name.endsWith("[x]")) 1102 name = name.substring(0, name.length() - 3); 1103 if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { 1104 para.b().addText(name); 1105 para.tx(": "); 1106 if (renderAsList(child) && p.getValues().size() > 1) { 1107 XhtmlNode list = x.ul(); 1108 for (BaseWrapper v : p.getValues()) 1109 renderLeaf(res, v, child, list.li(), false, showCodeDetails, displayHints, path, indent, rc); 1110 } else { 1111 boolean first = true; 1112 for (BaseWrapper v : p.getValues()) { 1113 if (first) 1114 first = false; 1115 else 1116 para.tx(", "); 1117 renderLeaf(res, v, child, para, false, showCodeDetails, displayHints, path, indent, rc); 1118 } 1119 } 1120 } 1121 } else if (canDoTable(path, p, grandChildren)) { 1122 x.addTag(getHeader()).addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); 1123 XhtmlNode tbl = x.table( "grid"); 1124 XhtmlNode tr = tbl.tr(); 1125 tr.td().tx("-"); // work around problem with empty table rows 1126 addColumnHeadings(tr, grandChildren); 1127 for (BaseWrapper v : p.getValues()) { 1128 if (v != null) { 1129 tr = tbl.tr(); 1130 tr.td().tx("*"); // work around problem with empty table rows 1131 addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path, indent, rc); 1132 } 1133 } 1134 } else { 1135 for (BaseWrapper v : p.getValues()) { 1136 if (v != null) { 1137 XhtmlNode bq = x.addTag("blockquote"); 1138 bq.para().b().addText(p.getName()); 1139 generateByProfile(res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails, indent+1, rc); 1140 } 1141 } 1142 } 1143 } 1144 } 1145 } 1146 } 1147 } 1148 } 1149 } 1150 1151 private String getHeader() { 1152 int i = 3; 1153 while (i <= headerLevelContext) 1154 i++; 1155 if (i > 6) 1156 i = 6; 1157 return "h"+Integer.toString(i); 1158 } 1159 1160 private void filterGrandChildren(List<ElementDefinition> grandChildren, String string, PropertyWrapper prop) { 1161 List<ElementDefinition> toRemove = new ArrayList<ElementDefinition>(); 1162 toRemove.addAll(grandChildren); 1163 for (BaseWrapper b : prop.getValues()) { 1164 List<ElementDefinition> list = new ArrayList<ElementDefinition>(); 1165 for (ElementDefinition ed : toRemove) { 1166 PropertyWrapper p = b.getChildByName(tail(ed.getPath())); 1167 if (p != null && p.hasValues()) 1168 list.add(ed); 1169 } 1170 toRemove.removeAll(list); 1171 } 1172 grandChildren.removeAll(toRemove); 1173 } 1174 1175 private List<PropertyWrapper> splitExtensions(StructureDefinition profile, List<PropertyWrapper> children) throws UnsupportedEncodingException, IOException, FHIRException { 1176 List<PropertyWrapper> results = new ArrayList<PropertyWrapper>(); 1177 Map<String, PropertyWrapper> map = new HashMap<String, PropertyWrapper>(); 1178 for (PropertyWrapper p : children) 1179 if (p.getName().equals("extension") || p.getName().equals("modifierExtension")) { 1180 // we're going to split these up, and create a property for each url 1181 if (p.hasValues()) { 1182 for (BaseWrapper v : p.getValues()) { 1183 Extension ex = (Extension) v.getBase(); 1184 String url = ex.getUrl(); 1185 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 1186 if (p.getName().equals("modifierExtension") && ed == null) 1187 throw new DefinitionException("Unknown modifier extension "+url); 1188 PropertyWrapper pe = map.get(p.getName()+"["+url+"]"); 1189 if (pe == null) { 1190 if (ed == null) { 1191 if (url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us")) 1192 throw new DefinitionException("unknown extension "+url); 1193 // System.out.println("unknown extension "+url); 1194 pe = new PropertyWrapperDirect(new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex)); 1195 } else { 1196 ElementDefinition def = ed.getSnapshot().getElement().get(0); 1197 pe = new PropertyWrapperDirect(new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex)); 1198 ((PropertyWrapperDirect) pe).wrapped.setStructure(ed); 1199 } 1200 results.add(pe); 1201 } else 1202 pe.getValues().add(v); 1203 } 1204 } 1205 } else 1206 results.add(p); 1207 return results; 1208 } 1209 1210 @SuppressWarnings("rawtypes") 1211 private boolean isDefaultValue(Map<String, String> displayHints, List<BaseWrapper> list) throws UnsupportedEncodingException, IOException, FHIRException { 1212 if (list.size() != 1) 1213 return false; 1214 if (list.get(0).getBase() instanceof PrimitiveType) 1215 return isDefault(displayHints, (PrimitiveType) list.get(0).getBase()); 1216 else 1217 return false; 1218 } 1219 1220 private boolean isDefault(Map<String, String> displayHints, PrimitiveType primitiveType) { 1221 String v = primitiveType.asStringValue(); 1222 if (!Utilities.noString(v) && displayHints.containsKey("default") && v.equals(displayHints.get("default"))) 1223 return true; 1224 return false; 1225 } 1226 1227 private boolean exemptFromRendering(ElementDefinition child) { 1228 if (child == null) 1229 return false; 1230 if ("Composition.subject".equals(child.getPath())) 1231 return true; 1232 if ("Composition.section".equals(child.getPath())) 1233 return true; 1234 return false; 1235 } 1236 1237 private boolean renderAsList(ElementDefinition child) { 1238 if (child.getType().size() == 1) { 1239 String t = child.getType().get(0).getWorkingCode(); 1240 if (t.equals("Address") || t.equals("Reference")) 1241 return true; 1242 } 1243 return false; 1244 } 1245 1246 private void addColumnHeadings(XhtmlNode tr, List<ElementDefinition> grandChildren) { 1247 for (ElementDefinition e : grandChildren) 1248 tr.td().b().addText(Utilities.capitalize(tail(e.getPath()))); 1249 } 1250 1251 private void addColumnValues(ResourceWrapper res, XhtmlNode tr, List<ElementDefinition> grandChildren, BaseWrapper v, boolean showCodeDetails, Map<String, String> displayHints, String path, int indent, ResourceContext rc) throws FHIRException, UnsupportedEncodingException, IOException { 1252 for (ElementDefinition e : grandChildren) { 1253 PropertyWrapper p = v.getChildByName(e.getPath().substring(e.getPath().lastIndexOf(".")+1)); 1254 if (p == null || p.getValues().size() == 0 || p.getValues().get(0) == null) 1255 tr.td().tx(" "); 1256 else 1257 renderLeaf(res, p.getValues().get(0), e, tr.td(), false, showCodeDetails, displayHints, path, indent, rc); 1258 } 1259 } 1260 1261 private String tail(String path) { 1262 return path.substring(path.lastIndexOf(".")+1); 1263 } 1264 1265 private boolean canDoTable(String path, PropertyWrapper p, List<ElementDefinition> grandChildren) { 1266 for (ElementDefinition e : grandChildren) { 1267 List<PropertyWrapper> values = getValues(path, p, e); 1268 if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e)) 1269 return false; 1270 } 1271 return true; 1272 } 1273 1274 private List<PropertyWrapper> getValues(String path, PropertyWrapper p, ElementDefinition e) { 1275 List<PropertyWrapper> res = new ArrayList<PropertyWrapper>(); 1276 for (BaseWrapper v : p.getValues()) { 1277 for (PropertyWrapper g : v.children()) { 1278 if ((path+"."+p.getName()+"."+g.getName()).equals(e.getPath())) 1279 res.add(p); 1280 } 1281 } 1282 return res; 1283 } 1284 1285 private boolean canCollapse(ElementDefinition e) { 1286 // we can collapse any data type 1287 return !e.getType().isEmpty(); 1288 } 1289 1290 private boolean isPrimitive(ElementDefinition e) { 1291 //we can tell if e is a primitive because it has types 1292 if (e.getType().isEmpty()) 1293 return false; 1294 if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode())) 1295 return false; 1296 return true; 1297// return !e.getType().isEmpty() 1298 } 1299 1300 private boolean isBase(String code) { 1301 return code.equals("Element") || code.equals("BackboneElement"); 1302 } 1303 1304 private ElementDefinition getElementDefinition(List<ElementDefinition> elements, String path, PropertyWrapper p) { 1305 for (ElementDefinition element : elements) 1306 if (element.getPath().equals(path)) 1307 return element; 1308 if (path.endsWith("\"]") && p.getStructure() != null) 1309 return p.getStructure().getSnapshot().getElement().get(0); 1310 return null; 1311 } 1312 1313 private void renderLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, boolean title, boolean showCodeDetails, Map<String, String> displayHints, String path, int indent, ResourceContext rc) throws FHIRException, UnsupportedEncodingException, IOException { 1314 if (ew == null) 1315 return; 1316 1317 1318 Base e = ew.getBase(); 1319 1320 if (e instanceof StringType) 1321 x.addText(((StringType) e).getValue()); 1322 else if (e instanceof CodeType) 1323 x.addText(((CodeType) e).getValue()); 1324 else if (e instanceof IdType) 1325 x.addText(((IdType) e).getValue()); 1326 else if (e instanceof Extension) 1327 return; 1328 else if (e instanceof InstantType) 1329 x.addText(((InstantType) e).toHumanDisplay()); 1330 else if (e instanceof DateTimeType) { 1331 if (e.hasPrimitiveValue()) 1332 x.addText(((DateTimeType) e).toHumanDisplay()); 1333 } else if (e instanceof Base64BinaryType) 1334 x.addText(new Base64().encodeAsString(((Base64BinaryType) e).getValue())); 1335 else if (e instanceof org.hl7.fhir.r4.model.DateType) 1336 x.addText(((org.hl7.fhir.r4.model.DateType) e).toHumanDisplay()); 1337 else if (e instanceof Enumeration) { 1338 Object ev = ((Enumeration<?>) e).getValue(); 1339 x.addText(ev == null ? "" : ev.toString()); // todo: look up a display name if there is one 1340 } else if (e instanceof BooleanType) 1341 x.addText(((BooleanType) e).getValue().toString()); 1342 else if (e instanceof CodeableConcept) { 1343 renderCodeableConcept((CodeableConcept) e, x, showCodeDetails); 1344 } else if (e instanceof Coding) { 1345 renderCoding((Coding) e, x, showCodeDetails); 1346 } else if (e instanceof Annotation) { 1347 renderAnnotation((Annotation) e, x); 1348 } else if (e instanceof Identifier) { 1349 renderIdentifier((Identifier) e, x); 1350 } else if (e instanceof org.hl7.fhir.r4.model.IntegerType) { 1351 x.addText(Integer.toString(((org.hl7.fhir.r4.model.IntegerType) e).getValue())); 1352 } else if (e instanceof org.hl7.fhir.r4.model.DecimalType) { 1353 x.addText(((org.hl7.fhir.r4.model.DecimalType) e).getValue().toString()); 1354 } else if (e instanceof HumanName) { 1355 renderHumanName((HumanName) e, x); 1356 } else if (e instanceof SampledData) { 1357 renderSampledData((SampledData) e, x); 1358 } else if (e instanceof Address) { 1359 renderAddress((Address) e, x); 1360 } else if (e instanceof ContactPoint) { 1361 renderContactPoint((ContactPoint) e, x); 1362 } else if (e instanceof UriType) { 1363 renderUri((UriType) e, x, defn.getPath(), rc != null && rc.resourceResource != null ? rc.resourceResource.getId() : null); 1364 } else if (e instanceof Timing) { 1365 renderTiming((Timing) e, x); 1366 } else if (e instanceof Range) { 1367 renderRange((Range) e, x); 1368 } else if (e instanceof Quantity) { 1369 renderQuantity((Quantity) e, x, showCodeDetails); 1370 } else if (e instanceof Ratio) { 1371 renderQuantity(((Ratio) e).getNumerator(), x, showCodeDetails); 1372 x.tx("/"); 1373 renderQuantity(((Ratio) e).getDenominator(), x, showCodeDetails); 1374 } else if (e instanceof Period) { 1375 Period p = (Period) e; 1376 x.addText(!p.hasStart() ? "??" : p.getStartElement().toHumanDisplay()); 1377 x.tx(" --> "); 1378 x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay()); 1379 } else if (e instanceof Reference) { 1380 Reference r = (Reference) e; 1381 XhtmlNode c = x; 1382 ResourceWithReference tr = null; 1383 if (r.hasReferenceElement()) { 1384 tr = resolveReference(res, r.getReference(), rc); 1385 1386 if (!r.getReference().startsWith("#")) { 1387 if (tr != null && tr.getReference() != null) 1388 c = x.ah(tr.getReference()); 1389 else 1390 c = x.ah(r.getReference()); 1391 } 1392 } 1393 // what to display: if text is provided, then that. if the reference was resolved, then show the generated narrative 1394 if (r.hasDisplayElement()) { 1395 c.addText(r.getDisplay()); 1396 if (tr != null && tr.getResource() != null) { 1397 c.tx(". Generated Summary: "); 1398 generateResourceSummary(c, tr.getResource(), true, r.getReference().startsWith("#"), rc); 1399 } 1400 } else if (tr != null && tr.getResource() != null) { 1401 generateResourceSummary(c, tr.getResource(), r.getReference().startsWith("#"), r.getReference().startsWith("#"), rc); 1402 } else { 1403 c.addText(r.getReference()); 1404 } 1405 } else if (e instanceof Resource) { 1406 return; 1407 } else if (e instanceof ElementDefinition) { 1408 x.tx("todo-bundle"); 1409 } else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) { 1410 StructureDefinition sd = context.fetchTypeDefinition(e.fhirType()); 1411 if (sd == null) 1412 throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet, and no structure found"); 1413 else 1414 generateByProfile(res, sd, ew, sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep(), 1415 getChildrenForPath(sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep().getPath()), x, path, showCodeDetails, indent + 1, rc); 1416 } 1417 } 1418 1419 private boolean displayLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails, ResourceContext rc) throws FHIRException, UnsupportedEncodingException, IOException { 1420 if (ew == null) 1421 return false; 1422 Base e = ew.getBase(); 1423 if (e == null) 1424 return false; 1425 1426 Map<String, String> displayHints = readDisplayHints(defn); 1427 1428 if (name.endsWith("[x]")) 1429 name = name.substring(0, name.length() - 3); 1430 1431 if (!showCodeDetails && e instanceof PrimitiveType && isDefault(displayHints, ((PrimitiveType) e))) 1432 return false; 1433 1434 if (e instanceof StringType) { 1435 x.addText(name+": "+((StringType) e).getValue()); 1436 return true; 1437 } else if (e instanceof CodeType) { 1438 x.addText(name+": "+((CodeType) e).getValue()); 1439 return true; 1440 } else if (e instanceof IdType) { 1441 x.addText(name+": "+((IdType) e).getValue()); 1442 return true; 1443 } else if (e instanceof UriType) { 1444 x.addText(name+": "+((UriType) e).getValue()); 1445 return true; 1446 } else if (e instanceof DateTimeType) { 1447 x.addText(name+": "+((DateTimeType) e).toHumanDisplay()); 1448 return true; 1449 } else if (e instanceof InstantType) { 1450 x.addText(name+": "+((InstantType) e).toHumanDisplay()); 1451 return true; 1452 } else if (e instanceof Extension) { 1453// x.tx("Extensions: todo"); 1454 return false; 1455 } else if (e instanceof org.hl7.fhir.r4.model.DateType) { 1456 x.addText(name+": "+((org.hl7.fhir.r4.model.DateType) e).toHumanDisplay()); 1457 return true; 1458 } else if (e instanceof Enumeration) { 1459 x.addText(((Enumeration<?>) e).getValue().toString()); // todo: look up a display name if there is one 1460 return true; 1461 } else if (e instanceof BooleanType) { 1462 if (((BooleanType) e).getValue()) { 1463 x.addText(name); 1464 return true; 1465 } 1466 } else if (e instanceof CodeableConcept) { 1467 renderCodeableConcept((CodeableConcept) e, x, showCodeDetails); 1468 return true; 1469 } else if (e instanceof Coding) { 1470 renderCoding((Coding) e, x, showCodeDetails); 1471 return true; 1472 } else if (e instanceof Annotation) { 1473 renderAnnotation((Annotation) e, x, showCodeDetails); 1474 return true; 1475 } else if (e instanceof org.hl7.fhir.r4.model.IntegerType) { 1476 x.addText(Integer.toString(((org.hl7.fhir.r4.model.IntegerType) e).getValue())); 1477 return true; 1478 } else if (e instanceof org.hl7.fhir.r4.model.DecimalType) { 1479 x.addText(((org.hl7.fhir.r4.model.DecimalType) e).getValue().toString()); 1480 return true; 1481 } else if (e instanceof Identifier) { 1482 renderIdentifier((Identifier) e, x); 1483 return true; 1484 } else if (e instanceof HumanName) { 1485 renderHumanName((HumanName) e, x); 1486 return true; 1487 } else if (e instanceof SampledData) { 1488 renderSampledData((SampledData) e, x); 1489 return true; 1490 } else if (e instanceof Address) { 1491 renderAddress((Address) e, x); 1492 return true; 1493 } else if (e instanceof ContactPoint) { 1494 renderContactPoint((ContactPoint) e, x); 1495 return true; 1496 } else if (e instanceof Timing) { 1497 renderTiming((Timing) e, x); 1498 return true; 1499 } else if (e instanceof Quantity) { 1500 renderQuantity((Quantity) e, x, showCodeDetails); 1501 return true; 1502 } else if (e instanceof Ratio) { 1503 renderQuantity(((Ratio) e).getNumerator(), x, showCodeDetails); 1504 x.tx("/"); 1505 renderQuantity(((Ratio) e).getDenominator(), x, showCodeDetails); 1506 return true; 1507 } else if (e instanceof Period) { 1508 Period p = (Period) e; 1509 x.addText(name+": "); 1510 x.addText(!p.hasStart() ? "??" : p.getStartElement().toHumanDisplay()); 1511 x.tx(" --> "); 1512 x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay()); 1513 return true; 1514 } else if (e instanceof Reference) { 1515 Reference r = (Reference) e; 1516 if (r.hasDisplayElement()) 1517 x.addText(r.getDisplay()); 1518 else if (r.hasReferenceElement()) { 1519 ResourceWithReference tr = resolveReference(res, r.getReference(), rc); 1520 x.addText(tr == null ? r.getReference() : "????"); // getDisplayForReference(tr.getReference())); 1521 } else 1522 x.tx("??"); 1523 return true; 1524 } else if (e instanceof Narrative) { 1525 return false; 1526 } else if (e instanceof Resource) { 1527 return false; 1528 } else if (e instanceof ContactDetail) { 1529 return false; 1530 } else if (e instanceof Range) { 1531 return false; 1532 } else if (e instanceof Meta) { 1533 return false; 1534 } else if (e instanceof Dosage) { 1535 return false; 1536 } else if (e instanceof Signature) { 1537 return false; 1538 } else if (e instanceof UsageContext) { 1539 return false; 1540 } else if (e instanceof RelatedArtifact) { 1541 return false; 1542 } else if (e instanceof ElementDefinition) { 1543 return false; 1544 } else if (!(e instanceof Attachment)) 1545 throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet"); 1546 return false; 1547 } 1548 1549 1550 private Map<String, String> readDisplayHints(ElementDefinition defn) throws DefinitionException { 1551 Map<String, String> hints = new HashMap<String, String>(); 1552 if (defn != null) { 1553 String displayHint = ToolingExtensions.getDisplayHint(defn); 1554 if (!Utilities.noString(displayHint)) { 1555 String[] list = displayHint.split(";"); 1556 for (String item : list) { 1557 String[] parts = item.split(":"); 1558 if (parts.length != 2) 1559 throw new DefinitionException("error reading display hint: '"+displayHint+"'"); 1560 hints.put(parts[0].trim(), parts[1].trim()); 1561 } 1562 } 1563 } 1564 return hints; 1565 } 1566 1567 public static String displayPeriod(Period p) { 1568 String s = !p.hasStart() ? "??" : p.getStartElement().toHumanDisplay(); 1569 s = s + " --> "; 1570 return s + (!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay()); 1571 } 1572 1573 private void generateResourceSummary(XhtmlNode x, ResourceWrapper res, boolean textAlready, boolean showCodeDetails, ResourceContext rc) throws FHIRException, UnsupportedEncodingException, IOException { 1574 if (!textAlready) { 1575 XhtmlNode div = res.getNarrative(); 1576 if (div != null) { 1577 if (div.allChildrenAreText()) 1578 x.getChildNodes().addAll(div.getChildNodes()); 1579 if (div.getChildNodes().size() == 1 && div.getChildNodes().get(0).allChildrenAreText()) 1580 x.getChildNodes().addAll(div.getChildNodes().get(0).getChildNodes()); 1581 } 1582 x.tx("Generated Summary: "); 1583 } 1584 String path = res.getName(); 1585 StructureDefinition profile = context.fetchResource(StructureDefinition.class, path); 1586 if (profile == null) 1587 x.tx("unknown resource " +path); 1588 else { 1589 boolean firstElement = true; 1590 boolean last = false; 1591 for (PropertyWrapper p : res.children()) { 1592 ElementDefinition child = getElementDefinition(profile.getSnapshot().getElement(), path+"."+p.getName(), p); 1593 if (p.getValues().size() > 0 && p.getValues().get(0) != null && child != null && isPrimitive(child) && includeInSummary(child)) { 1594 if (firstElement) 1595 firstElement = false; 1596 else if (last) 1597 x.tx("; "); 1598 boolean first = true; 1599 last = false; 1600 for (BaseWrapper v : p.getValues()) { 1601 if (first) 1602 first = false; 1603 else if (last) 1604 x.tx(", "); 1605 last = displayLeaf(res, v, child, x, p.getName(), showCodeDetails, rc) || last; 1606 } 1607 } 1608 } 1609 } 1610 } 1611 1612 1613 private boolean includeInSummary(ElementDefinition child) { 1614 if (child.getIsModifier()) 1615 return true; 1616 if (child.getMustSupport()) 1617 return true; 1618 if (child.getType().size() == 1) { 1619 String t = child.getType().get(0).getWorkingCode(); 1620 if (t.equals("Address") || t.equals("Contact") || t.equals("Reference") || t.equals("Uri") || t.equals("Url") || t.equals("Canonical")) 1621 return false; 1622 } 1623 return true; 1624 } 1625 1626 private ResourceWithReference resolveReference(ResourceWrapper res, String url, ResourceContext rc) { 1627 if (url == null) 1628 return null; 1629 if (url.startsWith("#")) { 1630 for (ResourceWrapper r : res.getContained()) { 1631 if (r.getId().equals(url.substring(1))) 1632 return new ResourceWithReference(null, r); 1633 } 1634 return null; 1635 } 1636 1637 if (rc!=null) { 1638 Resource bundleResource = rc.resolve(url); 1639 if (bundleResource!=null) { 1640 String bundleUrl = "#" + bundleResource.getResourceType().name().toLowerCase() + "_" + bundleResource.getId(); 1641 return new ResourceWithReference(bundleUrl, new ResourceWrapperDirect(bundleResource)); 1642 } 1643 } 1644 1645 Resource ae = context.fetchResource(null, url); 1646 if (ae != null) 1647 return new ResourceWithReference(url, new ResourceWrapperDirect(ae)); 1648 else if (resolver != null) { 1649 return resolver.resolve(url); 1650 } else 1651 return null; 1652 } 1653 1654 private void renderCodeableConcept(CodeableConcept cc, XhtmlNode x, boolean showCodeDetails) { 1655 String s = cc.getText(); 1656 if (Utilities.noString(s)) { 1657 for (Coding c : cc.getCoding()) { 1658 if (c.hasDisplayElement()) { 1659 s = c.getDisplay(); 1660 break; 1661 } 1662 } 1663 } 1664 if (Utilities.noString(s)) { 1665 // still? ok, let's try looking it up 1666 for (Coding c : cc.getCoding()) { 1667 if (c.hasCodeElement() && c.hasSystemElement()) { 1668 s = lookupCode(c.getSystem(), c.getCode()); 1669 if (!Utilities.noString(s)) 1670 break; 1671 } 1672 } 1673 } 1674 1675 if (Utilities.noString(s)) { 1676 if (cc.getCoding().isEmpty()) 1677 s = ""; 1678 else 1679 s = cc.getCoding().get(0).getCode(); 1680 } 1681 1682 if (showCodeDetails) { 1683 x.addText(s+" "); 1684 XhtmlNode sp = x.span("background: LightGoldenRodYellow", null); 1685 sp.tx("(Details "); 1686 boolean first = true; 1687 for (Coding c : cc.getCoding()) { 1688 if (first) { 1689 sp.tx(": "); 1690 first = false; 1691 } else 1692 sp.tx("; "); 1693 sp.tx("{"+describeSystem(c.getSystem())+" code '"+c.getCode()+"' = '"+lookupCode(c.getSystem(), c.getCode())+(c.hasDisplay() ? "', given as '"+c.getDisplay()+"'}" : "")); 1694 } 1695 sp.tx(")"); 1696 } else { 1697 1698 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1699 for (Coding c : cc.getCoding()) { 1700 if (c.hasCodeElement() && c.hasSystemElement()) { 1701 b.append("{"+c.getSystem()+" "+c.getCode()+"}"); 1702 } 1703 } 1704 1705 x.span(null, "Codes: "+b.toString()).addText(s); 1706 } 1707 } 1708 1709 private void renderAnnotation(Annotation a, XhtmlNode x, boolean showCodeDetails) throws FHIRException { 1710 StringBuilder s = new StringBuilder(); 1711 if (a.hasAuthor()) { 1712 s.append("Author: "); 1713 1714 if (a.hasAuthorReference()) 1715 s.append(a.getAuthorReference().getReference()); 1716 else if (a.hasAuthorStringType()) 1717 s.append(a.getAuthorStringType().getValue()); 1718 } 1719 1720 1721 if (a.hasTimeElement()) { 1722 if (s.length() > 0) 1723 s.append("; "); 1724 1725 s.append("Made: ").append(a.getTimeElement().toHumanDisplay()); 1726 } 1727 1728 if (a.hasText()) { 1729 if (s.length() > 0) 1730 s.append("; "); 1731 1732 s.append("Annotation: ").append(a.getText()); 1733 } 1734 1735 x.addText(s.toString()); 1736 } 1737 1738 private void renderCoding(Coding c, XhtmlNode x, boolean showCodeDetails) { 1739 String s = ""; 1740 if (c.hasDisplayElement()) 1741 s = c.getDisplay(); 1742 if (Utilities.noString(s)) 1743 s = lookupCode(c.getSystem(), c.getCode()); 1744 1745 if (Utilities.noString(s)) 1746 s = c.getCode(); 1747 1748 if (showCodeDetails) { 1749 x.addText(s+" (Details: "+describeSystem(c.getSystem())+" code "+c.getCode()+" = '"+lookupCode(c.getSystem(), c.getCode())+"', stated as '"+c.getDisplay()+"')"); 1750 } else 1751 x.span(null, "{"+c.getSystem()+" "+c.getCode()+"}").addText(s); 1752 } 1753 1754 public static String describeSystem(String system) { 1755 if (system == null) 1756 return "[not stated]"; 1757 if (system.equals("http://loinc.org")) 1758 return "LOINC"; 1759 if (system.startsWith("http://snomed.info")) 1760 return "SNOMED CT"; 1761 if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) 1762 return "RxNorm"; 1763 if (system.equals("http://hl7.org/fhir/sid/icd-9")) 1764 return "ICD-9"; 1765 if (system.equals("http://dicom.nema.org/resources/ontology/DCM")) 1766 return "DICOM"; 1767 if (system.equals("http://unitsofmeasure.org")) 1768 return "UCUM"; 1769 1770 return system; 1771 } 1772 1773 private String lookupCode(String system, String code) { 1774 ValidationResult t = context.validateCode(terminologyServiceOptions , system, code, null); 1775 1776 if (t != null && t.getDisplay() != null) 1777 return t.getDisplay(); 1778 else 1779 return code; 1780 1781 } 1782 1783 private ConceptDefinitionComponent findCode(String code, List<ConceptDefinitionComponent> list) { 1784 for (ConceptDefinitionComponent t : list) { 1785 if (code.equals(t.getCode())) 1786 return t; 1787 ConceptDefinitionComponent c = findCode(code, t.getConcept()); 1788 if (c != null) 1789 return c; 1790 } 1791 return null; 1792 } 1793 1794 public String displayCodeableConcept(CodeableConcept cc) { 1795 String s = cc.getText(); 1796 if (Utilities.noString(s)) { 1797 for (Coding c : cc.getCoding()) { 1798 if (c.hasDisplayElement()) { 1799 s = c.getDisplay(); 1800 break; 1801 } 1802 } 1803 } 1804 if (Utilities.noString(s)) { 1805 // still? ok, let's try looking it up 1806 for (Coding c : cc.getCoding()) { 1807 if (c.hasCode() && c.hasSystem()) { 1808 s = lookupCode(c.getSystem(), c.getCode()); 1809 if (!Utilities.noString(s)) 1810 break; 1811 } 1812 } 1813 } 1814 1815 if (Utilities.noString(s)) { 1816 if (cc.getCoding().isEmpty()) 1817 s = ""; 1818 else 1819 s = cc.getCoding().get(0).getCode(); 1820 } 1821 return s; 1822 } 1823 1824 private void renderIdentifier(Identifier ii, XhtmlNode x) { 1825 x.addText(displayIdentifier(ii)); 1826 } 1827 1828 private void renderTiming(Timing s, XhtmlNode x) throws FHIRException { 1829 x.addText(displayTiming(s)); 1830 } 1831 1832 private void renderQuantity(Quantity q, XhtmlNode x, boolean showCodeDetails) { 1833 if (q.hasComparator()) 1834 x.addText(q.getComparator().toCode()); 1835 x.addText(q.getValue().toString()); 1836 if (q.hasUnit()) 1837 x.tx(" "+q.getUnit()); 1838 else if (q.hasCode()) 1839 x.tx(" "+q.getCode()); 1840 if (showCodeDetails && q.hasCode()) { 1841 x.span("background: LightGoldenRodYellow", null).tx(" (Details: "+describeSystem(q.getSystem())+" code "+q.getCode()+" = '"+lookupCode(q.getSystem(), q.getCode())+"')"); 1842 } 1843 } 1844 1845 private void renderRange(Range q, XhtmlNode x) { 1846 if (q.hasLow()) 1847 x.addText(q.getLow().getValue().toString()); 1848 else 1849 x.tx("?"); 1850 x.tx("-"); 1851 if (q.hasHigh()) 1852 x.addText(q.getHigh().getValue().toString()); 1853 else 1854 x.tx("?"); 1855 if (q.getLow().hasUnit()) 1856 x.tx(" "+q.getLow().getUnit()); 1857 } 1858 1859 public String displayRange(Range q) { 1860 StringBuilder b = new StringBuilder(); 1861 if (q.hasLow()) 1862 b.append(q.getLow().getValue().toString()); 1863 else 1864 b.append("?"); 1865 b.append("-"); 1866 if (q.hasHigh()) 1867 b.append(q.getHigh().getValue().toString()); 1868 else 1869 b.append("?"); 1870 if (q.getLow().hasUnit()) 1871 b.append(" "+q.getLow().getUnit()); 1872 return b.toString(); 1873 } 1874 1875 private void renderHumanName(HumanName name, XhtmlNode x) { 1876 x.addText(displayHumanName(name)); 1877 } 1878 1879 private void renderAnnotation(Annotation annot, XhtmlNode x) { 1880 x.addText(annot.getText()); 1881 } 1882 1883 private void renderAddress(Address address, XhtmlNode x) { 1884 x.addText(displayAddress(address)); 1885 } 1886 1887 private void renderContactPoint(ContactPoint contact, XhtmlNode x) { 1888 x.addText(displayContactPoint(contact)); 1889 } 1890 1891 private void renderUri(UriType uri, XhtmlNode x, String path, String id) { 1892 String url = uri.getValue(); 1893 if (isCanonical(path)) { 1894 MetadataResource mr = context.fetchResource(null, url); 1895 if (mr != null) { 1896 if (path.startsWith(mr.fhirType()+".") && mr.getId().equals(id)) { 1897 url = null; // don't link to self whatever 1898 } else if (mr.hasUserData("path")) 1899 url = mr.getUserString("path"); 1900 } else if (!canonicalUrlsAsLinks) 1901 url = null; 1902 } 1903 if (url == null) 1904 x.b().tx(uri.getValue()); 1905 else if (uri.getValue().startsWith("mailto:")) 1906 x.ah(uri.getValue()).addText(uri.getValue().substring(7)); 1907 else 1908 x.ah(uri.getValue()).addText(uri.getValue()); 1909 } 1910 1911 private boolean isCanonical(String path) { 1912 if (!path.endsWith(".url")) 1913 return false; 1914 StructureDefinition sd = context.fetchTypeDefinition(path.substring(0, path.length()-4)); 1915 if (sd == null) 1916 return false; 1917 if (Utilities.existsInList(path.substring(0, path.length()-4), "CapabilityStatement", "StructureDefinition", "ImplementationGuide", "SearchParameter", "MessageDefinition", "OperationDefinition", "CompartmentDefinition", "StructureMap", "GraphDefinition", 1918 "ExampleScenario", "CodeSystem", "ValueSet", "ConceptMap", "NamingSystem", "TerminologyCapabilities")) 1919 return true; 1920 return sd.getBaseDefinitionElement().hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-codegen-super"); 1921 } 1922 1923 private void renderSampledData(SampledData sampledData, XhtmlNode x) { 1924 x.addText(displaySampledData(sampledData)); 1925 } 1926 1927 private String displaySampledData(SampledData s) { 1928 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1929 if (s.hasOrigin()) 1930 b.append("Origin: "+displayQuantity(s.getOrigin())); 1931 1932 if (s.hasPeriod()) 1933 b.append("Period: "+s.getPeriod().toString()); 1934 1935 if (s.hasFactor()) 1936 b.append("Factor: "+s.getFactor().toString()); 1937 1938 if (s.hasLowerLimit()) 1939 b.append("Lower: "+s.getLowerLimit().toString()); 1940 1941 if (s.hasUpperLimit()) 1942 b.append("Upper: "+s.getUpperLimit().toString()); 1943 1944 if (s.hasDimensions()) 1945 b.append("Dimensions: "+s.getDimensions()); 1946 1947 if (s.hasData()) 1948 b.append("Data: "+s.getData()); 1949 1950 return b.toString(); 1951 } 1952 1953 private String displayQuantity(Quantity q) { 1954 StringBuilder s = new StringBuilder(); 1955 1956 s.append("(system = '").append(describeSystem(q.getSystem())) 1957 .append("' code ").append(q.getCode()) 1958 .append(" = '").append(lookupCode(q.getSystem(), q.getCode())).append("')"); 1959 1960 return s.toString(); 1961 } 1962 1963 private String displayTiming(Timing s) throws FHIRException { 1964 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1965 if (s.hasCode()) 1966 b.append("Code: "+displayCodeableConcept(s.getCode())); 1967 1968 if (s.getEvent().size() > 0) { 1969 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 1970 for (DateTimeType p : s.getEvent()) { 1971 c.append(p.toHumanDisplay()); 1972 } 1973 b.append("Events: "+ c.toString()); 1974 } 1975 1976 if (s.hasRepeat()) { 1977 TimingRepeatComponent rep = s.getRepeat(); 1978 if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasStart()) 1979 b.append("Starting "+rep.getBoundsPeriod().getStartElement().toHumanDisplay()); 1980 if (rep.hasCount()) 1981 b.append("Count "+Integer.toString(rep.getCount())+" times"); 1982 if (rep.hasDuration()) 1983 b.append("Duration "+rep.getDuration().toPlainString()+displayTimeUnits(rep.getPeriodUnit())); 1984 1985 if (rep.hasWhen()) { 1986 String st = ""; 1987 if (rep.hasOffset()) { 1988 st = Integer.toString(rep.getOffset())+"min "; 1989 } 1990 b.append("Do "+st); 1991 for (Enumeration<EventTiming> wh : rep.getWhen()) 1992 b.append(displayEventCode(wh.getValue())); 1993 } else { 1994 String st = ""; 1995 if (!rep.hasFrequency() || (!rep.hasFrequencyMax() && rep.getFrequency() == 1) ) 1996 st = "Once"; 1997 else { 1998 st = Integer.toString(rep.getFrequency()); 1999 if (rep.hasFrequencyMax()) 2000 st = st + "-"+Integer.toString(rep.getFrequency()); 2001 } 2002 if (rep.hasPeriod()) { 2003 st = st + " per "+rep.getPeriod().toPlainString(); 2004 if (rep.hasPeriodMax()) 2005 st = st + "-"+rep.getPeriodMax().toPlainString(); 2006 st = st + " "+displayTimeUnits(rep.getPeriodUnit()); 2007 } 2008 b.append("Do "+st); 2009 } 2010 if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasEnd()) 2011 b.append("Until "+rep.getBoundsPeriod().getEndElement().toHumanDisplay()); 2012 } 2013 return b.toString(); 2014 } 2015 2016 private String displayEventCode(EventTiming when) { 2017 switch (when) { 2018 case C: return "at meals"; 2019 case CD: return "at lunch"; 2020 case CM: return "at breakfast"; 2021 case CV: return "at dinner"; 2022 case AC: return "before meals"; 2023 case ACD: return "before lunch"; 2024 case ACM: return "before breakfast"; 2025 case ACV: return "before dinner"; 2026 case HS: return "before sleeping"; 2027 case PC: return "after meals"; 2028 case PCD: return "after lunch"; 2029 case PCM: return "after breakfast"; 2030 case PCV: return "after dinner"; 2031 case WAKE: return "after waking"; 2032 default: return "??"; 2033 } 2034 } 2035 2036 private String displayTimeUnits(UnitsOfTime units) { 2037 if (units == null) 2038 return "??"; 2039 switch (units) { 2040 case A: return "years"; 2041 case D: return "days"; 2042 case H: return "hours"; 2043 case MIN: return "minutes"; 2044 case MO: return "months"; 2045 case S: return "seconds"; 2046 case WK: return "weeks"; 2047 default: return "??"; 2048 } 2049 } 2050 2051 public static String displayHumanName(HumanName name) { 2052 StringBuilder s = new StringBuilder(); 2053 if (name.hasText()) 2054 s.append(name.getText()); 2055 else { 2056 for (StringType p : name.getGiven()) { 2057 s.append(p.getValue()); 2058 s.append(" "); 2059 } 2060 if (name.hasFamily()) { 2061 s.append(name.getFamily()); 2062 s.append(" "); 2063 } 2064 } 2065 if (name.hasUse() && name.getUse() != NameUse.USUAL) 2066 s.append("("+name.getUse().toString()+")"); 2067 return s.toString(); 2068 } 2069 2070 private String displayAddress(Address address) { 2071 StringBuilder s = new StringBuilder(); 2072 if (address.hasText()) 2073 s.append(address.getText()); 2074 else { 2075 for (StringType p : address.getLine()) { 2076 s.append(p.getValue()); 2077 s.append(" "); 2078 } 2079 if (address.hasCity()) { 2080 s.append(address.getCity()); 2081 s.append(" "); 2082 } 2083 if (address.hasState()) { 2084 s.append(address.getState()); 2085 s.append(" "); 2086 } 2087 2088 if (address.hasPostalCode()) { 2089 s.append(address.getPostalCode()); 2090 s.append(" "); 2091 } 2092 2093 if (address.hasCountry()) { 2094 s.append(address.getCountry()); 2095 s.append(" "); 2096 } 2097 } 2098 if (address.hasUse()) 2099 s.append("("+address.getUse().toString()+")"); 2100 return s.toString(); 2101 } 2102 2103 public static String displayContactPoint(ContactPoint contact) { 2104 StringBuilder s = new StringBuilder(); 2105 s.append(describeSystem(contact.getSystem())); 2106 if (Utilities.noString(contact.getValue())) 2107 s.append("-unknown-"); 2108 else 2109 s.append(contact.getValue()); 2110 if (contact.hasUse()) 2111 s.append("("+contact.getUse().toString()+")"); 2112 return s.toString(); 2113 } 2114 2115 private static String describeSystem(ContactPointSystem system) { 2116 if (system == null) 2117 return ""; 2118 switch (system) { 2119 case PHONE: return "ph: "; 2120 case FAX: return "fax: "; 2121 default: 2122 return ""; 2123 } 2124 } 2125 2126 private String displayIdentifier(Identifier ii) { 2127 String s = Utilities.noString(ii.getValue()) ? "??" : ii.getValue(); 2128 2129 if (ii.hasType()) { 2130 if (ii.getType().hasText()) 2131 s = ii.getType().getText()+" = "+s; 2132 else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasDisplay()) 2133 s = ii.getType().getCoding().get(0).getDisplay()+" = "+s; 2134 else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasCode()) 2135 s = lookupCode(ii.getType().getCoding().get(0).getSystem(), ii.getType().getCoding().get(0).getCode())+" = "+s; 2136 } 2137 2138 if (ii.hasUse()) 2139 s = s + " ("+ii.getUse().toString()+")"; 2140 return s; 2141 } 2142 2143 private List<ElementDefinition> getChildrenForPath(List<ElementDefinition> elements, String path) throws DefinitionException { 2144 // do we need to do a name reference substitution? 2145 for (ElementDefinition e : elements) { 2146 if (e.getPath().equals(path) && e.hasContentReference()) { 2147 String ref = e.getContentReference(); 2148 ElementDefinition t = null; 2149 // now, resolve the name 2150 for (ElementDefinition e1 : elements) { 2151 if (ref.equals("#"+e1.getId())) 2152 t = e1; 2153 } 2154 if (t == null) 2155 throw new DefinitionException("Unable to resolve content reference "+ref+" trying to resolve "+path); 2156 path = t.getPath(); 2157 break; 2158 } 2159 } 2160 2161 List<ElementDefinition> results = new ArrayList<ElementDefinition>(); 2162 for (ElementDefinition e : elements) { 2163 if (e.getPath().startsWith(path+".") && !e.getPath().substring(path.length()+1).contains(".")) 2164 results.add(e); 2165 } 2166 return results; 2167 } 2168 2169 2170 public boolean generate(ResourceContext rcontext, ConceptMap cm) throws FHIRFormatError, DefinitionException, IOException { 2171 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 2172 x.h2().addText(cm.getName()+" ("+cm.getUrl()+")"); 2173 2174 XhtmlNode p = x.para(); 2175 p.tx("Mapping from "); 2176 if (cm.hasSource()) 2177 AddVsRef(rcontext, cm.getSource().primitiveValue(), p); 2178 else 2179 p.tx("(not specified)"); 2180 p.tx(" to "); 2181 if (cm.hasTarget()) 2182 AddVsRef(rcontext, cm.getTarget().primitiveValue(), p); 2183 else 2184 p.tx("(not specified)"); 2185 2186 p = x.para(); 2187 if (cm.getExperimental()) 2188 p.addText(Utilities.capitalize(cm.getStatus().toString())+" (not intended for production usage). "); 2189 else 2190 p.addText(Utilities.capitalize(cm.getStatus().toString())+". "); 2191 p.tx("Published on "+(cm.hasDate() ? cm.getDateElement().toHumanDisplay() : "??")+" by "+cm.getPublisher()); 2192 if (!cm.getContact().isEmpty()) { 2193 p.tx(" ("); 2194 boolean firsti = true; 2195 for (ContactDetail ci : cm.getContact()) { 2196 if (firsti) 2197 firsti = false; 2198 else 2199 p.tx(", "); 2200 if (ci.hasName()) 2201 p.addText(ci.getName()+": "); 2202 boolean first = true; 2203 for (ContactPoint c : ci.getTelecom()) { 2204 if (first) 2205 first = false; 2206 else 2207 p.tx(", "); 2208 addTelecom(p, c); 2209 } 2210 } 2211 p.tx(")"); 2212 } 2213 p.tx(". "); 2214 p.addText(cm.getCopyright()); 2215 if (!Utilities.noString(cm.getDescription())) 2216 addMarkdown(x, cm.getDescription()); 2217 2218 x.br(); 2219 CodeSystem cs = context.fetchCodeSystem("http://hl7.org/fhir/concept-map-equivalence"); 2220 String eqpath = cs.getUserString("path"); 2221 2222 for (ConceptMapGroupComponent grp : cm.getGroup()) { 2223 String src = grp.getSource(); 2224 boolean comment = false; 2225 boolean ok = true; 2226 Map<String, HashSet<String>> sources = new HashMap<String, HashSet<String>>(); 2227 Map<String, HashSet<String>> targets = new HashMap<String, HashSet<String>>(); 2228 sources.put("code", new HashSet<String>()); 2229 targets.put("code", new HashSet<String>()); 2230 SourceElementComponent cc = grp.getElement().get(0); 2231 String dst = grp.getTarget(); 2232 sources.get("code").add(grp.getSource()); 2233 targets.get("code").add(grp.getTarget()); 2234 for (SourceElementComponent ccl : grp.getElement()) { 2235 ok = ok && ccl.getTarget().size() == 1 && ccl.getTarget().get(0).getDependsOn().isEmpty() && ccl.getTarget().get(0).getProduct().isEmpty(); 2236 for (TargetElementComponent ccm : ccl.getTarget()) { 2237 comment = comment || !Utilities.noString(ccm.getComment()); 2238 for (OtherElementComponent d : ccm.getDependsOn()) { 2239 if (!sources.containsKey(d.getProperty())) 2240 sources.put(d.getProperty(), new HashSet<String>()); 2241 sources.get(d.getProperty()).add(d.getSystem()); 2242 } 2243 for (OtherElementComponent d : ccm.getProduct()) { 2244 if (!targets.containsKey(d.getProperty())) 2245 targets.put(d.getProperty(), new HashSet<String>()); 2246 targets.get(d.getProperty()).add(d.getSystem()); 2247 } 2248 2249 } 2250 } 2251 2252 String display; 2253 if (ok) { 2254 // simple 2255 XhtmlNode tbl = x.table( "grid"); 2256 XhtmlNode tr = tbl.tr(); 2257 tr.td().b().tx("Source Code"); 2258 tr.td().b().tx("Equivalence"); 2259 tr.td().b().tx("Destination Code"); 2260 if (comment) 2261 tr.td().b().tx("Comment"); 2262 for (SourceElementComponent ccl : grp.getElement()) { 2263 tr = tbl.tr(); 2264 XhtmlNode td = tr.td(); 2265 td.addText(ccl.getCode()); 2266 display = getDisplayForConcept(grp.getSource(), ccl.getCode()); 2267 if (display != null && !isSameCodeAndDisplay(ccl.getCode(), display)) 2268 td.tx(" ("+display+")"); 2269 TargetElementComponent ccm = ccl.getTarget().get(0); 2270 tr.td().addText(!ccm.hasEquivalence() ? "" : ccm.getEquivalence().toCode()); 2271 td = tr.td(); 2272 td.addText(ccm.getCode()); 2273 display = getDisplayForConcept(grp.getTarget(), ccm.getCode()); 2274 if (display != null && !isSameCodeAndDisplay(ccm.getCode(), display)) 2275 td.tx(" ("+display+")"); 2276 if (comment) 2277 tr.td().addText(ccm.getComment()); 2278 } 2279 } else { 2280 XhtmlNode tbl = x.table( "grid"); 2281 XhtmlNode tr = tbl.tr(); 2282 XhtmlNode td; 2283 tr.td().colspan(Integer.toString(sources.size())).b().tx("Source Concept Details"); 2284 tr.td().b().tx("Equivalence"); 2285 tr.td().colspan(Integer.toString(targets.size())).b().tx("Destination Concept Details"); 2286 if (comment) 2287 tr.td().b().tx("Comment"); 2288 tr = tbl.tr(); 2289 if (sources.get("code").size() == 1) { 2290 String url = sources.get("code").iterator().next(); 2291 renderCSDetailsLink(tr, url); 2292 } else 2293 tr.td().b().tx("Code"); 2294 for (String s : sources.keySet()) { 2295 if (!s.equals("code")) { 2296 if (sources.get(s).size() == 1) { 2297 String url = sources.get(s).iterator().next(); 2298 renderCSDetailsLink(tr, url); 2299 } else 2300 tr.td().b().addText(getDescForConcept(s)); 2301 } 2302 } 2303 tr.td(); 2304 if (targets.get("code").size() == 1) { 2305 String url = targets.get("code").iterator().next(); 2306 renderCSDetailsLink(tr, url); 2307 } else 2308 tr.td().b().tx("Code"); 2309 for (String s : targets.keySet()) { 2310 if (!s.equals("code")) { 2311 if (targets.get(s).size() == 1) { 2312 String url = targets.get(s).iterator().next(); 2313 renderCSDetailsLink(tr, url); 2314 } else 2315 tr.td().b().addText(getDescForConcept(s)); 2316 } 2317 } 2318 if (comment) 2319 tr.td(); 2320 2321 for (int si = 0; si < grp.getElement().size(); si++) { 2322 SourceElementComponent ccl = grp.getElement().get(si); 2323 boolean slast = si == grp.getElement().size()-1; 2324 boolean first = true; 2325 for (int ti = 0; ti < ccl.getTarget().size(); ti++) { 2326 TargetElementComponent ccm = ccl.getTarget().get(ti); 2327 boolean last = ti == ccl.getTarget().size()-1; 2328 tr = tbl.tr(); 2329 td = tr.td(); 2330 if (!first && !last) 2331 td.setAttribute("style", "border-top-style: none; border-bottom-style: none"); 2332 else if (!first) 2333 td.setAttribute("style", "border-top-style: none"); 2334 else if (!last) 2335 td.setAttribute("style", "border-bottom-style: none"); 2336 if (first) { 2337 if (sources.get("code").size() == 1) 2338 td.addText(ccl.getCode()); 2339 else 2340 td.addText(grp.getSource()+" / "+ccl.getCode()); 2341 display = getDisplayForConcept(grp.getSource(), ccl.getCode()); 2342 if (display != null) 2343 td.tx(" ("+display+")"); 2344 } 2345 for (String s : sources.keySet()) { 2346 if (!s.equals("code")) { 2347 td = tr.td(); 2348 if (first) { 2349 td.addText(getValue(ccm.getDependsOn(), s, sources.get(s).size() != 1)); 2350 display = getDisplay(ccm.getDependsOn(), s); 2351 if (display != null) 2352 td.tx(" ("+display+")"); 2353 } 2354 } 2355 } 2356 first = false; 2357 if (!ccm.hasEquivalence()) 2358 tr.td().tx(":"+"("+ConceptMapEquivalence.EQUIVALENT.toCode()+")"); 2359 else 2360 tr.td().ah(eqpath+"#"+ccm.getEquivalence().toCode()).tx(ccm.getEquivalence().toCode()); 2361 td = tr.td(); 2362 if (targets.get("code").size() == 1) 2363 td.addText(ccm.getCode()); 2364 else 2365 td.addText(grp.getTarget()+" / "+ccm.getCode()); 2366 display = getDisplayForConcept(grp.getTarget(), ccm.getCode()); 2367 if (display != null) 2368 td.tx(" ("+display+")"); 2369 2370 for (String s : targets.keySet()) { 2371 if (!s.equals("code")) { 2372 td = tr.td(); 2373 td.addText(getValue(ccm.getProduct(), s, targets.get(s).size() != 1)); 2374 display = getDisplay(ccm.getProduct(), s); 2375 if (display != null) 2376 td.tx(" ("+display+")"); 2377 } 2378 } 2379 if (comment) 2380 tr.td().addText(ccm.getComment()); 2381 } 2382 } 2383 } 2384 } 2385 2386 inject(cm, x, NarrativeStatus.GENERATED); 2387 return true; 2388 } 2389 2390 public void renderCSDetailsLink(XhtmlNode tr, String url) { 2391 CodeSystem cs; 2392 XhtmlNode td; 2393 cs = context.fetchCodeSystem(url); 2394 td = tr.td(); 2395 td.b().tx("Code"); 2396 td.tx(" from "); 2397 if (cs == null) 2398 td.tx(url); 2399 else 2400 td.ah(cs.getUserString("path")).attribute("title", url).tx(cs.present()); 2401 } 2402 2403 private boolean isSameCodeAndDisplay(String code, String display) { 2404 String c = code.replace(" ", "").replace("-", "").toLowerCase(); 2405 String d = display.replace(" ", "").replace("-", "").toLowerCase(); 2406 return c.equals(d); 2407 } 2408 2409 private void inject(DomainResource r, XhtmlNode x, NarrativeStatus status) { 2410 if (!x.hasAttribute("xmlns")) 2411 x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 2412 if (!r.hasText() || !r.getText().hasDiv() || r.getText().getDiv().getChildNodes().isEmpty()) { 2413 r.setText(new Narrative()); 2414 r.getText().setDiv(x); 2415 r.getText().setStatus(status); 2416 } else { 2417 XhtmlNode n = r.getText().getDiv(); 2418 n.hr(); 2419 n.getChildNodes().addAll(x.getChildNodes()); 2420 } 2421 } 2422 2423 public Element getNarrative(Element er) { 2424 Element txt = XMLUtil.getNamedChild(er, "text"); 2425 if (txt == null) 2426 return null; 2427 return XMLUtil.getNamedChild(txt, "div"); 2428 } 2429 2430 2431 private void inject(Element er, XhtmlNode x, NarrativeStatus status) { 2432 if (!x.hasAttribute("xmlns")) 2433 x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 2434 Element txt = XMLUtil.getNamedChild(er, "text"); 2435 if (txt == null) { 2436 txt = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "text"); 2437 Element n = XMLUtil.getFirstChild(er); 2438 while (n != null && (n.getNodeName().equals("id") || n.getNodeName().equals("meta") || n.getNodeName().equals("implicitRules") || n.getNodeName().equals("language"))) 2439 n = XMLUtil.getNextSibling(n); 2440 if (n == null) 2441 er.appendChild(txt); 2442 else 2443 er.insertBefore(txt, n); 2444 } 2445 Element st = XMLUtil.getNamedChild(txt, "status"); 2446 if (st == null) { 2447 st = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "status"); 2448 Element n = XMLUtil.getFirstChild(txt); 2449 if (n == null) 2450 txt.appendChild(st); 2451 else 2452 txt.insertBefore(st, n); 2453 } 2454 st.setAttribute("value", status.toCode()); 2455 Element div = XMLUtil.getNamedChild(txt, "div"); 2456 if (div == null) { 2457 div = er.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "div"); 2458 div.setAttribute("xmlns", FormatUtilities.XHTML_NS); 2459 txt.appendChild(div); 2460 } 2461 if (div.hasChildNodes()) 2462 div.appendChild(er.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "hr")); 2463 new XhtmlComposer(XhtmlComposer.XML, pretty).compose(div, x); 2464 } 2465 2466 private void inject(org.hl7.fhir.r4.elementmodel.Element er, XhtmlNode x, NarrativeStatus status) throws IOException, FHIRException { 2467 if (!x.hasAttribute("xmlns")) 2468 x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 2469 org.hl7.fhir.r4.elementmodel.Element txt = er.getNamedChild("text"); 2470 if (txt == null) { 2471 txt = new org.hl7.fhir.r4.elementmodel.Element("text", er.getProperty().getChild(null, "text")); 2472 int i = 0; 2473 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"))) 2474 i++; 2475 if (i >= er.getChildren().size()) 2476 er.getChildren().add(txt); 2477 else 2478 er.getChildren().add(i, txt); 2479 } 2480 org.hl7.fhir.r4.elementmodel.Element st = txt.getNamedChild("status"); 2481 if (st == null) { 2482 st = new org.hl7.fhir.r4.elementmodel.Element("status", txt.getProperty().getChild(null, "status")); 2483 txt.getChildren().add(0, st); 2484 } 2485 st.setValue(status.toCode()); 2486 org.hl7.fhir.r4.elementmodel.Element div = txt.getNamedChild("div"); 2487 if (div == null) { 2488 div = new org.hl7.fhir.r4.elementmodel.Element("div", txt.getProperty().getChild(null, "div")); 2489 txt.getChildren().add(div); 2490 div.setValue(new XhtmlComposer(XhtmlComposer.XML, pretty).compose(x)); 2491 } 2492 div.setXhtml(x); 2493 } 2494 2495 private String getDisplay(List<OtherElementComponent> list, String s) { 2496 for (OtherElementComponent c : list) { 2497 if (s.equals(c.getProperty())) 2498 return getDisplayForConcept(c.getSystem(), c.getValue()); 2499 } 2500 return null; 2501 } 2502 2503 private String getDisplayForConcept(String system, String value) { 2504 if (value == null || system == null) 2505 return null; 2506 ValidationResult cl = context.validateCode(terminologyServiceOptions, system, value, null); 2507 return cl == null ? null : cl.getDisplay(); 2508 } 2509 2510 2511 2512 private String getDescForConcept(String s) { 2513 if (s.startsWith("http://hl7.org/fhir/v2/element/")) 2514 return "v2 "+s.substring("http://hl7.org/fhir/v2/element/".length()); 2515 return s; 2516 } 2517 2518 private String getValue(List<OtherElementComponent> list, String s, boolean withSystem) { 2519 for (OtherElementComponent c : list) { 2520 if (s.equals(c.getProperty())) 2521 if (withSystem) 2522 return c.getSystem()+" / "+c.getValue(); 2523 else 2524 return c.getValue(); 2525 } 2526 return null; 2527 } 2528 2529 private void addTelecom(XhtmlNode p, ContactPoint c) { 2530 if (c.getSystem() == ContactPointSystem.PHONE) { 2531 p.tx("Phone: "+c.getValue()); 2532 } else if (c.getSystem() == ContactPointSystem.FAX) { 2533 p.tx("Fax: "+c.getValue()); 2534 } else if (c.getSystem() == ContactPointSystem.EMAIL) { 2535 p.ah( "mailto:"+c.getValue()).addText(c.getValue()); 2536 } else if (c.getSystem() == ContactPointSystem.URL) { 2537 if (c.getValue().length() > 30) 2538 p.ah(c.getValue()).addText(c.getValue().substring(0, 30)+"..."); 2539 else 2540 p.ah(c.getValue()).addText(c.getValue()); 2541 } 2542 } 2543 2544 /** 2545 * This generate is optimised for the FHIR build process itself in as much as it 2546 * generates hyperlinks in the narrative that are only going to be correct for 2547 * the purposes of the build. This is to be reviewed in the future. 2548 * 2549 * @param vs 2550 * @param codeSystems 2551 * @throws IOException 2552 * @throws DefinitionException 2553 * @throws FHIRFormatError 2554 * @throws Exception 2555 */ 2556 public boolean generate(ResourceContext rcontext, CodeSystem cs, boolean header, String lang) throws FHIRFormatError, DefinitionException, IOException { 2557 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 2558 boolean hasExtensions = false; 2559 hasExtensions = generateDefinition(x, cs, header, lang); 2560 inject(cs, x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 2561 return true; 2562 } 2563 2564 private boolean generateDefinition(XhtmlNode x, CodeSystem cs, boolean header, String lang) throws FHIRFormatError, DefinitionException, IOException { 2565 boolean hasExtensions = false; 2566 2567 if (header) { 2568 XhtmlNode h = x.h2(); 2569 h.addText(cs.hasTitle() ? cs.getTitle() : cs.getName()); 2570 addMarkdown(x, cs.getDescription()); 2571 if (cs.hasCopyright()) 2572 generateCopyright(x, cs, lang); 2573 } 2574 2575 generateProperties(x, cs, lang); 2576 generateFilters(x, cs, lang); 2577 List<UsedConceptMap> maps = new ArrayList<UsedConceptMap>(); 2578 hasExtensions = generateCodeSystemContent(x, cs, hasExtensions, maps, lang); 2579 2580 return hasExtensions; 2581 } 2582 2583 private void generateFilters(XhtmlNode x, CodeSystem cs, String lang) { 2584 if (cs.hasFilter()) { 2585 x.para().b().tx(context.translator().translate("xhtml-gen-cs", "Filters", lang)); 2586 XhtmlNode tbl = x.table("grid"); 2587 XhtmlNode tr = tbl.tr(); 2588 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Code", lang)); 2589 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Description", lang)); 2590 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "operator", lang)); 2591 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Value", lang)); 2592 for (CodeSystemFilterComponent f : cs.getFilter()) { 2593 tr = tbl.tr(); 2594 tr.td().tx(f.getCode()); 2595 tr.td().tx(f.getDescription()); 2596 XhtmlNode td = tr.td(); 2597 for (Enumeration<org.hl7.fhir.r4.model.CodeSystem.FilterOperator> t : f.getOperator()) 2598 td.tx(t.asStringValue()+" "); 2599 tr.td().tx(f.getValue()); 2600 } 2601 } 2602 } 2603 2604 private void generateProperties(XhtmlNode x, CodeSystem cs, String lang) { 2605 if (cs.hasProperty()) { 2606 x.para().b().tx(context.translator().translate("xhtml-gen-cs", "Properties", lang)); 2607 XhtmlNode tbl = x.table("grid"); 2608 XhtmlNode tr = tbl.tr(); 2609 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Code", lang)); 2610 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "URL", lang)); 2611 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Description", lang)); 2612 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Type", lang)); 2613 for (PropertyComponent p : cs.getProperty()) { 2614 tr = tbl.tr(); 2615 tr.td().tx(p.getCode()); 2616 tr.td().tx(p.getUri()); 2617 tr.td().tx(p.getDescription()); 2618 tr.td().tx(p.hasType() ? p.getType().toCode() : ""); 2619 } 2620 } 2621 } 2622 2623 private boolean generateCodeSystemContent(XhtmlNode x, CodeSystem cs, boolean hasExtensions, List<UsedConceptMap> maps, String lang) throws FHIRFormatError, DefinitionException, IOException { 2624 XhtmlNode p = x.para(); 2625 if (cs.getContent() == CodeSystemContentMode.COMPLETE) 2626 p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines the following codes", cs.getUrl())+":"); 2627 else if (cs.getContent() == CodeSystemContentMode.EXAMPLE) 2628 p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines many codes, of which the following are some examples", cs.getUrl())+":"); 2629 else if (cs.getContent() == CodeSystemContentMode.FRAGMENT ) 2630 p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines many codes, of which the following are a subset", cs.getUrl())+":"); 2631 else if (cs.getContent() == CodeSystemContentMode.NOTPRESENT ) { 2632 p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines many codes, but they are not represented here", cs.getUrl())); 2633 return false; 2634 } 2635 XhtmlNode t = x.table( "codes"); 2636 boolean commentS = false; 2637 boolean deprecated = false; 2638 boolean display = false; 2639 boolean hierarchy = false; 2640 boolean version = false; 2641 for (ConceptDefinitionComponent c : cs.getConcept()) { 2642 commentS = commentS || conceptsHaveComments(c); 2643 deprecated = deprecated || conceptsHaveDeprecated(cs, c); 2644 display = display || conceptsHaveDisplay(c); 2645 version = version || conceptsHaveVersion(c); 2646 hierarchy = hierarchy || c.hasConcept(); 2647 } 2648 addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, true, commentS, version, deprecated, lang), maps); 2649 for (ConceptDefinitionComponent c : cs.getConcept()) { 2650 hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, commentS, version, deprecated, maps, cs.getUrl(), cs, lang) || hasExtensions; 2651 } 2652// if (langs.size() > 0) { 2653// Collections.sort(langs); 2654// x.para().b().tx("Additional Language Displays"); 2655// t = x.table( "codes"); 2656// XhtmlNode tr = t.tr(); 2657// tr.td().b().tx("Code"); 2658// for (String lang : langs) 2659// tr.td().b().addText(describeLang(lang)); 2660// for (ConceptDefinitionComponent c : cs.getConcept()) { 2661// addLanguageRow(c, t, langs); 2662// } 2663// } 2664 return hasExtensions; 2665 } 2666 2667 private int countConcepts(List<ConceptDefinitionComponent> list) { 2668 int count = list.size(); 2669 for (ConceptDefinitionComponent c : list) 2670 if (c.hasConcept()) 2671 count = count + countConcepts(c.getConcept()); 2672 return count; 2673 } 2674 2675 private void generateCopyright(XhtmlNode x, CodeSystem cs, String lang) { 2676 XhtmlNode p = x.para(); 2677 p.b().tx(context.translator().translate("xhtml-gen-cs", "Copyright Statement:", lang)); 2678 smartAddText(p, " " + cs.getCopyright()); 2679 } 2680 2681 2682 /** 2683 * This generate is optimised for the FHIR build process itself in as much as it 2684 * generates hyperlinks in the narrative that are only going to be correct for 2685 * the purposes of the build. This is to be reviewed in the future. 2686 * 2687 * @param vs 2688 * @param codeSystems 2689 * @throws FHIRException 2690 * @throws IOException 2691 * @throws Exception 2692 */ 2693 public boolean generate(ResourceContext rcontext, ValueSet vs, boolean header) throws FHIRException, IOException { 2694 generate(rcontext, vs, null, header); 2695 return true; 2696 } 2697 2698 public void generate(ResourceContext rcontext, ValueSet vs, ValueSet src, boolean header) throws FHIRException, IOException { 2699 List<UsedConceptMap> maps = findReleventMaps(vs); 2700 2701 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 2702 boolean hasExtensions; 2703 if (vs.hasExpansion()) { 2704 // for now, we just accept an expansion if there is one 2705 hasExtensions = generateExpansion(x, vs, src, header, maps); 2706 } else { 2707 hasExtensions = generateComposition(rcontext, x, vs, header, maps); 2708 } 2709 inject(vs, x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 2710 } 2711 2712 private List<UsedConceptMap> findReleventMaps(ValueSet vs) throws FHIRException { 2713 List<UsedConceptMap> res = new ArrayList<UsedConceptMap>(); 2714 for (MetadataResource md : context.allConformanceResources()) { 2715 if (md instanceof ConceptMap) { 2716 ConceptMap cm = (ConceptMap) md; 2717 if (isSource(vs, cm.getSource())) { 2718 ConceptMapRenderInstructions re = findByTarget(cm.getTarget()); 2719 if (re != null) { 2720 ValueSet vst = cm.hasTarget() ? context.fetchResource(ValueSet.class, cm.hasTargetCanonicalType() ? cm.getTargetCanonicalType().getValue() : cm.getTargetUriType().asStringValue()) : null; 2721 res.add(new UsedConceptMap(re, vst == null ? cm.getUserString("path") : vst.getUserString("path"), cm)); 2722 } 2723 } 2724 } 2725 } 2726 return res; 2727// Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>(); 2728// for (ConceptMap a : context.findMapsForSource(vs.getUrl())) { 2729// String url = ""; 2730// ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference()); 2731// if (vsr != null) 2732// url = (String) vsr.getUserData("filename"); 2733// mymaps.put(a, url); 2734// } 2735// Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>(); 2736// for (ConceptMap a : context.findMapsForSource(cs.getValueSet())) { 2737// String url = ""; 2738// ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference()); 2739// if (vsr != null) 2740// url = (String) vsr.getUserData("filename"); 2741// mymaps.put(a, url); 2742// } 2743 // also, look in the contained resources for a concept map 2744// for (Resource r : cs.getContained()) { 2745// if (r instanceof ConceptMap) { 2746// ConceptMap cm = (ConceptMap) r; 2747// if (((Reference) cm.getSource()).getReference().equals(cs.getValueSet())) { 2748// String url = ""; 2749// ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) cm.getTarget()).getReference()); 2750// if (vsr != null) 2751// url = (String) vsr.getUserData("filename"); 2752// mymaps.put(cm, url); 2753// } 2754// } 2755// } 2756 } 2757 2758 private ConceptMapRenderInstructions findByTarget(Type source) { 2759 String src = source.primitiveValue(); 2760 if (src != null) 2761 for (ConceptMapRenderInstructions t : renderingMaps) { 2762 if (src.equals(t.url)) 2763 return t; 2764 } 2765 return null; 2766 } 2767 2768 private boolean isSource(ValueSet vs, Type source) { 2769 return vs.hasUrl() && source != null && vs.getUrl().equals(source.primitiveValue()); 2770 } 2771 2772 private Integer countMembership(ValueSet vs) { 2773 int count = 0; 2774 if (vs.hasExpansion()) 2775 count = count + conceptCount(vs.getExpansion().getContains()); 2776 else { 2777 if (vs.hasCompose()) { 2778 if (vs.getCompose().hasExclude()) { 2779 try { 2780 ValueSetExpansionOutcome vse = context.expandVS(vs, true, false); 2781 count = 0; 2782 count += conceptCount(vse.getValueset().getExpansion().getContains()); 2783 return count; 2784 } catch (Exception e) { 2785 return null; 2786 } 2787 } 2788 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 2789 if (inc.hasFilter()) 2790 return null; 2791 if (!inc.hasConcept()) 2792 return null; 2793 count = count + inc.getConcept().size(); 2794 } 2795 } 2796 } 2797 return count; 2798 } 2799 2800 private int conceptCount(List<ValueSetExpansionContainsComponent> list) { 2801 int count = 0; 2802 for (ValueSetExpansionContainsComponent c : list) { 2803 if (!c.getAbstract()) 2804 count++; 2805 count = count + conceptCount(c.getContains()); 2806 } 2807 return count; 2808 } 2809 2810 private boolean generateExpansion(XhtmlNode x, ValueSet vs, ValueSet src, boolean header, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException { 2811 boolean hasExtensions = false; 2812 List<String> langs = new ArrayList<String>(); 2813 2814 2815 if (header) { 2816 XhtmlNode h = x.addTag(getHeader()); 2817 h.tx("Value Set Contents"); 2818 if (IsNotFixedExpansion(vs)) 2819 addMarkdown(x, vs.getDescription()); 2820 if (vs.hasCopyright()) 2821 generateCopyright(x, vs); 2822 } 2823 if (ToolingExtensions.hasExtension(vs.getExpansion(), "http://hl7.org/fhir/StructureDefinition/valueset-toocostly")) 2824 x.para().setAttribute("style", "border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(vs.getExpansion().getContains().isEmpty() ? tooCostlyNoteEmpty : tooCostlyNoteNotEmpty ); 2825 else { 2826 Integer count = countMembership(vs); 2827 if (count == null) 2828 x.para().tx("This value set does not contain a fixed number of concepts"); 2829 else 2830 x.para().tx("This value set contains "+count.toString()+" concepts"); 2831 } 2832 2833 generateVersionNotice(x, vs.getExpansion()); 2834 2835 CodeSystem allCS = null; 2836 boolean doLevel = false; 2837 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 2838 if (cc.hasContains()) { 2839 doLevel = true; 2840 break; 2841 } 2842 } 2843 2844 boolean doSystem = true; // checkDoSystem(vs, src); 2845 boolean doDefinition = checkDoDefinition(vs.getExpansion().getContains()); 2846 if (doSystem && allFromOneSystem(vs)) { 2847 doSystem = false; 2848 XhtmlNode p = x.para(); 2849 p.tx("All codes from system "); 2850 allCS = context.fetchCodeSystem(vs.getExpansion().getContains().get(0).getSystem()); 2851 String ref = null; 2852 if (allCS != null) 2853 ref = getCsRef(allCS); 2854 if (ref == null) 2855 p.code(vs.getExpansion().getContains().get(0).getSystem()); 2856 else 2857 p.ah(prefix+ref).code(vs.getExpansion().getContains().get(0).getSystem()); 2858 } 2859 XhtmlNode t = x.table( "codes"); 2860 XhtmlNode tr = t.tr(); 2861 if (doLevel) 2862 tr.td().b().tx("Lvl"); 2863 tr.td().attribute("style", "white-space:nowrap").b().tx("Code"); 2864 if (doSystem) 2865 tr.td().b().tx("System"); 2866 tr.td().b().tx("Display"); 2867 if (doDefinition) 2868 tr.td().b().tx("Definition"); 2869 2870 addMapHeaders(tr, maps); 2871 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 2872 addExpansionRowToTable(t, c, 0, doLevel, doSystem, doDefinition, maps, allCS, langs); 2873 } 2874 2875 // now, build observed languages 2876 2877 if (langs.size() > 0) { 2878 Collections.sort(langs); 2879 x.para().b().tx("Additional Language Displays"); 2880 t = x.table( "codes"); 2881 tr = t.tr(); 2882 tr.td().b().tx("Code"); 2883 for (String lang : langs) 2884 tr.td().b().addText(describeLang(lang)); 2885 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 2886 addLanguageRow(c, t, langs); 2887 } 2888 } 2889 2890 return hasExtensions; 2891 } 2892 2893 @SuppressWarnings("rawtypes") 2894 private void generateVersionNotice(XhtmlNode x, ValueSetExpansionComponent expansion) { 2895 Map<String, String> versions = new HashMap<String, String>(); 2896 boolean firstVersion = true; 2897 for (ValueSetExpansionParameterComponent p : expansion.getParameter()) { 2898 if (p.getName().equals("version")) { 2899 String[] parts = ((PrimitiveType) p.getValue()).asStringValue().split("\\|"); 2900 if (parts.length == 2) 2901 versions.put(parts[0], parts[1]); 2902 if (!versions.isEmpty()) { 2903 StringBuilder b = new StringBuilder(); 2904 if (firstVersion) { 2905 // the first version 2906 // set the <p> tag and style attribute 2907 x.para().setAttribute("style", "border: black 1px dotted; background-color: #EEEEEE; padding: 8px"); 2908 firstVersion = false; 2909 } else { 2910 // the second (or greater) version 2911 x.br(); // add line break before the version text 2912 } 2913 b.append("Expansion based on "); 2914 boolean firstPart = true; 2915 for (String s : versions.keySet()) { 2916 if (firstPart) 2917 firstPart = false; 2918 else 2919 b.append(", "); 2920 if (!s.equals("http://snomed.info/sct")) 2921 b.append(describeSystem(s)+" version "+versions.get(s)); 2922 else { 2923 parts = versions.get(s).split("\\/"); 2924 if (parts.length >= 5) { 2925 String m = describeModule(parts[4]); 2926 if (parts.length == 7) 2927 b.append("SNOMED CT "+m+" edition "+formatSCTDate(parts[6])); 2928 else 2929 b.append("SNOMED CT "+m+" edition"); 2930 } else 2931 b.append(describeSystem(s)+" version "+versions.get(s)); 2932 } 2933 } 2934 x.addText(b.toString()); // add the version text 2935 } 2936 } 2937 } 2938 } 2939 2940 private String formatSCTDate(String ds) { 2941 SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd"); 2942 Date date; 2943 try { 2944 date = format.parse(ds); 2945 } catch (ParseException e) { 2946 return ds; 2947 } 2948 return new SimpleDateFormat("dd-MMM yyyy", new Locale("en", "US")).format(date); 2949 } 2950 2951 private String describeModule(String module) { 2952 if ("900000000000207008".equals(module)) 2953 return "International"; 2954 if ("731000124108".equals(module)) 2955 return "United States"; 2956 if ("32506021000036107".equals(module)) 2957 return "Australian"; 2958 if ("449081005".equals(module)) 2959 return "Spanish"; 2960 if ("554471000005108".equals(module)) 2961 return "Danish"; 2962 if ("11000146104".equals(module)) 2963 return "Dutch"; 2964 if ("45991000052106".equals(module)) 2965 return "Swedish"; 2966 if ("999000041000000102".equals(module)) 2967 return "United Kingdon"; 2968 return module; 2969 } 2970 2971 private boolean hasVersionParameter(ValueSetExpansionComponent expansion) { 2972 for (ValueSetExpansionParameterComponent p : expansion.getParameter()) { 2973 if (p.getName().equals("version")) 2974 return true; 2975 } 2976 return false; 2977 } 2978 2979 private void addLanguageRow(ValueSetExpansionContainsComponent c, XhtmlNode t, List<String> langs) { 2980 XhtmlNode tr = t.tr(); 2981 tr.td().addText(c.getCode()); 2982 for (String lang : langs) { 2983 String d = null; 2984 for (Extension ext : c.getExtension()) { 2985 if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) { 2986 String l = ToolingExtensions.readStringExtension(ext, "lang"); 2987 if (lang.equals(l)) 2988 d = ToolingExtensions.readStringExtension(ext, "content"); 2989 } 2990 } 2991 tr.td().addText(d == null ? "" : d); 2992 } 2993 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 2994 addLanguageRow(cc, t, langs); 2995 } 2996 } 2997 2998 2999 private String describeLang(String lang) { 3000 ValueSet v = context.fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages"); 3001 if (v != null) { 3002 ConceptReferenceComponent l = null; 3003 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 3004 if (cc.getCode().equals(lang)) 3005 l = cc; 3006 } 3007 if (l == null) { 3008 if (lang.contains("-")) 3009 lang = lang.substring(0, lang.indexOf("-")); 3010 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 3011 if (cc.getCode().equals(lang) || cc.getCode().startsWith(lang+"-")) 3012 l = cc; 3013 } 3014 } 3015 if (l != null) { 3016 if (lang.contains("-")) 3017 lang = lang.substring(0, lang.indexOf("-")); 3018 String en = l.getDisplay(); 3019 String nativelang = null; 3020 for (ConceptReferenceDesignationComponent cd : l.getDesignation()) { 3021 if (cd.getLanguage().equals(lang)) 3022 nativelang = cd.getValue(); 3023 } 3024 if (nativelang == null) 3025 return en+" ("+lang+")"; 3026 else 3027 return nativelang+" ("+en+", "+lang+")"; 3028 } 3029 } 3030 return lang; 3031 } 3032 3033 3034 private boolean checkDoDefinition(List<ValueSetExpansionContainsComponent> contains) { 3035 for (ValueSetExpansionContainsComponent c : contains) { 3036 CodeSystem cs = context.fetchCodeSystem(c.getSystem()); 3037 if (cs != null) 3038 return true; 3039 if (checkDoDefinition(c.getContains())) 3040 return true; 3041 } 3042 return false; 3043 } 3044 3045 3046 private boolean allFromOneSystem(ValueSet vs) { 3047 if (vs.getExpansion().getContains().isEmpty()) 3048 return false; 3049 String system = vs.getExpansion().getContains().get(0).getSystem(); 3050 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 3051 if (!checkSystemMatches(system, cc)) 3052 return false; 3053 } 3054 return true; 3055 } 3056 3057 3058 private boolean checkSystemMatches(String system, ValueSetExpansionContainsComponent cc) { 3059 if (!system.equals(cc.getSystem())) 3060 return false; 3061 for (ValueSetExpansionContainsComponent cc1 : cc.getContains()) { 3062 if (!checkSystemMatches(system, cc1)) 3063 return false; 3064 } 3065 return true; 3066 } 3067 3068 3069 private boolean checkDoSystem(ValueSet vs, ValueSet src) { 3070 if (src != null) 3071 vs = src; 3072 return vs.hasCompose(); 3073 } 3074 3075 private boolean IsNotFixedExpansion(ValueSet vs) { 3076 if (vs.hasCompose()) 3077 return false; 3078 3079 3080 // it's not fixed if it has any includes that are not version fixed 3081 for (ConceptSetComponent cc : vs.getCompose().getInclude()) { 3082 if (cc.hasValueSet()) 3083 return true; 3084 if (!cc.hasVersion()) 3085 return true; 3086 } 3087 return false; 3088 } 3089 3090 3091 private void addLanguageRow(ConceptDefinitionComponent c, XhtmlNode t, List<String> langs) { 3092 XhtmlNode tr = t.tr(); 3093 tr.td().addText(c.getCode()); 3094 for (String lang : langs) { 3095 ConceptDefinitionDesignationComponent d = null; 3096 for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) { 3097 if (designation.hasLanguage()) { 3098 if (lang.equals(designation.getLanguage())) 3099 d = designation; 3100 } 3101 } 3102 tr.td().addText(d == null ? "" : d.getValue()); 3103 } 3104 } 3105 3106// private void scanLangs(ConceptDefinitionComponent c, List<String> langs) { 3107// for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) { 3108// if (designation.hasLanguage()) { 3109// String lang = designation.getLanguage(); 3110// if (langs != null && !langs.contains(lang) && c.hasDisplay() && !c.getDisplay().equalsIgnoreCase(designation.getValue())) 3111// langs.add(lang); 3112// } 3113// } 3114// for (ConceptDefinitionComponent g : c.getConcept()) 3115// scanLangs(g, langs); 3116// } 3117 3118 private void addMapHeaders(XhtmlNode tr, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException { 3119 for (UsedConceptMap m : maps) { 3120 XhtmlNode td = tr.td(); 3121 XhtmlNode b = td.b(); 3122 XhtmlNode a = b.ah(prefix+m.getLink()); 3123 a.addText(m.getDetails().getName()); 3124 if (m.getDetails().isDoDescription() && m.getMap().hasDescription()) 3125 addMarkdown(td, m.getMap().getDescription()); 3126 } 3127 } 3128 3129 private void smartAddText(XhtmlNode p, String text) { 3130 if (text == null) 3131 return; 3132 3133 String[] lines = text.split("\\r\\n"); 3134 for (int i = 0; i < lines.length; i++) { 3135 if (i > 0) 3136 p.br(); 3137 p.addText(lines[i]); 3138 } 3139 } 3140 3141 private boolean conceptsHaveComments(ConceptDefinitionComponent c) { 3142 if (ToolingExtensions.hasCSComment(c)) 3143 return true; 3144 for (ConceptDefinitionComponent g : c.getConcept()) 3145 if (conceptsHaveComments(g)) 3146 return true; 3147 return false; 3148 } 3149 3150 private boolean conceptsHaveDisplay(ConceptDefinitionComponent c) { 3151 if (c.hasDisplay()) 3152 return true; 3153 for (ConceptDefinitionComponent g : c.getConcept()) 3154 if (conceptsHaveDisplay(g)) 3155 return true; 3156 return false; 3157 } 3158 3159 private boolean conceptsHaveVersion(ConceptDefinitionComponent c) { 3160 if (c.hasUserData("cs.version.notes")) 3161 return true; 3162 for (ConceptDefinitionComponent g : c.getConcept()) 3163 if (conceptsHaveVersion(g)) 3164 return true; 3165 return false; 3166 } 3167 3168 private boolean conceptsHaveDeprecated(CodeSystem cs, ConceptDefinitionComponent c) { 3169 if (CodeSystemUtilities.isDeprecated(cs, c)) 3170 return true; 3171 for (ConceptDefinitionComponent g : c.getConcept()) 3172 if (conceptsHaveDeprecated(cs, g)) 3173 return true; 3174 return false; 3175 } 3176 3177 private void generateCopyright(XhtmlNode x, ValueSet vs) { 3178 XhtmlNode p = x.para(); 3179 p.b().tx("Copyright Statement:"); 3180 smartAddText(p, " " + vs.getCopyright()); 3181 } 3182 3183 3184 private XhtmlNode addTableHeaderRowStandard(XhtmlNode t, boolean hasHierarchy, boolean hasDisplay, boolean definitions, boolean comments, boolean version, boolean deprecated, String lang) { 3185 XhtmlNode tr = t.tr(); 3186 if (hasHierarchy) 3187 tr.td().b().tx("Lvl"); 3188 tr.td().attribute("style", "white-space:nowrap").b().tx(context.translator().translate("xhtml-gen-cs", "Code", lang)); 3189 if (hasDisplay) 3190 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Display", lang)); 3191 if (definitions) 3192 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Definition", lang)); 3193 if (deprecated) 3194 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Deprecated", lang)); 3195 if (comments) 3196 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Comments", lang)); 3197 if (version) 3198 tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Version", lang)); 3199 return tr; 3200 } 3201 3202 private void addExpansionRowToTable(XhtmlNode t, ValueSetExpansionContainsComponent c, int i, boolean doLevel, boolean doSystem, boolean doDefinition, List<UsedConceptMap> maps, CodeSystem allCS, List<String> langs) { 3203 XhtmlNode tr = t.tr(); 3204 XhtmlNode td = tr.td(); 3205 3206 String tgt = makeAnchor(c.getSystem(), c.getCode()); 3207 td.an(tgt); 3208 3209 if (doLevel) { 3210 td.addText(Integer.toString(i)); 3211 td = tr.td(); 3212 } 3213 String s = Utilities.padLeft("", '\u00A0', i*2); 3214 td.attribute("style", "white-space:nowrap").addText(s); 3215 addCodeToTable(c.getAbstract(), c.getSystem(), c.getCode(), c.getDisplay(), td); 3216 if (doSystem) { 3217 td = tr.td(); 3218 td.addText(c.getSystem()); 3219 } 3220 td = tr.td(); 3221 if (c.hasDisplayElement()) 3222 td.addText(c.getDisplay()); 3223 3224 if (doDefinition) { 3225 CodeSystem cs = allCS; 3226 if (cs == null) 3227 cs = context.fetchCodeSystem(c.getSystem()); 3228 td = tr.td(); 3229 if (cs != null) 3230 td.addText(CodeSystemUtilities.getCodeDefinition(cs, c.getCode())); 3231 } 3232 for (UsedConceptMap m : maps) { 3233 td = tr.td(); 3234 List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap()); 3235 boolean first = true; 3236 for (TargetElementComponentWrapper mapping : mappings) { 3237 if (!first) 3238 td.br(); 3239 first = false; 3240 XhtmlNode span = td.span(null, mapping.comp.getEquivalence().toString()); 3241 span.addText(getCharForEquivalence(mapping.comp)); 3242 addRefToCode(td, mapping.group.getTarget(), m.getLink(), mapping.comp.getCode()); 3243 if (!Utilities.noString(mapping.comp.getComment())) 3244 td.i().tx("("+mapping.comp.getComment()+")"); 3245 } 3246 } 3247 for (Extension ext : c.getExtension()) { 3248 if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) { 3249 String lang = ToolingExtensions.readStringExtension(ext, "lang"); 3250 if (!Utilities.noString(lang) && !langs.contains(lang)) 3251 langs.add(lang); 3252 } 3253 } 3254 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 3255 addExpansionRowToTable(t, cc, i+1, doLevel, doSystem, doDefinition, maps, allCS, langs); 3256 } 3257 } 3258 3259 private void addCodeToTable(boolean isAbstract, String system, String code, String display, XhtmlNode td) { 3260 CodeSystem e = context.fetchCodeSystem(system); 3261 if (e == null || e.getContent() != org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode.COMPLETE) { 3262 if (isAbstract) 3263 td.i().setAttribute("title", ABSTRACT_CODE_HINT).addText(code); 3264 else if ("http://snomed.info/sct".equals(system)) { 3265 td.ah(sctLink(code)).addText(code); 3266 } else if ("http://loinc.org".equals(system)) { 3267 td.ah(LoincLinker.getLinkForCode(code)).addText(code); 3268 } else 3269 td.addText(code); 3270 } else { 3271 String href = prefix+getCsRef(e); 3272 if (href.contains("#")) 3273 href = href + "-"+Utilities.nmtokenize(code); 3274 else 3275 href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(code); 3276 if (isAbstract) 3277 td.ah(href).setAttribute("title", ABSTRACT_CODE_HINT).i().addText(code); 3278 else 3279 td.ah(href).addText(code); 3280 } 3281 } 3282 3283 public String sctLink(String code) { 3284// if (snomedEdition != null) 3285// http://browser.ihtsdotools.org/?perspective=full&conceptId1=428041000124106&edition=us-edition&release=v20180301&server=https://prod-browser-exten.ihtsdotools.org/api/snomed&langRefset=900000000000509007 3286 return "http://browser.ihtsdotools.org/?perspective=full&conceptId1="+code; 3287 } 3288 3289 private class TargetElementComponentWrapper { 3290 private ConceptMapGroupComponent group; 3291 private TargetElementComponent comp; 3292 public TargetElementComponentWrapper(ConceptMapGroupComponent group, TargetElementComponent comp) { 3293 super(); 3294 this.group = group; 3295 this.comp = comp; 3296 } 3297 3298 } 3299 3300 private String langDisplay(String l, boolean isShort) { 3301 ValueSet vs = context.fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages"); 3302 for (ConceptReferenceComponent vc : vs.getCompose().getInclude().get(0).getConcept()) { 3303 if (vc.getCode().equals(l)) { 3304 for (ConceptReferenceDesignationComponent cd : vc.getDesignation()) { 3305 if (cd.getLanguage().equals(l)) 3306 return cd.getValue()+(isShort ? "" : " ("+vc.getDisplay()+")"); 3307 } 3308 return vc.getDisplay(); 3309 } 3310 } 3311 return "??Lang"; 3312 } 3313 3314 private boolean addDefineRowToTable(XhtmlNode t, ConceptDefinitionComponent c, int i, boolean hasHierarchy, boolean hasDisplay, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, String lang) throws FHIRFormatError, DefinitionException, IOException { 3315 boolean hasExtensions = false; 3316 XhtmlNode tr = t.tr(); 3317 XhtmlNode td = tr.td(); 3318 if (hasHierarchy) { 3319 td.addText(Integer.toString(i+1)); 3320 td = tr.td(); 3321 String s = Utilities.padLeft("", '\u00A0', i*2); 3322 td.addText(s); 3323 } 3324 td.attribute("style", "white-space:nowrap").addText(c.getCode()); 3325 XhtmlNode a; 3326 if (c.hasCodeElement()) { 3327 td.an(cs.getId()+"-" + Utilities.nmtokenize(c.getCode())); 3328 } 3329 3330 if (hasDisplay) { 3331 td = tr.td(); 3332 if (c.hasDisplayElement()) { 3333 if (lang == null) { 3334 td.addText(c.getDisplay()); 3335 } else if (lang.equals("*")) { 3336 boolean sl = false; 3337 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) 3338 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && !c.getDisplay().equalsIgnoreCase(cd.getValue())) 3339 sl = true; 3340 td.addText((sl ? cs.getLanguage("en")+": " : "")+c.getDisplay()); 3341 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 3342 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && !c.getDisplay().equalsIgnoreCase(cd.getValue())) { 3343 td.br(); 3344 td.addText(cd.getLanguage()+": "+cd.getValue()); 3345 } 3346 } 3347 } else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) { 3348 td.addText(c.getDisplay()); 3349 } else { 3350 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 3351 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(lang)) { 3352 td.addText(cd.getValue()); 3353 } 3354 } 3355 } 3356 } 3357 } 3358 td = tr.td(); 3359 if (c != null && 3360 c.hasDefinitionElement()) { 3361 if (lang == null) { 3362 if (hasMarkdownInDefinitions(cs)) 3363 addMarkdown(td, c.getDefinition()); 3364 else 3365 td.addText(c.getDefinition()); 3366 } else if (lang.equals("*")) { 3367 boolean sl = false; 3368 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) 3369 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) 3370 sl = true; 3371 td.addText((sl ? cs.getLanguage("en")+": " : "")+c.getDefinition()); 3372 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 3373 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) { 3374 td.br(); 3375 td.addText(cd.getLanguage()+": "+cd.getValue()); 3376 } 3377 } 3378 } else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) { 3379 td.addText(c.getDefinition()); 3380 } else { 3381 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 3382 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && cd.getLanguage().equals(lang)) { 3383 td.addText(cd.getValue()); 3384 } 3385 } 3386 } 3387 } 3388 if (deprecated) { 3389 td = tr.td(); 3390 Boolean b = CodeSystemUtilities.isDeprecated(cs, c); 3391 if (b != null && b) { 3392 smartAddText(td, context.translator().translate("xhtml-gen-cs", "Deprecated", lang)); 3393 hasExtensions = true; 3394 if (ToolingExtensions.hasExtension(c, ToolingExtensions.EXT_REPLACED_BY)) { 3395 Coding cc = (Coding) ToolingExtensions.getExtension(c, ToolingExtensions.EXT_REPLACED_BY).getValue(); 3396 td.tx(" (replaced by "); 3397 String url = getCodingReference(cc, system); 3398 if (url != null) { 3399 td.ah(url).addText(cc.getCode()); 3400 td.tx(": "+cc.getDisplay()+")"); 3401 } else 3402 td.addText(cc.getCode()+" '"+cc.getDisplay()+"' in "+cc.getSystem()+")"); 3403 } 3404 } 3405 } 3406 if (comment) { 3407 td = tr.td(); 3408 Extension ext = c.getExtensionByUrl(ToolingExtensions.EXT_CS_COMMENT); 3409 if (ext != null) { 3410 hasExtensions = true; 3411 String bc = ext.hasValue() ? ext.getValue().primitiveValue() : null; 3412 Map<String, String> translations = ToolingExtensions.getLanguageTranslations(ext.getValue()); 3413 3414 if (lang == null) { 3415 if (bc != null) 3416 td.addText(bc); 3417 } else if (lang.equals("*")) { 3418 boolean sl = false; 3419 for (String l : translations.keySet()) 3420 if (bc == null || !bc.equalsIgnoreCase(translations.get(l))) 3421 sl = true; 3422 if (bc != null) { 3423 td.addText((sl ? cs.getLanguage("en")+": " : "")+bc); 3424 } 3425 for (String l : translations.keySet()) { 3426 if (bc == null || !bc.equalsIgnoreCase(translations.get(l))) { 3427 if (!td.getChildNodes().isEmpty()) 3428 td.br(); 3429 td.addText(l+": "+translations.get(l)); 3430 } 3431 } 3432 } else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) { 3433 if (bc != null) 3434 td.addText(bc); 3435 } else { 3436 if (bc != null) 3437 translations.put(cs.getLanguage("en"), bc); 3438 for (String l : translations.keySet()) { 3439 if (l.equals(lang)) { 3440 td.addText(translations.get(l)); 3441 } 3442 } 3443 } 3444 } 3445 } 3446 if (version) { 3447 td = tr.td(); 3448 if (c.hasUserData("cs.version.notes")) 3449 td.addText(c.getUserString("cs.version.notes")); 3450 } 3451 for (UsedConceptMap m : maps) { 3452 td = tr.td(); 3453 List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap()); 3454 boolean first = true; 3455 for (TargetElementComponentWrapper mapping : mappings) { 3456 if (!first) 3457 td.br(); 3458 first = false; 3459 XhtmlNode span = td.span(null, mapping.comp.hasEquivalence() ? mapping.comp.getEquivalence().toCode() : ""); 3460 span.addText(getCharForEquivalence(mapping.comp)); 3461 a = td.ah(prefix+m.getLink()+"#"+makeAnchor(mapping.group.getTarget(), mapping.comp.getCode())); 3462 a.addText(mapping.comp.getCode()); 3463 if (!Utilities.noString(mapping.comp.getComment())) 3464 td.i().tx("("+mapping.comp.getComment()+")"); 3465 } 3466 } 3467 for (String e : CodeSystemUtilities.getOtherChildren(cs, c)) { 3468 tr = t.tr(); 3469 td = tr.td(); 3470 String s = Utilities.padLeft("", '.', i*2); 3471 td.addText(s); 3472 a = td.ah("#"+Utilities.nmtokenize(e)); 3473 a.addText(c.getCode()); 3474 } 3475 for (ConceptDefinitionComponent cc : c.getConcept()) { 3476 hasExtensions = addDefineRowToTable(t, cc, i+1, hasHierarchy, hasDisplay, comment, version, deprecated, maps, system, cs, lang) || hasExtensions; 3477 } 3478 return hasExtensions; 3479 } 3480 3481 3482 private boolean hasMarkdownInDefinitions(CodeSystem cs) { 3483 return ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown"); 3484 } 3485 3486 private String makeAnchor(String codeSystem, String code) { 3487 String s = codeSystem+'-'+code; 3488 StringBuilder b = new StringBuilder(); 3489 for (char c : s.toCharArray()) { 3490 if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '.') 3491 b.append(c); 3492 else 3493 b.append('-'); 3494 } 3495 return b.toString(); 3496 } 3497 3498 private String getCodingReference(Coding cc, String system) { 3499 if (cc.getSystem().equals(system)) 3500 return "#"+cc.getCode(); 3501 if (cc.getSystem().equals("http://snomed.info/sct")) 3502 return "http://snomed.info/sct/"+cc.getCode(); 3503 if (cc.getSystem().equals("http://loinc.org")) 3504 return LoincLinker.getLinkForCode(cc.getCode()); 3505 return null; 3506 } 3507 3508 private String getCharForEquivalence(TargetElementComponent mapping) { 3509 if (!mapping.hasEquivalence()) 3510 return ""; 3511 switch (mapping.getEquivalence()) { 3512 case EQUAL : return "="; 3513 case EQUIVALENT : return "~"; 3514 case WIDER : return "<"; 3515 case NARROWER : return ">"; 3516 case INEXACT : return "><"; 3517 case UNMATCHED : return "-"; 3518 case DISJOINT : return "!="; 3519 case NULL: return null; 3520 default: return "?"; 3521 } 3522 } 3523 3524 private List<TargetElementComponentWrapper> findMappingsForCode(String code, ConceptMap map) { 3525 List<TargetElementComponentWrapper> mappings = new ArrayList<TargetElementComponentWrapper>(); 3526 3527 for (ConceptMapGroupComponent g : map.getGroup()) { 3528 for (SourceElementComponent c : g.getElement()) { 3529 if (c.getCode().equals(code)) 3530 for (TargetElementComponent cc : c.getTarget()) 3531 mappings.add(new TargetElementComponentWrapper(g, cc)); 3532 } 3533 } 3534 return mappings; 3535 } 3536 3537 private boolean generateComposition(ResourceContext rcontext, XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRException, IOException { 3538 boolean hasExtensions = false; 3539 List<String> langs = new ArrayList<String>(); 3540 3541 if (header) { 3542 XhtmlNode h = x.h2(); 3543 h.addText(vs.present()); 3544 addMarkdown(x, vs.getDescription()); 3545 if (vs.hasCopyrightElement()) 3546 generateCopyright(x, vs); 3547 } 3548 XhtmlNode p = x.para(); 3549 p.tx("This value set includes codes from the following code systems:"); 3550 3551 XhtmlNode ul = x.ul(); 3552 XhtmlNode li; 3553 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 3554 hasExtensions = genInclude(rcontext, ul, inc, "Include", langs, maps) || hasExtensions; 3555 } 3556 for (ConceptSetComponent exc : vs.getCompose().getExclude()) { 3557 hasExtensions = genInclude(rcontext, ul, exc, "Exclude", langs, maps) || hasExtensions; 3558 } 3559 3560 // now, build observed languages 3561 3562 if (langs.size() > 0) { 3563 Collections.sort(langs); 3564 x.para().b().tx("Additional Language Displays"); 3565 XhtmlNode t = x.table( "codes"); 3566 XhtmlNode tr = t.tr(); 3567 tr.td().b().tx("Code"); 3568 for (String lang : langs) 3569 tr.td().b().addText(describeLang(lang)); 3570 for (ConceptSetComponent c : vs.getCompose().getInclude()) { 3571 for (ConceptReferenceComponent cc : c.getConcept()) { 3572 addLanguageRow(cc, t, langs); 3573 } 3574 } 3575 } 3576 3577 return hasExtensions; 3578 } 3579 3580 private void addLanguageRow(ConceptReferenceComponent c, XhtmlNode t, List<String> langs) { 3581 XhtmlNode tr = t.tr(); 3582 tr.td().addText(c.getCode()); 3583 for (String lang : langs) { 3584 String d = null; 3585 for (ConceptReferenceDesignationComponent cd : c.getDesignation()) { 3586 String l = cd.getLanguage(); 3587 if (lang.equals(l)) 3588 d = cd.getValue(); 3589 } 3590 tr.td().addText(d == null ? "" : d); 3591 } 3592 } 3593 3594 private void AddVsRef(ResourceContext rcontext, String value, XhtmlNode li) { 3595 Resource res = rcontext == null ? null : rcontext.resolve(value); 3596 if (res != null && !(res instanceof MetadataResource)) { 3597 li.addText(value); 3598 return; 3599 } 3600 MetadataResource vs = (MetadataResource) res; 3601 if (vs == null) 3602 vs = context.fetchResource(ValueSet.class, value); 3603 if (vs == null) 3604 vs = context.fetchResource(StructureDefinition.class, value); 3605// if (vs == null) 3606 // vs = context.fetchResource(DataElement.class, value); 3607 if (vs == null) 3608 vs = context.fetchResource(Questionnaire.class, value); 3609 if (vs != null) { 3610 String ref = (String) vs.getUserData("path"); 3611 3612 ref = adjustForPath(ref); 3613 XhtmlNode a = li.ah(ref == null ? "??" : ref.replace("\\", "/")); 3614 a.addText(value); 3615 } else { 3616 CodeSystem cs = context.fetchCodeSystem(value); 3617 if (cs != null) { 3618 String ref = (String) cs.getUserData("path"); 3619 ref = adjustForPath(ref); 3620 XhtmlNode a = li.ah(ref == null ? "??" : ref.replace("\\", "/")); 3621 a.addText(value); 3622 } else if (value.equals("http://snomed.info/sct") || value.equals("http://snomed.info/id")) { 3623 XhtmlNode a = li.ah(value); 3624 a.tx("SNOMED-CT"); 3625 } 3626 else { 3627 if (value.startsWith("http://hl7.org") && !Utilities.existsInList(value, "http://hl7.org/fhir/sid/icd-10-us")) 3628 System.out.println("Unable to resolve value set "+value); 3629 li.addText(value); 3630 } 3631 } 3632 } 3633 3634 private String adjustForPath(String ref) { 3635 if (prefix == null) 3636 return ref; 3637 else 3638 return prefix+ref; 3639 } 3640 3641 private boolean genInclude(ResourceContext rcontext, XhtmlNode ul, ConceptSetComponent inc, String type, List<String> langs, List<UsedConceptMap> maps) throws FHIRException, IOException { 3642 boolean hasExtensions = false; 3643 XhtmlNode li; 3644 li = ul.li(); 3645 CodeSystem e = context.fetchCodeSystem(inc.getSystem()); 3646 3647 if (inc.hasSystem()) { 3648 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 3649 li.addText(type+" all codes defined in "); 3650 addCsRef(inc, li, e); 3651 } else { 3652 if (inc.getConcept().size() > 0) { 3653 li.addText(type+" these codes as defined in "); 3654 addCsRef(inc, li, e); 3655 3656 XhtmlNode t = li.table("none"); 3657 boolean hasComments = false; 3658 boolean hasDefinition = false; 3659 for (ConceptReferenceComponent c : inc.getConcept()) { 3660 hasComments = hasComments || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT); 3661 hasDefinition = hasDefinition || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION); 3662 } 3663 if (hasComments || hasDefinition) 3664 hasExtensions = true; 3665 addMapHeaders(addTableHeaderRowStandard(t, false, true, hasDefinition, hasComments, false, false, null), maps); 3666 for (ConceptReferenceComponent c : inc.getConcept()) { 3667 XhtmlNode tr = t.tr(); 3668 XhtmlNode td = tr.td(); 3669 ConceptDefinitionComponent cc = getConceptForCode(e, c.getCode(), inc); 3670 addCodeToTable(false, inc.getSystem(), c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td); 3671 3672 td = tr.td(); 3673 if (!Utilities.noString(c.getDisplay())) 3674 td.addText(c.getDisplay()); 3675 else if (cc != null && !Utilities.noString(cc.getDisplay())) 3676 td.addText(cc.getDisplay()); 3677 3678 td = tr.td(); 3679 if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION)) 3680 smartAddText(td, ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_DEFINITION)); 3681 else if (cc != null && !Utilities.noString(cc.getDefinition())) 3682 smartAddText(td, cc.getDefinition()); 3683 3684 if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT)) { 3685 smartAddText(tr.td(), "Note: "+ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_VS_COMMENT)); 3686 } 3687 for (ConceptReferenceDesignationComponent cd : c.getDesignation()) { 3688 if (cd.hasLanguage() && !langs.contains(cd.getLanguage())) 3689 langs.add(cd.getLanguage()); 3690 } 3691 } 3692 } 3693 boolean first = true; 3694 for (ConceptSetFilterComponent f : inc.getFilter()) { 3695 if (first) { 3696 li.addText(type+" codes from "); 3697 first = false; 3698 } else 3699 li.tx(" and "); 3700 addCsRef(inc, li, e); 3701 li.tx(" where "+f.getProperty()+" "+describe(f.getOp())+" "); 3702 if (e != null && codeExistsInValueSet(e, f.getValue())) { 3703 String href = prefix+getCsRef(e); 3704 if (href.contains("#")) 3705 href = href + "-"+Utilities.nmtokenize(f.getValue()); 3706 else 3707 href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue()); 3708 li.ah(href).addText(f.getValue()); 3709 } else if ("concept".equals(f.getProperty()) && inc.hasSystem()) { 3710 li.addText(f.getValue()); 3711 ValidationResult vr = context.validateCode(terminologyServiceOptions, inc.getSystem(), f.getValue(), null); 3712 if (vr.isOk()) { 3713 li.tx(" ("+vr.getDisplay()+")"); 3714 } 3715 } 3716 else 3717 li.addText(f.getValue()); 3718 String disp = ToolingExtensions.getDisplayHint(f); 3719 if (disp != null) 3720 li.tx(" ("+disp+")"); 3721 } 3722 } 3723 if (inc.hasValueSet()) { 3724 li.tx(", where the codes are contained in "); 3725 boolean first = true; 3726 for (UriType vs : inc.getValueSet()) { 3727 if (first) 3728 first = false; 3729 else 3730 li.tx(", "); 3731 AddVsRef(rcontext, vs.asStringValue(), li); 3732 } 3733 } 3734 } else { 3735 li.tx("Import all the codes that are contained in "); 3736 boolean first = true; 3737 for (UriType vs : inc.getValueSet()) { 3738 if (first) 3739 first = false; 3740 else 3741 li.tx(", "); 3742 AddVsRef(rcontext, vs.asStringValue(), li); 3743 } 3744 } 3745 return hasExtensions; 3746 } 3747 3748 private String describe(FilterOperator op) { 3749 switch (op) { 3750 case EQUAL: return " = "; 3751 case ISA: return " is-a "; 3752 case ISNOTA: return " is-not-a "; 3753 case REGEX: return " matches (by regex) "; 3754 case NULL: return " ?? "; 3755 case IN: return " in "; 3756 case NOTIN: return " not in "; 3757 case DESCENDENTOF: return " descends from "; 3758 case EXISTS: return " exists "; 3759 case GENERALIZES: return " generalizes "; 3760 } 3761 return null; 3762 } 3763 3764 private ConceptDefinitionComponent getConceptForCode(CodeSystem e, String code, ConceptSetComponent inc) { 3765 // first, look in the code systems 3766 if (e == null) 3767 e = context.fetchCodeSystem(inc.getSystem()); 3768 if (e != null) { 3769 ConceptDefinitionComponent v = getConceptForCode(e.getConcept(), code); 3770 if (v != null) 3771 return v; 3772 } 3773 3774 if (!context.hasCache()) { 3775 ValueSetExpansionComponent vse; 3776 try { 3777 ValueSetExpansionOutcome vso = context.expandVS(inc, false); 3778 ValueSet valueset = vso.getValueset(); 3779 if (valueset == null) 3780 throw new TerminologyServiceException("Error Expanding ValueSet: "+vso.getError()); 3781 vse = valueset.getExpansion(); 3782 3783 } catch (TerminologyServiceException e1) { 3784 return null; 3785 } 3786 if (vse != null) { 3787 ConceptDefinitionComponent v = getConceptForCodeFromExpansion(vse.getContains(), code); 3788 if (v != null) 3789 return v; 3790 } 3791 } 3792 3793 return context.validateCode(terminologyServiceOptions, inc.getSystem(), code, null).asConceptDefinition(); 3794 } 3795 3796 3797 3798 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> list, String code) { 3799 for (ConceptDefinitionComponent c : list) { 3800 if (code.equals(c.getCode())) 3801 return c; 3802 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 3803 if (v != null) 3804 return v; 3805 } 3806 return null; 3807 } 3808 3809 private ConceptDefinitionComponent getConceptForCodeFromExpansion(List<ValueSetExpansionContainsComponent> list, String code) { 3810 for (ValueSetExpansionContainsComponent c : list) { 3811 if (code.equals(c.getCode())) { 3812 ConceptDefinitionComponent res = new ConceptDefinitionComponent(); 3813 res.setCode(c.getCode()); 3814 res.setDisplay(c.getDisplay()); 3815 return res; 3816 } 3817 ConceptDefinitionComponent v = getConceptForCodeFromExpansion(c.getContains(), code); 3818 if (v != null) 3819 return v; 3820 } 3821 return null; 3822 } 3823 3824 private void addRefToCode(XhtmlNode td, String target, String vslink, String code) { 3825 CodeSystem cs = context.fetchCodeSystem(target); 3826 String cslink = getCsRef(cs); 3827 XhtmlNode a = null; 3828 if (cslink != null) 3829 a = td.ah(prefix+cslink+"#"+cs.getId()+"-"+code); 3830 else 3831 a = td.ah(prefix+vslink+"#"+code); 3832 a.addText(code); 3833 } 3834 3835 private <T extends Resource> void addCsRef(ConceptSetComponent inc, XhtmlNode li, T cs) { 3836 String ref = null; 3837 boolean addHtml = true; 3838 if (cs != null) { 3839 ref = (String) cs.getUserData("external.url"); 3840 if (Utilities.noString(ref)) 3841 ref = (String) cs.getUserData("filename"); 3842 else 3843 addHtml = false; 3844 if (Utilities.noString(ref)) 3845 ref = (String) cs.getUserData("path"); 3846 } 3847 String spec = getSpecialReference(inc.getSystem()); 3848 if (spec != null) { 3849 XhtmlNode a = li.ah(spec); 3850 a.code(inc.getSystem()); 3851 } else if (cs != null && ref != null) { 3852 if (!Utilities.noString(prefix) && ref.startsWith("http://hl7.org/fhir/")) 3853 ref = ref.substring(20)+"/index.html"; 3854 else if (addHtml && !ref.contains(".html")) 3855 ref = ref + ".html"; 3856 XhtmlNode a = li.ah(prefix+ref.replace("\\", "/")); 3857 a.code(inc.getSystem()); 3858 } else { 3859 li.code(inc.getSystem()); 3860 } 3861 } 3862 3863 private String getSpecialReference(String system) { 3864 if ("http://snomed.info/sct".equals(system)) 3865 return "http://www.snomed.org/"; 3866 if (Utilities.existsInList(system, "http://loinc.org", "http://unitsofmeasure.org", "http://www.nlm.nih.gov/research/umls/rxnorm", "http://ncimeta.nci.nih.gov", "http://fdasis.nlm.nih.gov", 3867 "http://www.radlex.org", "http://www.whocc.no/atc", "http://dicom.nema.org/resources/ontology/DCM", "http://www.genenames.org", "http://www.ensembl.org", "http://www.ncbi.nlm.nih.gov/nuccore", 3868 "http://www.ncbi.nlm.nih.gov/clinvar", "http://sequenceontology.org", "http://www.hgvs.org/mutnomen", "http://www.ncbi.nlm.nih.gov/projects/SNP", "http://cancer.sanger.ac.uk/cancergenome/projects/cosmic", 3869 "http://www.lrg-sequence.org", "http://www.omim.org", "http://www.ncbi.nlm.nih.gov/pubmed", "http://www.pharmgkb.org", "http://clinicaltrials.gov", "http://www.ebi.ac.uk/ipd/imgt/hla/")) 3870 return system; 3871 3872 return null; 3873 } 3874 3875 private String getCsRef(String system) { 3876 CodeSystem cs = context.fetchCodeSystem(system); 3877 return getCsRef(cs); 3878 } 3879 3880 private <T extends Resource> String getCsRef(T cs) { 3881 String ref = (String) cs.getUserData("filename"); 3882 if (ref == null) 3883 ref = (String) cs.getUserData("path"); 3884 if (ref == null) 3885 return "??.html"; 3886 if (!ref.contains(".html")) 3887 ref = ref + ".html"; 3888 return ref.replace("\\", "/"); 3889 } 3890 3891 private boolean codeExistsInValueSet(CodeSystem cs, String code) { 3892 for (ConceptDefinitionComponent c : cs.getConcept()) { 3893 if (inConcept(code, c)) 3894 return true; 3895 } 3896 return false; 3897 } 3898 3899 private boolean inConcept(String code, ConceptDefinitionComponent c) { 3900 if (c.hasCodeElement() && c.getCode().equals(code)) 3901 return true; 3902 for (ConceptDefinitionComponent g : c.getConcept()) { 3903 if (inConcept(code, g)) 3904 return true; 3905 } 3906 return false; 3907 } 3908 3909 /** 3910 * This generate is optimised for the build tool in that it tracks the source extension. 3911 * But it can be used for any other use. 3912 * 3913 * @param vs 3914 * @param codeSystems 3915 * @throws DefinitionException 3916 * @throws Exception 3917 */ 3918 public boolean generate(ResourceContext rcontext, OperationOutcome op) throws DefinitionException { 3919 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 3920 boolean hasSource = false; 3921 boolean success = true; 3922 for (OperationOutcomeIssueComponent i : op.getIssue()) { 3923 success = success && i.getSeverity() == IssueSeverity.INFORMATION; 3924 hasSource = hasSource || ExtensionHelper.hasExtension(i, ToolingExtensions.EXT_ISSUE_SOURCE); 3925 } 3926 if (success) 3927 x.para().tx("All OK"); 3928 if (op.getIssue().size() > 0) { 3929 XhtmlNode tbl = x.table("grid"); // on the basis that we'll most likely be rendered using the standard fhir css, but it doesn't really matter 3930 XhtmlNode tr = tbl.tr(); 3931 tr.td().b().tx("Severity"); 3932 tr.td().b().tx("Location"); 3933 tr.td().b().tx("Code"); 3934 tr.td().b().tx("Details"); 3935 tr.td().b().tx("Diagnostics"); 3936 if (hasSource) 3937 tr.td().b().tx("Source"); 3938 for (OperationOutcomeIssueComponent i : op.getIssue()) { 3939 tr = tbl.tr(); 3940 tr.td().addText(i.getSeverity().toString()); 3941 XhtmlNode td = tr.td(); 3942 boolean d = false; 3943 for (StringType s : i.getLocation()) { 3944 if (d) 3945 td.tx(", "); 3946 else 3947 d = true; 3948 td.addText(s.getValue()); 3949 } 3950 tr.td().addText(i.getCode().getDisplay()); 3951 tr.td().addText(gen(i.getDetails())); 3952 smartAddText(tr.td(), i.getDiagnostics()); 3953 if (hasSource) { 3954 Extension ext = ExtensionHelper.getExtension(i, ToolingExtensions.EXT_ISSUE_SOURCE); 3955 tr.td().addText(ext == null ? "" : gen(ext)); 3956 } 3957 } 3958 } 3959 inject(op, x, hasSource ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 3960 return true; 3961 } 3962 3963 3964 public String genType(Type type) throws DefinitionException { 3965 if (type instanceof Coding) 3966 return gen((Coding) type); 3967 if (type instanceof CodeableConcept) 3968 return displayCodeableConcept((CodeableConcept) type); 3969 if (type instanceof Quantity) 3970 return displayQuantity((Quantity) type); 3971 if (type instanceof Range) 3972 return displayRange((Range) type); 3973 return null; 3974 } 3975 private String gen(Extension extension) throws DefinitionException { 3976 if (extension.getValue() instanceof CodeType) 3977 return ((CodeType) extension.getValue()).getValue(); 3978 if (extension.getValue() instanceof Coding) 3979 return gen((Coding) extension.getValue()); 3980 3981 throw new DefinitionException("Unhandled type "+extension.getValue().getClass().getName()); 3982 } 3983 3984 public String gen(CodeableConcept code) { 3985 if (code == null) 3986 return null; 3987 if (code.hasText()) 3988 return code.getText(); 3989 if (code.hasCoding()) 3990 return gen(code.getCoding().get(0)); 3991 return null; 3992 } 3993 3994 public String gen(Coding code) { 3995 if (code == null) 3996 return null; 3997 if (code.hasDisplayElement()) 3998 return code.getDisplay(); 3999 if (code.hasCodeElement()) 4000 return code.getCode(); 4001 return null; 4002 } 4003 4004 public boolean generate(ResourceContext rcontext, StructureDefinition sd, Set<String> outputTracker) throws EOperationOutcome, FHIRException, IOException { 4005 ProfileUtilities pu = new ProfileUtilities(context, null, pkp); 4006 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4007 x.getChildNodes().add(pu.generateTable(definitionsTarget, sd, true, destDir, false, sd.getId(), false, corePath, "", false, false, outputTracker)); 4008 inject(sd, x, NarrativeStatus.GENERATED); 4009 return true; 4010 } 4011 public boolean generate(ResourceContext rcontext, ImplementationGuide ig) throws EOperationOutcome, FHIRException, IOException { 4012 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4013 x.h2().addText(ig.getName()); 4014 x.para().tx("The official URL for this implementation guide is: "); 4015 x.pre().tx(ig.getUrl()); 4016 addMarkdown(x, ig.getDescription()); 4017 inject(ig, x, NarrativeStatus.GENERATED); 4018 return true; 4019 } 4020 public boolean generate(ResourceContext rcontext, OperationDefinition opd) throws EOperationOutcome, FHIRException, IOException { 4021 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4022 x.h2().addText(opd.getName()); 4023 x.para().addText(Utilities.capitalize(opd.getKind().toString())+": "+opd.getName()); 4024 x.para().tx("The official URL for this operation definition is: "); 4025 x.pre().tx(opd.getUrl()); 4026 addMarkdown(x, opd.getDescription()); 4027 4028 if (opd.getSystem()) 4029 x.para().tx("URL: [base]/$"+opd.getCode()); 4030 for (CodeType c : opd.getResource()) { 4031 if (opd.getType()) 4032 x.para().tx("URL: [base]/"+c.getValue()+"/$"+opd.getCode()); 4033 if (opd.getInstance()) 4034 x.para().tx("URL: [base]/"+c.getValue()+"/[id]/$"+opd.getCode()); 4035 } 4036 4037 x.para().tx("Parameters"); 4038 XhtmlNode tbl = x.table( "grid"); 4039 XhtmlNode tr = tbl.tr(); 4040 tr.td().b().tx("Use"); 4041 tr.td().b().tx("Name"); 4042 tr.td().b().tx("Cardinality"); 4043 tr.td().b().tx("Type"); 4044 tr.td().b().tx("Binding"); 4045 tr.td().b().tx("Documentation"); 4046 for (OperationDefinitionParameterComponent p : opd.getParameter()) { 4047 genOpParam(rcontext, tbl, "", p); 4048 } 4049 addMarkdown(x, opd.getComment()); 4050 inject(opd, x, NarrativeStatus.GENERATED); 4051 return true; 4052 } 4053 4054 private void genOpParam(ResourceContext rcontext, XhtmlNode tbl, String path, OperationDefinitionParameterComponent p) throws EOperationOutcome, FHIRException, IOException { 4055 XhtmlNode tr; 4056 tr = tbl.tr(); 4057 tr.td().addText(p.getUse().toString()); 4058 tr.td().addText(path+p.getName()); 4059 tr.td().addText(Integer.toString(p.getMin())+".."+p.getMax()); 4060 XhtmlNode td = tr.td(); 4061 StructureDefinition sd = context.fetchTypeDefinition(p.getType()); 4062 if (sd == null) 4063 td.tx(p.hasType() ? p.getType() : ""); 4064 else if (sd.getAbstract() && p.hasExtension(ToolingExtensions.EXT_ALLOWED_TYPE)) { 4065 boolean first = true; 4066 for (Extension ex : p.getExtensionsByUrl(ToolingExtensions.EXT_ALLOWED_TYPE)) { 4067 if (first) first = false; else td.tx(" | "); 4068 String s = ex.getValue().primitiveValue(); 4069 StructureDefinition sdt = context.fetchTypeDefinition(s); 4070 if (sdt == null) 4071 td.tx(p.hasType() ? p.getType() : ""); 4072 else 4073 td.ah(sdt.getUserString("path")).tx(s); 4074 } 4075 } else 4076 td.ah(sd.getUserString("path")).tx(p.hasType() ? p.getType() : ""); 4077 if (p.hasSearchType()) { 4078 td.br(); 4079 td.tx("("); 4080 td.ah( corePath == null ? "search.html#"+p.getSearchType().toCode() : Utilities.pathURL(corePath, "search.html#"+p.getSearchType().toCode())).tx(p.getSearchType().toCode()); 4081 td.tx(")"); 4082 } 4083 td = tr.td(); 4084 if (p.hasBinding() && p.getBinding().hasValueSet()) { 4085 AddVsRef(rcontext, p.getBinding().getValueSet(), td); 4086 td.tx(" ("+p.getBinding().getStrength().getDisplay()+")"); 4087 } 4088 addMarkdown(tr.td(), p.getDocumentation()); 4089 if (!p.hasType()) { 4090 for (OperationDefinitionParameterComponent pp : p.getPart()) { 4091 genOpParam(rcontext, tbl, path+p.getName()+".", pp); 4092 } 4093 } 4094 } 4095 4096 private void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException { 4097 if (text != null) { 4098 // 1. custom FHIR extensions 4099 while (text.contains("[[[")) { 4100 String left = text.substring(0, text.indexOf("[[[")); 4101 String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]")); 4102 String right = text.substring(text.indexOf("]]]")+3); 4103 String url = link; 4104 String[] parts = link.split("\\#"); 4105 StructureDefinition p = context.fetchResource(StructureDefinition.class, parts[0]); 4106 if (p == null) 4107 p = context.fetchTypeDefinition(parts[0]); 4108 if (p == null) 4109 p = context.fetchResource(StructureDefinition.class, link); 4110 if (p != null) { 4111 url = p.getUserString("path"); 4112 if (url == null) 4113 url = p.getUserString("filename"); 4114 } else 4115 throw new DefinitionException("Unable to resolve markdown link "+link); 4116 4117 text = left+"["+link+"]("+url+")"+right; 4118 } 4119 4120 // 2. markdown 4121 String s = markdown.process(Utilities.escapeXml(text), "narrative generator"); 4122 XhtmlParser p = new XhtmlParser(); 4123 XhtmlNode m; 4124 try { 4125 m = p.parse("<div>"+s+"</div>", "div"); 4126 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 4127 throw new FHIRFormatError(e.getMessage(), e); 4128 } 4129 x.getChildNodes().addAll(m.getChildNodes()); 4130 } 4131 } 4132 4133 public boolean generate(ResourceContext rcontext, CompartmentDefinition cpd) { 4134 StringBuilder in = new StringBuilder(); 4135 StringBuilder out = new StringBuilder(); 4136 for (CompartmentDefinitionResourceComponent cc: cpd.getResource()) { 4137 CommaSeparatedStringBuilder rules = new CommaSeparatedStringBuilder(); 4138 if (!cc.hasParam()) { 4139 out.append(" <li><a href=\"").append(cc.getCode().toLowerCase()).append(".html\">").append(cc.getCode()).append("</a></li>\r\n"); 4140 } else if (!rules.equals("{def}")) { 4141 for (StringType p : cc.getParam()) 4142 rules.append(p.asStringValue()); 4143 in.append(" <tr><td><a href=\"").append(cc.getCode().toLowerCase()).append(".html\">").append(cc.getCode()).append("</a></td><td>").append(rules.toString()).append("</td></tr>\r\n"); 4144 } 4145 } 4146 XhtmlNode x; 4147 try { 4148 x = new XhtmlParser().parseFragment("<div><p>\r\nThe following resources may be in this compartment:\r\n</p>\r\n" + 4149 "<table class=\"grid\">\r\n"+ 4150 " <tr><td><b>Resource</b></td><td><b>Inclusion Criteria</b></td></tr>\r\n"+ 4151 in.toString()+ 4152 "</table>\r\n"+ 4153 "<p>\r\nA resource is in this compartment if the nominated search parameter (or chain) refers to the patient resource that defines the compartment.\r\n</p>\r\n" + 4154 "<p>\r\n\r\n</p>\r\n" + 4155 "<p>\r\nThe following resources are never in this compartment:\r\n</p>\r\n" + 4156 "<ul>\r\n"+ 4157 out.toString()+ 4158 "</ul></div>\r\n"); 4159 inject(cpd, x, NarrativeStatus.GENERATED); 4160 return true; 4161 } catch (Exception e) { 4162 e.printStackTrace(); 4163 return false; 4164 } 4165 } 4166 4167 public boolean generate(ResourceContext rcontext, CapabilityStatement conf) throws FHIRFormatError, DefinitionException, IOException { 4168 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4169 x.h2().addText(conf.getName()); 4170 addMarkdown(x, conf.getDescription()); 4171 if (conf.getRest().size() > 0) { 4172 CapabilityStatementRestComponent rest = conf.getRest().get(0); 4173 XhtmlNode t = x.table(null); 4174 addTableRow(t, "Mode", rest.getMode().toString()); 4175 addTableRow(t, "Description", rest.getDocumentation()); 4176 4177 addTableRow(t, "Transaction", showOp(rest, SystemRestfulInteraction.TRANSACTION)); 4178 addTableRow(t, "System History", showOp(rest, SystemRestfulInteraction.HISTORYSYSTEM)); 4179 addTableRow(t, "System Search", showOp(rest, SystemRestfulInteraction.SEARCHSYSTEM)); 4180 4181 boolean hasVRead = false; 4182 boolean hasPatch = false; 4183 boolean hasDelete = false; 4184 boolean hasHistory = false; 4185 boolean hasUpdates = false; 4186 for (CapabilityStatementRestResourceComponent r : rest.getResource()) { 4187 hasVRead = hasVRead || hasOp(r, TypeRestfulInteraction.VREAD); 4188 hasPatch = hasPatch || hasOp(r, TypeRestfulInteraction.PATCH); 4189 hasDelete = hasDelete || hasOp(r, TypeRestfulInteraction.DELETE); 4190 hasHistory = hasHistory || hasOp(r, TypeRestfulInteraction.HISTORYTYPE); 4191 hasUpdates = hasUpdates || hasOp(r, TypeRestfulInteraction.HISTORYINSTANCE); 4192 } 4193 4194 t = x.table(null); 4195 XhtmlNode tr = t.tr(); 4196 tr.th().b().tx("Resource Type"); 4197 tr.th().b().tx("Profile"); 4198 tr.th().b().attribute("title", "GET a resource (read interaction)").tx("Read"); 4199 if (hasVRead) 4200 tr.th().b().attribute("title", "GET past versions of resources (vread interaction)").tx("V-Read"); 4201 tr.th().b().attribute("title", "GET all set of resources of the type (search interaction)").tx("Search"); 4202 tr.th().b().attribute("title", "PUT a new resource version (update interaction)").tx("Update"); 4203 if (hasPatch) 4204 tr.th().b().attribute("title", "PATCH a new resource version (patch interaction)").tx("Patch"); 4205 tr.th().b().attribute("title", "POST a new resource (create interaction)").tx("Create"); 4206 if (hasDelete) 4207 tr.th().b().attribute("title", "DELETE a resource (delete interaction)").tx("Delete"); 4208 if (hasUpdates) 4209 tr.th().b().attribute("title", "GET changes to a resource (history interaction on instance)").tx("Updates"); 4210 if (hasHistory) 4211 tr.th().b().attribute("title", "GET changes for all resources of the type (history interaction on type)").tx("History"); 4212 4213 for (CapabilityStatementRestResourceComponent r : rest.getResource()) { 4214 tr = t.tr(); 4215 tr.td().addText(r.getType()); 4216 if (r.hasProfile()) { 4217 tr.td().ah(prefix+r.getProfile()).addText(r.getProfile()); 4218 } 4219 tr.td().addText(showOp(r, TypeRestfulInteraction.READ)); 4220 if (hasVRead) 4221 tr.td().addText(showOp(r, TypeRestfulInteraction.VREAD)); 4222 tr.td().addText(showOp(r, TypeRestfulInteraction.SEARCHTYPE)); 4223 tr.td().addText(showOp(r, TypeRestfulInteraction.UPDATE)); 4224 if (hasPatch) 4225 tr.td().addText(showOp(r, TypeRestfulInteraction.PATCH)); 4226 tr.td().addText(showOp(r, TypeRestfulInteraction.CREATE)); 4227 if (hasDelete) 4228 tr.td().addText(showOp(r, TypeRestfulInteraction.DELETE)); 4229 if (hasUpdates) 4230 tr.td().addText(showOp(r, TypeRestfulInteraction.HISTORYINSTANCE)); 4231 if (hasHistory) 4232 tr.td().addText(showOp(r, TypeRestfulInteraction.HISTORYTYPE)); 4233 } 4234 } 4235 4236 inject(conf, x, NarrativeStatus.GENERATED); 4237 return true; 4238 } 4239 4240 private boolean hasOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction on) { 4241 for (ResourceInteractionComponent op : r.getInteraction()) { 4242 if (op.getCode() == on) 4243 return true; 4244 } 4245 return false; 4246 } 4247 4248 private String showOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction on) { 4249 for (ResourceInteractionComponent op : r.getInteraction()) { 4250 if (op.getCode() == on) 4251 return "y"; 4252 } 4253 return ""; 4254 } 4255 4256 private String showOp(CapabilityStatementRestComponent r, SystemRestfulInteraction on) { 4257 for (SystemInteractionComponent op : r.getInteraction()) { 4258 if (op.getCode() == on) 4259 return "y"; 4260 } 4261 return ""; 4262 } 4263 4264 private void addTableRow(XhtmlNode t, String name, String value) { 4265 XhtmlNode tr = t.tr(); 4266 tr.td().addText(name); 4267 tr.td().addText(value); 4268 } 4269 4270 public XhtmlNode generateDocumentNarrative(Bundle feed) { 4271 /* 4272 When the document is presented for human consumption, applications must present the collated narrative portions of the following resources in order: 4273 * The Composition resource 4274 * The Subject resource 4275 * Resources referenced in the section.content 4276 */ 4277 XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); 4278 Composition comp = (Composition) feed.getEntry().get(0).getResource(); 4279 root.getChildNodes().add(comp.getText().getDiv()); 4280 Resource subject = ResourceUtilities.getById(feed, null, comp.getSubject().getReference()); 4281 if (subject != null && subject instanceof DomainResource) { 4282 root.hr(); 4283 root.getChildNodes().add(((DomainResource)subject).getText().getDiv()); 4284 } 4285 List<SectionComponent> sections = comp.getSection(); 4286 renderSections(feed, root, sections, 1); 4287 return root; 4288 } 4289 4290 private void renderSections(Bundle feed, XhtmlNode node, List<SectionComponent> sections, int level) { 4291 for (SectionComponent section : sections) { 4292 node.hr(); 4293 if (section.hasTitleElement()) 4294 node.addTag("h"+Integer.toString(level)).addText(section.getTitle()); 4295// else if (section.hasCode()) 4296// node.addTag("h"+Integer.toString(level)).addText(displayCodeableConcept(section.getCode())); 4297 4298// if (section.hasText()) { 4299// node.getChildNodes().add(section.getText().getDiv()); 4300// } 4301// 4302// if (!section.getSection().isEmpty()) { 4303// renderSections(feed, node.addTag("blockquote"), section.getSection(), level+1); 4304// } 4305 } 4306 } 4307 4308 4309 public class ObservationNode { 4310 private String ref; 4311 private ResourceWrapper obs; 4312 private List<ObservationNode> contained = new ArrayList<NarrativeGenerator.ObservationNode>(); 4313 } 4314 4315 public XhtmlNode generateDiagnosticReport(ResourceWrapper dr) { 4316 XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); 4317 XhtmlNode h2 = root.h2(); 4318 displayCodeableConcept(h2, getProperty(dr, "code").value()); 4319 h2.tx(" "); 4320 PropertyWrapper pw = getProperty(dr, "category"); 4321 if (valued(pw)) { 4322 h2.tx("("); 4323 displayCodeableConcept(h2, pw.value()); 4324 h2.tx(") "); 4325 } 4326 displayDate(h2, getProperty(dr, "issued").value()); 4327 4328 XhtmlNode tbl = root.table( "grid"); 4329 XhtmlNode tr = tbl.tr(); 4330 XhtmlNode tdl = tr.td(); 4331 XhtmlNode tdr = tr.td(); 4332 populateSubjectSummary(tdl, getProperty(dr, "subject").value()); 4333 tdr.b().tx("Report Details"); 4334 tdr.br(); 4335 pw = getProperty(dr, "perfomer"); 4336 if (valued(pw)) { 4337 tdr.addText(pluralise("Performer", pw.getValues().size())+":"); 4338 for (BaseWrapper v : pw.getValues()) { 4339 tdr.tx(" "); 4340 displayReference(tdr, v); 4341 } 4342 tdr.br(); 4343 } 4344 pw = getProperty(dr, "identifier"); 4345 if (valued(pw)) { 4346 tdr.addText(pluralise("Identifier", pw.getValues().size())+":"); 4347 for (BaseWrapper v : pw.getValues()) { 4348 tdr.tx(" "); 4349 displayIdentifier(tdr, v); 4350 } 4351 tdr.br(); 4352 } 4353 pw = getProperty(dr, "request"); 4354 if (valued(pw)) { 4355 tdr.addText(pluralise("Request", pw.getValues().size())+":"); 4356 for (BaseWrapper v : pw.getValues()) { 4357 tdr.tx(" "); 4358 displayReferenceId(tdr, v); 4359 } 4360 tdr.br(); 4361 } 4362 4363 pw = getProperty(dr, "result"); 4364 if (valued(pw)) { 4365 List<ObservationNode> observations = fetchObservations(pw.getValues()); 4366 buildObservationsTable(root, observations); 4367 } 4368 4369 pw = getProperty(dr, "conclusion"); 4370 if (valued(pw)) 4371 displayText(root.para(), pw.value()); 4372 4373 pw = getProperty(dr, "result"); 4374 if (valued(pw)) { 4375 XhtmlNode p = root.para(); 4376 p.b().tx("Coded Diagnoses :"); 4377 for (BaseWrapper v : pw.getValues()) { 4378 tdr.tx(" "); 4379 displayCodeableConcept(tdr, v); 4380 } 4381 } 4382 return root; 4383 } 4384 4385 private void buildObservationsTable(XhtmlNode root, List<ObservationNode> observations) { 4386 XhtmlNode tbl = root.table( "none"); 4387 for (ObservationNode o : observations) { 4388 addObservationToTable(tbl, o, 0); 4389 } 4390 } 4391 4392 private void addObservationToTable(XhtmlNode tbl, ObservationNode o, int i) { 4393 XhtmlNode tr = tbl.tr(); 4394 if (o.obs == null) { 4395 XhtmlNode td = tr.td().colspan("6"); 4396 td.i().tx("This Observation could not be resolved"); 4397 } else { 4398 addObservationToTable(tr, o.obs, i); 4399 // todo: contained observations 4400 } 4401 for (ObservationNode c : o.contained) { 4402 addObservationToTable(tbl, c, i+1); 4403 } 4404 } 4405 4406 private void addObservationToTable(XhtmlNode tr, ResourceWrapper obs, int i) { 4407 // TODO Auto-generated method stub 4408 4409 // code (+bodysite) 4410 XhtmlNode td = tr.td(); 4411 PropertyWrapper pw = getProperty(obs, "result"); 4412 if (valued(pw)) { 4413 displayCodeableConcept(td, pw.value()); 4414 } 4415 pw = getProperty(obs, "bodySite"); 4416 if (valued(pw)) { 4417 td.tx(" ("); 4418 displayCodeableConcept(td, pw.value()); 4419 td.tx(")"); 4420 } 4421 4422 // value / dataAbsentReason (in red) 4423 td = tr.td(); 4424 pw = getProperty(obs, "value[x]"); 4425 if (valued(pw)) { 4426 if (pw.getTypeCode().equals("CodeableConcept")) 4427 displayCodeableConcept(td, pw.value()); 4428 else if (pw.getTypeCode().equals("string")) 4429 displayText(td, pw.value()); 4430 else 4431 td.addText(pw.getTypeCode()+" not rendered yet"); 4432 } 4433 4434 // units 4435 td = tr.td(); 4436 td.tx("to do"); 4437 4438 // reference range 4439 td = tr.td(); 4440 td.tx("to do"); 4441 4442 // flags (status other than F, interpretation, ) 4443 td = tr.td(); 4444 td.tx("to do"); 4445 4446 // issued if different to DR 4447 td = tr.td(); 4448 td.tx("to do"); 4449 } 4450 4451 private boolean valued(PropertyWrapper pw) { 4452 return pw != null && pw.hasValues(); 4453 } 4454 4455 private void displayText(XhtmlNode c, BaseWrapper v) { 4456 c.addText(v.toString()); 4457 } 4458 4459 private String pluralise(String name, int size) { 4460 return size == 1 ? name : name+"s"; 4461 } 4462 4463 private void displayIdentifier(XhtmlNode c, BaseWrapper v) { 4464 String hint = ""; 4465 PropertyWrapper pw = v.getChildByName("type"); 4466 if (valued(pw)) { 4467 hint = genCC(pw.value()); 4468 } else { 4469 pw = v.getChildByName("system"); 4470 if (valued(pw)) { 4471 hint = pw.value().toString(); 4472 } 4473 } 4474 displayText(c.span(null, hint), v.getChildByName("value").value()); 4475 } 4476 4477 private String genCoding(BaseWrapper value) { 4478 PropertyWrapper pw = value.getChildByName("display"); 4479 if (valued(pw)) 4480 return pw.value().toString(); 4481 pw = value.getChildByName("code"); 4482 if (valued(pw)) 4483 return pw.value().toString(); 4484 return ""; 4485 } 4486 4487 private String genCC(BaseWrapper value) { 4488 PropertyWrapper pw = value.getChildByName("text"); 4489 if (valued(pw)) 4490 return pw.value().toString(); 4491 pw = value.getChildByName("coding"); 4492 if (valued(pw)) 4493 return genCoding(pw.getValues().get(0)); 4494 return ""; 4495 } 4496 4497 private void displayReference(XhtmlNode c, BaseWrapper v) { 4498 c.tx("to do"); 4499 } 4500 4501 4502 private void displayDate(XhtmlNode c, BaseWrapper baseWrapper) { 4503 c.tx("to do"); 4504 } 4505 4506 private void displayCodeableConcept(XhtmlNode c, BaseWrapper property) { 4507 c.tx("to do"); 4508 } 4509 4510 private void displayReferenceId(XhtmlNode c, BaseWrapper v) { 4511 c.tx("to do"); 4512 } 4513 4514 private PropertyWrapper getProperty(ResourceWrapper res, String name) { 4515 for (PropertyWrapper t : res.children()) { 4516 if (t.getName().equals(name)) 4517 return t; 4518 } 4519 return null; 4520 } 4521 4522 private void populateSubjectSummary(XhtmlNode container, BaseWrapper subject) { 4523 ResourceWrapper r = fetchResource(subject); 4524 if (r == null) 4525 container.tx("Unable to get Patient Details"); 4526 else if (r.getName().equals("Patient")) 4527 generatePatientSummary(container, r); 4528 else 4529 container.tx("Not done yet"); 4530 } 4531 4532 private void generatePatientSummary(XhtmlNode c, ResourceWrapper r) { 4533 c.tx("to do"); 4534 } 4535 4536 private ResourceWrapper fetchResource(BaseWrapper subject) { 4537 if (resolver == null) 4538 return null; 4539 String url = subject.getChildByName("reference").value().toString(); 4540 ResourceWithReference rr = resolver.resolve(url); 4541 return rr == null ? null : rr.resource; 4542 } 4543 4544 private List<ObservationNode> fetchObservations(List<BaseWrapper> list) { 4545 return new ArrayList<NarrativeGenerator.ObservationNode>(); 4546 } 4547 4548 public XhtmlNode renderBundle(Bundle b) throws FHIRException { 4549 if (b.getType() == BundleType.DOCUMENT) { 4550 if (!b.hasEntry() || !(b.getEntryFirstRep().hasResource() && b.getEntryFirstRep().getResource() instanceof Composition)) 4551 throw new FHIRException("Invalid document - first entry is not a Composition"); 4552 Composition dr = (Composition) b.getEntryFirstRep().getResource(); 4553 return dr.getText().getDiv(); 4554 } else { 4555 XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); 4556 root.para().addText("Bundle "+b.getId()+" of type "+b.getType().toCode()); 4557 int i = 0; 4558 for (BundleEntryComponent be : b.getEntry()) { 4559 i++; 4560 if (be.hasResource() && be.getResource().hasId()) 4561 root.an(be.getResource().getResourceType().name().toLowerCase() + "_" + be.getResource().getId()); 4562 root.hr(); 4563 root.para().addText("Entry "+Integer.toString(i)+(be.hasFullUrl() ? " - Full URL = " + be.getFullUrl() : "")); 4564 if (be.hasRequest()) 4565 renderRequest(root, be.getRequest()); 4566 if (be.hasSearch()) 4567 renderSearch(root, be.getSearch()); 4568 if (be.hasResponse()) 4569 renderResponse(root, be.getResponse()); 4570 if (be.hasResource()) { 4571 root.para().addText("Resource "+be.getResource().fhirType()+":"); 4572 if (be.hasResource() && be.getResource() instanceof DomainResource) { 4573 DomainResource dr = (DomainResource) be.getResource(); 4574 if ( dr.getText().hasDiv()) 4575 root.blockquote().getChildNodes().addAll(dr.getText().getDiv().getChildNodes()); 4576 } 4577 } 4578 } 4579 return root; 4580 } 4581 } 4582 4583 private void renderSearch(XhtmlNode root, BundleEntrySearchComponent search) { 4584 StringBuilder b = new StringBuilder(); 4585 b.append("Search: "); 4586 if (search.hasMode()) 4587 b.append("mode = "+search.getMode().toCode()); 4588 if (search.hasScore()) { 4589 if (search.hasMode()) 4590 b.append(","); 4591 b.append("score = "+search.getScore()); 4592 } 4593 root.para().addText(b.toString()); 4594 } 4595 4596 private void renderResponse(XhtmlNode root, BundleEntryResponseComponent response) { 4597 root.para().addText("Request:"); 4598 StringBuilder b = new StringBuilder(); 4599 b.append(response.getStatus()+"\r\n"); 4600 if (response.hasLocation()) 4601 b.append("Location: "+response.getLocation()+"\r\n"); 4602 if (response.hasEtag()) 4603 b.append("E-Tag: "+response.getEtag()+"\r\n"); 4604 if (response.hasLastModified()) 4605 b.append("LastModified: "+response.getEtag()+"\r\n"); 4606 root.pre().addText(b.toString()); 4607 } 4608 4609 private void renderRequest(XhtmlNode root, BundleEntryRequestComponent request) { 4610 root.para().addText("Response:"); 4611 StringBuilder b = new StringBuilder(); 4612 b.append(request.getMethod()+" "+request.getUrl()+"\r\n"); 4613 if (request.hasIfNoneMatch()) 4614 b.append("If-None-Match: "+request.getIfNoneMatch()+"\r\n"); 4615 if (request.hasIfModifiedSince()) 4616 b.append("If-Modified-Since: "+request.getIfModifiedSince()+"\r\n"); 4617 if (request.hasIfMatch()) 4618 b.append("If-Match: "+request.getIfMatch()+"\r\n"); 4619 if (request.hasIfNoneExist()) 4620 b.append("If-None-Exist: "+request.getIfNoneExist()+"\r\n"); 4621 root.pre().addText(b.toString()); 4622 } 4623 4624 public XhtmlNode renderBundle(org.hl7.fhir.r4.elementmodel.Element element) throws FHIRException { 4625 XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); 4626 for (Base b : element.listChildrenByName("entry")) { 4627 org.hl7.fhir.r4.elementmodel.Element r = ((org.hl7.fhir.r4.elementmodel.Element) b).getNamedChild("resource"); 4628 if (r!=null) { 4629 XhtmlNode c = getHtmlForResource(r); 4630 if (c != null) 4631 root.getChildNodes().addAll(c.getChildNodes()); 4632 root.hr(); 4633 } 4634 } 4635 return root; 4636 } 4637 4638 private XhtmlNode getHtmlForResource(org.hl7.fhir.r4.elementmodel.Element element) { 4639 org.hl7.fhir.r4.elementmodel.Element text = element.getNamedChild("text"); 4640 if (text == null) 4641 return null; 4642 org.hl7.fhir.r4.elementmodel.Element div = text.getNamedChild("div"); 4643 if (div == null) 4644 return null; 4645 else 4646 return div.getXhtml(); 4647 } 4648 4649 public String getDefinitionsTarget() { 4650 return definitionsTarget; 4651 } 4652 4653 public void setDefinitionsTarget(String definitionsTarget) { 4654 this.definitionsTarget = definitionsTarget; 4655 } 4656 4657 public String getCorePath() { 4658 return corePath; 4659 } 4660 4661 public void setCorePath(String corePath) { 4662 this.corePath = corePath; 4663 } 4664 4665 public String getDestDir() { 4666 return destDir; 4667 } 4668 4669 public void setDestDir(String destDir) { 4670 this.destDir = destDir; 4671 } 4672 4673 public ProfileKnowledgeProvider getPkp() { 4674 return pkp; 4675 } 4676 4677 public NarrativeGenerator setPkp(ProfileKnowledgeProvider pkp) { 4678 this.pkp = pkp; 4679 return this; 4680 } 4681 4682 public boolean isPretty() { 4683 return pretty; 4684 } 4685 4686 public NarrativeGenerator setPretty(boolean pretty) { 4687 this.pretty = pretty; 4688 return this; 4689 } 4690 4691 public boolean isCanonicalUrlsAsLinks() { 4692 return canonicalUrlsAsLinks; 4693 } 4694 4695 @Override 4696 public void setCanonicalUrlsAsLinks(boolean canonicalUrlsAsLinks) { 4697 this.canonicalUrlsAsLinks = canonicalUrlsAsLinks; 4698 } 4699 4700 public String getSnomedEdition() { 4701 return snomedEdition; 4702 } 4703 4704 public NarrativeGenerator setSnomedEdition(String snomedEdition) { 4705 this.snomedEdition = snomedEdition; 4706 return this; 4707 } 4708 4709 public TerminologyServiceOptions getTerminologyServiceOptions() { 4710 return terminologyServiceOptions; 4711 } 4712 4713 public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) { 4714 this.terminologyServiceOptions = terminologyServiceOptions; 4715 } 4716 4717 4718}