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