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