001package org.hl7.fhir.dstu2016may.utils; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.io.IOException; 035import java.io.UnsupportedEncodingException; 036 037/* 038Copyright (c) 2011+, HL7, Inc 039 All rights reserved. 040 041 Redistribution and use in source and binary forms, with or without modification, 042 are permitted provided that the following conditions are met: 043 044 * Redistributions of source code must retain the above copyright notice, this 045 list of conditions and the following disclaimer. 046 * Redistributions in binary form must reproduce the above copyright notice, 047 this list of conditions and the following disclaimer in the documentation 048 and/or other materials provided with the distribution. 049 * Neither the name of HL7 nor the names of its contributors may be used to 050 endorse or promote products derived from this software without specific 051 prior written permission. 052 053 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 054 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 055 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 056 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 057 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 058 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 059 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 060 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 061 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 062 POSSIBILITY OF SUCH DAMAGE. 063 064*/ 065 066import java.util.ArrayList; 067import java.util.Collections; 068import java.util.HashMap; 069import java.util.HashSet; 070import java.util.List; 071import java.util.Map; 072 073import org.apache.commons.codec.binary.Base64; 074import org.apache.commons.lang3.NotImplementedException; 075import org.hl7.fhir.dstu2016may.formats.FormatUtilities; 076import org.hl7.fhir.dstu2016may.formats.IParser.OutputStyle; 077import org.hl7.fhir.dstu2016may.model.Address; 078import org.hl7.fhir.dstu2016may.model.Annotation; 079import org.hl7.fhir.dstu2016may.model.Attachment; 080import org.hl7.fhir.dstu2016may.model.Base; 081import org.hl7.fhir.dstu2016may.model.Base64BinaryType; 082import org.hl7.fhir.dstu2016may.model.BooleanType; 083import org.hl7.fhir.dstu2016may.model.Bundle; 084import org.hl7.fhir.dstu2016may.model.CodeSystem; 085import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionComponent; 086import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionDesignationComponent; 087import org.hl7.fhir.dstu2016may.model.CodeType; 088import org.hl7.fhir.dstu2016may.model.CodeableConcept; 089import org.hl7.fhir.dstu2016may.model.Coding; 090import org.hl7.fhir.dstu2016may.model.CompartmentDefinition; 091import org.hl7.fhir.dstu2016may.model.CompartmentDefinition.CompartmentDefinitionResourceComponent; 092import org.hl7.fhir.dstu2016may.model.Composition; 093import org.hl7.fhir.dstu2016may.model.Composition.SectionComponent; 094import org.hl7.fhir.dstu2016may.model.ConceptMap; 095import org.hl7.fhir.dstu2016may.model.ConceptMap.ConceptMapContactComponent; 096import org.hl7.fhir.dstu2016may.model.ConceptMap.OtherElementComponent; 097import org.hl7.fhir.dstu2016may.model.ConceptMap.SourceElementComponent; 098import org.hl7.fhir.dstu2016may.model.ConceptMap.TargetElementComponent; 099import org.hl7.fhir.dstu2016may.model.Conformance; 100import org.hl7.fhir.dstu2016may.model.Conformance.ConformanceRestComponent; 101import org.hl7.fhir.dstu2016may.model.Conformance.ConformanceRestResourceComponent; 102import org.hl7.fhir.dstu2016may.model.Conformance.ResourceInteractionComponent; 103import org.hl7.fhir.dstu2016may.model.Conformance.SystemInteractionComponent; 104import org.hl7.fhir.dstu2016may.model.Conformance.SystemRestfulInteraction; 105import org.hl7.fhir.dstu2016may.model.Conformance.TypeRestfulInteraction; 106import org.hl7.fhir.dstu2016may.model.ContactPoint; 107import org.hl7.fhir.dstu2016may.model.ContactPoint.ContactPointSystem; 108import org.hl7.fhir.dstu2016may.model.DateTimeType; 109import org.hl7.fhir.dstu2016may.model.DomainResource; 110import org.hl7.fhir.dstu2016may.model.ElementDefinition; 111import org.hl7.fhir.dstu2016may.model.ElementDefinition.TypeRefComponent; 112import org.hl7.fhir.dstu2016may.model.Enumeration; 113import org.hl7.fhir.dstu2016may.model.Extension; 114import org.hl7.fhir.dstu2016may.model.ExtensionHelper; 115import org.hl7.fhir.dstu2016may.model.HumanName; 116import org.hl7.fhir.dstu2016may.model.HumanName.NameUse; 117import org.hl7.fhir.dstu2016may.model.IdType; 118import org.hl7.fhir.dstu2016may.model.Identifier; 119import org.hl7.fhir.dstu2016may.model.InstantType; 120import org.hl7.fhir.dstu2016may.model.Meta; 121import org.hl7.fhir.dstu2016may.model.Narrative; 122import org.hl7.fhir.dstu2016may.model.Narrative.NarrativeStatus; 123import org.hl7.fhir.dstu2016may.model.OperationDefinition; 124import org.hl7.fhir.dstu2016may.model.OperationDefinition.OperationDefinitionParameterComponent; 125import org.hl7.fhir.dstu2016may.model.OperationOutcome; 126import org.hl7.fhir.dstu2016may.model.OperationOutcome.IssueSeverity; 127import org.hl7.fhir.dstu2016may.model.OperationOutcome.OperationOutcomeIssueComponent; 128import org.hl7.fhir.dstu2016may.model.Period; 129import org.hl7.fhir.dstu2016may.model.PrimitiveType; 130import org.hl7.fhir.dstu2016may.model.Property; 131import org.hl7.fhir.dstu2016may.model.Quantity; 132import org.hl7.fhir.dstu2016may.model.Range; 133import org.hl7.fhir.dstu2016may.model.Ratio; 134import org.hl7.fhir.dstu2016may.model.Reference; 135import org.hl7.fhir.dstu2016may.model.Resource; 136import org.hl7.fhir.dstu2016may.model.SampledData; 137import org.hl7.fhir.dstu2016may.model.StringType; 138import org.hl7.fhir.dstu2016may.model.StructureDefinition; 139import org.hl7.fhir.dstu2016may.model.Timing; 140import org.hl7.fhir.dstu2016may.model.Timing.EventTiming; 141import org.hl7.fhir.dstu2016may.model.Timing.TimingRepeatComponent; 142import org.hl7.fhir.dstu2016may.model.Timing.UnitsOfTime; 143import org.hl7.fhir.dstu2016may.model.UriType; 144import org.hl7.fhir.dstu2016may.model.ValueSet; 145import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptReferenceComponent; 146import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent; 147import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetFilterComponent; 148import org.hl7.fhir.dstu2016may.model.ValueSet.FilterOperator; 149import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionContainsComponent; 150import org.hl7.fhir.dstu2016may.terminologies.CodeSystemUtilities; 151import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 152import org.hl7.fhir.dstu2016may.utils.IWorkerContext.ValidationResult; 153import org.hl7.fhir.exceptions.DefinitionException; 154import org.hl7.fhir.exceptions.FHIRException; 155import org.hl7.fhir.exceptions.FHIRFormatError; 156import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 157import org.hl7.fhir.utilities.MarkDownProcessor; 158import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; 159import org.hl7.fhir.utilities.Utilities; 160import org.hl7.fhir.utilities.xhtml.NodeType; 161import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 162import org.hl7.fhir.utilities.xhtml.XhtmlNode; 163import org.hl7.fhir.utilities.xhtml.XhtmlParser; 164import org.hl7.fhir.utilities.xml.XMLUtil; 165import org.hl7.fhir.utilities.xml.XmlGenerator; 166import org.w3c.dom.Element; 167 168public class NarrativeGenerator implements INarrativeGenerator { 169 170 private interface PropertyWrapper { 171 public String getName(); 172 public boolean hasValues(); 173 public List<BaseWrapper> getValues(); 174 public String getTypeCode(); 175 public String getDefinition(); 176 public int getMinCardinality(); 177 public int getMaxCardinality(); 178 public StructureDefinition getStructure(); 179 } 180 181 private interface ResourceWrapper { 182 public List<ResourceWrapper> getContained(); 183 public String getId(); 184 public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException; 185 public String getName(); 186 public List<PropertyWrapper> children(); 187 } 188 189 private interface BaseWrapper { 190 public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException; 191 public List<PropertyWrapper> children(); 192 public PropertyWrapper getChildByName(String tail); 193 } 194 195 private class BaseWrapperElement implements BaseWrapper { 196 private Element element; 197 private String type; 198 private StructureDefinition structure; 199 private ElementDefinition definition; 200 private List<ElementDefinition> children; 201 private List<PropertyWrapper> list; 202 203 public BaseWrapperElement(Element element, String type, StructureDefinition structure, ElementDefinition definition) { 204 this.element = element; 205 this.type = type; 206 this.structure = structure; 207 this.definition = definition; 208 } 209 210 @Override 211 public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException { 212 if (type == null || type.equals("Resource") || type.equals("BackboneElement") || type.equals("Element")) 213 return null; 214 215 String xml; 216 try { 217 xml = new XmlGenerator().generate(element); 218 } catch (org.hl7.fhir.exceptions.FHIRException e) { 219 throw new FHIRException(e.getMessage(), e); 220 } 221 return context.newXmlParser().setOutputStyle(OutputStyle.PRETTY).parseType(xml, type); 222 } 223 224 @Override 225 public List<PropertyWrapper> children() { 226 if (list == null) { 227 children = ProfileUtilities.getChildList(structure, definition); 228 list = new ArrayList<NarrativeGenerator.PropertyWrapper>(); 229 for (ElementDefinition child : children) { 230 List<Element> elements = new ArrayList<Element>(); 231 XMLUtil.getNamedChildrenWithWildcard(element, tail(child.getPath()), elements); 232 list.add(new PropertyWrapperElement(structure, child, elements)); 233 } 234 } 235 return list; 236 } 237 238 @Override 239 public PropertyWrapper getChildByName(String name) { 240 for (PropertyWrapper p : children()) 241 if (p.getName().equals(name)) 242 return p; 243 return null; 244 } 245 246 } 247 248 private class PropertyWrapperElement implements PropertyWrapper { 249 250 private StructureDefinition structure; 251 private ElementDefinition definition; 252 private List<Element> values; 253 private List<BaseWrapper> list; 254 255 public PropertyWrapperElement(StructureDefinition structure, ElementDefinition definition, List<Element> values) { 256 this.structure = structure; 257 this.definition = definition; 258 this.values = values; 259 } 260 261 @Override 262 public String getName() { 263 return tail(definition.getPath()); 264 } 265 266 @Override 267 public boolean hasValues() { 268 return values.size() > 0; 269 } 270 271 @Override 272 public List<BaseWrapper> getValues() { 273 if (list == null) { 274 list = new ArrayList<NarrativeGenerator.BaseWrapper>(); 275 for (Element e : values) 276 list.add(new BaseWrapperElement(e, determineType(e), structure, definition)); 277 } 278 return list; 279 } 280 private String determineType(Element e) { 281 if (definition.getType().isEmpty()) 282 return null; 283 if (definition.getType().size() == 1) { 284 if (definition.getType().get(0).getCode().equals("Element") || definition.getType().get(0).getCode().equals("BackboneElement")) 285 return null; 286 return definition.getType().get(0).getCode(); 287 } 288 String t = e.getNodeName().substring(tail(definition.getPath()).length()-3); 289 boolean allReference = true; 290 for (TypeRefComponent tr : definition.getType()) { 291 if (!tr.getCode().equals("Reference")) 292 allReference = false; 293 } 294 if (allReference) 295 return "Reference"; 296 297 if (ProfileUtilities.isPrimitive(t)) 298 return Utilities.uncapitalize(t); 299 else 300 return t; 301 } 302 303 @Override 304 public String getTypeCode() { 305 throw new Error("todo"); 306 } 307 308 @Override 309 public String getDefinition() { 310 throw new Error("todo"); 311 } 312 313 @Override 314 public int getMinCardinality() { 315 throw new Error("todo"); 316// return definition.getMin(); 317 } 318 319 @Override 320 public int getMaxCardinality() { 321 throw new Error("todo"); 322 } 323 324 @Override 325 public StructureDefinition getStructure() { 326 return structure; 327 } 328 329 } 330 331 private class ResurceWrapperElement implements ResourceWrapper { 332 333 private Element wrapped; 334 private StructureDefinition definition; 335 private List<ResourceWrapper> list; 336 private List<PropertyWrapper> list2; 337 338 public ResurceWrapperElement(Element wrapped, StructureDefinition definition) { 339 this.wrapped = wrapped; 340 this.definition = definition; 341 } 342 343 @Override 344 public List<ResourceWrapper> getContained() { 345 if (list == null) { 346 List<Element> children = new ArrayList<Element>(); 347 XMLUtil.getNamedChildren(wrapped, "contained", children); 348 list = new ArrayList<NarrativeGenerator.ResourceWrapper>(); 349 for (Element e : children) { 350 Element c = XMLUtil.getFirstChild(e); 351 list.add(new ResurceWrapperElement(c, context.fetchTypeDefinition(c.getNodeName()))); 352 } 353 } 354 return list; 355 } 356 357 @Override 358 public String getId() { 359 return XMLUtil.getNamedChildValue(wrapped, "id"); 360 } 361 362 @Override 363 public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException { 364 Element txt = XMLUtil.getNamedChild(wrapped, "text"); 365 if (txt == null) 366 return null; 367 Element div = XMLUtil.getNamedChild(txt, "div"); 368 if (div == null) 369 return null; 370 try { 371 return new XhtmlParser().parse(new XmlGenerator().generate(div), "div"); 372 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 373 throw new FHIRFormatError(e.getMessage(), e); 374 } catch (org.hl7.fhir.exceptions.FHIRException e) { 375 throw new FHIRException(e.getMessage(), e); 376 } 377 } 378 379 @Override 380 public String getName() { 381 return wrapped.getNodeName(); 382 } 383 384 @Override 385 public List<PropertyWrapper> children() { 386 if (list2 == null) { 387 List<ElementDefinition> children = ProfileUtilities.getChildList(definition, definition.getSnapshot().getElement().get(0)); 388 list2 = new ArrayList<NarrativeGenerator.PropertyWrapper>(); 389 for (ElementDefinition child : children) { 390 List<Element> elements = new ArrayList<Element>(); 391 XMLUtil.getNamedChildrenWithWildcard(wrapped, tail(child.getPath()), elements); 392 list2.add(new PropertyWrapperElement(definition, child, elements)); 393 } 394 } 395 return list2; 396 } 397 } 398 399 private class PropertyWrapperDirect implements PropertyWrapper { 400 private Property wrapped; 401 private List<BaseWrapper> list; 402 403 private PropertyWrapperDirect(Property wrapped) { 404 super(); 405 if (wrapped == null) 406 throw new Error("wrapped == null"); 407 this.wrapped = wrapped; 408 } 409 410 @Override 411 public String getName() { 412 return wrapped.getName(); 413 } 414 415 @Override 416 public boolean hasValues() { 417 return wrapped.hasValues(); 418 } 419 420 @Override 421 public List<BaseWrapper> getValues() { 422 if (list == null) { 423 list = new ArrayList<NarrativeGenerator.BaseWrapper>(); 424 for (Base b : wrapped.getValues()) 425 list.add(b == null ? null : new BaseWrapperDirect(b)); 426 } 427 return list; 428 } 429 430 @Override 431 public String getTypeCode() { 432 return wrapped.getTypeCode(); 433 } 434 435 @Override 436 public String getDefinition() { 437 return wrapped.getDefinition(); 438 } 439 440 @Override 441 public int getMinCardinality() { 442 return wrapped.getMinCardinality(); 443 } 444 445 @Override 446 public int getMaxCardinality() { 447 return wrapped.getMinCardinality(); 448 } 449 450 @Override 451 public StructureDefinition getStructure() { 452 return wrapped.getStructure(); 453 } 454 } 455 456 private class BaseWrapperDirect implements BaseWrapper { 457 private Base wrapped; 458 private List<PropertyWrapper> list; 459 460 private BaseWrapperDirect(Base wrapped) { 461 super(); 462 if (wrapped == null) 463 throw new Error("wrapped == null"); 464 this.wrapped = wrapped; 465 } 466 467 @Override 468 public Base getBase() { 469 return wrapped; 470 } 471 472 @Override 473 public List<PropertyWrapper> children() { 474 if (list == null) { 475 list = new ArrayList<NarrativeGenerator.PropertyWrapper>(); 476 for (Property p : wrapped.children()) 477 list.add(new PropertyWrapperDirect(p)); 478 } 479 return list; 480 481 } 482 483 @Override 484 public PropertyWrapper getChildByName(String name) { 485 Property p = wrapped.getChildByName(name); 486 if (p == null) 487 return null; 488 else 489 return new PropertyWrapperDirect(p); 490 } 491 492 } 493 494 private class ResourceWrapperDirect implements ResourceWrapper { 495 private Resource wrapped; 496 497 private ResourceWrapperDirect(Resource wrapped) { 498 super(); 499 if (wrapped == null) 500 throw new Error("wrapped == null"); 501 this.wrapped = wrapped; 502 } 503 504 @Override 505 public List<ResourceWrapper> getContained() { 506 List<ResourceWrapper> list = new ArrayList<NarrativeGenerator.ResourceWrapper>(); 507 if (wrapped instanceof DomainResource) { 508 DomainResource dr = (DomainResource) wrapped; 509 for (Resource c : dr.getContained()) { 510 list.add(new ResourceWrapperDirect(c)); 511 } 512 } 513 return list; 514 } 515 516 @Override 517 public String getId() { 518 return wrapped.getId(); 519 } 520 521 @Override 522 public XhtmlNode getNarrative() { 523 if (wrapped instanceof DomainResource) { 524 DomainResource dr = (DomainResource) wrapped; 525 if (dr.hasText() && dr.getText().hasDiv()) 526 return dr.getText().getDiv(); 527 } 528 return null; 529 } 530 531 @Override 532 public String getName() { 533 return wrapped.getResourceType().toString(); 534 } 535 536 @Override 537 public List<PropertyWrapper> children() { 538 List<PropertyWrapper> list = new ArrayList<PropertyWrapper>(); 539 for (Property c : wrapped.children()) 540 list.add(new PropertyWrapperDirect(c)); 541 return list; 542 } 543 } 544 545 public class ResourceWithReference { 546 547 private String reference; 548 private ResourceWrapper resource; 549 550 public ResourceWithReference(String reference, ResourceWrapper resource) { 551 this.reference = reference; 552 this.resource = resource; 553 } 554 555 public String getReference() { 556 return reference; 557 } 558 559 public ResourceWrapper getResource() { 560 return resource; 561 } 562 } 563 564 private String prefix; 565 private IWorkerContext context; 566 private String basePath; 567 private String tooCostlyNote; 568 569 570 public NarrativeGenerator(String prefix, String basePath, IWorkerContext context) { 571 super(); 572 this.prefix = prefix; 573 this.context = context; 574 this.basePath = basePath; 575 } 576 577 578 public String getTooCostlyNote() { 579 return tooCostlyNote; 580 } 581 582 583 public NarrativeGenerator setTooCostlyNote(String tooCostlyNote) { 584 this.tooCostlyNote = tooCostlyNote; 585 return this; 586 } 587 588 589 public void generate(DomainResource r) throws EOperationOutcome, FHIRException, IOException { 590 if (r instanceof ConceptMap) { 591 generate((ConceptMap) r); // Maintainer = Grahame 592 } else if (r instanceof ValueSet) { 593 generate((ValueSet) r, true); // Maintainer = Grahame 594 } else if (r instanceof CodeSystem) { 595 generate((CodeSystem) r, true); // Maintainer = Grahame 596 } else if (r instanceof OperationOutcome) { 597 generate((OperationOutcome) r); // Maintainer = Grahame 598 } else if (r instanceof Conformance) { 599 generate((Conformance) r); // Maintainer = Grahame 600 } else if (r instanceof CompartmentDefinition) { 601 generate((CompartmentDefinition) r); // Maintainer = Grahame 602 } else if (r instanceof OperationDefinition) { 603 generate((OperationDefinition) r); // Maintainer = Grahame 604 } else { 605 StructureDefinition p = null; 606 if (r.hasMeta()) 607 for (UriType pu : r.getMeta().getProfile()) 608 if (p == null) 609 p = context.fetchResource(StructureDefinition.class, pu.getValue()); 610 if (p == null) 611 p = context.fetchResource(StructureDefinition.class, r.getResourceType().toString()); 612 if (p == null) 613 p = context.fetchTypeDefinition(r.getResourceType().toString().toLowerCase()); 614 if (p != null) 615 generateByProfile(r, p, true); 616 } 617 } 618 619 // dom based version, for build program 620 public String generate(Element doc) throws IOException { 621 String rt = "http://hl7.org/fhir/StructureDefinition/"+doc.getNodeName(); 622 StructureDefinition p = context.fetchResource(StructureDefinition.class, rt); 623 return generateByProfile(doc, p, true); 624 } 625 626 private void generateByProfile(DomainResource r, StructureDefinition profile, boolean showCodeDetails) { 627 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 628 x.addTag("p").addTag("b").addText("Generated Narrative"+(showCodeDetails ? " with Details" : "")); 629 try { 630 generateByProfile(r, profile, r, profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile.getSnapshot().getElement(), r.getResourceType().toString()), x, r.getResourceType().toString(), showCodeDetails); 631 } catch (Exception e) { 632 e.printStackTrace(); 633 x.addTag("p").addTag("b").setAttribute("style", "color: maroon").addText("Exception generating Narrative: "+e.getMessage()); 634 } 635 inject(r, x, NarrativeStatus.GENERATED); 636 } 637 638 private String generateByProfile(Element er, StructureDefinition profile, boolean showCodeDetails) throws IOException { 639 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 640 x.addTag("p").addTag("b").addText("Generated Narrative"+(showCodeDetails ? " with Details" : "")); 641 try { 642 generateByProfile(er, profile, er, profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile.getSnapshot().getElement(), er.getLocalName()), x, er.getLocalName(), showCodeDetails); 643 } catch (Exception e) { 644 e.printStackTrace(); 645 x.addTag("p").addTag("b").setAttribute("style", "color: maroon").addText("Exception generating Narrative: "+e.getMessage()); 646 } 647 inject(er, x, NarrativeStatus.GENERATED); 648 return new XhtmlComposer(true, false).compose(x); 649 } 650 651 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 { 652 653 ResurceWrapperElement resw = new ResurceWrapperElement(eres, profile); 654 BaseWrapperElement base = new BaseWrapperElement(ee, null, profile, profile.getSnapshot().getElement().get(0)); 655 generateByProfile(resw, profile, base, allElements, defn, children, x, path, showCodeDetails); 656 } 657 658 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 { 659 generateByProfile(new ResourceWrapperDirect(res), profile, new BaseWrapperDirect(e), allElements, defn, children, x, path, showCodeDetails); 660 } 661 662 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 { 663 if (children.isEmpty()) { 664 renderLeaf(res, e, defn, x, false, showCodeDetails, readDisplayHints(defn)); 665 } else { 666 for (PropertyWrapper p : splitExtensions(profile, e.children())) { 667 if (p.hasValues()) { 668 ElementDefinition child = getElementDefinition(children, path+"."+p.getName(), p); 669 if (child != null) { 670 Map<String, String> displayHints = readDisplayHints(child); 671 if (!exemptFromRendering(child)) { 672 List<ElementDefinition> grandChildren = getChildrenForPath(allElements, path+"."+p.getName()); 673 filterGrandChildren(grandChildren, path+"."+p.getName(), p); 674 if (p.getValues().size() > 0 && child != null) { 675 if (isPrimitive(child)) { 676 XhtmlNode para = x.addTag("p"); 677 String name = p.getName(); 678 if (name.endsWith("[x]")) 679 name = name.substring(0, name.length() - 3); 680 if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { 681 para.addTag("b").addText(name); 682 para.addText(": "); 683 if (renderAsList(child) && p.getValues().size() > 1) { 684 XhtmlNode list = x.addTag("ul"); 685 for (BaseWrapper v : p.getValues()) 686 renderLeaf(res, v, child, list.addTag("li"), false, showCodeDetails, displayHints); 687 } else { 688 boolean first = true; 689 for (BaseWrapper v : p.getValues()) { 690 if (first) 691 first = false; 692 else 693 para.addText(", "); 694 renderLeaf(res, v, child, para, false, showCodeDetails, displayHints); 695 } 696 } 697 } 698 } else if (canDoTable(path, p, grandChildren)) { 699 x.addTag("h3").addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); 700 XhtmlNode tbl = x.addTag("table").setAttribute("class", "grid"); 701 XhtmlNode tr = tbl.addTag("tr"); 702 tr.addTag("td").addText("-"); // work around problem with empty table rows 703 addColumnHeadings(tr, grandChildren); 704 for (BaseWrapper v : p.getValues()) { 705 if (v != null) { 706 tr = tbl.addTag("tr"); 707 tr.addTag("td").addText("*"); // work around problem with empty table rows 708 addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints); 709 } 710 } 711 } else { 712 for (BaseWrapper v : p.getValues()) { 713 if (v != null) { 714 XhtmlNode bq = x.addTag("blockquote"); 715 bq.addTag("p").addTag("b").addText(p.getName()); 716 generateByProfile(res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails); 717 } 718 } 719 } 720 } 721 } 722 } 723 } 724 } 725 } 726 } 727 728 private void filterGrandChildren(List<ElementDefinition> grandChildren, String string, PropertyWrapper prop) { 729 List<ElementDefinition> toRemove = new ArrayList<ElementDefinition>(); 730 toRemove.addAll(grandChildren); 731 for (BaseWrapper b : prop.getValues()) { 732 List<ElementDefinition> list = new ArrayList<ElementDefinition>(); 733 for (ElementDefinition ed : toRemove) { 734 PropertyWrapper p = b.getChildByName(tail(ed.getPath())); 735 if (p != null && p.hasValues()) 736 list.add(ed); 737 } 738 toRemove.removeAll(list); 739 } 740 grandChildren.removeAll(toRemove); 741 } 742 743 private List<PropertyWrapper> splitExtensions(StructureDefinition profile, List<PropertyWrapper> children) throws UnsupportedEncodingException, IOException, FHIRException { 744 List<PropertyWrapper> results = new ArrayList<PropertyWrapper>(); 745 Map<String, PropertyWrapper> map = new HashMap<String, PropertyWrapper>(); 746 for (PropertyWrapper p : children) 747 if (p.getName().equals("extension") || p.getName().equals("modifierExtension")) { 748 // we're going to split these up, and create a property for each url 749 if (p.hasValues()) { 750 for (BaseWrapper v : p.getValues()) { 751 Extension ex = (Extension) v.getBase(); 752 String url = ex.getUrl(); 753 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 754 if (p.getName().equals("modifierExtension") && ed == null) 755 throw new DefinitionException("Unknown modifier extension "+url); 756 PropertyWrapper pe = map.get(p.getName()+"["+url+"]"); 757 if (pe == null) { 758 if (ed == null) { 759 if (url.startsWith("http://hl7.org/fhir")) 760 throw new DefinitionException("unknown extension "+url); 761 System.out.println("unknown extension "+url); 762 pe = new PropertyWrapperDirect(new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex)); 763 } else { 764 ElementDefinition def = ed.getSnapshot().getElement().get(0); 765 pe = new PropertyWrapperDirect(new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex)); 766 ((PropertyWrapperDirect) pe).wrapped.setStructure(ed); 767 } 768 results.add(pe); 769 } else 770 pe.getValues().add(v); 771 } 772 } 773 } else 774 results.add(p); 775 return results; 776 } 777 778 @SuppressWarnings("rawtypes") 779 private boolean isDefaultValue(Map<String, String> displayHints, List<BaseWrapper> list) throws UnsupportedEncodingException, IOException, FHIRException { 780 if (list.size() != 1) 781 return false; 782 if (list.get(0).getBase() instanceof PrimitiveType) 783 return isDefault(displayHints, (PrimitiveType) list.get(0).getBase()); 784 else 785 return false; 786 } 787 788 private boolean isDefault(Map<String, String> displayHints, PrimitiveType primitiveType) { 789 String v = primitiveType.asStringValue(); 790 if (!Utilities.noString(v) && displayHints.containsKey("default") && v.equals(displayHints.get("default"))) 791 return true; 792 return false; 793 } 794 795 private boolean exemptFromRendering(ElementDefinition child) { 796 if (child == null) 797 return false; 798 if ("Composition.subject".equals(child.getPath())) 799 return true; 800 if ("Composition.section".equals(child.getPath())) 801 return true; 802 return false; 803 } 804 805 private boolean renderAsList(ElementDefinition child) { 806 if (child.getType().size() == 1) { 807 String t = child.getType().get(0).getCode(); 808 if (t.equals("Address") || t.equals("Reference")) 809 return true; 810 } 811 return false; 812 } 813 814 private void addColumnHeadings(XhtmlNode tr, List<ElementDefinition> grandChildren) { 815 for (ElementDefinition e : grandChildren) 816 tr.addTag("td").addTag("b").addText(Utilities.capitalize(tail(e.getPath()))); 817 } 818 819 private void addColumnValues(ResourceWrapper res, XhtmlNode tr, List<ElementDefinition> grandChildren, BaseWrapper v, boolean showCodeDetails, Map<String, String> displayHints) throws FHIRException, UnsupportedEncodingException, IOException { 820 for (ElementDefinition e : grandChildren) { 821 PropertyWrapper p = v.getChildByName(e.getPath().substring(e.getPath().lastIndexOf(".")+1)); 822 if (p == null || p.getValues().size() == 0 || p.getValues().get(0) == null) 823 tr.addTag("td").addText(" "); 824 else 825 renderLeaf(res, p.getValues().get(0), e, tr.addTag("td"), false, showCodeDetails, displayHints); 826 } 827 } 828 829 private String tail(String path) { 830 return path.substring(path.lastIndexOf(".")+1); 831 } 832 833 private boolean canDoTable(String path, PropertyWrapper p, List<ElementDefinition> grandChildren) { 834 for (ElementDefinition e : grandChildren) { 835 List<PropertyWrapper> values = getValues(path, p, e); 836 if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e)) 837 return false; 838 } 839 return true; 840 } 841 842 private List<PropertyWrapper> getValues(String path, PropertyWrapper p, ElementDefinition e) { 843 List<PropertyWrapper> res = new ArrayList<PropertyWrapper>(); 844 for (BaseWrapper v : p.getValues()) { 845 for (PropertyWrapper g : v.children()) { 846 if ((path+"."+p.getName()+"."+g.getName()).equals(e.getPath())) 847 res.add(p); 848 } 849 } 850 return res; 851 } 852 853 private boolean canCollapse(ElementDefinition e) { 854 // we can collapse any data type 855 return !e.getType().isEmpty(); 856 } 857 858 private boolean isPrimitive(ElementDefinition e) { 859 //we can tell if e is a primitive because it has types 860 if (e.getType().isEmpty()) 861 return false; 862 if (e.getType().size() == 1 && isBase(e.getType().get(0).getCode())) 863 return false; 864 return true; 865// return !e.getType().isEmpty() 866 } 867 868 private boolean isBase(String code) { 869 return code.equals("Element") || code.equals("BackboneElement"); 870 } 871 872 private ElementDefinition getElementDefinition(List<ElementDefinition> elements, String path, PropertyWrapper p) { 873 for (ElementDefinition element : elements) 874 if (element.getPath().equals(path)) 875 return element; 876 if (path.endsWith("\"]") && p.getStructure() != null) 877 return p.getStructure().getSnapshot().getElement().get(0); 878 return null; 879 } 880 881 private void renderLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, boolean title, boolean showCodeDetails, Map<String, String> displayHints) throws FHIRException, UnsupportedEncodingException, IOException { 882 if (ew == null) 883 return; 884 885 Base e = ew.getBase(); 886 887 if (e instanceof StringType) 888 x.addText(((StringType) e).getValue()); 889 else if (e instanceof CodeType) 890 x.addText(((CodeType) e).getValue()); 891 else if (e instanceof IdType) 892 x.addText(((IdType) e).getValue()); 893 else if (e instanceof Extension) 894 x.addText("Extensions: Todo"); 895 else if (e instanceof InstantType) 896 x.addText(((InstantType) e).toHumanDisplay()); 897 else if (e instanceof DateTimeType) 898 x.addText(((DateTimeType) e).toHumanDisplay()); 899 else if (e instanceof Base64BinaryType) 900 x.addText(new Base64().encodeAsString(((Base64BinaryType) e).getValue())); 901 else if (e instanceof org.hl7.fhir.dstu2016may.model.DateType) 902 x.addText(((org.hl7.fhir.dstu2016may.model.DateType) e).toHumanDisplay()); 903 else if (e instanceof Enumeration) { 904 Object ev = ((Enumeration<?>) e).getValue(); 905 x.addText(ev == null ? "" : ev.toString()); // todo: look up a display name if there is one 906 } else if (e instanceof BooleanType) 907 x.addText(((BooleanType) e).getValue().toString()); 908 else if (e instanceof CodeableConcept) { 909 renderCodeableConcept((CodeableConcept) e, x, showCodeDetails); 910 } else if (e instanceof Coding) { 911 renderCoding((Coding) e, x, showCodeDetails); 912 } else if (e instanceof Annotation) { 913 renderAnnotation((Annotation) e, x); 914 } else if (e instanceof Identifier) { 915 renderIdentifier((Identifier) e, x); 916 } else if (e instanceof org.hl7.fhir.dstu2016may.model.IntegerType) { 917 x.addText(Integer.toString(((org.hl7.fhir.dstu2016may.model.IntegerType) e).getValue())); 918 } else if (e instanceof org.hl7.fhir.dstu2016may.model.DecimalType) { 919 x.addText(((org.hl7.fhir.dstu2016may.model.DecimalType) e).getValue().toString()); 920 } else if (e instanceof HumanName) { 921 renderHumanName((HumanName) e, x); 922 } else if (e instanceof SampledData) { 923 renderSampledData((SampledData) e, x); 924 } else if (e instanceof Address) { 925 renderAddress((Address) e, x); 926 } else if (e instanceof ContactPoint) { 927 renderContactPoint((ContactPoint) e, x); 928 } else if (e instanceof UriType) { 929 renderUri((UriType) e, x); 930 } else if (e instanceof Timing) { 931 renderTiming((Timing) e, x); 932 } else if (e instanceof Range) { 933 renderRange((Range) e, x); 934 } else if (e instanceof Quantity) { 935 renderQuantity((Quantity) e, x, showCodeDetails); 936 } else if (e instanceof Ratio) { 937 renderQuantity(((Ratio) e).getNumerator(), x, showCodeDetails); 938 x.addText("/"); 939 renderQuantity(((Ratio) e).getDenominator(), x, showCodeDetails); 940 } else if (e instanceof Period) { 941 Period p = (Period) e; 942 x.addText(!p.hasStart() ? "??" : p.getStartElement().toHumanDisplay()); 943 x.addText(" --> "); 944 x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay()); 945 } else if (e instanceof Reference) { 946 Reference r = (Reference) e; 947 XhtmlNode c = x; 948 ResourceWithReference tr = null; 949 if (r.hasReferenceElement()) { 950 tr = resolveReference(res, r.getReference()); 951 if (!r.getReference().startsWith("#")) { 952 if (tr != null && tr.getReference() != null) 953 c = x.addTag("a").attribute("href", tr.getReference()); 954 else 955 c = x.addTag("a").attribute("href", r.getReference()); 956 } 957 } 958 // what to display: if text is provided, then that. if the reference was resolved, then show the generated narrative 959 if (r.hasDisplayElement()) { 960 c.addText(r.getDisplay()); 961 if (tr != null) { 962 c.addText(". Generated Summary: "); 963 generateResourceSummary(c, tr.getResource(), true, r.getReference().startsWith("#")); 964 } 965 } else if (tr != null) { 966 generateResourceSummary(c, tr.getResource(), r.getReference().startsWith("#"), r.getReference().startsWith("#")); 967 } else { 968 c.addText(r.getReference()); 969 } 970 } else if (e instanceof Resource) { 971 return; 972 } else if (e instanceof ElementDefinition) { 973 x.addText("todo-bundle"); 974 } else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) 975 throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet"); 976 } 977 978 private boolean displayLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException { 979 if (ew == null) 980 return false; 981 Base e = ew.getBase(); 982 Map<String, String> displayHints = readDisplayHints(defn); 983 984 if (name.endsWith("[x]")) 985 name = name.substring(0, name.length() - 3); 986 987 if (!showCodeDetails && e instanceof PrimitiveType && isDefault(displayHints, ((PrimitiveType) e))) 988 return false; 989 990 if (e instanceof StringType) { 991 x.addText(name+": "+((StringType) e).getValue()); 992 return true; 993 } else if (e instanceof CodeType) { 994 x.addText(name+": "+((CodeType) e).getValue()); 995 return true; 996 } else if (e instanceof IdType) { 997 x.addText(name+": "+((IdType) e).getValue()); 998 return true; 999 } else if (e instanceof DateTimeType) { 1000 x.addText(name+": "+((DateTimeType) e).toHumanDisplay()); 1001 return true; 1002 } else if (e instanceof InstantType) { 1003 x.addText(name+": "+((InstantType) e).toHumanDisplay()); 1004 return true; 1005 } else if (e instanceof Extension) { 1006 x.addText("Extensions: todo"); 1007 return true; 1008 } else if (e instanceof org.hl7.fhir.dstu2016may.model.DateType) { 1009 x.addText(name+": "+((org.hl7.fhir.dstu2016may.model.DateType) e).toHumanDisplay()); 1010 return true; 1011 } else if (e instanceof Enumeration) { 1012 x.addText(((Enumeration<?>) e).getValue().toString()); // todo: look up a display name if there is one 1013 return true; 1014 } else if (e instanceof BooleanType) { 1015 if (((BooleanType) e).getValue()) { 1016 x.addText(name); 1017 return true; 1018 } 1019 } else if (e instanceof CodeableConcept) { 1020 renderCodeableConcept((CodeableConcept) e, x, showCodeDetails); 1021 return true; 1022 } else if (e instanceof Coding) { 1023 renderCoding((Coding) e, x, showCodeDetails); 1024 return true; 1025 } else if (e instanceof Annotation) { 1026 renderAnnotation((Annotation) e, x, showCodeDetails); 1027 return true; 1028 } else if (e instanceof org.hl7.fhir.dstu2016may.model.IntegerType) { 1029 x.addText(Integer.toString(((org.hl7.fhir.dstu2016may.model.IntegerType) e).getValue())); 1030 return true; 1031 } else if (e instanceof org.hl7.fhir.dstu2016may.model.DecimalType) { 1032 x.addText(((org.hl7.fhir.dstu2016may.model.DecimalType) e).getValue().toString()); 1033 return true; 1034 } else if (e instanceof Identifier) { 1035 renderIdentifier((Identifier) e, x); 1036 return true; 1037 } else if (e instanceof HumanName) { 1038 renderHumanName((HumanName) e, x); 1039 return true; 1040 } else if (e instanceof SampledData) { 1041 renderSampledData((SampledData) e, x); 1042 return true; 1043 } else if (e instanceof Address) { 1044 renderAddress((Address) e, x); 1045 return true; 1046 } else if (e instanceof ContactPoint) { 1047 renderContactPoint((ContactPoint) e, x); 1048 return true; 1049 } else if (e instanceof Timing) { 1050 renderTiming((Timing) e, x); 1051 return true; 1052 } else if (e instanceof Quantity) { 1053 renderQuantity((Quantity) e, x, showCodeDetails); 1054 return true; 1055 } else if (e instanceof Ratio) { 1056 renderQuantity(((Ratio) e).getNumerator(), x, showCodeDetails); 1057 x.addText("/"); 1058 renderQuantity(((Ratio) e).getDenominator(), x, showCodeDetails); 1059 return true; 1060 } else if (e instanceof Period) { 1061 Period p = (Period) e; 1062 x.addText(name+": "); 1063 x.addText(!p.hasStart() ? "??" : p.getStartElement().toHumanDisplay()); 1064 x.addText(" --> "); 1065 x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay()); 1066 return true; 1067 } else if (e instanceof Reference) { 1068 Reference r = (Reference) e; 1069 if (r.hasDisplayElement()) 1070 x.addText(r.getDisplay()); 1071 else if (r.hasReferenceElement()) { 1072 ResourceWithReference tr = resolveReference(res, r.getReference()); 1073 x.addText(tr == null ? r.getReference() : "????"); // getDisplayForReference(tr.getReference())); 1074 } else 1075 x.addText("??"); 1076 return true; 1077 } else if (e instanceof Narrative) { 1078 return false; 1079 } else if (e instanceof Resource) { 1080 return false; 1081 } else if (!(e instanceof Attachment)) 1082 throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet"); 1083 return false; 1084 } 1085 1086 1087 private Map<String, String> readDisplayHints(ElementDefinition defn) throws DefinitionException { 1088 Map<String, String> hints = new HashMap<String, String>(); 1089 if (defn != null) { 1090 String displayHint = ToolingExtensions.getDisplayHint(defn); 1091 if (!Utilities.noString(displayHint)) { 1092 String[] list = displayHint.split(";"); 1093 for (String item : list) { 1094 String[] parts = item.split(":"); 1095 if (parts.length != 2) 1096 throw new DefinitionException("error reading display hint: '"+displayHint+"'"); 1097 hints.put(parts[0].trim(), parts[1].trim()); 1098 } 1099 } 1100 } 1101 return hints; 1102 } 1103 1104 public static String displayPeriod(Period p) { 1105 String s = !p.hasStart() ? "??" : p.getStartElement().toHumanDisplay(); 1106 s = s + " --> "; 1107 return s + (!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay()); 1108 } 1109 1110 private void generateResourceSummary(XhtmlNode x, ResourceWrapper res, boolean textAlready, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException { 1111 if (!textAlready) { 1112 XhtmlNode div = res.getNarrative(); 1113 if (div != null) { 1114 if (div.allChildrenAreText()) 1115 x.getChildNodes().addAll(div.getChildNodes()); 1116 if (div.getChildNodes().size() == 1 && div.getChildNodes().get(0).allChildrenAreText()) 1117 x.getChildNodes().addAll(div.getChildNodes().get(0).getChildNodes()); 1118 } 1119 x.addText("Generated Summary: "); 1120 } 1121 String path = res.getName(); 1122 StructureDefinition profile = context.fetchResource(StructureDefinition.class, path); 1123 if (profile == null) 1124 x.addText("unknown resource " +path); 1125 else { 1126 boolean firstElement = true; 1127 boolean last = false; 1128 for (PropertyWrapper p : res.children()) { 1129 ElementDefinition child = getElementDefinition(profile.getSnapshot().getElement(), path+"."+p.getName(), p); 1130 if (p.getValues().size() > 0 && p.getValues().get(0) != null && child != null && isPrimitive(child) && includeInSummary(child)) { 1131 if (firstElement) 1132 firstElement = false; 1133 else if (last) 1134 x.addText("; "); 1135 boolean first = true; 1136 last = false; 1137 for (BaseWrapper v : p.getValues()) { 1138 if (first) 1139 first = false; 1140 else if (last) 1141 x.addText(", "); 1142 last = displayLeaf(res, v, child, x, p.getName(), showCodeDetails) || last; 1143 } 1144 } 1145 } 1146 } 1147 } 1148 1149 1150 private boolean includeInSummary(ElementDefinition child) { 1151 if (child.getIsModifier()) 1152 return true; 1153 if (child.getMustSupport()) 1154 return true; 1155 if (child.getType().size() == 1) { 1156 String t = child.getType().get(0).getCode(); 1157 if (t.equals("Address") || t.equals("Contact") || t.equals("Reference") || t.equals("Uri")) 1158 return false; 1159 } 1160 return true; 1161 } 1162 1163 private ResourceWithReference resolveReference(ResourceWrapper res, String url) { 1164 if (url == null) 1165 return null; 1166 if (url.startsWith("#")) { 1167 for (ResourceWrapper r : res.getContained()) { 1168 if (r.getId().equals(url.substring(1))) 1169 return new ResourceWithReference(null, r); 1170 } 1171 return null; 1172 } 1173 1174 Resource ae = context.fetchResource(null, url); 1175 if (ae == null) 1176 return null; 1177 else 1178 return new ResourceWithReference(url, new ResourceWrapperDirect(ae)); 1179 } 1180 1181 private void renderCodeableConcept(CodeableConcept cc, XhtmlNode x, boolean showCodeDetails) { 1182 String s = cc.getText(); 1183 if (Utilities.noString(s)) { 1184 for (Coding c : cc.getCoding()) { 1185 if (c.hasDisplayElement()) { 1186 s = c.getDisplay(); 1187 break; 1188 } 1189 } 1190 } 1191 if (Utilities.noString(s)) { 1192 // still? ok, let's try looking it up 1193 for (Coding c : cc.getCoding()) { 1194 if (c.hasCodeElement() && c.hasSystemElement()) { 1195 s = lookupCode(c.getSystem(), c.getCode()); 1196 if (!Utilities.noString(s)) 1197 break; 1198 } 1199 } 1200 } 1201 1202 if (Utilities.noString(s)) { 1203 if (cc.getCoding().isEmpty()) 1204 s = ""; 1205 else 1206 s = cc.getCoding().get(0).getCode(); 1207 } 1208 1209 if (showCodeDetails) { 1210 x.addText(s+" "); 1211 XhtmlNode sp = x.addTag("span"); 1212 sp.setAttribute("style", "background: LightGoldenRodYellow "); 1213 sp.addText("(Details "); 1214 boolean first = true; 1215 for (Coding c : cc.getCoding()) { 1216 if (first) { 1217 sp.addText(": "); 1218 first = false; 1219 } else 1220 sp.addText("; "); 1221 sp.addText("{"+describeSystem(c.getSystem())+" code '"+c.getCode()+"' = '"+lookupCode(c.getSystem(), c.getCode())+(c.hasDisplay() ? "', given as '"+c.getDisplay()+"'}" : "")); 1222 } 1223 sp.addText(")"); 1224 } else { 1225 1226 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1227 for (Coding c : cc.getCoding()) { 1228 if (c.hasCodeElement() && c.hasSystemElement()) { 1229 b.append("{"+c.getSystem()+" "+c.getCode()+"}"); 1230 } 1231 } 1232 1233 x.addTag("span").setAttribute("title", "Codes: "+b.toString()).addText(s); 1234 } 1235 } 1236 1237 private void renderAnnotation(Annotation a, XhtmlNode x, boolean showCodeDetails) throws FHIRException { 1238 StringBuilder s = new StringBuilder(); 1239 if (a.hasAuthor()) { 1240 s.append("Author: "); 1241 1242 if (a.hasAuthorReference()) 1243 s.append(a.getAuthorReference().getReference()); 1244 else if (a.hasAuthorStringType()) 1245 s.append(a.getAuthorStringType().getValue()); 1246 } 1247 1248 1249 if (a.hasTimeElement()) { 1250 if (s.length() > 0) 1251 s.append("; "); 1252 1253 s.append("Made: ").append(a.getTimeElement().toHumanDisplay()); 1254 } 1255 1256 if (a.hasText()) { 1257 if (s.length() > 0) 1258 s.append("; "); 1259 1260 s.append("Annotation: ").append(a.getText()); 1261 } 1262 1263 x.addText(s.toString()); 1264 } 1265 1266 private void renderCoding(Coding c, XhtmlNode x, boolean showCodeDetails) { 1267 String s = ""; 1268 if (c.hasDisplayElement()) 1269 s = c.getDisplay(); 1270 if (Utilities.noString(s)) 1271 s = lookupCode(c.getSystem(), c.getCode()); 1272 1273 if (Utilities.noString(s)) 1274 s = c.getCode(); 1275 1276 if (showCodeDetails) { 1277 x.addText(s+" (Details: "+describeSystem(c.getSystem())+" code "+c.getCode()+" = '"+lookupCode(c.getSystem(), c.getCode())+"', stated as '"+c.getDisplay()+"')"); 1278 } else 1279 x.addTag("span").setAttribute("title", "{"+c.getSystem()+" "+c.getCode()+"}").addText(s); 1280 } 1281 1282 private String describeSystem(String system) { 1283 if (system == null) 1284 return "[not stated]"; 1285 if (system.equals("http://loinc.org")) 1286 return "LOINC"; 1287 if (system.startsWith("http://snomed.info")) 1288 return "SNOMED CT"; 1289 if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) 1290 return "RxNorm"; 1291 if (system.equals("http://hl7.org/fhir/sid/icd-9")) 1292 return "ICD-9"; 1293 1294 return system; 1295 } 1296 1297 private String lookupCode(String system, String code) { 1298 ValidationResult t = context.validateCode(system, code, null); 1299 1300 if (t != null && t.getDisplay() != null) 1301 return t.getDisplay(); 1302 else 1303 return code; 1304 1305 } 1306 1307 private ConceptDefinitionComponent findCode(String code, List<ConceptDefinitionComponent> list) { 1308 for (ConceptDefinitionComponent t : list) { 1309 if (code.equals(t.getCode())) 1310 return t; 1311 ConceptDefinitionComponent c = findCode(code, t.getConcept()); 1312 if (c != null) 1313 return c; 1314 } 1315 return null; 1316 } 1317 1318 private String displayCodeableConcept(CodeableConcept cc) { 1319 String s = cc.getText(); 1320 if (Utilities.noString(s)) { 1321 for (Coding c : cc.getCoding()) { 1322 if (c.hasDisplayElement()) { 1323 s = c.getDisplay(); 1324 break; 1325 } 1326 } 1327 } 1328 if (Utilities.noString(s)) { 1329 // still? ok, let's try looking it up 1330 for (Coding c : cc.getCoding()) { 1331 if (c.hasCode() && c.hasSystem()) { 1332 s = lookupCode(c.getSystem(), c.getCode()); 1333 if (!Utilities.noString(s)) 1334 break; 1335 } 1336 } 1337 } 1338 1339 if (Utilities.noString(s)) { 1340 if (cc.getCoding().isEmpty()) 1341 s = ""; 1342 else 1343 s = cc.getCoding().get(0).getCode(); 1344 } 1345 return s; 1346 } 1347 1348 private void renderIdentifier(Identifier ii, XhtmlNode x) { 1349 x.addText(displayIdentifier(ii)); 1350 } 1351 1352 private void renderTiming(Timing s, XhtmlNode x) throws FHIRException { 1353 x.addText(displayTiming(s)); 1354 } 1355 1356 private void renderQuantity(Quantity q, XhtmlNode x, boolean showCodeDetails) { 1357 if (q.hasComparator()) 1358 x.addText(q.getComparator().toCode()); 1359 x.addText(q.getValue().toString()); 1360 if (q.hasUnit()) 1361 x.addText(" "+q.getUnit()); 1362 else if (q.hasCode()) 1363 x.addText(" "+q.getCode()); 1364 if (showCodeDetails && q.hasCode()) { 1365 XhtmlNode sp = x.addTag("span"); 1366 sp.setAttribute("style", "background: LightGoldenRodYellow "); 1367 sp.addText(" (Details: "+describeSystem(q.getSystem())+" code "+q.getCode()+" = '"+lookupCode(q.getSystem(), q.getCode())+"')"); 1368 } 1369 } 1370 1371 private void renderRange(Range q, XhtmlNode x) { 1372 if (q.hasLow()) 1373 x.addText(q.getLow().getValue().toString()); 1374 else 1375 x.addText("?"); 1376 x.addText("-"); 1377 if (q.hasHigh()) 1378 x.addText(q.getHigh().getValue().toString()); 1379 else 1380 x.addText("?"); 1381 if (q.getLow().hasUnit()) 1382 x.addText(" "+q.getLow().getUnit()); 1383 } 1384 1385 private void renderHumanName(HumanName name, XhtmlNode x) { 1386 x.addText(displayHumanName(name)); 1387 } 1388 1389 private void renderAnnotation(Annotation annot, XhtmlNode x) { 1390 x.addText(annot.getText()); 1391 } 1392 1393 private void renderAddress(Address address, XhtmlNode x) { 1394 x.addText(displayAddress(address)); 1395 } 1396 1397 private void renderContactPoint(ContactPoint contact, XhtmlNode x) { 1398 x.addText(displayContactPoint(contact)); 1399 } 1400 1401 private void renderUri(UriType uri, XhtmlNode x) { 1402 x.addTag("a").setAttribute("href", uri.getValue()).addText(uri.getValue()); 1403 } 1404 1405 private void renderSampledData(SampledData sampledData, XhtmlNode x) { 1406 x.addText(displaySampledData(sampledData)); 1407 } 1408 1409 private String displaySampledData(SampledData s) { 1410 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1411 if (s.hasOrigin()) 1412 b.append("Origin: "+displayQuantity(s.getOrigin())); 1413 1414 if (s.hasPeriod()) 1415 b.append("Period: "+s.getPeriod().toString()); 1416 1417 if (s.hasFactor()) 1418 b.append("Factor: "+s.getFactor().toString()); 1419 1420 if (s.hasLowerLimit()) 1421 b.append("Lower: "+s.getLowerLimit().toString()); 1422 1423 if (s.hasUpperLimit()) 1424 b.append("Upper: "+s.getUpperLimit().toString()); 1425 1426 if (s.hasDimensions()) 1427 b.append("Dimensions: "+s.getDimensions()); 1428 1429 if (s.hasData()) 1430 b.append("Data: "+s.getData()); 1431 1432 return b.toString(); 1433 } 1434 1435 private String displayQuantity(Quantity q) { 1436 StringBuilder s = new StringBuilder(); 1437 1438 s.append("(system = '").append(describeSystem(q.getSystem())) 1439 .append("' code ").append(q.getCode()) 1440 .append(" = '").append(lookupCode(q.getSystem(), q.getCode())).append("')"); 1441 1442 return s.toString(); 1443 } 1444 1445 private String displayTiming(Timing s) throws FHIRException { 1446 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1447 if (s.hasCode()) 1448 b.append("Code: "+displayCodeableConcept(s.getCode())); 1449 1450 if (s.getEvent().size() > 0) { 1451 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 1452 for (DateTimeType p : s.getEvent()) { 1453 c.append(p.toHumanDisplay()); 1454 } 1455 b.append("Events: "+ c.toString()); 1456 } 1457 1458 if (s.hasRepeat()) { 1459 TimingRepeatComponent rep = s.getRepeat(); 1460 if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasStart()) 1461 b.append("Starting "+rep.getBoundsPeriod().getStartElement().toHumanDisplay()); 1462 if (rep.hasCount()) 1463 b.append("Count "+Integer.toString(rep.getCount())+" times"); 1464 if (rep.hasDuration()) 1465 b.append("Duration "+rep.getDuration().toPlainString()+displayTimeUnits(rep.getPeriodUnit())); 1466 1467 if (rep.hasWhen()) { 1468 String st = ""; 1469 if (rep.hasPeriod()) { 1470 st = rep.getPeriod().toPlainString(); 1471 if (rep.hasPeriodMax()) 1472 st = st + "-"+rep.getPeriodMax().toPlainString(); 1473 st = st + displayTimeUnits(rep.getPeriodUnit()); 1474 } 1475 b.append("Do "+st+displayEventCode(rep.getWhen())); 1476 } else { 1477 String st = ""; 1478 if (!rep.hasFrequency() || (!rep.hasFrequencyMax() && rep.getFrequency() == 1) ) 1479 st = "Once"; 1480 else { 1481 st = Integer.toString(rep.getFrequency()); 1482 if (rep.hasFrequencyMax()) 1483 st = st + "-"+Integer.toString(rep.getFrequency()); 1484 } 1485 if (rep.hasPeriod()) { 1486 st = st + " per "+rep.getPeriod().toPlainString(); 1487 if (rep.hasPeriodMax()) 1488 st = st + "-"+rep.getPeriodMax().toPlainString(); 1489 st = st + " "+displayTimeUnits(rep.getPeriodUnit()); 1490 } 1491 b.append("Do "+st); 1492 } 1493 if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasEnd()) 1494 b.append("Until "+rep.getBoundsPeriod().getEndElement().toHumanDisplay()); 1495 } 1496 return b.toString(); 1497 } 1498 1499 private Object displayEventCode(EventTiming when) { 1500 switch (when) { 1501 case C: return "at meals"; 1502 case CD: return "at lunch"; 1503 case CM: return "at breakfast"; 1504 case CV: return "at dinner"; 1505 case AC: return "before meals"; 1506 case ACD: return "before lunch"; 1507 case ACM: return "before breakfast"; 1508 case ACV: return "before dinner"; 1509 case HS: return "before sleeping"; 1510 case PC: return "after meals"; 1511 case PCD: return "after lunch"; 1512 case PCM: return "after breakfast"; 1513 case PCV: return "after dinner"; 1514 case WAKE: return "after waking"; 1515 default: return "??"; 1516 } 1517 } 1518 1519 private String displayTimeUnits(UnitsOfTime units) { 1520 if (units == null) 1521 return "??"; 1522 switch (units) { 1523 case A: return "years"; 1524 case D: return "days"; 1525 case H: return "hours"; 1526 case MIN: return "minutes"; 1527 case MO: return "months"; 1528 case S: return "seconds"; 1529 case WK: return "weeks"; 1530 default: return "??"; 1531 } 1532 } 1533 1534 public static String displayHumanName(HumanName name) { 1535 StringBuilder s = new StringBuilder(); 1536 if (name.hasText()) 1537 s.append(name.getText()); 1538 else { 1539 for (StringType p : name.getGiven()) { 1540 s.append(p.getValue()); 1541 s.append(" "); 1542 } 1543 for (StringType p : name.getFamily()) { 1544 s.append(p.getValue()); 1545 s.append(" "); 1546 } 1547 } 1548 if (name.hasUse() && name.getUse() != NameUse.USUAL) 1549 s.append("("+name.getUse().toString()+")"); 1550 return s.toString(); 1551 } 1552 1553 private String displayAddress(Address address) { 1554 StringBuilder s = new StringBuilder(); 1555 if (address.hasText()) 1556 s.append(address.getText()); 1557 else { 1558 for (StringType p : address.getLine()) { 1559 s.append(p.getValue()); 1560 s.append(" "); 1561 } 1562 if (address.hasCity()) { 1563 s.append(address.getCity()); 1564 s.append(" "); 1565 } 1566 if (address.hasState()) { 1567 s.append(address.getState()); 1568 s.append(" "); 1569 } 1570 1571 if (address.hasPostalCode()) { 1572 s.append(address.getPostalCode()); 1573 s.append(" "); 1574 } 1575 1576 if (address.hasCountry()) { 1577 s.append(address.getCountry()); 1578 s.append(" "); 1579 } 1580 } 1581 if (address.hasUse()) 1582 s.append("("+address.getUse().toString()+")"); 1583 return s.toString(); 1584 } 1585 1586 public static String displayContactPoint(ContactPoint contact) { 1587 StringBuilder s = new StringBuilder(); 1588 s.append(describeSystem(contact.getSystem())); 1589 if (Utilities.noString(contact.getValue())) 1590 s.append("-unknown-"); 1591 else 1592 s.append(contact.getValue()); 1593 if (contact.hasUse()) 1594 s.append("("+contact.getUse().toString()+")"); 1595 return s.toString(); 1596 } 1597 1598 private static String describeSystem(ContactPointSystem system) { 1599 if (system == null) 1600 return ""; 1601 switch (system) { 1602 case PHONE: return "ph: "; 1603 case FAX: return "fax: "; 1604 default: 1605 return ""; 1606 } 1607 } 1608 1609 private String displayIdentifier(Identifier ii) { 1610 String s = Utilities.noString(ii.getValue()) ? "??" : ii.getValue(); 1611 1612 if (ii.hasType()) { 1613 if (ii.getType().hasText()) 1614 s = ii.getType().getText()+" = "+s; 1615 else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasDisplay()) 1616 s = ii.getType().getCoding().get(0).getDisplay()+" = "+s; 1617 else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasCode()) 1618 s = lookupCode(ii.getType().getCoding().get(0).getSystem(), ii.getType().getCoding().get(0).getCode())+" = "+s; 1619 } 1620 1621 if (ii.hasUse()) 1622 s = s + " ("+ii.getUse().toString()+")"; 1623 return s; 1624 } 1625 1626 private List<ElementDefinition> getChildrenForPath(List<ElementDefinition> elements, String path) throws DefinitionException { 1627 // do we need to do a name reference substitution? 1628 for (ElementDefinition e : elements) { 1629 if (e.getPath().equals(path) && e.hasContentReference()) { 1630 String ref = e.getContentReference(); 1631 ElementDefinition t = null; 1632 // now, resolve the name 1633 for (ElementDefinition e1 : elements) { 1634 if (ref.equals("#"+e1.getId())) 1635 t = e1; 1636 } 1637 if (t == null) 1638 throw new DefinitionException("Unable to resolve content reference "+ref+" trying to resolve "+path); 1639 path = t.getPath(); 1640 break; 1641 } 1642 } 1643 1644 List<ElementDefinition> results = new ArrayList<ElementDefinition>(); 1645 for (ElementDefinition e : elements) { 1646 if (e.getPath().startsWith(path+".") && !e.getPath().substring(path.length()+1).contains(".")) 1647 results.add(e); 1648 } 1649 return results; 1650 } 1651 1652 1653 public void generate(ConceptMap cm) { 1654 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 1655 x.addTag("h2").addText(cm.getName()+" ("+cm.getUrl()+")"); 1656 1657 XhtmlNode p = x.addTag("p"); 1658 p.addText("Mapping from "); 1659 AddVsRef(((Reference) cm.getSource()).getReference(), p); 1660 p.addText(" to "); 1661 AddVsRef(((Reference) cm.getTarget()).getReference(), p); 1662 1663 p = x.addTag("p"); 1664 if (cm.getExperimental()) 1665 p.addText(Utilities.capitalize(cm.getStatus().toString())+" (not intended for production usage). "); 1666 else 1667 p.addText(Utilities.capitalize(cm.getStatus().toString())+". "); 1668 p.addText("Published on "+cm.getDateElement().toHumanDisplay()+" by "+cm.getPublisher()); 1669 if (!cm.getContact().isEmpty()) { 1670 p.addText(" ("); 1671 boolean firsti = true; 1672 for (ConceptMapContactComponent ci : cm.getContact()) { 1673 if (firsti) 1674 firsti = false; 1675 else 1676 p.addText(", "); 1677 if (ci.hasName()) 1678 p.addText(ci.getName()+": "); 1679 boolean first = true; 1680 for (ContactPoint c : ci.getTelecom()) { 1681 if (first) 1682 first = false; 1683 else 1684 p.addText(", "); 1685 addTelecom(p, c); 1686 } 1687 p.addText("; "); 1688 } 1689 p.addText(")"); 1690 } 1691 p.addText(". "); 1692 p.addText(cm.getCopyright()); 1693 if (!Utilities.noString(cm.getDescription())) 1694 x.addTag("p").addText(cm.getDescription()); 1695 1696 x.addTag("br"); 1697 1698 if (!cm.getElement().isEmpty()) { 1699 SourceElementComponent cc = cm.getElement().get(0); 1700 String src = cc.getSystem(); 1701 boolean comments = false; 1702 boolean ok = cc.getTarget().size() == 1; 1703 Map<String, HashSet<String>> sources = new HashMap<String, HashSet<String>>(); 1704 sources.put("code", new HashSet<String>()); 1705 Map<String, HashSet<String>> targets = new HashMap<String, HashSet<String>>(); 1706 targets.put("code", new HashSet<String>()); 1707 if (ok) { 1708 String dst = cc.getTarget().get(0).getSystem(); 1709 for (SourceElementComponent ccl : cm.getElement()) { 1710 ok = ok && src.equals(ccl.getSystem()) && ccl.getTarget().size() == 1 && dst.equals(ccl.getTarget().get(0).getSystem()) && ccl.getTarget().get(0).getDependsOn().isEmpty() && ccl.getTarget().get(0).getProduct().isEmpty(); 1711 if (ccl.hasSystem()) 1712 sources.get("code").add(ccl.getSystem()); 1713 for (TargetElementComponent ccm : ccl.getTarget()) { 1714 comments = comments || !Utilities.noString(ccm.getComments()); 1715 for (OtherElementComponent d : ccm.getDependsOn()) { 1716 if (!sources.containsKey(d.getElement())) 1717 sources.put(d.getElement(), new HashSet<String>()); 1718 sources.get(d.getElement()).add(d.getSystem()); 1719 } 1720 if (ccm.hasSystem()) 1721 targets.get("code").add(ccm.getSystem()); 1722 for (OtherElementComponent d : ccm.getProduct()) { 1723 if (!targets.containsKey(d.getElement())) 1724 targets.put(d.getElement(), new HashSet<String>()); 1725 targets.get(d.getElement()).add(d.getSystem()); 1726 } 1727 1728 } 1729 } 1730 } 1731 1732 String display; 1733 if (ok) { 1734 // simple 1735 XhtmlNode tbl = x.addTag("table").setAttribute("class", "grid"); 1736 XhtmlNode tr = tbl.addTag("tr"); 1737 tr.addTag("td").addTag("b").addText("Source Code"); 1738 tr.addTag("td").addTag("b").addText("Equivalence"); 1739 tr.addTag("td").addTag("b").addText("Destination Code"); 1740 if (comments) 1741 tr.addTag("td").addTag("b").addText("Comments"); 1742 for (SourceElementComponent ccl : cm.getElement()) { 1743 tr = tbl.addTag("tr"); 1744 XhtmlNode td = tr.addTag("td"); 1745 td.addText(ccl.getCode()); 1746 display = getDisplayForConcept(ccl.getSystem(), ccl.getCode()); 1747 if (display != null) 1748 td.addText(" ("+display+")"); 1749 TargetElementComponent ccm = ccl.getTarget().get(0); 1750 tr.addTag("td").addText(!ccm.hasEquivalence() ? "" : ccm.getEquivalence().toCode()); 1751 td = tr.addTag("td"); 1752 td.addText(ccm.getCode()); 1753 display = getDisplayForConcept(ccm.getSystem(), ccm.getCode()); 1754 if (display != null) 1755 td.addText(" ("+display+")"); 1756 if (comments) 1757 tr.addTag("td").addText(ccm.getComments()); 1758 } 1759 } else { 1760 XhtmlNode tbl = x.addTag("table").setAttribute("class", "grid"); 1761 XhtmlNode tr = tbl.addTag("tr"); 1762 XhtmlNode td; 1763 tr.addTag("td").setAttribute("colspan", Integer.toString(sources.size())).addTag("b").addText("Source Concept"); 1764 tr.addTag("td").addTag("b").addText("Equivalence"); 1765 tr.addTag("td").setAttribute("colspan", Integer.toString(targets.size())).addTag("b").addText("Destination Concept"); 1766 if (comments) 1767 tr.addTag("td").addTag("b").addText("Comments"); 1768 tr = tbl.addTag("tr"); 1769 if (sources.get("code").size() == 1) 1770 tr.addTag("td").addTag("b").addText("Code "+sources.get("code").toString()+""); 1771 else 1772 tr.addTag("td").addTag("b").addText("Code"); 1773 for (String s : sources.keySet()) { 1774 if (!s.equals("code")) { 1775 if (sources.get(s).size() == 1) 1776 tr.addTag("td").addTag("b").addText(getDescForConcept(s) +" "+sources.get(s).toString()); 1777 else 1778 tr.addTag("td").addTag("b").addText(getDescForConcept(s)); 1779 } 1780 } 1781 tr.addTag("td"); 1782 if (targets.get("code").size() == 1) 1783 tr.addTag("td").addTag("b").addText("Code "+targets.get("code").toString()); 1784 else 1785 tr.addTag("td").addTag("b").addText("Code"); 1786 for (String s : targets.keySet()) { 1787 if (!s.equals("code")) { 1788 if (targets.get(s).size() == 1) 1789 tr.addTag("td").addTag("b").addText(getDescForConcept(s) +" "+targets.get(s).toString()+""); 1790 else 1791 tr.addTag("td").addTag("b").addText(getDescForConcept(s)); 1792 } 1793 } 1794 if (comments) 1795 tr.addTag("td"); 1796 1797 for (SourceElementComponent ccl : cm.getElement()) { 1798 tr = tbl.addTag("tr"); 1799 td = tr.addTag("td"); 1800 if (sources.get("code").size() == 1) 1801 td.addText(ccl.getCode()); 1802 else 1803 td.addText(ccl.getSystem()+" / "+ccl.getCode()); 1804 display = getDisplayForConcept(ccl.getSystem(), ccl.getCode()); 1805 if (display != null) 1806 td.addText(" ("+display+")"); 1807 1808 TargetElementComponent ccm = ccl.getTarget().get(0); 1809 for (String s : sources.keySet()) { 1810 if (!s.equals("code")) { 1811 td = tr.addTag("td"); 1812 td.addText(getCode(ccm.getDependsOn(), s, sources.get(s).size() != 1)); 1813 display = getDisplay(ccm.getDependsOn(), s); 1814 if (display != null) 1815 td.addText(" ("+display+")"); 1816 } 1817 } 1818 tr.addTag("td").addText(ccm.getEquivalence().toString()); 1819 td = tr.addTag("td"); 1820 if (targets.get("code").size() == 1) 1821 td.addText(ccm.getCode()); 1822 else 1823 td.addText(ccm.getSystem()+" / "+ccm.getCode()); 1824 display = getDisplayForConcept(ccm.getSystem(), ccm.getCode()); 1825 if (display != null) 1826 td.addText(" ("+display+")"); 1827 1828 for (String s : targets.keySet()) { 1829 if (!s.equals("code")) { 1830 td = tr.addTag("td"); 1831 td.addText(getCode(ccm.getProduct(), s, targets.get(s).size() != 1)); 1832 display = getDisplay(ccm.getProduct(), s); 1833 if (display != null) 1834 td.addText(" ("+display+")"); 1835 } 1836 } 1837 if (comments) 1838 tr.addTag("td").addText(ccm.getComments()); 1839 } 1840 } 1841 } 1842 1843 inject(cm, x, NarrativeStatus.GENERATED); 1844 } 1845 1846 1847 1848 private void inject(DomainResource r, XhtmlNode x, NarrativeStatus status) { 1849 if (!r.hasText() || !r.getText().hasDiv() || r.getText().getDiv().getChildNodes().isEmpty()) { 1850 r.setText(new Narrative()); 1851 r.getText().setDiv(x); 1852 r.getText().setStatus(status); 1853 } else { 1854 XhtmlNode n = r.getText().getDiv(); 1855 n.addTag("hr"); 1856 n.getChildNodes().addAll(x.getChildNodes()); 1857 } 1858 } 1859 1860 public Element getNarrative(Element er) { 1861 Element txt = XMLUtil.getNamedChild(er, "text"); 1862 if (txt == null) 1863 return null; 1864 return XMLUtil.getNamedChild(txt, "div"); 1865 } 1866 1867 1868 private void inject(Element er, XhtmlNode x, NarrativeStatus status) { 1869 Element txt = XMLUtil.getNamedChild(er, "text"); 1870 if (txt == null) { 1871 txt = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "text"); 1872 Element n = XMLUtil.getFirstChild(er); 1873 while (n != null && (n.getNodeName().equals("id") || n.getNodeName().equals("meta") || n.getNodeName().equals("implicitRules") || n.getNodeName().equals("language"))) 1874 n = XMLUtil.getNextSibling(n); 1875 if (n == null) 1876 er.appendChild(txt); 1877 else 1878 er.insertBefore(txt, n); 1879 } 1880 Element st = XMLUtil.getNamedChild(txt, "status"); 1881 if (st == null) { 1882 st = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "status"); 1883 Element n = XMLUtil.getFirstChild(txt); 1884 if (n == null) 1885 txt.appendChild(st); 1886 else 1887 txt.insertBefore(st, n); 1888 } 1889 st.setAttribute("value", status.toCode()); 1890 Element div = XMLUtil.getNamedChild(txt, "div"); 1891 if (div == null) { 1892 div = er.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "div"); 1893 div.setAttribute("xmlns", FormatUtilities.XHTML_NS); 1894 txt.appendChild(div); 1895 } 1896 if (div.hasChildNodes()) 1897 div.appendChild(er.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "hr")); 1898 new XhtmlComposer(true, false).compose(div, x); 1899 } 1900 1901 private String getDisplay(List<OtherElementComponent> list, String s) { 1902 for (OtherElementComponent c : list) { 1903 if (s.equals(c.getElement())) 1904 return getDisplayForConcept(c.getSystem(), c.getCode()); 1905 } 1906 return null; 1907 } 1908 1909 private String getDisplayForConcept(String system, String code) { 1910 if (code == null) 1911 return null; 1912 ValidationResult cl = context.validateCode(system, code, null); 1913 return cl == null ? null : cl.getDisplay(); 1914 } 1915 1916 1917 1918 private String getDescForConcept(String s) { 1919 if (s.startsWith("http://hl7.org/fhir/v2/element/")) 1920 return "v2 "+s.substring("http://hl7.org/fhir/v2/element/".length()); 1921 return s; 1922 } 1923 1924 private String getCode(List<OtherElementComponent> list, String s, boolean withSystem) { 1925 for (OtherElementComponent c : list) { 1926 if (s.equals(c.getElement())) 1927 if (withSystem) 1928 return c.getSystem()+" / "+c.getCode(); 1929 else 1930 return c.getCode(); 1931 } 1932 return null; 1933 } 1934 1935 private void addTelecom(XhtmlNode p, ContactPoint c) { 1936 if (c.getSystem() == ContactPointSystem.PHONE) { 1937 p.addText("Phone: "+c.getValue()); 1938 } else if (c.getSystem() == ContactPointSystem.FAX) { 1939 p.addText("Fax: "+c.getValue()); 1940 } else if (c.getSystem() == ContactPointSystem.EMAIL) { 1941 p.addTag("a").setAttribute("href", "mailto:"+c.getValue()).addText(c.getValue()); 1942 } else if (c.getSystem() == ContactPointSystem.OTHER) { 1943 if (c.getValue().length() > 30) 1944 p.addTag("a").setAttribute("href", c.getValue()).addText(c.getValue().substring(0, 30)+"..."); 1945 else 1946 p.addTag("a").setAttribute("href", c.getValue()).addText(c.getValue()); 1947 } 1948 } 1949 1950 /** 1951 * This generate is optimised for the FHIR build process itself in as much as it 1952 * generates hyperlinks in the narrative that are only going to be correct for 1953 * the purposes of the build. This is to be reviewed in the future. 1954 * 1955 * @param vs 1956 * @param codeSystems 1957 * @throws Exception 1958 */ 1959 public void generate(CodeSystem cs, boolean header) { 1960 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 1961 boolean hasExtensions = false; 1962 hasExtensions = generateDefinition(x, cs, header); 1963 inject(cs, x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 1964 } 1965 1966 private boolean generateDefinition(XhtmlNode x, CodeSystem cs, boolean header) { 1967 boolean hasExtensions = false; 1968 Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>(); 1969 for (ConceptMap a : context.findMapsForSource(cs.getValueSet())) { 1970 String url = ""; 1971 ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference()); 1972 if (vsr != null) 1973 url = (String) vsr.getUserData("filename"); 1974 mymaps.put(a, url); 1975 } 1976 // also, look in the contained resources for a concept map 1977 for (Resource r : cs.getContained()) { 1978 if (r instanceof ConceptMap) { 1979 ConceptMap cm = (ConceptMap) r; 1980 if (((Reference) cm.getSource()).getReference().equals(cs.getValueSet())) { 1981 String url = ""; 1982 ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) cm.getTarget()).getReference()); 1983 if (vsr != null) 1984 url = (String) vsr.getUserData("filename"); 1985 mymaps.put(cm, url); 1986 } 1987 } 1988 } 1989 List<String> langs = new ArrayList<String>(); 1990 1991 if (header) { 1992 XhtmlNode h = x.addTag("h2"); 1993 h.addText(cs.getName()); 1994 XhtmlNode p = x.addTag("p"); 1995 smartAddText(p, cs.getDescription()); 1996 if (cs.hasCopyright()) 1997 generateCopyright(x, cs); 1998 } 1999 XhtmlNode p = x.addTag("p"); 2000 p.addText("This code system "+cs.getUrl()+" defines the following codes:"); 2001 XhtmlNode t = x.addTag("table").setAttribute("class", "codes"); 2002 boolean commentS = false; 2003 boolean deprecated = false; 2004 boolean display = false; 2005 boolean hierarchy = false; 2006 for (ConceptDefinitionComponent c : cs.getConcept()) { 2007 commentS = commentS || conceptsHaveComments(c); 2008 deprecated = deprecated || conceptsHaveDeprecated(cs, c); 2009 display = display || conceptsHaveDisplay(c); 2010 hierarchy = hierarchy || c.hasConcept(); 2011 scanLangs(c, langs); 2012 } 2013 addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, true, commentS, deprecated), mymaps); 2014 for (ConceptDefinitionComponent c : cs.getConcept()) { 2015 hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, commentS, deprecated, mymaps, cs.getUrl(), cs) || hasExtensions; 2016 } 2017 if (langs.size() > 0) { 2018 Collections.sort(langs); 2019 x.addTag("p").addTag("b").addText("Additional Language Displays"); 2020 t = x.addTag("table").setAttribute("class", "codes"); 2021 XhtmlNode tr = t.addTag("tr"); 2022 tr.addTag("td").addTag("b").addText("Code"); 2023 for (String lang : langs) 2024 tr.addTag("td").addTag("b").addText(lang); 2025 for (ConceptDefinitionComponent c : cs.getConcept()) { 2026 addLanguageRow(c, t, langs); 2027 } 2028 } 2029 return hasExtensions; 2030 } 2031 2032 private int countConcepts(List<ConceptDefinitionComponent> list) { 2033 int count = list.size(); 2034 for (ConceptDefinitionComponent c : list) 2035 if (c.hasConcept()) 2036 count = count + countConcepts(c.getConcept()); 2037 return count; 2038 } 2039 2040 private void generateCopyright(XhtmlNode x, CodeSystem cs) { 2041 XhtmlNode p = x.addTag("p"); 2042 p.addTag("b").addText("Copyright Statement:"); 2043 smartAddText(p, " " + cs.getCopyright()); 2044 } 2045 2046 2047 /** 2048 * This generate is optimised for the FHIR build process itself in as much as it 2049 * generates hyperlinks in the narrative that are only going to be correct for 2050 * the purposes of the build. This is to be reviewed in the future. 2051 * 2052 * @param vs 2053 * @param codeSystems 2054 * @throws Exception 2055 */ 2056 public void generate(ValueSet vs, boolean header) { 2057 generate(vs, null, header); 2058 } 2059 2060 public void generate(ValueSet vs, ValueSet src, boolean header) { 2061 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 2062 if (vs.hasExpansion()) { 2063 // for now, we just accept an expansion if there is one 2064 generateExpansion(x, vs, src, header); 2065 } 2066 2067 boolean hasExtensions = generateComposition(x, vs, header); 2068 inject(vs, x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 2069 } 2070 2071 private Integer countMembership(ValueSet vs) { 2072 int count = 0; 2073 if (vs.hasExpansion()) 2074 count = count + conceptCount(vs.getExpansion().getContains()); 2075 else { 2076 if (vs.hasCompose()) { 2077 if (vs.getCompose().hasExclude()) { 2078 try { 2079 ValueSetExpansionOutcome vse = context.expandVS(vs, true); 2080 count = 0; 2081 count += conceptCount(vse.getValueset().getExpansion().getContains()); 2082 return count; 2083 } catch (Exception e) { 2084 return null; 2085 } 2086 } 2087 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 2088 if (inc.hasFilter()) 2089 return null; 2090 if (!inc.hasConcept()) 2091 return null; 2092 count = count + inc.getConcept().size(); 2093 } 2094 } 2095 } 2096 return count; 2097 } 2098 2099 private int conceptCount(List<ValueSetExpansionContainsComponent> list) { 2100 int count = 0; 2101 for (ValueSetExpansionContainsComponent c : list) { 2102 if (!c.getAbstract()) 2103 count++; 2104 count = count + conceptCount(c.getContains()); 2105 } 2106 return count; 2107 } 2108 2109 private boolean generateExpansion(XhtmlNode x, ValueSet vs, ValueSet src, boolean header) { 2110 boolean hasExtensions = false; 2111 Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>(); 2112 for (ConceptMap a : context.findMapsForSource(vs.getUrl())) { 2113 String url = ""; 2114 ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference()); 2115 if (vsr != null) 2116 url = (String) vsr.getUserData("filename"); 2117 mymaps.put(a, url); 2118 } 2119 2120 if (header) { 2121 XhtmlNode h = x.addTag("h3"); 2122 h.addText("Value Set Contents"); 2123 if (IsNotFixedExpansion(vs)) 2124 x.addTag("p").addText(vs.getDescription()); 2125 if (vs.hasCopyright()) 2126 generateCopyright(x, vs); 2127 } 2128 if (ToolingExtensions.hasExtension(vs.getExpansion(), "http://hl7.org/fhir/StructureDefinition/valueset-toocostly")) 2129 x.addTag("p").setAttribute("style", "border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(tooCostlyNote); 2130 else { 2131 Integer count = countMembership(vs); 2132 if (count == null) 2133 x.addTag("p").addText("This value set does not contain a fixed number of concepts"); 2134 else 2135 x.addTag("p").addText("This value set contains "+count.toString()+" concepts"); 2136 } 2137 2138 CodeSystem allCS = null; 2139 boolean doSystem = checkDoSystem(vs, src); 2140 boolean doDefinition = checkDoDefinition(vs.getExpansion().getContains()); 2141 if (doSystem && allFromOneSystem(vs)) { 2142 doSystem = false; 2143 XhtmlNode p = x.addTag("p"); 2144 p.addText("All codes from system "); 2145 allCS = context.fetchCodeSystem(vs.getExpansion().getContains().get(0).getSystem()); 2146 String ref = null; 2147 if (allCS != null) 2148 ref = getCsRef(allCS); 2149 if (ref == null) 2150 p.addTag("code").addText(vs.getExpansion().getContains().get(0).getSystem()); 2151 else 2152 p.addTag("a").setAttribute("href", ref).addTag("code").addText(vs.getExpansion().getContains().get(0).getSystem()); 2153 } 2154 XhtmlNode t = x.addTag("table").setAttribute("class", "codes"); 2155 XhtmlNode tr = t.addTag("tr"); 2156 tr.addTag("td").addTag("b").addText("Code"); 2157 if (doSystem) 2158 tr.addTag("td").addTag("b").addText("System"); 2159 tr.addTag("td").addTag("b").addText("Display"); 2160 if (doDefinition) 2161 tr.addTag("td").addTag("b").addText("Definition"); 2162 2163 addMapHeaders(tr, mymaps); 2164 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 2165 addExpansionRowToTable(t, c, 0, doSystem, doDefinition, mymaps, allCS); 2166 } 2167 return hasExtensions; 2168 } 2169 2170 private boolean checkDoDefinition(List<ValueSetExpansionContainsComponent> contains) { 2171 for (ValueSetExpansionContainsComponent c : contains) { 2172 CodeSystem cs = context.fetchCodeSystem(c.getSystem()); 2173 if (cs != null) 2174 return true; 2175 if (checkDoDefinition(c.getContains())) 2176 return true; 2177 } 2178 return false; 2179 } 2180 2181 2182 private boolean allFromOneSystem(ValueSet vs) { 2183 if (vs.getExpansion().getContains().isEmpty()) 2184 return false; 2185 String system = vs.getExpansion().getContains().get(0).getSystem(); 2186 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 2187 if (!checkSystemMatches(system, cc)) 2188 return false; 2189 } 2190 return true; 2191 } 2192 2193 2194 private boolean checkSystemMatches(String system, ValueSetExpansionContainsComponent cc) { 2195 if (!system.equals(cc.getSystem())) 2196 return false; 2197 for (ValueSetExpansionContainsComponent cc1 : cc.getContains()) { 2198 if (!checkSystemMatches(system, cc1)) 2199 return false; 2200 } 2201 return true; 2202 } 2203 2204 2205 private boolean checkDoSystem(ValueSet vs, ValueSet src) { 2206 if (src != null) 2207 vs = src; 2208 if (vs.hasCompose()) 2209 return true; 2210 return false; 2211 } 2212 2213 private boolean IsNotFixedExpansion(ValueSet vs) { 2214 if (vs.hasCompose()) 2215 return false; 2216 2217 if (vs.getCompose().hasImport()) 2218 return true; 2219 2220 // it's not fixed if it has any includes that are not version fixed 2221 for (ConceptSetComponent cc : vs.getCompose().getInclude()) 2222 if (!cc.hasVersion()) 2223 return true; 2224 return false; 2225 } 2226 2227 2228 private void addLanguageRow(ConceptDefinitionComponent c, XhtmlNode t, List<String> langs) { 2229 XhtmlNode tr = t.addTag("tr"); 2230 tr.addTag("td").addText(c.getCode()); 2231 for (String lang : langs) { 2232 ConceptDefinitionDesignationComponent d = null; 2233 for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) { 2234 if (lang.equals(designation.getLanguage())) 2235 d = designation; 2236 } 2237 tr.addTag("td").addText(d == null ? "" : d.getValue()); 2238 } 2239 } 2240 2241 private void scanLangs(ConceptDefinitionComponent c, List<String> langs) { 2242 for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) { 2243 String lang = designation.getLanguage(); 2244 if (langs != null && !langs.contains(lang)) 2245 langs.add(lang); 2246 } 2247 for (ConceptDefinitionComponent g : c.getConcept()) 2248 scanLangs(g, langs); 2249 } 2250 2251 private void addMapHeaders(XhtmlNode tr, Map<ConceptMap, String> mymaps) { 2252 for (ConceptMap m : mymaps.keySet()) { 2253 XhtmlNode td = tr.addTag("td"); 2254 XhtmlNode b = td.addTag("b"); 2255 XhtmlNode a = b.addTag("a"); 2256 a.setAttribute("href", prefix+mymaps.get(m)); 2257 a.addText(m.hasDescription() ? m.getDescription() : m.getName()); 2258 } 2259 } 2260 2261 private void smartAddText(XhtmlNode p, String text) { 2262 if (text == null) 2263 return; 2264 2265 String[] lines = text.split("\\r\\n"); 2266 for (int i = 0; i < lines.length; i++) { 2267 if (i > 0) 2268 p.addTag("br"); 2269 p.addText(lines[i]); 2270 } 2271 } 2272 2273 private boolean conceptsHaveComments(ConceptDefinitionComponent c) { 2274 if (ToolingExtensions.hasComment(c)) 2275 return true; 2276 for (ConceptDefinitionComponent g : c.getConcept()) 2277 if (conceptsHaveComments(g)) 2278 return true; 2279 return false; 2280 } 2281 2282 private boolean conceptsHaveDisplay(ConceptDefinitionComponent c) { 2283 if (c.hasDisplay()) 2284 return true; 2285 for (ConceptDefinitionComponent g : c.getConcept()) 2286 if (conceptsHaveDisplay(g)) 2287 return true; 2288 return false; 2289 } 2290 2291 private boolean conceptsHaveDeprecated(CodeSystem cs, ConceptDefinitionComponent c) { 2292 if (CodeSystemUtilities.isDeprecated(cs, c)) 2293 return true; 2294 for (ConceptDefinitionComponent g : c.getConcept()) 2295 if (conceptsHaveDeprecated(cs, g)) 2296 return true; 2297 return false; 2298 } 2299 2300 private void generateCopyright(XhtmlNode x, ValueSet vs) { 2301 XhtmlNode p = x.addTag("p"); 2302 p.addTag("b").addText("Copyright Statement:"); 2303 smartAddText(p, " " + vs.getCopyright()); 2304 } 2305 2306 2307 private XhtmlNode addTableHeaderRowStandard(XhtmlNode t, boolean hasHierarchy, boolean hasDisplay, boolean definitions, boolean comments, boolean deprecated) { 2308 XhtmlNode tr = t.addTag("tr"); 2309 if (hasHierarchy) 2310 tr.addTag("td").addTag("b").addText("Lvl"); 2311 tr.addTag("td").addTag("b").addText("Code"); 2312 if (hasDisplay) 2313 tr.addTag("td").addTag("b").addText("Display"); 2314 if (definitions) 2315 tr.addTag("td").addTag("b").addText("Definition"); 2316 if (deprecated) 2317 tr.addTag("td").addTag("b").addText("Deprecated"); 2318 if (comments) 2319 tr.addTag("td").addTag("b").addText("Comments"); 2320 return tr; 2321 } 2322 2323 private void addExpansionRowToTable(XhtmlNode t, ValueSetExpansionContainsComponent c, int i, boolean doSystem, boolean doDefinition, Map<ConceptMap, String> mymaps, CodeSystem allCS) { 2324 XhtmlNode tr = t.addTag("tr"); 2325 XhtmlNode td = tr.addTag("td"); 2326 2327 String tgt = makeAnchor(c.getSystem(), c.getCode()); 2328 td.addTag("a").setAttribute("name", tgt).addText(" "); 2329 2330 String s = Utilities.padLeft("", '.', i*2); 2331 2332 td.addText(s); 2333 Resource e = context.fetchCodeSystem(c.getSystem()); 2334 if (e == null) 2335 td.addText(c.getCode()); 2336 else { 2337 XhtmlNode a = td.addTag("a"); 2338 a.addText(c.getCode()); 2339 a.setAttribute("href", prefix+getCsRef(e)+"#"+Utilities.nmtokenize(c.getCode())); 2340 } 2341 if (doSystem) { 2342 td = tr.addTag("td"); 2343 td.addText(c.getSystem()); 2344 } 2345 td = tr.addTag("td"); 2346 if (c.hasDisplayElement()) 2347 td.addText(c.getDisplay()); 2348 2349 if (doDefinition) { 2350 CodeSystem cs = allCS; 2351 if (cs == null) 2352 cs = context.fetchCodeSystem(c.getSystem()); 2353 td = tr.addTag("td"); 2354 if (cs != null) 2355 td.addText(CodeSystemUtilities.getCodeDefinition(cs, c.getCode())); 2356 } 2357 for (ConceptMap m : mymaps.keySet()) { 2358 td = tr.addTag("td"); 2359 List<TargetElementComponent> mappings = findMappingsForCode(c.getCode(), m); 2360 boolean first = true; 2361 for (TargetElementComponent mapping : mappings) { 2362 if (!first) 2363 td.addTag("br"); 2364 first = false; 2365 XhtmlNode span = td.addTag("span"); 2366 span.setAttribute("title", mapping.getEquivalence().toString()); 2367 span.addText(getCharForEquivalence(mapping)); 2368 XhtmlNode a = td.addTag("a"); 2369 a.setAttribute("href", prefix+mymaps.get(m)+"#"+mapping.getCode()); 2370 a.addText(mapping.getCode()); 2371 if (!Utilities.noString(mapping.getComments())) 2372 td.addTag("i").addText("("+mapping.getComments()+")"); 2373 } 2374 } 2375 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 2376 addExpansionRowToTable(t, cc, i+1, doSystem, doDefinition, mymaps, allCS); 2377 } 2378 } 2379 2380 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) { 2381 boolean hasExtensions = false; 2382 XhtmlNode tr = t.addTag("tr"); 2383 XhtmlNode td = tr.addTag("td"); 2384 if (hasHierarchy) { 2385 td.addText(Integer.toString(i+1)); 2386 td = tr.addTag("td"); 2387 String s = Utilities.padLeft("", '\u00A0', i*2); 2388 td.addText(s); 2389 } 2390 td.addText(c.getCode()); 2391 XhtmlNode a; 2392 if (c.hasCodeElement()) { 2393 a = td.addTag("a"); 2394 a.setAttribute("name", Utilities.nmtokenize(c.getCode())); 2395 a.addText(" "); 2396 } 2397 2398 if (hasDisplay) { 2399 td = tr.addTag("td"); 2400 if (c.hasDisplayElement()) 2401 td.addText(c.getDisplay()); 2402 } 2403 td = tr.addTag("td"); 2404 if (c != null) 2405 smartAddText(td, c.getDefinition()); 2406 if (deprecated) { 2407 td = tr.addTag("td"); 2408 Boolean b = CodeSystemUtilities.isDeprecated(cs, c); 2409 if (b != null && b) { 2410 smartAddText(td, "Deprecated"); 2411 hasExtensions = true; 2412 if (ToolingExtensions.hasExtension(c, ToolingExtensions.EXT_REPLACED_BY)) { 2413 Coding cc = (Coding) ToolingExtensions.getExtension(c, ToolingExtensions.EXT_REPLACED_BY).getValue(); 2414 td.addText(" (replaced by "); 2415 String url = getCodingReference(cc, system); 2416 if (url != null) { 2417 td.addTag("a").setAttribute("href", url).addText(cc.getCode()); 2418 td.addText(": "+cc.getDisplay()+")"); 2419 } else 2420 td.addText(cc.getCode()+" '"+cc.getDisplay()+"' in "+cc.getSystem()+")"); 2421 } 2422 } 2423 } 2424 if (comment) { 2425 td = tr.addTag("td"); 2426 String s = ToolingExtensions.getComment(c); 2427 if (s != null) { 2428 smartAddText(td, s); 2429 hasExtensions = true; 2430 } 2431 } 2432 for (ConceptMap m : maps.keySet()) { 2433 td = tr.addTag("td"); 2434 List<TargetElementComponent> mappings = findMappingsForCode(c.getCode(), m); 2435 boolean first = true; 2436 for (TargetElementComponent mapping : mappings) { 2437 if (!first) 2438 td.addTag("br"); 2439 first = false; 2440 XhtmlNode span = td.addTag("span"); 2441 span.setAttribute("title", mapping.hasEquivalence() ? mapping.getEquivalence().toCode() : ""); 2442 span.addText(getCharForEquivalence(mapping)); 2443 a = td.addTag("a"); 2444 a.setAttribute("href", prefix+maps.get(m)+"#"+makeAnchor(mapping.getSystem(), mapping.getCode())); 2445 a.addText(mapping.getCode()); 2446 if (!Utilities.noString(mapping.getComments())) 2447 td.addTag("i").addText("("+mapping.getComments()+")"); 2448 } 2449 } 2450 for (CodeType e : ToolingExtensions.getSubsumes(c)) { 2451 hasExtensions = true; 2452 tr = t.addTag("tr"); 2453 td = tr.addTag("td"); 2454 String s = Utilities.padLeft("", '.', i*2); 2455 td.addText(s); 2456 a = td.addTag("a"); 2457 a.setAttribute("href", "#"+Utilities.nmtokenize(e.getValue())); 2458 a.addText(c.getCode()); 2459 } 2460 for (ConceptDefinitionComponent cc : c.getConcept()) { 2461 hasExtensions = addDefineRowToTable(t, cc, i+1, hasHierarchy, hasDisplay, comment, deprecated, maps, system, cs) || hasExtensions; 2462 } 2463 return hasExtensions; 2464 } 2465 2466 2467 private String makeAnchor(String codeSystem, String code) { 2468 String s = codeSystem+'-'+code; 2469 StringBuilder b = new StringBuilder(); 2470 for (char c : s.toCharArray()) { 2471 if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '.') 2472 b.append(c); 2473 else 2474 b.append('-'); 2475 } 2476 return b.toString(); 2477 } 2478 2479 private String getCodingReference(Coding cc, String system) { 2480 if (cc.getSystem().equals(system)) 2481 return "#"+cc.getCode(); 2482 if (cc.getSystem().equals("http://snomed.info/sct")) 2483 return "http://snomed.info/sct/"+cc.getCode(); 2484 if (cc.getSystem().equals("http://loinc.org")) 2485 return "http://s.details.loinc.org/LOINC/"+cc.getCode()+".html"; 2486 return null; 2487 } 2488 2489 private String getCharForEquivalence(TargetElementComponent mapping) { 2490 if (!mapping.hasEquivalence()) 2491 return ""; 2492 switch (mapping.getEquivalence()) { 2493 case EQUAL : return "="; 2494 case EQUIVALENT : return "~"; 2495 case WIDER : return "<"; 2496 case NARROWER : return ">"; 2497 case INEXACT : return "><"; 2498 case UNMATCHED : return "-"; 2499 case DISJOINT : return "!="; 2500 case NULL: return null; 2501 default: return "?"; 2502 } 2503 } 2504 2505 private List<TargetElementComponent> findMappingsForCode(String code, ConceptMap map) { 2506 List<TargetElementComponent> mappings = new ArrayList<TargetElementComponent>(); 2507 2508 for (SourceElementComponent c : map.getElement()) { 2509 if (c.getCode().equals(code)) 2510 mappings.addAll(c.getTarget()); 2511 } 2512 return mappings; 2513 } 2514 2515 private boolean generateComposition(XhtmlNode x, ValueSet vs, boolean header) { 2516 boolean hasExtensions = false; 2517 if (header) { 2518 XhtmlNode h = x.addTag("h2"); 2519 h.addText(vs.getName()); 2520 XhtmlNode p = x.addTag("p"); 2521 smartAddText(p, vs.getDescription()); 2522 if (vs.hasCopyrightElement()) 2523 generateCopyright(x, vs); 2524 } 2525 XhtmlNode p = x.addTag("p"); 2526 p.addText("This value set includes codes from the following code systems:"); 2527 2528 XhtmlNode ul = x.addTag("ul"); 2529 XhtmlNode li; 2530 for (UriType imp : vs.getCompose().getImport()) { 2531 li = ul.addTag("li"); 2532 li.addText("Import all the codes that are contained in "); 2533 AddVsRef(imp.getValue(), li); 2534 } 2535 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 2536 hasExtensions = genInclude(ul, inc, "Include") || hasExtensions; 2537 } 2538 for (ConceptSetComponent exc : vs.getCompose().getExclude()) { 2539 hasExtensions = genInclude(ul, exc, "Exclude") || hasExtensions; 2540 } 2541 return hasExtensions; 2542 } 2543 2544 private void AddVsRef(String value, XhtmlNode li) { 2545 2546 ValueSet vs = context.fetchResource(ValueSet.class, value); 2547 if (vs != null) { 2548 String ref = (String) vs.getUserData("path"); 2549 ref = adjustForPath(ref); 2550 XhtmlNode a = li.addTag("a"); 2551 a.setAttribute("href", ref == null ? "??" : ref.replace("\\", "/")); 2552 a.addText(value); 2553 } else { 2554 CodeSystem cs = context.fetchCodeSystem(value); 2555 if (cs != null) { 2556 String ref = (String) cs.getUserData("path"); 2557 ref = adjustForPath(ref); 2558 XhtmlNode a = li.addTag("a"); 2559 a.setAttribute("href", ref == null ? "??" : ref.replace("\\", "/")); 2560 a.addText(value); 2561 } else if (value.equals("http://snomed.info/sct") || value.equals("http://snomed.info/id")) { 2562 XhtmlNode a = li.addTag("a"); 2563 a.setAttribute("href", value); 2564 a.addText("SNOMED-CT"); 2565 } 2566 else { 2567 if (value.startsWith("http://hl7.org") && !Utilities.existsInList(value, "http://hl7.org/fhir/sid/icd-10-us")) 2568 throw new Error("Unable to resolve value set "+value); 2569 li.addText(value); 2570 } 2571 } 2572 } 2573 2574 private String adjustForPath(String ref) { 2575 if (prefix == null) 2576 return ref; 2577 else 2578 return prefix+ref; 2579 } 2580 2581 private boolean genInclude(XhtmlNode ul, ConceptSetComponent inc, String type) { 2582 boolean hasExtensions = false; 2583 XhtmlNode li; 2584 li = ul.addTag("li"); 2585 CodeSystem e = context.fetchCodeSystem(inc.getSystem()); 2586 2587 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 2588 li.addText(type+" all codes defined in "); 2589 addCsRef(inc, li, e); 2590 } else { 2591 if (inc.getConcept().size() > 0) { 2592 li.addText(type+" these codes as defined in "); 2593 addCsRef(inc, li, e); 2594 2595 XhtmlNode t = li.addTag("table"); 2596 boolean hasComments = false; 2597 boolean hasDefinition = false; 2598 for (ConceptReferenceComponent c : inc.getConcept()) { 2599 hasComments = hasComments || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_COMMENT); 2600 hasDefinition = hasDefinition || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION); 2601 } 2602 if (hasComments || hasDefinition) 2603 hasExtensions = true; 2604 addTableHeaderRowStandard(t, false, true, hasDefinition, hasComments, false); 2605 for (ConceptReferenceComponent c : inc.getConcept()) { 2606 XhtmlNode tr = t.addTag("tr"); 2607 tr.addTag("td").addText(c.getCode()); 2608 ConceptDefinitionComponent cc = getConceptForCode(e, c.getCode(), inc.getSystem()); 2609 2610 XhtmlNode td = tr.addTag("td"); 2611 if (!Utilities.noString(c.getDisplay())) 2612 td.addText(c.getDisplay()); 2613 else if (cc != null && !Utilities.noString(cc.getDisplay())) 2614 td.addText(cc.getDisplay()); 2615 2616 td = tr.addTag("td"); 2617 if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION)) 2618 smartAddText(td, ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_DEFINITION)); 2619 else if (cc != null && !Utilities.noString(cc.getDefinition())) 2620 smartAddText(td, cc.getDefinition()); 2621 2622 if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_COMMENT)) { 2623 smartAddText(tr.addTag("td"), "Note: "+ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_COMMENT)); 2624 } 2625 } 2626 } 2627 boolean first = true; 2628 for (ConceptSetFilterComponent f : inc.getFilter()) { 2629 if (first) { 2630 li.addText(type+" codes from "); 2631 first = false; 2632 } else 2633 li.addText(" and "); 2634 addCsRef(inc, li, e); 2635 li.addText(" where "+f.getProperty()+" "+describe(f.getOp())+" "); 2636 if (e != null && codeExistsInValueSet(e, f.getValue())) { 2637 XhtmlNode a = li.addTag("a"); 2638 a.addText(f.getValue()); 2639 a.setAttribute("href", prefix+getCsRef(e)+"#"+Utilities.nmtokenize(f.getValue())); 2640 } else 2641 li.addText(f.getValue()); 2642 String disp = ToolingExtensions.getDisplayHint(f); 2643 if (disp != null) 2644 li.addText(" ("+disp+")"); 2645 } 2646 } 2647 return hasExtensions; 2648 } 2649 2650 private String describe(FilterOperator opSimple) { 2651 switch (opSimple) { 2652 case EQUAL: return " = "; 2653 case ISA: return " is-a "; 2654 case ISNOTA: return " is-not-a "; 2655 case REGEX: return " matches (by regex) "; 2656 case NULL: return " ?? "; 2657 case IN: return " in "; 2658 case NOTIN: return " not in "; 2659 } 2660 return null; 2661 } 2662 2663 private ConceptDefinitionComponent getConceptForCode(CodeSystem e, String code, String system) { 2664 if (e == null) { 2665 return context.validateCode(system, code, null).asConceptDefinition(); 2666 } 2667 for (ConceptDefinitionComponent c : e.getConcept()) { 2668 ConceptDefinitionComponent v = getConceptForCode(c, code); 2669 if (v != null) 2670 return v; 2671 } 2672 return null; 2673 } 2674 2675 2676 2677 private ConceptDefinitionComponent getConceptForCode(ConceptDefinitionComponent c, String code) { 2678 if (code.equals(c.getCode())) 2679 return c; 2680 for (ConceptDefinitionComponent cc : c.getConcept()) { 2681 ConceptDefinitionComponent v = getConceptForCode(cc, code); 2682 if (v != null) 2683 return v; 2684 } 2685 return null; 2686 } 2687 2688 private <T extends Resource> void addCsRef(ConceptSetComponent inc, XhtmlNode li, T cs) { 2689 String ref = null; 2690 if (cs != null) { 2691 ref = (String) cs.getUserData("filename"); 2692 if (Utilities.noString(ref)) 2693 ref = (String) cs.getUserData("path"); 2694 } 2695 if (cs != null && ref != null) { 2696 if (!Utilities.noString(prefix) && ref.startsWith("http://hl7.org/fhir/")) 2697 ref = ref.substring(20)+"/index.html"; 2698 else if (!ref.endsWith(".html")) 2699 ref = ref + ".html"; 2700 XhtmlNode a = li.addTag("a"); 2701 a.setAttribute("href", prefix+ref.replace("\\", "/")); 2702 a.addText(inc.getSystem().toString()); 2703 } else 2704 li.addText(inc.getSystem().toString()); 2705 } 2706 2707 private String getCsRef(String system) { 2708 CodeSystem cs = context.fetchCodeSystem(system); 2709 return getCsRef(cs); 2710 } 2711 2712 private <T extends Resource> String getCsRef(T cs) { 2713 String ref = (String) cs.getUserData("filename"); 2714 if (ref == null) 2715 return "v2/0136/index.html"; // fix me - csvs! 2716 if (!ref.endsWith(".html")) 2717 ref = ref + ".html"; 2718 return ref.replace("\\", "/"); 2719 } 2720 2721 private boolean codeExistsInValueSet(CodeSystem cs, String code) { 2722 for (ConceptDefinitionComponent c : cs.getConcept()) { 2723 if (inConcept(code, c)) 2724 return true; 2725 } 2726 return false; 2727 } 2728 2729 private boolean inConcept(String code, ConceptDefinitionComponent c) { 2730 if (c.hasCodeElement() && c.getCode().equals(code)) 2731 return true; 2732 for (ConceptDefinitionComponent g : c.getConcept()) { 2733 if (inConcept(code, g)) 2734 return true; 2735 } 2736 return false; 2737 } 2738 2739 /** 2740 * This generate is optimised for the build tool in that it tracks the source extension. 2741 * But it can be used for any other use. 2742 * 2743 * @param vs 2744 * @param codeSystems 2745 * @throws DefinitionException 2746 * @throws Exception 2747 */ 2748 public void generate(OperationOutcome op) throws DefinitionException { 2749 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 2750 boolean hasSource = false; 2751 boolean success = true; 2752 for (OperationOutcomeIssueComponent i : op.getIssue()) { 2753 success = success && i.getSeverity() == IssueSeverity.INFORMATION; 2754 hasSource = hasSource || ExtensionHelper.hasExtension(i, ToolingExtensions.EXT_ISSUE_SOURCE); 2755 } 2756 if (success) 2757 x.addTag("p").addText("All OK"); 2758 if (op.getIssue().size() > 0) { 2759 XhtmlNode tbl = x.addTag("table"); 2760 tbl.setAttribute("class", "grid"); // on the basis that we'll most likely be rendered using the standard fhir css, but it doesn't really matter 2761 XhtmlNode tr = tbl.addTag("tr"); 2762 tr.addTag("td").addTag("b").addText("Severity"); 2763 tr.addTag("td").addTag("b").addText("Location"); 2764 tr.addTag("td").addTag("b").addText("Code"); 2765 tr.addTag("td").addTag("b").addText("Details"); 2766 tr.addTag("td").addTag("b").addText("Diagnostics"); 2767 if (hasSource) 2768 tr.addTag("td").addTag("b").addText("Source"); 2769 for (OperationOutcomeIssueComponent i : op.getIssue()) { 2770 tr = tbl.addTag("tr"); 2771 tr.addTag("td").addText(i.getSeverity().toString()); 2772 XhtmlNode td = tr.addTag("td"); 2773 boolean d = false; 2774 for (StringType s : i.getLocation()) { 2775 if (d) 2776 td.addText(", "); 2777 else 2778 d = true; 2779 td.addText(s.getValue()); 2780 } 2781 tr.addTag("td").addText(i.getCode().getDisplay()); 2782 tr.addTag("td").addText(gen(i.getDetails())); 2783 smartAddText(tr.addTag("td"), i.getDiagnostics()); 2784 if (hasSource) { 2785 Extension ext = ExtensionHelper.getExtension(i, ToolingExtensions.EXT_ISSUE_SOURCE); 2786 tr.addTag("td").addText(ext == null ? "" : gen(ext)); 2787 } 2788 } 2789 } 2790 inject(op, x, hasSource ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 2791 } 2792 2793 2794 private String gen(Extension extension) throws DefinitionException { 2795 if (extension.getValue() instanceof CodeType) 2796 return ((CodeType) extension.getValue()).getValue(); 2797 if (extension.getValue() instanceof Coding) 2798 return gen((Coding) extension.getValue()); 2799 2800 throw new DefinitionException("Unhandled type "+extension.getValue().getClass().getName()); 2801 } 2802 2803 private String gen(CodeableConcept code) { 2804 if (code == null) 2805 return null; 2806 if (code.hasText()) 2807 return code.getText(); 2808 if (code.hasCoding()) 2809 return gen(code.getCoding().get(0)); 2810 return null; 2811 } 2812 2813 private String gen(Coding code) { 2814 if (code == null) 2815 return null; 2816 if (code.hasDisplayElement()) 2817 return code.getDisplay(); 2818 if (code.hasCodeElement()) 2819 return code.getCode(); 2820 return null; 2821 } 2822 2823 public void generate(OperationDefinition opd) throws EOperationOutcome, FHIRException, IOException { 2824 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 2825 x.addTag("h2").addText(opd.getName()); 2826 x.addTag("p").addText(Utilities.capitalize(opd.getKind().toString())+": "+opd.getName()); 2827 addMarkdown(x, opd.getDescription()); 2828 2829 if (opd.getSystem()) 2830 x.addTag("p").addText("URL: [base]/$"+opd.getCode()); 2831 for (CodeType c : opd.getType()) { 2832 x.addTag("p").addText("URL: [base]/"+c.getValue()+"/$"+opd.getCode()); 2833 if (opd.getInstance()) 2834 x.addTag("p").addText("URL: [base]/"+c.getValue()+"/[id]/$"+opd.getCode()); 2835 } 2836 2837 x.addTag("p").addText("Parameters"); 2838 XhtmlNode tbl = x.addTag("table").setAttribute("class", "grid"); 2839 XhtmlNode tr = tbl.addTag("tr"); 2840 tr.addTag("td").addTag("b").addText("Use"); 2841 tr.addTag("td").addTag("b").addText("Name"); 2842 tr.addTag("td").addTag("b").addText("Cardinality"); 2843 tr.addTag("td").addTag("b").addText("Type"); 2844 tr.addTag("td").addTag("b").addText("Binding"); 2845 tr.addTag("td").addTag("b").addText("Documentation"); 2846 for (OperationDefinitionParameterComponent p : opd.getParameter()) { 2847 genOpParam(tbl, "", p); 2848 } 2849 addMarkdown(x, opd.getComment()); 2850 inject(opd, x, NarrativeStatus.GENERATED); 2851 } 2852 2853 private void genOpParam(XhtmlNode tbl, String path, OperationDefinitionParameterComponent p) throws EOperationOutcome, FHIRException, IOException { 2854 XhtmlNode tr; 2855 tr = tbl.addTag("tr"); 2856 tr.addTag("td").addText(p.getUse().toString()); 2857 tr.addTag("td").addText(path+p.getName()); 2858 tr.addTag("td").addText(Integer.toString(p.getMin())+".."+p.getMax()); 2859 tr.addTag("td").addText(p.hasType() ? p.getType() : ""); 2860 XhtmlNode td = tr.addTag("td"); 2861 if (p.hasBinding() && p.getBinding().hasValueSet()) { 2862 if (p.getBinding().getValueSet() instanceof Reference) 2863 AddVsRef(p.getBinding().getValueSetReference().getReference(), td); 2864 else 2865 td.addTag("a").setAttribute("href", p.getBinding().getValueSetUriType().getValue()).addText("External Reference"); 2866 td.addText(" ("+p.getBinding().getStrength().getDisplay()+")"); 2867 } 2868 addMarkdown(tr.addTag("td"), p.getDocumentation()); 2869 if (!p.hasType()) { 2870 for (OperationDefinitionParameterComponent pp : p.getPart()) { 2871 genOpParam(tbl, path+p.getName()+".", pp); 2872 } 2873 } 2874 } 2875 2876 private void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException { 2877 if (text != null) { 2878 // 1. custom FHIR extensions 2879 while (text.contains("[[[")) { 2880 String left = text.substring(0, text.indexOf("[[[")); 2881 String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]")); 2882 String right = text.substring(text.indexOf("]]]")+3); 2883 String url = link; 2884 String[] parts = link.split("\\#"); 2885 StructureDefinition p = context.fetchResource(StructureDefinition.class, parts[0]); 2886 if (p == null) 2887 p = context.fetchTypeDefinition(parts[0]); 2888 if (p == null) 2889 p = context.fetchResource(StructureDefinition.class, link); 2890 if (p != null) { 2891 url = p.getUserString("path"); 2892 if (url == null) 2893 url = p.getUserString("filename"); 2894 } else 2895 throw new DefinitionException("Unable to resolve markdown link "+link); 2896 2897 text = left+"["+link+"]("+url+")"+right; 2898 } 2899 2900 // 2. markdown 2901 String s = new MarkDownProcessor(Dialect.DARING_FIREBALL).process(Utilities.escapeXml(text), "NarrativeGenerator"); 2902 XhtmlParser p = new XhtmlParser(); 2903 XhtmlNode m; 2904 try { 2905 m = p.parse("<div>"+s+"</div>", "div"); 2906 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 2907 throw new FHIRFormatError(e.getMessage(), e); 2908 } 2909 x.getChildNodes().addAll(m.getChildNodes()); 2910 } 2911 } 2912 2913 public void generate(CompartmentDefinition cpd) { 2914 StringBuilder in = new StringBuilder(); 2915 StringBuilder out = new StringBuilder(); 2916 for (CompartmentDefinitionResourceComponent cc: cpd.getResource()) { 2917 CommaSeparatedStringBuilder rules = new CommaSeparatedStringBuilder(); 2918 if (!cc.hasParam()) { 2919 out.append(" <li><a href=\"").append(cc.getCode().toLowerCase()).append(".html\">").append(cc.getCode()).append("</a></li>\r\n"); 2920 } else if (!rules.equals("{def}")) { 2921 for (StringType p : cc.getParam()) 2922 rules.append(p.asStringValue()); 2923 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"); 2924 } 2925 } 2926 XhtmlNode x; 2927 try { 2928 x = new XhtmlParser().parseFragment("<div><p>\r\nThe following resources may be in this compartment:\r\n</p>\r\n" + 2929 "<table class=\"grid\">\r\n"+ 2930 " <tr><td><b>Resource</b></td><td><b>Inclusion Criteria</b></td></tr>\r\n"+ 2931 in.toString()+ 2932 "</table>\r\n"+ 2933 "<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" + 2934 "<p>\r\n\r\n</p>\r\n" + 2935 "<p>\r\nThe following resources are never in this compartment:\r\n</p>\r\n" + 2936 "<ul>\r\n"+ 2937 out.toString()+ 2938 "</ul></div>\r\n"); 2939 inject(cpd, x, NarrativeStatus.GENERATED); 2940 } catch (Exception e) { 2941 e.printStackTrace(); 2942 } 2943 } 2944 2945 public void generate(Conformance conf) { 2946 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 2947 x.addTag("h2").addText(conf.getName()); 2948 smartAddText(x.addTag("p"), conf.getDescription()); 2949 ConformanceRestComponent rest = conf.getRest().get(0); 2950 XhtmlNode t = x.addTag("table"); 2951 addTableRow(t, "Mode", rest.getMode().toString()); 2952 addTableRow(t, "Description", rest.getDocumentation()); 2953 2954 addTableRow(t, "Transaction", showOp(rest, SystemRestfulInteraction.TRANSACTION)); 2955 addTableRow(t, "System History", showOp(rest, SystemRestfulInteraction.HISTORYSYSTEM)); 2956 addTableRow(t, "System Search", showOp(rest, SystemRestfulInteraction.SEARCHSYSTEM)); 2957 2958 t = x.addTag("table"); 2959 XhtmlNode tr = t.addTag("tr"); 2960 tr.addTag("th").addTag("b").addText("Resource Type"); 2961 tr.addTag("th").addTag("b").addText("Profile"); 2962 tr.addTag("th").addTag("b").addText("Read"); 2963 tr.addTag("th").addTag("b").addText("V-Read"); 2964 tr.addTag("th").addTag("b").addText("Search"); 2965 tr.addTag("th").addTag("b").addText("Update"); 2966 tr.addTag("th").addTag("b").addText("Updates"); 2967 tr.addTag("th").addTag("b").addText("Create"); 2968 tr.addTag("th").addTag("b").addText("Delete"); 2969 tr.addTag("th").addTag("b").addText("History"); 2970 2971 for (ConformanceRestResourceComponent r : rest.getResource()) { 2972 tr = t.addTag("tr"); 2973 tr.addTag("td").addText(r.getType()); 2974 if (r.hasProfile()) { 2975 XhtmlNode a = tr.addTag("td").addTag("a"); 2976 a.addText(r.getProfile().getReference()); 2977 a.setAttribute("href", prefix+r.getProfile().getReference()); 2978 } 2979 tr.addTag("td").addText(showOp(r, TypeRestfulInteraction.READ)); 2980 tr.addTag("td").addText(showOp(r, TypeRestfulInteraction.VREAD)); 2981 tr.addTag("td").addText(showOp(r, TypeRestfulInteraction.SEARCHTYPE)); 2982 tr.addTag("td").addText(showOp(r, TypeRestfulInteraction.UPDATE)); 2983 tr.addTag("td").addText(showOp(r, TypeRestfulInteraction.HISTORYINSTANCE)); 2984 tr.addTag("td").addText(showOp(r, TypeRestfulInteraction.CREATE)); 2985 tr.addTag("td").addText(showOp(r, TypeRestfulInteraction.DELETE)); 2986 tr.addTag("td").addText(showOp(r, TypeRestfulInteraction.HISTORYTYPE)); 2987 } 2988 2989 inject(conf, x, NarrativeStatus.GENERATED); 2990 } 2991 2992 private String showOp(ConformanceRestResourceComponent r, TypeRestfulInteraction on) { 2993 for (ResourceInteractionComponent op : r.getInteraction()) { 2994 if (op.getCode() == on) 2995 return "y"; 2996 } 2997 return ""; 2998 } 2999 3000 private String showOp(ConformanceRestComponent r, SystemRestfulInteraction on) { 3001 for (SystemInteractionComponent op : r.getInteraction()) { 3002 if (op.getCode() == on) 3003 return "y"; 3004 } 3005 return ""; 3006 } 3007 3008 private void addTableRow(XhtmlNode t, String name, String value) { 3009 XhtmlNode tr = t.addTag("tr"); 3010 tr.addTag("td").addText(name); 3011 tr.addTag("td").addText(value); 3012 } 3013 3014 public XhtmlNode generateDocumentNarrative(Bundle feed) { 3015 /* 3016 When the document is presented for human consumption, applications must present the collated narrative portions of the following resources in order: 3017 * The Composition resource 3018 * The Subject resource 3019 * Resources referenced in the section.content 3020 */ 3021 XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); 3022 Composition comp = (Composition) feed.getEntry().get(0).getResource(); 3023 root.getChildNodes().add(comp.getText().getDiv()); 3024 Resource subject = ResourceUtilities.getById(feed, null, comp.getSubject().getReference()); 3025 if (subject != null && subject instanceof DomainResource) { 3026 root.addTag("hr"); 3027 root.getChildNodes().add(((DomainResource)subject).getText().getDiv()); 3028 } 3029 List<SectionComponent> sections = comp.getSection(); 3030 renderSections(feed, root, sections, 1); 3031 return root; 3032 } 3033 3034 private void renderSections(Bundle feed, XhtmlNode node, List<SectionComponent> sections, int level) { 3035 for (SectionComponent section : sections) { 3036 node.addTag("hr"); 3037 if (section.hasTitleElement()) 3038 node.addTag("h"+Integer.toString(level)).addText(section.getTitle()); 3039// else if (section.hasCode()) 3040// node.addTag("h"+Integer.toString(level)).addText(displayCodeableConcept(section.getCode())); 3041 3042// if (section.hasText()) { 3043// node.getChildNodes().add(section.getText().getDiv()); 3044// } 3045// 3046// if (!section.getSection().isEmpty()) { 3047// renderSections(feed, node.addTag("blockquote"), section.getSection(), level+1); 3048// } 3049 } 3050 } 3051 3052}