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