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