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