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