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 case NULL: return null; 3519 default: return "?"; 3520 } 3521 } 3522 3523 private List<TargetElementComponentWrapper> findMappingsForCode(String code, ConceptMap map) { 3524 List<TargetElementComponentWrapper> mappings = new ArrayList<TargetElementComponentWrapper>(); 3525 3526 for (ConceptMapGroupComponent g : map.getGroup()) { 3527 for (SourceElementComponent c : g.getElement()) { 3528 if (c.getCode().equals(code)) 3529 for (TargetElementComponent cc : c.getTarget()) 3530 mappings.add(new TargetElementComponentWrapper(g, cc)); 3531 } 3532 } 3533 return mappings; 3534 } 3535 3536 private boolean generateComposition(ResourceContext rcontext, XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRException, IOException { 3537 boolean hasExtensions = false; 3538 List<String> langs = new ArrayList<String>(); 3539 3540 if (header) { 3541 XhtmlNode h = x.h2(); 3542 h.addText(vs.present()); 3543 addMarkdown(x, vs.getDescription()); 3544 if (vs.hasCopyrightElement()) 3545 generateCopyright(x, vs); 3546 } 3547 XhtmlNode p = x.para(); 3548 p.tx("This value set includes codes from the following code systems:"); 3549 3550 XhtmlNode ul = x.ul(); 3551 XhtmlNode li; 3552 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 3553 hasExtensions = genInclude(rcontext, ul, inc, "Include", langs, maps) || hasExtensions; 3554 } 3555 for (ConceptSetComponent exc : vs.getCompose().getExclude()) { 3556 hasExtensions = genInclude(rcontext, ul, exc, "Exclude", langs, maps) || hasExtensions; 3557 } 3558 3559 // now, build observed languages 3560 3561 if (langs.size() > 0) { 3562 Collections.sort(langs); 3563 x.para().b().tx("Additional Language Displays"); 3564 XhtmlNode t = x.table( "codes"); 3565 XhtmlNode tr = t.tr(); 3566 tr.td().b().tx("Code"); 3567 for (String lang : langs) 3568 tr.td().b().addText(describeLang(lang)); 3569 for (ConceptSetComponent c : vs.getCompose().getInclude()) { 3570 for (ConceptReferenceComponent cc : c.getConcept()) { 3571 addLanguageRow(cc, t, langs); 3572 } 3573 } 3574 } 3575 3576 return hasExtensions; 3577 } 3578 3579 private void addLanguageRow(ConceptReferenceComponent c, XhtmlNode t, List<String> langs) { 3580 XhtmlNode tr = t.tr(); 3581 tr.td().addText(c.getCode()); 3582 for (String lang : langs) { 3583 String d = null; 3584 for (ConceptReferenceDesignationComponent cd : c.getDesignation()) { 3585 String l = cd.getLanguage(); 3586 if (lang.equals(l)) 3587 d = cd.getValue(); 3588 } 3589 tr.td().addText(d == null ? "" : d); 3590 } 3591 } 3592 3593 private void AddVsRef(ResourceContext rcontext, String value, XhtmlNode li) { 3594 Resource res = rcontext == null ? null : rcontext.resolve(value); 3595 if (res != null && !(res instanceof MetadataResource)) { 3596 li.addText(value); 3597 return; 3598 } 3599 MetadataResource vs = (MetadataResource) res; 3600 if (vs == null) 3601 vs = context.fetchResource(ValueSet.class, value); 3602 if (vs == null) 3603 vs = context.fetchResource(StructureDefinition.class, value); 3604// if (vs == null) 3605 // vs = context.fetchResource(DataElement.class, value); 3606 if (vs == null) 3607 vs = context.fetchResource(Questionnaire.class, value); 3608 if (vs != null) { 3609 String ref = (String) vs.getUserData("path"); 3610 3611 ref = adjustForPath(ref); 3612 XhtmlNode a = li.ah(ref == null ? "??" : ref.replace("\\", "/")); 3613 a.addText(value); 3614 } else { 3615 CodeSystem cs = context.fetchCodeSystem(value); 3616 if (cs != null) { 3617 String ref = (String) cs.getUserData("path"); 3618 ref = adjustForPath(ref); 3619 XhtmlNode a = li.ah(ref == null ? "??" : ref.replace("\\", "/")); 3620 a.addText(value); 3621 } else if (value.equals("http://snomed.info/sct") || value.equals("http://snomed.info/id")) { 3622 XhtmlNode a = li.ah(value); 3623 a.tx("SNOMED-CT"); 3624 } 3625 else { 3626 if (value.startsWith("http://hl7.org") && !Utilities.existsInList(value, "http://hl7.org/fhir/sid/icd-10-us")) 3627 System.out.println("Unable to resolve value set "+value); 3628 li.addText(value); 3629 } 3630 } 3631 } 3632 3633 private String adjustForPath(String ref) { 3634 if (prefix == null) 3635 return ref; 3636 else 3637 return prefix+ref; 3638 } 3639 3640 private boolean genInclude(ResourceContext rcontext, XhtmlNode ul, ConceptSetComponent inc, String type, List<String> langs, List<UsedConceptMap> maps) throws FHIRException, IOException { 3641 boolean hasExtensions = false; 3642 XhtmlNode li; 3643 li = ul.li(); 3644 CodeSystem e = context.fetchCodeSystem(inc.getSystem()); 3645 3646 if (inc.hasSystem()) { 3647 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 3648 li.addText(type+" all codes defined in "); 3649 addCsRef(inc, li, e); 3650 } else { 3651 if (inc.getConcept().size() > 0) { 3652 li.addText(type+" these codes as defined in "); 3653 addCsRef(inc, li, e); 3654 3655 XhtmlNode t = li.table("none"); 3656 boolean hasComments = false; 3657 boolean hasDefinition = false; 3658 for (ConceptReferenceComponent c : inc.getConcept()) { 3659 hasComments = hasComments || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT); 3660 hasDefinition = hasDefinition || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION); 3661 } 3662 if (hasComments || hasDefinition) 3663 hasExtensions = true; 3664 addMapHeaders(addTableHeaderRowStandard(t, false, true, hasDefinition, hasComments, false, false, null), maps); 3665 for (ConceptReferenceComponent c : inc.getConcept()) { 3666 XhtmlNode tr = t.tr(); 3667 XhtmlNode td = tr.td(); 3668 ConceptDefinitionComponent cc = getConceptForCode(e, c.getCode(), inc); 3669 addCodeToTable(false, inc.getSystem(), c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td); 3670 3671 td = tr.td(); 3672 if (!Utilities.noString(c.getDisplay())) 3673 td.addText(c.getDisplay()); 3674 else if (cc != null && !Utilities.noString(cc.getDisplay())) 3675 td.addText(cc.getDisplay()); 3676 3677 td = tr.td(); 3678 if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION)) 3679 smartAddText(td, ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_DEFINITION)); 3680 else if (cc != null && !Utilities.noString(cc.getDefinition())) 3681 smartAddText(td, cc.getDefinition()); 3682 3683 if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT)) { 3684 smartAddText(tr.td(), "Note: "+ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_VS_COMMENT)); 3685 } 3686 for (ConceptReferenceDesignationComponent cd : c.getDesignation()) { 3687 if (cd.hasLanguage() && !langs.contains(cd.getLanguage())) 3688 langs.add(cd.getLanguage()); 3689 } 3690 } 3691 } 3692 boolean first = true; 3693 for (ConceptSetFilterComponent f : inc.getFilter()) { 3694 if (first) { 3695 li.addText(type+" codes from "); 3696 first = false; 3697 } else 3698 li.tx(" and "); 3699 addCsRef(inc, li, e); 3700 li.tx(" where "+f.getProperty()+" "+describe(f.getOp())+" "); 3701 if (e != null && codeExistsInValueSet(e, f.getValue())) { 3702 String href = prefix+getCsRef(e); 3703 if (href.contains("#")) 3704 href = href + "-"+Utilities.nmtokenize(f.getValue()); 3705 else 3706 href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue()); 3707 li.ah(href).addText(f.getValue()); 3708 } else if ("concept".equals(f.getProperty()) && inc.hasSystem()) { 3709 li.addText(f.getValue()); 3710 ValidationResult vr = context.validateCode(terminologyServiceOptions, inc.getSystem(), f.getValue(), null); 3711 if (vr.isOk()) { 3712 li.tx(" ("+vr.getDisplay()+")"); 3713 } 3714 } 3715 else 3716 li.addText(f.getValue()); 3717 String disp = ToolingExtensions.getDisplayHint(f); 3718 if (disp != null) 3719 li.tx(" ("+disp+")"); 3720 } 3721 } 3722 if (inc.hasValueSet()) { 3723 li.tx(", where the codes are contained in "); 3724 boolean first = true; 3725 for (UriType vs : inc.getValueSet()) { 3726 if (first) 3727 first = false; 3728 else 3729 li.tx(", "); 3730 AddVsRef(rcontext, vs.asStringValue(), li); 3731 } 3732 } 3733 } else { 3734 li.tx("Import all the codes that are contained in "); 3735 boolean first = true; 3736 for (UriType vs : inc.getValueSet()) { 3737 if (first) 3738 first = false; 3739 else 3740 li.tx(", "); 3741 AddVsRef(rcontext, vs.asStringValue(), li); 3742 } 3743 } 3744 return hasExtensions; 3745 } 3746 3747 private String describe(FilterOperator op) { 3748 switch (op) { 3749 case EQUAL: return " = "; 3750 case ISA: return " is-a "; 3751 case ISNOTA: return " is-not-a "; 3752 case REGEX: return " matches (by regex) "; 3753 case NULL: return " ?? "; 3754 case IN: return " in "; 3755 case NOTIN: return " not in "; 3756 case DESCENDENTOF: return " descends from "; 3757 case EXISTS: return " exists "; 3758 case GENERALIZES: return " generalizes "; 3759 } 3760 return null; 3761 } 3762 3763 private ConceptDefinitionComponent getConceptForCode(CodeSystem e, String code, ConceptSetComponent inc) { 3764 // first, look in the code systems 3765 if (e == null) 3766 e = context.fetchCodeSystem(inc.getSystem()); 3767 if (e != null) { 3768 ConceptDefinitionComponent v = getConceptForCode(e.getConcept(), code); 3769 if (v != null) 3770 return v; 3771 } 3772 3773 if (!context.hasCache()) { 3774 ValueSetExpansionComponent vse; 3775 try { 3776 ValueSetExpansionOutcome vso = context.expandVS(inc, false); 3777 ValueSet valueset = vso.getValueset(); 3778 if (valueset == null) 3779 throw new TerminologyServiceException("Error Expanding ValueSet: "+vso.getError()); 3780 vse = valueset.getExpansion(); 3781 3782 } catch (TerminologyServiceException e1) { 3783 return null; 3784 } 3785 if (vse != null) { 3786 ConceptDefinitionComponent v = getConceptForCodeFromExpansion(vse.getContains(), code); 3787 if (v != null) 3788 return v; 3789 } 3790 } 3791 3792 return context.validateCode(terminologyServiceOptions, inc.getSystem(), code, null).asConceptDefinition(); 3793 } 3794 3795 3796 3797 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> list, String code) { 3798 for (ConceptDefinitionComponent c : list) { 3799 if (code.equals(c.getCode())) 3800 return c; 3801 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 3802 if (v != null) 3803 return v; 3804 } 3805 return null; 3806 } 3807 3808 private ConceptDefinitionComponent getConceptForCodeFromExpansion(List<ValueSetExpansionContainsComponent> list, String code) { 3809 for (ValueSetExpansionContainsComponent c : list) { 3810 if (code.equals(c.getCode())) { 3811 ConceptDefinitionComponent res = new ConceptDefinitionComponent(); 3812 res.setCode(c.getCode()); 3813 res.setDisplay(c.getDisplay()); 3814 return res; 3815 } 3816 ConceptDefinitionComponent v = getConceptForCodeFromExpansion(c.getContains(), code); 3817 if (v != null) 3818 return v; 3819 } 3820 return null; 3821 } 3822 3823 private void addRefToCode(XhtmlNode td, String target, String vslink, String code) { 3824 CodeSystem cs = context.fetchCodeSystem(target); 3825 String cslink = getCsRef(cs); 3826 XhtmlNode a = null; 3827 if (cslink != null) 3828 a = td.ah(prefix+cslink+"#"+cs.getId()+"-"+code); 3829 else 3830 a = td.ah(prefix+vslink+"#"+code); 3831 a.addText(code); 3832 } 3833 3834 private <T extends Resource> void addCsRef(ConceptSetComponent inc, XhtmlNode li, T cs) { 3835 String ref = null; 3836 boolean addHtml = true; 3837 if (cs != null) { 3838 ref = (String) cs.getUserData("external.url"); 3839 if (Utilities.noString(ref)) 3840 ref = (String) cs.getUserData("filename"); 3841 else 3842 addHtml = false; 3843 if (Utilities.noString(ref)) 3844 ref = (String) cs.getUserData("path"); 3845 } 3846 String spec = getSpecialReference(inc.getSystem()); 3847 if (spec != null) { 3848 XhtmlNode a = li.ah(spec); 3849 a.code(inc.getSystem()); 3850 } else if (cs != null && ref != null) { 3851 if (!Utilities.noString(prefix) && ref.startsWith("http://hl7.org/fhir/")) 3852 ref = ref.substring(20)+"/index.html"; 3853 else if (addHtml && !ref.contains(".html")) 3854 ref = ref + ".html"; 3855 XhtmlNode a = li.ah(prefix+ref.replace("\\", "/")); 3856 a.code(inc.getSystem()); 3857 } else { 3858 li.code(inc.getSystem()); 3859 } 3860 } 3861 3862 private String getSpecialReference(String system) { 3863 if ("http://snomed.info/sct".equals(system)) 3864 return "http://www.snomed.org/"; 3865 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", 3866 "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", 3867 "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", 3868 "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/")) 3869 return system; 3870 3871 return null; 3872 } 3873 3874 private String getCsRef(String system) { 3875 CodeSystem cs = context.fetchCodeSystem(system); 3876 return getCsRef(cs); 3877 } 3878 3879 private <T extends Resource> String getCsRef(T cs) { 3880 String ref = (String) cs.getUserData("filename"); 3881 if (ref == null) 3882 ref = (String) cs.getUserData("path"); 3883 if (ref == null) 3884 return "??.html"; 3885 if (!ref.contains(".html")) 3886 ref = ref + ".html"; 3887 return ref.replace("\\", "/"); 3888 } 3889 3890 private boolean codeExistsInValueSet(CodeSystem cs, String code) { 3891 for (ConceptDefinitionComponent c : cs.getConcept()) { 3892 if (inConcept(code, c)) 3893 return true; 3894 } 3895 return false; 3896 } 3897 3898 private boolean inConcept(String code, ConceptDefinitionComponent c) { 3899 if (c.hasCodeElement() && c.getCode().equals(code)) 3900 return true; 3901 for (ConceptDefinitionComponent g : c.getConcept()) { 3902 if (inConcept(code, g)) 3903 return true; 3904 } 3905 return false; 3906 } 3907 3908 /** 3909 * This generate is optimised for the build tool in that it tracks the source extension. 3910 * But it can be used for any other use. 3911 * 3912 * @param vs 3913 * @param codeSystems 3914 * @throws DefinitionException 3915 * @throws Exception 3916 */ 3917 public boolean generate(ResourceContext rcontext, OperationOutcome op) throws DefinitionException { 3918 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 3919 boolean hasSource = false; 3920 boolean success = true; 3921 for (OperationOutcomeIssueComponent i : op.getIssue()) { 3922 success = success && i.getSeverity() == IssueSeverity.INFORMATION; 3923 hasSource = hasSource || ExtensionHelper.hasExtension(i, ToolingExtensions.EXT_ISSUE_SOURCE); 3924 } 3925 if (success) 3926 x.para().tx("All OK"); 3927 if (op.getIssue().size() > 0) { 3928 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 3929 XhtmlNode tr = tbl.tr(); 3930 tr.td().b().tx("Severity"); 3931 tr.td().b().tx("Location"); 3932 tr.td().b().tx("Code"); 3933 tr.td().b().tx("Details"); 3934 tr.td().b().tx("Diagnostics"); 3935 if (hasSource) 3936 tr.td().b().tx("Source"); 3937 for (OperationOutcomeIssueComponent i : op.getIssue()) { 3938 tr = tbl.tr(); 3939 tr.td().addText(i.getSeverity().toString()); 3940 XhtmlNode td = tr.td(); 3941 boolean d = false; 3942 for (StringType s : i.getLocation()) { 3943 if (d) 3944 td.tx(", "); 3945 else 3946 d = true; 3947 td.addText(s.getValue()); 3948 } 3949 tr.td().addText(i.getCode().getDisplay()); 3950 tr.td().addText(gen(i.getDetails())); 3951 smartAddText(tr.td(), i.getDiagnostics()); 3952 if (hasSource) { 3953 Extension ext = ExtensionHelper.getExtension(i, ToolingExtensions.EXT_ISSUE_SOURCE); 3954 tr.td().addText(ext == null ? "" : gen(ext)); 3955 } 3956 } 3957 } 3958 inject(op, x, hasSource ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 3959 return true; 3960 } 3961 3962 3963 public String genType(Type type) throws DefinitionException { 3964 if (type instanceof Coding) 3965 return gen((Coding) type); 3966 if (type instanceof CodeableConcept) 3967 return displayCodeableConcept((CodeableConcept) type); 3968 if (type instanceof Quantity) 3969 return displayQuantity((Quantity) type); 3970 if (type instanceof Range) 3971 return displayRange((Range) type); 3972 return null; 3973 } 3974 private String gen(Extension extension) throws DefinitionException { 3975 if (extension.getValue() instanceof CodeType) 3976 return ((CodeType) extension.getValue()).getValue(); 3977 if (extension.getValue() instanceof Coding) 3978 return gen((Coding) extension.getValue()); 3979 3980 throw new DefinitionException("Unhandled type "+extension.getValue().getClass().getName()); 3981 } 3982 3983 public String gen(CodeableConcept code) { 3984 if (code == null) 3985 return null; 3986 if (code.hasText()) 3987 return code.getText(); 3988 if (code.hasCoding()) 3989 return gen(code.getCoding().get(0)); 3990 return null; 3991 } 3992 3993 public String gen(Coding code) { 3994 if (code == null) 3995 return null; 3996 if (code.hasDisplayElement()) 3997 return code.getDisplay(); 3998 if (code.hasCodeElement()) 3999 return code.getCode(); 4000 return null; 4001 } 4002 4003 public boolean generate(ResourceContext rcontext, StructureDefinition sd, Set<String> outputTracker) throws EOperationOutcome, FHIRException, IOException { 4004 ProfileUtilities pu = new ProfileUtilities(context, null, pkp); 4005 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4006 x.getChildNodes().add(pu.generateTable(definitionsTarget, sd, true, destDir, false, sd.getId(), false, corePath, "", false, false, outputTracker)); 4007 inject(sd, x, NarrativeStatus.GENERATED); 4008 return true; 4009 } 4010 public boolean generate(ResourceContext rcontext, ImplementationGuide ig) throws EOperationOutcome, FHIRException, IOException { 4011 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4012 x.h2().addText(ig.getName()); 4013 x.para().tx("The official URL for this implementation guide is: "); 4014 x.pre().tx(ig.getUrl()); 4015 addMarkdown(x, ig.getDescription()); 4016 inject(ig, x, NarrativeStatus.GENERATED); 4017 return true; 4018 } 4019 public boolean generate(ResourceContext rcontext, OperationDefinition opd) throws EOperationOutcome, FHIRException, IOException { 4020 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4021 x.h2().addText(opd.getName()); 4022 x.para().addText(Utilities.capitalize(opd.getKind().toString())+": "+opd.getName()); 4023 x.para().tx("The official URL for this operation definition is: "); 4024 x.pre().tx(opd.getUrl()); 4025 addMarkdown(x, opd.getDescription()); 4026 4027 if (opd.getSystem()) 4028 x.para().tx("URL: [base]/$"+opd.getCode()); 4029 for (CodeType c : opd.getResource()) { 4030 if (opd.getType()) 4031 x.para().tx("URL: [base]/"+c.getValue()+"/$"+opd.getCode()); 4032 if (opd.getInstance()) 4033 x.para().tx("URL: [base]/"+c.getValue()+"/[id]/$"+opd.getCode()); 4034 } 4035 4036 x.para().tx("Parameters"); 4037 XhtmlNode tbl = x.table( "grid"); 4038 XhtmlNode tr = tbl.tr(); 4039 tr.td().b().tx("Use"); 4040 tr.td().b().tx("Name"); 4041 tr.td().b().tx("Cardinality"); 4042 tr.td().b().tx("Type"); 4043 tr.td().b().tx("Binding"); 4044 tr.td().b().tx("Documentation"); 4045 for (OperationDefinitionParameterComponent p : opd.getParameter()) { 4046 genOpParam(rcontext, tbl, "", p); 4047 } 4048 addMarkdown(x, opd.getComment()); 4049 inject(opd, x, NarrativeStatus.GENERATED); 4050 return true; 4051 } 4052 4053 private void genOpParam(ResourceContext rcontext, XhtmlNode tbl, String path, OperationDefinitionParameterComponent p) throws EOperationOutcome, FHIRException, IOException { 4054 XhtmlNode tr; 4055 tr = tbl.tr(); 4056 tr.td().addText(p.getUse().toString()); 4057 tr.td().addText(path+p.getName()); 4058 tr.td().addText(Integer.toString(p.getMin())+".."+p.getMax()); 4059 XhtmlNode td = tr.td(); 4060 StructureDefinition sd = context.fetchTypeDefinition(p.getType()); 4061 if (sd == null) 4062 td.tx(p.hasType() ? p.getType() : ""); 4063 else if (sd.getAbstract() && p.hasExtension(ToolingExtensions.EXT_ALLOWED_TYPE)) { 4064 boolean first = true; 4065 for (Extension ex : p.getExtensionsByUrl(ToolingExtensions.EXT_ALLOWED_TYPE)) { 4066 if (first) first = false; else td.tx(" | "); 4067 String s = ex.getValue().primitiveValue(); 4068 StructureDefinition sdt = context.fetchTypeDefinition(s); 4069 if (sdt == null) 4070 td.tx(p.hasType() ? p.getType() : ""); 4071 else 4072 td.ah(sdt.getUserString("path")).tx(s); 4073 } 4074 } else 4075 td.ah(sd.getUserString("path")).tx(p.hasType() ? p.getType() : ""); 4076 if (p.hasSearchType()) { 4077 td.br(); 4078 td.tx("("); 4079 td.ah( corePath == null ? "search.html#"+p.getSearchType().toCode() : Utilities.pathURL(corePath, "search.html#"+p.getSearchType().toCode())).tx(p.getSearchType().toCode()); 4080 td.tx(")"); 4081 } 4082 td = tr.td(); 4083 if (p.hasBinding() && p.getBinding().hasValueSet()) { 4084 AddVsRef(rcontext, p.getBinding().getValueSet(), td); 4085 td.tx(" ("+p.getBinding().getStrength().getDisplay()+")"); 4086 } 4087 addMarkdown(tr.td(), p.getDocumentation()); 4088 if (!p.hasType()) { 4089 for (OperationDefinitionParameterComponent pp : p.getPart()) { 4090 genOpParam(rcontext, tbl, path+p.getName()+".", pp); 4091 } 4092 } 4093 } 4094 4095 private void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException { 4096 if (text != null) { 4097 // 1. custom FHIR extensions 4098 while (text.contains("[[[")) { 4099 String left = text.substring(0, text.indexOf("[[[")); 4100 String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]")); 4101 String right = text.substring(text.indexOf("]]]")+3); 4102 String url = link; 4103 String[] parts = link.split("\\#"); 4104 StructureDefinition p = context.fetchResource(StructureDefinition.class, parts[0]); 4105 if (p == null) 4106 p = context.fetchTypeDefinition(parts[0]); 4107 if (p == null) 4108 p = context.fetchResource(StructureDefinition.class, link); 4109 if (p != null) { 4110 url = p.getUserString("path"); 4111 if (url == null) 4112 url = p.getUserString("filename"); 4113 } else 4114 throw new DefinitionException("Unable to resolve markdown link "+link); 4115 4116 text = left+"["+link+"]("+url+")"+right; 4117 } 4118 4119 // 2. markdown 4120 String s = markdown.process(Utilities.escapeXml(text), "narrative generator"); 4121 XhtmlParser p = new XhtmlParser(); 4122 XhtmlNode m; 4123 try { 4124 m = p.parse("<div>"+s+"</div>", "div"); 4125 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 4126 throw new FHIRFormatError(e.getMessage(), e); 4127 } 4128 x.getChildNodes().addAll(m.getChildNodes()); 4129 } 4130 } 4131 4132 public boolean generate(ResourceContext rcontext, CompartmentDefinition cpd) { 4133 StringBuilder in = new StringBuilder(); 4134 StringBuilder out = new StringBuilder(); 4135 for (CompartmentDefinitionResourceComponent cc: cpd.getResource()) { 4136 CommaSeparatedStringBuilder rules = new CommaSeparatedStringBuilder(); 4137 if (!cc.hasParam()) { 4138 out.append(" <li><a href=\"").append(cc.getCode().toLowerCase()).append(".html\">").append(cc.getCode()).append("</a></li>\r\n"); 4139 } else if (!rules.equals("{def}")) { 4140 for (StringType p : cc.getParam()) 4141 rules.append(p.asStringValue()); 4142 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"); 4143 } 4144 } 4145 XhtmlNode x; 4146 try { 4147 x = new XhtmlParser().parseFragment("<div><p>\r\nThe following resources may be in this compartment:\r\n</p>\r\n" + 4148 "<table class=\"grid\">\r\n"+ 4149 " <tr><td><b>Resource</b></td><td><b>Inclusion Criteria</b></td></tr>\r\n"+ 4150 in.toString()+ 4151 "</table>\r\n"+ 4152 "<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" + 4153 "<p>\r\n\r\n</p>\r\n" + 4154 "<p>\r\nThe following resources are never in this compartment:\r\n</p>\r\n" + 4155 "<ul>\r\n"+ 4156 out.toString()+ 4157 "</ul></div>\r\n"); 4158 inject(cpd, x, NarrativeStatus.GENERATED); 4159 return true; 4160 } catch (Exception e) { 4161 e.printStackTrace(); 4162 return false; 4163 } 4164 } 4165 4166 public boolean generate(ResourceContext rcontext, CapabilityStatement conf) throws FHIRFormatError, DefinitionException, IOException { 4167 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4168 x.h2().addText(conf.getName()); 4169 addMarkdown(x, conf.getDescription()); 4170 if (conf.getRest().size() > 0) { 4171 CapabilityStatementRestComponent rest = conf.getRest().get(0); 4172 XhtmlNode t = x.table(null); 4173 addTableRow(t, "Mode", rest.getMode().toString()); 4174 addTableRow(t, "Description", rest.getDocumentation()); 4175 4176 addTableRow(t, "Transaction", showOp(rest, SystemRestfulInteraction.TRANSACTION)); 4177 addTableRow(t, "System History", showOp(rest, SystemRestfulInteraction.HISTORYSYSTEM)); 4178 addTableRow(t, "System Search", showOp(rest, SystemRestfulInteraction.SEARCHSYSTEM)); 4179 4180 boolean hasVRead = false; 4181 boolean hasPatch = false; 4182 boolean hasDelete = false; 4183 boolean hasHistory = false; 4184 boolean hasUpdates = false; 4185 for (CapabilityStatementRestResourceComponent r : rest.getResource()) { 4186 hasVRead = hasVRead || hasOp(r, TypeRestfulInteraction.VREAD); 4187 hasPatch = hasPatch || hasOp(r, TypeRestfulInteraction.PATCH); 4188 hasDelete = hasDelete || hasOp(r, TypeRestfulInteraction.DELETE); 4189 hasHistory = hasHistory || hasOp(r, TypeRestfulInteraction.HISTORYTYPE); 4190 hasUpdates = hasUpdates || hasOp(r, TypeRestfulInteraction.HISTORYINSTANCE); 4191 } 4192 4193 t = x.table(null); 4194 XhtmlNode tr = t.tr(); 4195 tr.th().b().tx("Resource Type"); 4196 tr.th().b().tx("Profile"); 4197 tr.th().b().attribute("title", "GET a resource (read interaction)").tx("Read"); 4198 if (hasVRead) 4199 tr.th().b().attribute("title", "GET past versions of resources (vread interaction)").tx("V-Read"); 4200 tr.th().b().attribute("title", "GET all set of resources of the type (search interaction)").tx("Search"); 4201 tr.th().b().attribute("title", "PUT a new resource version (update interaction)").tx("Update"); 4202 if (hasPatch) 4203 tr.th().b().attribute("title", "PATCH a new resource version (patch interaction)").tx("Patch"); 4204 tr.th().b().attribute("title", "POST a new resource (create interaction)").tx("Create"); 4205 if (hasDelete) 4206 tr.th().b().attribute("title", "DELETE a resource (delete interaction)").tx("Delete"); 4207 if (hasUpdates) 4208 tr.th().b().attribute("title", "GET changes to a resource (history interaction on instance)").tx("Updates"); 4209 if (hasHistory) 4210 tr.th().b().attribute("title", "GET changes for all resources of the type (history interaction on type)").tx("History"); 4211 4212 for (CapabilityStatementRestResourceComponent r : rest.getResource()) { 4213 tr = t.tr(); 4214 tr.td().addText(r.getType()); 4215 if (r.hasProfile()) { 4216 tr.td().ah(prefix+r.getProfile()).addText(r.getProfile()); 4217 } 4218 tr.td().addText(showOp(r, TypeRestfulInteraction.READ)); 4219 if (hasVRead) 4220 tr.td().addText(showOp(r, TypeRestfulInteraction.VREAD)); 4221 tr.td().addText(showOp(r, TypeRestfulInteraction.SEARCHTYPE)); 4222 tr.td().addText(showOp(r, TypeRestfulInteraction.UPDATE)); 4223 if (hasPatch) 4224 tr.td().addText(showOp(r, TypeRestfulInteraction.PATCH)); 4225 tr.td().addText(showOp(r, TypeRestfulInteraction.CREATE)); 4226 if (hasDelete) 4227 tr.td().addText(showOp(r, TypeRestfulInteraction.DELETE)); 4228 if (hasUpdates) 4229 tr.td().addText(showOp(r, TypeRestfulInteraction.HISTORYINSTANCE)); 4230 if (hasHistory) 4231 tr.td().addText(showOp(r, TypeRestfulInteraction.HISTORYTYPE)); 4232 } 4233 } 4234 4235 inject(conf, x, NarrativeStatus.GENERATED); 4236 return true; 4237 } 4238 4239 private boolean hasOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction on) { 4240 for (ResourceInteractionComponent op : r.getInteraction()) { 4241 if (op.getCode() == on) 4242 return true; 4243 } 4244 return false; 4245 } 4246 4247 private String showOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction on) { 4248 for (ResourceInteractionComponent op : r.getInteraction()) { 4249 if (op.getCode() == on) 4250 return "y"; 4251 } 4252 return ""; 4253 } 4254 4255 private String showOp(CapabilityStatementRestComponent r, SystemRestfulInteraction on) { 4256 for (SystemInteractionComponent op : r.getInteraction()) { 4257 if (op.getCode() == on) 4258 return "y"; 4259 } 4260 return ""; 4261 } 4262 4263 private void addTableRow(XhtmlNode t, String name, String value) { 4264 XhtmlNode tr = t.tr(); 4265 tr.td().addText(name); 4266 tr.td().addText(value); 4267 } 4268 4269 public XhtmlNode generateDocumentNarrative(Bundle feed) { 4270 /* 4271 When the document is presented for human consumption, applications must present the collated narrative portions of the following resources in order: 4272 * The Composition resource 4273 * The Subject resource 4274 * Resources referenced in the section.content 4275 */ 4276 XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); 4277 Composition comp = (Composition) feed.getEntry().get(0).getResource(); 4278 root.getChildNodes().add(comp.getText().getDiv()); 4279 Resource subject = ResourceUtilities.getById(feed, null, comp.getSubject().getReference()); 4280 if (subject != null && subject instanceof DomainResource) { 4281 root.hr(); 4282 root.getChildNodes().add(((DomainResource)subject).getText().getDiv()); 4283 } 4284 List<SectionComponent> sections = comp.getSection(); 4285 renderSections(feed, root, sections, 1); 4286 return root; 4287 } 4288 4289 private void renderSections(Bundle feed, XhtmlNode node, List<SectionComponent> sections, int level) { 4290 for (SectionComponent section : sections) { 4291 node.hr(); 4292 if (section.hasTitleElement()) 4293 node.addTag("h"+Integer.toString(level)).addText(section.getTitle()); 4294// else if (section.hasCode()) 4295// node.addTag("h"+Integer.toString(level)).addText(displayCodeableConcept(section.getCode())); 4296 4297// if (section.hasText()) { 4298// node.getChildNodes().add(section.getText().getDiv()); 4299// } 4300// 4301// if (!section.getSection().isEmpty()) { 4302// renderSections(feed, node.addTag("blockquote"), section.getSection(), level+1); 4303// } 4304 } 4305 } 4306 4307 4308 public class ObservationNode { 4309 private String ref; 4310 private ResourceWrapper obs; 4311 private List<ObservationNode> contained = new ArrayList<NarrativeGenerator.ObservationNode>(); 4312 } 4313 4314 public XhtmlNode generateDiagnosticReport(ResourceWrapper dr) { 4315 XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); 4316 XhtmlNode h2 = root.h2(); 4317 displayCodeableConcept(h2, getProperty(dr, "code").value()); 4318 h2.tx(" "); 4319 PropertyWrapper pw = getProperty(dr, "category"); 4320 if (valued(pw)) { 4321 h2.tx("("); 4322 displayCodeableConcept(h2, pw.value()); 4323 h2.tx(") "); 4324 } 4325 displayDate(h2, getProperty(dr, "issued").value()); 4326 4327 XhtmlNode tbl = root.table( "grid"); 4328 XhtmlNode tr = tbl.tr(); 4329 XhtmlNode tdl = tr.td(); 4330 XhtmlNode tdr = tr.td(); 4331 populateSubjectSummary(tdl, getProperty(dr, "subject").value()); 4332 tdr.b().tx("Report Details"); 4333 tdr.br(); 4334 pw = getProperty(dr, "perfomer"); 4335 if (valued(pw)) { 4336 tdr.addText(pluralise("Performer", pw.getValues().size())+":"); 4337 for (BaseWrapper v : pw.getValues()) { 4338 tdr.tx(" "); 4339 displayReference(tdr, v); 4340 } 4341 tdr.br(); 4342 } 4343 pw = getProperty(dr, "identifier"); 4344 if (valued(pw)) { 4345 tdr.addText(pluralise("Identifier", pw.getValues().size())+":"); 4346 for (BaseWrapper v : pw.getValues()) { 4347 tdr.tx(" "); 4348 displayIdentifier(tdr, v); 4349 } 4350 tdr.br(); 4351 } 4352 pw = getProperty(dr, "request"); 4353 if (valued(pw)) { 4354 tdr.addText(pluralise("Request", pw.getValues().size())+":"); 4355 for (BaseWrapper v : pw.getValues()) { 4356 tdr.tx(" "); 4357 displayReferenceId(tdr, v); 4358 } 4359 tdr.br(); 4360 } 4361 4362 pw = getProperty(dr, "result"); 4363 if (valued(pw)) { 4364 List<ObservationNode> observations = fetchObservations(pw.getValues()); 4365 buildObservationsTable(root, observations); 4366 } 4367 4368 pw = getProperty(dr, "conclusion"); 4369 if (valued(pw)) 4370 displayText(root.para(), pw.value()); 4371 4372 pw = getProperty(dr, "result"); 4373 if (valued(pw)) { 4374 XhtmlNode p = root.para(); 4375 p.b().tx("Coded Diagnoses :"); 4376 for (BaseWrapper v : pw.getValues()) { 4377 tdr.tx(" "); 4378 displayCodeableConcept(tdr, v); 4379 } 4380 } 4381 return root; 4382 } 4383 4384 private void buildObservationsTable(XhtmlNode root, List<ObservationNode> observations) { 4385 XhtmlNode tbl = root.table( "none"); 4386 for (ObservationNode o : observations) { 4387 addObservationToTable(tbl, o, 0); 4388 } 4389 } 4390 4391 private void addObservationToTable(XhtmlNode tbl, ObservationNode o, int i) { 4392 XhtmlNode tr = tbl.tr(); 4393 if (o.obs == null) { 4394 XhtmlNode td = tr.td().colspan("6"); 4395 td.i().tx("This Observation could not be resolved"); 4396 } else { 4397 addObservationToTable(tr, o.obs, i); 4398 // todo: contained observations 4399 } 4400 for (ObservationNode c : o.contained) { 4401 addObservationToTable(tbl, c, i+1); 4402 } 4403 } 4404 4405 private void addObservationToTable(XhtmlNode tr, ResourceWrapper obs, int i) { 4406 // TODO Auto-generated method stub 4407 4408 // code (+bodysite) 4409 XhtmlNode td = tr.td(); 4410 PropertyWrapper pw = getProperty(obs, "result"); 4411 if (valued(pw)) { 4412 displayCodeableConcept(td, pw.value()); 4413 } 4414 pw = getProperty(obs, "bodySite"); 4415 if (valued(pw)) { 4416 td.tx(" ("); 4417 displayCodeableConcept(td, pw.value()); 4418 td.tx(")"); 4419 } 4420 4421 // value / dataAbsentReason (in red) 4422 td = tr.td(); 4423 pw = getProperty(obs, "value[x]"); 4424 if (valued(pw)) { 4425 if (pw.getTypeCode().equals("CodeableConcept")) 4426 displayCodeableConcept(td, pw.value()); 4427 else if (pw.getTypeCode().equals("string")) 4428 displayText(td, pw.value()); 4429 else 4430 td.addText(pw.getTypeCode()+" not rendered yet"); 4431 } 4432 4433 // units 4434 td = tr.td(); 4435 td.tx("to do"); 4436 4437 // reference range 4438 td = tr.td(); 4439 td.tx("to do"); 4440 4441 // flags (status other than F, interpretation, ) 4442 td = tr.td(); 4443 td.tx("to do"); 4444 4445 // issued if different to DR 4446 td = tr.td(); 4447 td.tx("to do"); 4448 } 4449 4450 private boolean valued(PropertyWrapper pw) { 4451 return pw != null && pw.hasValues(); 4452 } 4453 4454 private void displayText(XhtmlNode c, BaseWrapper v) { 4455 c.addText(v.toString()); 4456 } 4457 4458 private String pluralise(String name, int size) { 4459 return size == 1 ? name : name+"s"; 4460 } 4461 4462 private void displayIdentifier(XhtmlNode c, BaseWrapper v) { 4463 String hint = ""; 4464 PropertyWrapper pw = v.getChildByName("type"); 4465 if (valued(pw)) { 4466 hint = genCC(pw.value()); 4467 } else { 4468 pw = v.getChildByName("system"); 4469 if (valued(pw)) { 4470 hint = pw.value().toString(); 4471 } 4472 } 4473 displayText(c.span(null, hint), v.getChildByName("value").value()); 4474 } 4475 4476 private String genCoding(BaseWrapper value) { 4477 PropertyWrapper pw = value.getChildByName("display"); 4478 if (valued(pw)) 4479 return pw.value().toString(); 4480 pw = value.getChildByName("code"); 4481 if (valued(pw)) 4482 return pw.value().toString(); 4483 return ""; 4484 } 4485 4486 private String genCC(BaseWrapper value) { 4487 PropertyWrapper pw = value.getChildByName("text"); 4488 if (valued(pw)) 4489 return pw.value().toString(); 4490 pw = value.getChildByName("coding"); 4491 if (valued(pw)) 4492 return genCoding(pw.getValues().get(0)); 4493 return ""; 4494 } 4495 4496 private void displayReference(XhtmlNode c, BaseWrapper v) { 4497 c.tx("to do"); 4498 } 4499 4500 4501 private void displayDate(XhtmlNode c, BaseWrapper baseWrapper) { 4502 c.tx("to do"); 4503 } 4504 4505 private void displayCodeableConcept(XhtmlNode c, BaseWrapper property) { 4506 c.tx("to do"); 4507 } 4508 4509 private void displayReferenceId(XhtmlNode c, BaseWrapper v) { 4510 c.tx("to do"); 4511 } 4512 4513 private PropertyWrapper getProperty(ResourceWrapper res, String name) { 4514 for (PropertyWrapper t : res.children()) { 4515 if (t.getName().equals(name)) 4516 return t; 4517 } 4518 return null; 4519 } 4520 4521 private void populateSubjectSummary(XhtmlNode container, BaseWrapper subject) { 4522 ResourceWrapper r = fetchResource(subject); 4523 if (r == null) 4524 container.tx("Unable to get Patient Details"); 4525 else if (r.getName().equals("Patient")) 4526 generatePatientSummary(container, r); 4527 else 4528 container.tx("Not done yet"); 4529 } 4530 4531 private void generatePatientSummary(XhtmlNode c, ResourceWrapper r) { 4532 c.tx("to do"); 4533 } 4534 4535 private ResourceWrapper fetchResource(BaseWrapper subject) { 4536 if (resolver == null) 4537 return null; 4538 String url = subject.getChildByName("reference").value().toString(); 4539 ResourceWithReference rr = resolver.resolve(url); 4540 return rr == null ? null : rr.resource; 4541 } 4542 4543 private List<ObservationNode> fetchObservations(List<BaseWrapper> list) { 4544 return new ArrayList<NarrativeGenerator.ObservationNode>(); 4545 } 4546 4547 public XhtmlNode renderBundle(Bundle b) throws FHIRException { 4548 if (b.getType() == BundleType.DOCUMENT) { 4549 if (!b.hasEntry() || !(b.getEntryFirstRep().hasResource() && b.getEntryFirstRep().getResource() instanceof Composition)) 4550 throw new FHIRException("Invalid document - first entry is not a Composition"); 4551 Composition dr = (Composition) b.getEntryFirstRep().getResource(); 4552 return dr.getText().getDiv(); 4553 } else { 4554 XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); 4555 root.para().addText("Bundle "+b.getId()+" of type "+b.getType().toCode()); 4556 int i = 0; 4557 for (BundleEntryComponent be : b.getEntry()) { 4558 i++; 4559 if (be.hasResource() && be.getResource().hasId()) 4560 root.an(be.getResource().getResourceType().name().toLowerCase() + "_" + be.getResource().getId()); 4561 root.hr(); 4562 root.para().addText("Entry "+Integer.toString(i)+(be.hasFullUrl() ? " - Full URL = " + be.getFullUrl() : "")); 4563 if (be.hasRequest()) 4564 renderRequest(root, be.getRequest()); 4565 if (be.hasSearch()) 4566 renderSearch(root, be.getSearch()); 4567 if (be.hasResponse()) 4568 renderResponse(root, be.getResponse()); 4569 if (be.hasResource()) { 4570 root.para().addText("Resource "+be.getResource().fhirType()+":"); 4571 if (be.hasResource() && be.getResource() instanceof DomainResource) { 4572 DomainResource dr = (DomainResource) be.getResource(); 4573 if ( dr.getText().hasDiv()) 4574 root.blockquote().getChildNodes().addAll(dr.getText().getDiv().getChildNodes()); 4575 } 4576 } 4577 } 4578 return root; 4579 } 4580 } 4581 4582 private void renderSearch(XhtmlNode root, BundleEntrySearchComponent search) { 4583 StringBuilder b = new StringBuilder(); 4584 b.append("Search: "); 4585 if (search.hasMode()) 4586 b.append("mode = "+search.getMode().toCode()); 4587 if (search.hasScore()) { 4588 if (search.hasMode()) 4589 b.append(","); 4590 b.append("score = "+search.getScore()); 4591 } 4592 root.para().addText(b.toString()); 4593 } 4594 4595 private void renderResponse(XhtmlNode root, BundleEntryResponseComponent response) { 4596 root.para().addText("Request:"); 4597 StringBuilder b = new StringBuilder(); 4598 b.append(response.getStatus()+"\r\n"); 4599 if (response.hasLocation()) 4600 b.append("Location: "+response.getLocation()+"\r\n"); 4601 if (response.hasEtag()) 4602 b.append("E-Tag: "+response.getEtag()+"\r\n"); 4603 if (response.hasLastModified()) 4604 b.append("LastModified: "+response.getEtag()+"\r\n"); 4605 root.pre().addText(b.toString()); 4606 } 4607 4608 private void renderRequest(XhtmlNode root, BundleEntryRequestComponent request) { 4609 root.para().addText("Response:"); 4610 StringBuilder b = new StringBuilder(); 4611 b.append(request.getMethod()+" "+request.getUrl()+"\r\n"); 4612 if (request.hasIfNoneMatch()) 4613 b.append("If-None-Match: "+request.getIfNoneMatch()+"\r\n"); 4614 if (request.hasIfModifiedSince()) 4615 b.append("If-Modified-Since: "+request.getIfModifiedSince()+"\r\n"); 4616 if (request.hasIfMatch()) 4617 b.append("If-Match: "+request.getIfMatch()+"\r\n"); 4618 if (request.hasIfNoneExist()) 4619 b.append("If-None-Exist: "+request.getIfNoneExist()+"\r\n"); 4620 root.pre().addText(b.toString()); 4621 } 4622 4623 public XhtmlNode renderBundle(org.hl7.fhir.r4.elementmodel.Element element) throws FHIRException { 4624 XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); 4625 for (Base b : element.listChildrenByName("entry")) { 4626 org.hl7.fhir.r4.elementmodel.Element r = ((org.hl7.fhir.r4.elementmodel.Element) b).getNamedChild("resource"); 4627 if (r!=null) { 4628 XhtmlNode c = getHtmlForResource(r); 4629 if (c != null) 4630 root.getChildNodes().addAll(c.getChildNodes()); 4631 root.hr(); 4632 } 4633 } 4634 return root; 4635 } 4636 4637 private XhtmlNode getHtmlForResource(org.hl7.fhir.r4.elementmodel.Element element) { 4638 org.hl7.fhir.r4.elementmodel.Element text = element.getNamedChild("text"); 4639 if (text == null) 4640 return null; 4641 org.hl7.fhir.r4.elementmodel.Element div = text.getNamedChild("div"); 4642 if (div == null) 4643 return null; 4644 else 4645 return div.getXhtml(); 4646 } 4647 4648 public String getDefinitionsTarget() { 4649 return definitionsTarget; 4650 } 4651 4652 public void setDefinitionsTarget(String definitionsTarget) { 4653 this.definitionsTarget = definitionsTarget; 4654 } 4655 4656 public String getCorePath() { 4657 return corePath; 4658 } 4659 4660 public void setCorePath(String corePath) { 4661 this.corePath = corePath; 4662 } 4663 4664 public String getDestDir() { 4665 return destDir; 4666 } 4667 4668 public void setDestDir(String destDir) { 4669 this.destDir = destDir; 4670 } 4671 4672 public ProfileKnowledgeProvider getPkp() { 4673 return pkp; 4674 } 4675 4676 public NarrativeGenerator setPkp(ProfileKnowledgeProvider pkp) { 4677 this.pkp = pkp; 4678 return this; 4679 } 4680 4681 public boolean isPretty() { 4682 return pretty; 4683 } 4684 4685 public NarrativeGenerator setPretty(boolean pretty) { 4686 this.pretty = pretty; 4687 return this; 4688 } 4689 4690 public boolean isCanonicalUrlsAsLinks() { 4691 return canonicalUrlsAsLinks; 4692 } 4693 4694 @Override 4695 public void setCanonicalUrlsAsLinks(boolean canonicalUrlsAsLinks) { 4696 this.canonicalUrlsAsLinks = canonicalUrlsAsLinks; 4697 } 4698 4699 public String getSnomedEdition() { 4700 return snomedEdition; 4701 } 4702 4703 public NarrativeGenerator setSnomedEdition(String snomedEdition) { 4704 this.snomedEdition = snomedEdition; 4705 return this; 4706 } 4707 4708 public TerminologyServiceOptions getTerminologyServiceOptions() { 4709 return terminologyServiceOptions; 4710 } 4711 4712 public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) { 4713 this.terminologyServiceOptions = terminologyServiceOptions; 4714 } 4715 4716 4717}