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