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