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