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