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