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