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