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