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