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