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