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