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