001package org.hl7.fhir.dstu3.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;
036import java.text.ParseException;
037import java.text.SimpleDateFormat;
038
039/*
040Copyright (c) 2011+, HL7, Inc
041  All rights reserved.
042
043  Redistribution and use in source and binary forms, with or without modification,
044  are permitted provided that the following conditions are met:
045
046   * Redistributions of source code must retain the above copyright notice, this
047     list of conditions and the following disclaimer.
048   * Redistributions in binary form must reproduce the above copyright notice,
049     this list of conditions and the following disclaimer in the documentation
050     and/or other materials provided with the distribution.
051   * Neither the name of HL7 nor the names of its contributors may be used to
052     endorse or promote products derived from this software without specific
053     prior written permission.
054
055  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
056  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
057  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
058  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
059  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
060  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
061  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
062  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
063  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
064  POSSIBILITY OF SUCH DAMAGE.
065
066*/
067
068import java.util.ArrayList;
069import java.util.Collections;
070import java.util.Date;
071import java.util.HashMap;
072import java.util.HashSet;
073import java.util.List;
074import java.util.Map;
075
076import org.apache.commons.codec.binary.Base64;
077import org.apache.commons.io.output.ByteArrayOutputStream;
078import org.apache.commons.lang3.NotImplementedException;
079import org.hl7.fhir.dstu3.conformance.ProfileUtilities;
080import org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider;
081import org.hl7.fhir.dstu3.context.IWorkerContext;
082import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult;
083import org.hl7.fhir.dstu3.formats.FormatUtilities;
084import org.hl7.fhir.dstu3.formats.IParser.OutputStyle;
085import org.hl7.fhir.dstu3.model.Address;
086import org.hl7.fhir.dstu3.model.Annotation;
087import org.hl7.fhir.dstu3.model.Attachment;
088import org.hl7.fhir.dstu3.model.Base;
089import org.hl7.fhir.dstu3.model.Base64BinaryType;
090import org.hl7.fhir.dstu3.model.BooleanType;
091import org.hl7.fhir.dstu3.model.Bundle;
092import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
093import org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent;
094import org.hl7.fhir.dstu3.model.Bundle.BundleEntryResponseComponent;
095import org.hl7.fhir.dstu3.model.Bundle.BundleEntrySearchComponent;
096import org.hl7.fhir.dstu3.model.Bundle.BundleType;
097import org.hl7.fhir.dstu3.model.CapabilityStatement;
098import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestComponent;
099import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
100import org.hl7.fhir.dstu3.model.CapabilityStatement.ResourceInteractionComponent;
101import org.hl7.fhir.dstu3.model.CapabilityStatement.SystemInteractionComponent;
102import org.hl7.fhir.dstu3.model.CapabilityStatement.SystemRestfulInteraction;
103import org.hl7.fhir.dstu3.model.CapabilityStatement.TypeRestfulInteraction;
104import org.hl7.fhir.dstu3.model.CodeSystem;
105import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode;
106import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemFilterComponent;
107import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
108import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionDesignationComponent;
109import org.hl7.fhir.dstu3.model.CodeSystem.PropertyComponent;
110import org.hl7.fhir.dstu3.model.CodeType;
111import org.hl7.fhir.dstu3.model.CodeableConcept;
112import org.hl7.fhir.dstu3.model.Coding;
113import org.hl7.fhir.dstu3.model.CompartmentDefinition;
114import org.hl7.fhir.dstu3.model.CompartmentDefinition.CompartmentDefinitionResourceComponent;
115import org.hl7.fhir.dstu3.model.Composition;
116import org.hl7.fhir.dstu3.model.Composition.SectionComponent;
117import org.hl7.fhir.dstu3.model.ConceptMap;
118import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent;
119import org.hl7.fhir.dstu3.model.ConceptMap.OtherElementComponent;
120import org.hl7.fhir.dstu3.model.ConceptMap.SourceElementComponent;
121import org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent;
122import org.hl7.fhir.dstu3.model.ContactDetail;
123import org.hl7.fhir.dstu3.model.ContactPoint;
124import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem;
125import org.hl7.fhir.dstu3.model.DateTimeType;
126import org.hl7.fhir.dstu3.model.DiagnosticReport;
127import org.hl7.fhir.dstu3.model.DomainResource;
128import org.hl7.fhir.dstu3.model.Dosage;
129import org.hl7.fhir.dstu3.model.ElementDefinition;
130import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
131import org.hl7.fhir.dstu3.model.Enumeration;
132import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence;
133import org.hl7.fhir.dstu3.model.Extension;
134import org.hl7.fhir.dstu3.model.ExtensionHelper;
135import org.hl7.fhir.dstu3.model.HumanName;
136import org.hl7.fhir.dstu3.model.HumanName.NameUse;
137import org.hl7.fhir.dstu3.model.IdType;
138import org.hl7.fhir.dstu3.model.Identifier;
139import org.hl7.fhir.dstu3.model.ImplementationGuide;
140import org.hl7.fhir.dstu3.model.InstantType;
141import org.hl7.fhir.dstu3.model.Meta;
142import org.hl7.fhir.dstu3.model.MetadataResource;
143import org.hl7.fhir.dstu3.model.Narrative;
144import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus;
145import org.hl7.fhir.dstu3.model.OperationDefinition;
146import org.hl7.fhir.dstu3.model.OperationDefinition.OperationDefinitionParameterComponent;
147import org.hl7.fhir.dstu3.model.OperationOutcome;
148import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity;
149import org.hl7.fhir.dstu3.model.OperationOutcome.OperationOutcomeIssueComponent;
150import org.hl7.fhir.dstu3.model.Period;
151import org.hl7.fhir.dstu3.model.PrimitiveType;
152import org.hl7.fhir.dstu3.model.Property;
153import org.hl7.fhir.dstu3.model.Quantity;
154import org.hl7.fhir.dstu3.model.Questionnaire;
155import org.hl7.fhir.dstu3.model.Range;
156import org.hl7.fhir.dstu3.model.Ratio;
157import org.hl7.fhir.dstu3.model.Reference;
158import org.hl7.fhir.dstu3.model.Resource;
159import org.hl7.fhir.dstu3.model.SampledData;
160import org.hl7.fhir.dstu3.model.Signature;
161import org.hl7.fhir.dstu3.model.StringType;
162import org.hl7.fhir.dstu3.model.StructureDefinition;
163import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
164import org.hl7.fhir.dstu3.model.Timing;
165import org.hl7.fhir.dstu3.model.Timing.EventTiming;
166import org.hl7.fhir.dstu3.model.Timing.TimingRepeatComponent;
167import org.hl7.fhir.dstu3.model.Timing.UnitsOfTime;
168import org.hl7.fhir.dstu3.model.Type;
169import org.hl7.fhir.dstu3.model.UriType;
170import org.hl7.fhir.dstu3.model.UsageContext;
171import org.hl7.fhir.dstu3.model.ValueSet;
172import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent;
173import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceDesignationComponent;
174import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
175import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetFilterComponent;
176import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator;
177import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
178import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
179import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionParameterComponent;
180import org.hl7.fhir.dstu3.terminologies.CodeSystemUtilities;
181import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
182import org.hl7.fhir.exceptions.DefinitionException;
183import org.hl7.fhir.exceptions.FHIRException;
184import org.hl7.fhir.exceptions.FHIRFormatError;
185import org.hl7.fhir.exceptions.TerminologyServiceException;
186import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
187import org.hl7.fhir.utilities.LoincLinker;
188import org.hl7.fhir.utilities.MarkDownProcessor;
189import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
190import org.hl7.fhir.utilities.Utilities;
191import org.hl7.fhir.utilities.xhtml.NodeType;
192import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
193import org.hl7.fhir.utilities.xhtml.XhtmlNode;
194import org.hl7.fhir.utilities.xhtml.XhtmlParser;
195import org.hl7.fhir.utilities.xml.XMLUtil;
196import org.hl7.fhir.utilities.xml.XmlGenerator;
197import org.w3c.dom.Element;
198
199@Deprecated
200public class NarrativeGenerator implements INarrativeGenerator {
201
202  public class ResourceContext {
203    Bundle bundleResource;
204    
205    DomainResource resourceResource;
206    
207    public ResourceContext(Bundle bundle, DomainResource dr) {
208      super();
209      this.bundleResource = bundle;
210      this.resourceResource = dr;
211    }
212
213    public ResourceContext(Element bundle, Element doc) {
214    }
215
216    public ResourceContext(org.hl7.fhir.dstu3.elementmodel.Element bundle, org.hl7.fhir.dstu3.elementmodel.Element er) {
217    }
218
219    public Resource resolve(String value) {
220      if (value.startsWith("#")) {
221        for (Resource r : resourceResource.getContained()) {
222          if (r.getId().equals(value.substring(1)))
223            return r;
224        }
225        return null;
226      }
227      if (bundleResource != null) {
228        for (BundleEntryComponent be : bundleResource.getEntry()) {
229          if (be.getFullUrl().equals(value))
230            return be.getResource();
231          if (value.equals(be.getResource().fhirType()+"/"+be.getResource().getId()))
232            return be.getResource();
233        }
234      }
235      return null;
236    }
237
238  }
239
240  private static final String ABSTRACT_CODE_HINT = "This code is not selectable ('Abstract')";
241
242  public interface IReferenceResolver {
243
244    ResourceWithReference resolve(String url);
245
246  }
247
248  private Bundle bundle;
249  private String definitionsTarget;
250  private String corePath;
251  private String destDir;
252  private ProfileKnowledgeProvider pkp;
253  
254  public boolean generate(Bundle b, boolean evenIfAlreadyHasNarrative) throws EOperationOutcome, FHIRException, IOException {
255    boolean res = false;
256    this.bundle = b;
257    for (BundleEntryComponent be : b.getEntry()) {
258      if (be.hasResource() && be.getResource() instanceof DomainResource) {
259        DomainResource dr = (DomainResource) be.getResource();
260        if (evenIfAlreadyHasNarrative || !dr.getText().hasDiv())
261          res = generate(new ResourceContext(b, dr), dr) || res;
262      }
263    }
264    return res;
265  }
266
267  public boolean generate(DomainResource r) throws EOperationOutcome, FHIRException, IOException {
268    return generate(null, r);
269  }
270  
271  public boolean generate(ResourceContext rcontext, DomainResource r) throws EOperationOutcome, FHIRException, IOException {
272    if (rcontext == null)
273      rcontext = new ResourceContext(null, r);
274    
275    if (r instanceof ConceptMap) {
276      return generate(rcontext, (ConceptMap) r); // Maintainer = Grahame
277    } else if (r instanceof ValueSet) {
278      return generate(rcontext, (ValueSet) r, true); // Maintainer = Grahame
279    } else if (r instanceof CodeSystem) {
280      return generate(rcontext, (CodeSystem) r, true); // Maintainer = Grahame
281    } else if (r instanceof OperationOutcome) {
282      return generate(rcontext, (OperationOutcome) r); // Maintainer = Grahame
283    } else if (r instanceof CapabilityStatement) {
284      return generate(rcontext, (CapabilityStatement) r);   // Maintainer = Grahame
285    } else if (r instanceof CompartmentDefinition) {
286      return generate(rcontext, (CompartmentDefinition) r);   // Maintainer = Grahame
287    } else if (r instanceof OperationDefinition) {
288      return generate(rcontext, (OperationDefinition) r);   // Maintainer = Grahame
289    } else if (r instanceof StructureDefinition) {
290      return generate(rcontext, (StructureDefinition) r);   // Maintainer = Grahame
291    } else if (r instanceof ImplementationGuide) {
292      return generate(rcontext, (ImplementationGuide) r);   // Maintainer = Lloyd (until Grahame wants to take over . . . :))
293    } else if (r instanceof DiagnosticReport) {
294      inject(r, generateDiagnosticReport(new ResourceWrapperDirect(r)),  NarrativeStatus.GENERATED);   // Maintainer = Grahame
295      return true;
296    } else {
297      StructureDefinition p = null;
298      if (r.hasMeta())
299        for (UriType pu : r.getMeta().getProfile())
300          if (p == null)
301            p = context.fetchResource(StructureDefinition.class, pu.getValue());
302      if (p == null)
303        p = context.fetchResource(StructureDefinition.class, r.getResourceType().toString());
304      if (p == null)
305        p = context.fetchTypeDefinition(r.getResourceType().toString().toLowerCase());
306      if (p != null)
307        return generateByProfile(r, p, true);
308      else
309        return false;
310    }
311  }
312
313  private interface PropertyWrapper {
314    public String getName();
315    public boolean hasValues();
316    public List<BaseWrapper> getValues();
317    public String getTypeCode();
318    public String getDefinition();
319    public int getMinCardinality();
320    public int getMaxCardinality();
321    public StructureDefinition getStructure();
322    public BaseWrapper value();
323  }
324
325  private interface ResourceWrapper {
326    public List<ResourceWrapper> getContained();
327    public String getId();
328    public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException;
329    public String getName();
330    public List<PropertyWrapper> children();
331  }
332
333  private interface BaseWrapper {
334    public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException;
335    public List<PropertyWrapper> children();
336    public PropertyWrapper getChildByName(String tail);
337  }
338
339  private class BaseWrapperElement implements BaseWrapper {
340    private Element element;
341    private String type;
342    private StructureDefinition structure;
343    private ElementDefinition definition;
344    private List<ElementDefinition> children;
345    private List<PropertyWrapper> list;
346
347    public BaseWrapperElement(Element element, String type, StructureDefinition structure, ElementDefinition definition) {
348      this.element = element;
349      this.type = type;
350      this.structure = structure;
351      this.definition = definition;
352    }
353
354    @Override
355    public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException {
356      if (type == null || type.equals("Resource") || type.equals("BackboneElement") || type.equals("Element"))
357        return null;
358
359      String xml;
360                try {
361                        xml = new XmlGenerator().generate(element);
362                } catch (org.hl7.fhir.exceptions.FHIRException e) {
363                        throw new FHIRException(e.getMessage(), e);
364                }
365      return context.newXmlParser().setOutputStyle(OutputStyle.PRETTY).parseType(xml, type);
366    }
367
368    @Override
369    public List<PropertyWrapper> children() {
370      if (list == null) {
371        children = ProfileUtilities.getChildList(structure, definition);
372        list = new ArrayList<NarrativeGenerator.PropertyWrapper>();
373        for (ElementDefinition child : children) {
374          List<Element> elements = new ArrayList<Element>();
375          XMLUtil.getNamedChildrenWithWildcard(element, tail(child.getPath()), elements);
376          list.add(new PropertyWrapperElement(structure, child, elements));
377        }
378      }
379      return list;
380    }
381
382    @Override
383    public PropertyWrapper getChildByName(String name) {
384      for (PropertyWrapper p : children())
385        if (p.getName().equals(name))
386          return p;
387      return null;
388    }
389
390  }
391
392  private class PropertyWrapperElement implements PropertyWrapper {
393
394    private StructureDefinition structure;
395    private ElementDefinition definition;
396    private List<Element> values;
397    private List<BaseWrapper> list;
398
399    public PropertyWrapperElement(StructureDefinition structure, ElementDefinition definition, List<Element> values) {
400      this.structure = structure;
401      this.definition = definition;
402      this.values = values;
403    }
404
405    @Override
406    public String getName() {
407      return tail(definition.getPath());
408    }
409
410    @Override
411    public boolean hasValues() {
412      return values.size() > 0;
413    }
414
415    @Override
416    public List<BaseWrapper> getValues() {
417      if (list == null) {
418        list = new ArrayList<NarrativeGenerator.BaseWrapper>();
419        for (Element e : values)
420          list.add(new BaseWrapperElement(e, determineType(e), structure, definition));
421      }
422      return list;
423    }
424    private String determineType(Element e) {
425      if (definition.getType().isEmpty())
426        return null;
427      if (definition.getType().size() == 1) {
428        if (definition.getType().get(0).getCode().equals("Element") || definition.getType().get(0).getCode().equals("BackboneElement"))
429          return null;
430        return definition.getType().get(0).getCode();
431      }
432      String t = e.getNodeName().substring(tail(definition.getPath()).length()-3);
433      boolean allReference = true;
434      for (TypeRefComponent tr : definition.getType()) {
435        if (!tr.getCode().equals("Reference"))
436          allReference = false;
437      }
438      if (allReference)
439        return "Reference";
440
441      if (isPrimitive(Utilities.uncapitalize(t)))
442        return Utilities.uncapitalize(t);
443      else
444        return t;
445    }
446
447    private boolean isPrimitive(String code) {
448      StructureDefinition sd = context.fetchTypeDefinition(code);
449      return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
450    }
451
452    @Override
453    public String getTypeCode() {
454      if (definition == null || definition.getType().size() != 1)
455        throw new Error("not handled");
456      return definition.getType().get(0).getCode();
457    }
458
459    @Override
460    public String getDefinition() {
461      if (definition == null)
462        throw new Error("not handled");
463      return definition.getDefinition();
464    }
465
466    @Override
467    public int getMinCardinality() {
468      if (definition == null)
469        throw new Error("not handled");
470      return definition.getMin();
471    }
472
473    @Override
474    public int getMaxCardinality() {
475      if (definition == null)
476        throw new Error("not handled");
477      return definition.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(definition.getMax());
478    }
479
480    @Override
481    public StructureDefinition getStructure() {
482      return structure;
483    }
484
485    @Override
486    public BaseWrapper value() {
487      if (getValues().size() != 1)
488        throw new Error("Access single value, but value count is "+getValues().size());
489      return getValues().get(0);
490    }
491
492  }
493
494  private class BaseWrapperMetaElement implements BaseWrapper {
495    private org.hl7.fhir.dstu3.elementmodel.Element element;
496    private String type;
497    private StructureDefinition structure;
498    private ElementDefinition definition;
499    private List<ElementDefinition> children;
500    private List<PropertyWrapper> list;
501
502    public BaseWrapperMetaElement(org.hl7.fhir.dstu3.elementmodel.Element element, String type, StructureDefinition structure, ElementDefinition definition) {
503      this.element = element;
504      this.type = type;
505      this.structure = structure;
506      this.definition = definition;
507    }
508
509    @Override
510    public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException {
511      if (type == null || type.equals("Resource") || type.equals("BackboneElement") || type.equals("Element"))
512        return null;
513
514      if (element.hasElementProperty())
515        return null;
516      ByteArrayOutputStream xml = new ByteArrayOutputStream();
517      try {
518        new org.hl7.fhir.dstu3.elementmodel.XmlParser(context).compose(element, xml, OutputStyle.PRETTY, null);
519      } catch (Exception e) {
520        throw new FHIRException(e.getMessage(), e);
521      }
522      return context.newXmlParser().setOutputStyle(OutputStyle.PRETTY).parseType(xml.toString(), type);
523    }
524
525    @Override
526    public List<PropertyWrapper> children() {
527      if (list == null) {
528        children = ProfileUtilities.getChildList(structure, definition);
529        list = new ArrayList<NarrativeGenerator.PropertyWrapper>();
530        for (ElementDefinition child : children) {
531          List<org.hl7.fhir.dstu3.elementmodel.Element> elements = new ArrayList<org.hl7.fhir.dstu3.elementmodel.Element>();
532          String name = tail(child.getPath());
533          if (name.endsWith("[x]"))
534            element.getNamedChildrenWithWildcard(name, elements);
535          else
536            element.getNamedChildren(name, elements);
537          list.add(new PropertyWrapperMetaElement(structure, child, elements));
538        }
539      }
540      return list;
541    }
542
543    @Override
544    public PropertyWrapper getChildByName(String name) {
545      for (PropertyWrapper p : children())
546        if (p.getName().equals(name))
547          return p;
548      return null;
549    }
550
551  }
552  public class ResurceWrapperMetaElement implements ResourceWrapper {
553    private org.hl7.fhir.dstu3.elementmodel.Element wrapped;
554    private List<ResourceWrapper> list;
555    private List<PropertyWrapper> list2;
556    private StructureDefinition definition;
557    public ResurceWrapperMetaElement(org.hl7.fhir.dstu3.elementmodel.Element wrapped) {
558      this.wrapped = wrapped;
559      this.definition = wrapped.getProperty().getStructure();
560    }
561
562    @Override
563    public List<ResourceWrapper> getContained() {
564      if (list == null) {
565        List<org.hl7.fhir.dstu3.elementmodel.Element> children = wrapped.getChildrenByName("contained");
566        list = new ArrayList<NarrativeGenerator.ResourceWrapper>();
567        for (org.hl7.fhir.dstu3.elementmodel.Element e : children) {
568          list.add(new ResurceWrapperMetaElement(e));
569        }
570      }
571      return list;
572    }
573
574    @Override
575    public String getId() {
576      return wrapped.getNamedChildValue("id");
577    }
578
579    @Override
580    public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException {
581      org.hl7.fhir.dstu3.elementmodel.Element txt = wrapped.getNamedChild("text");
582      if (txt == null)
583        return null;
584      org.hl7.fhir.dstu3.elementmodel.Element div = txt.getNamedChild("div");
585      if (div == null)
586        return null;
587      else
588        return div.getXhtml();
589    }
590
591    @Override
592    public String getName() {
593      return wrapped.getName();
594    }
595
596    @Override
597    public List<PropertyWrapper> children() {
598      if (list2 == null) {
599        List<ElementDefinition> children = ProfileUtilities.getChildList(definition, definition.getSnapshot().getElement().get(0));
600        list2 = new ArrayList<NarrativeGenerator.PropertyWrapper>();
601        for (ElementDefinition child : children) {
602          List<org.hl7.fhir.dstu3.elementmodel.Element> elements = new ArrayList<org.hl7.fhir.dstu3.elementmodel.Element>();
603          if (child.getPath().endsWith("[x]"))
604            wrapped.getNamedChildrenWithWildcard(tail(child.getPath()), elements);
605          else
606            wrapped.getNamedChildren(tail(child.getPath()), elements);
607          list2.add(new PropertyWrapperMetaElement(definition, child, elements));
608        }
609      }
610      return list2;
611    }
612  }
613
614  private class PropertyWrapperMetaElement implements PropertyWrapper {
615
616    private StructureDefinition structure;
617    private ElementDefinition definition;
618    private List<org.hl7.fhir.dstu3.elementmodel.Element> values;
619    private List<BaseWrapper> list;
620
621    public PropertyWrapperMetaElement(StructureDefinition structure, ElementDefinition definition, List<org.hl7.fhir.dstu3.elementmodel.Element> values) {
622      this.structure = structure;
623      this.definition = definition;
624      this.values = values;
625    }
626
627    @Override
628    public String getName() {
629      return tail(definition.getPath());
630    }
631
632    @Override
633    public boolean hasValues() {
634      return values.size() > 0;
635    }
636
637    @Override
638    public List<BaseWrapper> getValues() {
639      if (list == null) {
640        list = new ArrayList<NarrativeGenerator.BaseWrapper>();
641        for (org.hl7.fhir.dstu3.elementmodel.Element e : values)
642          list.add(new BaseWrapperMetaElement(e, e.fhirType(), structure, definition));
643      }
644      return list;
645    }
646
647    @Override
648    public String getTypeCode() {
649      throw new Error("todo");
650    }
651
652    @Override
653    public String getDefinition() {
654      throw new Error("todo");
655    }
656
657    @Override
658    public int getMinCardinality() {
659      throw new Error("todo");
660      //      return definition.getMin();
661    }
662
663    @Override
664    public int getMaxCardinality() {
665      throw new Error("todo");
666    }
667
668    @Override
669    public StructureDefinition getStructure() {
670      return structure;
671    }
672
673    @Override
674    public BaseWrapper value() {
675      if (getValues().size() != 1)
676        throw new Error("Access single value, but value count is "+getValues().size());
677      return getValues().get(0);
678    }
679
680  }
681
682  private class ResurceWrapperElement implements ResourceWrapper {
683
684    private Element wrapped;
685    private StructureDefinition definition;
686    private List<ResourceWrapper> list;
687    private List<PropertyWrapper> list2;
688
689    public ResurceWrapperElement(Element wrapped, StructureDefinition definition) {
690      this.wrapped = wrapped;
691      this.definition = definition;
692    }
693
694    @Override
695    public List<ResourceWrapper> getContained() {
696      if (list == null) {
697        List<Element> children = new ArrayList<Element>();
698        XMLUtil.getNamedChildren(wrapped, "contained", children);
699        list = new ArrayList<NarrativeGenerator.ResourceWrapper>();
700        for (Element e : children) {
701          Element c = XMLUtil.getFirstChild(e);
702          list.add(new ResurceWrapperElement(c, context.fetchTypeDefinition(c.getNodeName())));
703        }
704      }
705      return list;
706    }
707
708    @Override
709    public String getId() {
710      return XMLUtil.getNamedChildValue(wrapped, "id");
711    }
712
713    @Override
714    public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException {
715      Element txt = XMLUtil.getNamedChild(wrapped, "text");
716      if (txt == null)
717        return null;
718      Element div = XMLUtil.getNamedChild(txt, "div");
719      if (div == null)
720        return null;
721      try {
722                        return new XhtmlParser().parse(new XmlGenerator().generate(div), "div");
723                } catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
724                        throw new FHIRFormatError(e.getMessage(), e);
725                } catch (org.hl7.fhir.exceptions.FHIRException e) {
726                        throw new FHIRException(e.getMessage(), e);
727                }
728    }
729
730    @Override
731    public String getName() {
732      return wrapped.getNodeName();
733    }
734
735    @Override
736    public List<PropertyWrapper> children() {
737      if (list2 == null) {
738        List<ElementDefinition> children = ProfileUtilities.getChildList(definition, definition.getSnapshot().getElement().get(0));
739        list2 = new ArrayList<NarrativeGenerator.PropertyWrapper>();
740        for (ElementDefinition child : children) {
741          List<Element> elements = new ArrayList<Element>();
742          XMLUtil.getNamedChildrenWithWildcard(wrapped, tail(child.getPath()), elements);
743          list2.add(new PropertyWrapperElement(definition, child, elements));
744        }
745      }
746      return list2;
747    }
748  }
749
750  private class PropertyWrapperDirect implements PropertyWrapper {
751    private Property wrapped;
752    private List<BaseWrapper> list;
753
754    private PropertyWrapperDirect(Property wrapped) {
755      super();
756      if (wrapped == null)
757        throw new Error("wrapped == null");
758      this.wrapped = wrapped;
759    }
760
761    @Override
762    public String getName() {
763      return wrapped.getName();
764    }
765
766    @Override
767    public boolean hasValues() {
768      return wrapped.hasValues();
769    }
770
771    @Override
772    public List<BaseWrapper> getValues() {
773      if (list == null) {
774        list = new ArrayList<NarrativeGenerator.BaseWrapper>();
775        for (Base b : wrapped.getValues())
776          list.add(b == null ? null : new BaseWrapperDirect(b));
777      }
778      return list;
779    }
780
781    @Override
782    public String getTypeCode() {
783      return wrapped.getTypeCode();
784    }
785
786    @Override
787    public String getDefinition() {
788      return wrapped.getDefinition();
789    }
790
791    @Override
792    public int getMinCardinality() {
793      return wrapped.getMinCardinality();
794    }
795
796    @Override
797    public int getMaxCardinality() {
798      return wrapped.getMinCardinality();
799    }
800
801    @Override
802    public StructureDefinition getStructure() {
803      return wrapped.getStructure();
804    }
805
806    @Override
807    public BaseWrapper value() {
808      if (getValues().size() != 1)
809        throw new Error("Access single value, but value count is "+getValues().size());
810      return getValues().get(0);
811    }
812  }
813
814  private class BaseWrapperDirect implements BaseWrapper {
815    private Base wrapped;
816    private List<PropertyWrapper> list;
817
818    private BaseWrapperDirect(Base wrapped) {
819      super();
820      if (wrapped == null)
821        throw new Error("wrapped == null");
822      this.wrapped = wrapped;
823    }
824
825    @Override
826    public Base getBase() {
827      return wrapped;
828    }
829
830    @Override
831    public List<PropertyWrapper> children() {
832      if (list == null) {
833        list = new ArrayList<NarrativeGenerator.PropertyWrapper>();
834        for (Property p : wrapped.children())
835          list.add(new PropertyWrapperDirect(p));
836      }
837      return list;
838
839    }
840
841    @Override
842    public PropertyWrapper getChildByName(String name) {
843      Property p = wrapped.getChildByName(name);
844      if (p == null)
845        return null;
846      else
847        return new PropertyWrapperDirect(p);
848    }
849
850  }
851
852  public class ResourceWrapperDirect implements ResourceWrapper {
853    private Resource wrapped;
854
855    public ResourceWrapperDirect(Resource wrapped) {
856      super();
857      if (wrapped == null)
858        throw new Error("wrapped == null");
859      this.wrapped = wrapped;
860    }
861
862    @Override
863    public List<ResourceWrapper> getContained() {
864      List<ResourceWrapper> list = new ArrayList<NarrativeGenerator.ResourceWrapper>();
865      if (wrapped instanceof DomainResource) {
866        DomainResource dr = (DomainResource) wrapped;
867        for (Resource c : dr.getContained()) {
868          list.add(new ResourceWrapperDirect(c));
869        }
870      }
871      return list;
872    }
873
874    @Override
875    public String getId() {
876      return wrapped.getId();
877    }
878
879    @Override
880    public XhtmlNode getNarrative() {
881      if (wrapped instanceof DomainResource) {
882        DomainResource dr = (DomainResource) wrapped;
883        if (dr.hasText() && dr.getText().hasDiv())
884          return dr.getText().getDiv();
885      }
886      return null;
887    }
888
889    @Override
890    public String getName() {
891      return wrapped.getResourceType().toString();
892    }
893
894    @Override
895    public List<PropertyWrapper> children() {
896      List<PropertyWrapper> list = new ArrayList<PropertyWrapper>();
897      for (Property c : wrapped.children())
898        list.add(new PropertyWrapperDirect(c));
899      return list;
900    }
901  }
902
903  public static class ResourceWithReference {
904
905    private String reference;
906    private ResourceWrapper resource;
907
908    public ResourceWithReference(String reference, ResourceWrapper resource) {
909      this.reference = reference;
910      this.resource = resource;
911    }
912
913    public String getReference() {
914      return reference;
915    }
916
917    public ResourceWrapper getResource() {
918      return resource;
919    }
920  }
921
922  private String prefix;
923  private IWorkerContext context;
924  private String basePath;
925  private String tooCostlyNoteEmpty;
926  private String tooCostlyNoteNotEmpty;
927  private IReferenceResolver resolver;
928  private int headerLevelContext;
929
930  public NarrativeGenerator(String prefix, String basePath, IWorkerContext context) {
931    super();
932    this.prefix = prefix;
933    this.context = context;
934    this.basePath = basePath;
935  }
936
937  public NarrativeGenerator(String prefix, String basePath, IWorkerContext context, IReferenceResolver resolver) {
938    super();
939    this.prefix = prefix;
940    this.context = context;
941    this.basePath = basePath;
942    this.resolver = resolver;
943  }
944
945
946  public int getHeaderLevelContext() {
947    return headerLevelContext;
948  }
949
950  public NarrativeGenerator setHeaderLevelContext(int headerLevelContext) {
951    this.headerLevelContext = headerLevelContext;
952    return this;
953  }
954
955  public String getTooCostlyNoteEmpty() {
956    return tooCostlyNoteEmpty;
957  }
958
959
960  public NarrativeGenerator setTooCostlyNoteEmpty(String tooCostlyNoteEmpty) {
961    this.tooCostlyNoteEmpty = tooCostlyNoteEmpty;
962    return this;
963  }
964
965
966  public String getTooCostlyNoteNotEmpty() {
967    return tooCostlyNoteNotEmpty;
968  }
969
970
971  public NarrativeGenerator setTooCostlyNoteNotEmpty(String tooCostlyNoteNotEmpty) {
972    this.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty;
973    return this;
974  }
975
976
977  // dom based version, for build program
978  public String generate(Element doc) throws IOException, org.hl7.fhir.exceptions.FHIRException {
979    return generate(null, doc);
980  }
981  public String generate(ResourceContext rcontext, Element doc) throws IOException, org.hl7.fhir.exceptions.FHIRException {
982    if (rcontext == null)
983      rcontext = new ResourceContext(null, doc);
984    String rt = "http://hl7.org/fhir/StructureDefinition/"+doc.getNodeName();
985    StructureDefinition p = context.fetchResource(StructureDefinition.class, rt);
986    return generateByProfile(doc, p, true);
987  }
988
989  // dom based version, for build program
990  public String generate(org.hl7.fhir.dstu3.elementmodel.Element er, boolean showCodeDetails) throws IOException, DefinitionException {
991    return generate(null, er, showCodeDetails);
992  }
993  
994  public String generate(ResourceContext rcontext, org.hl7.fhir.dstu3.elementmodel.Element er, boolean showCodeDetails) throws IOException, DefinitionException {
995    if (rcontext == null)
996      rcontext = new ResourceContext(null, er);
997    
998    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
999    x.para().b().tx("Generated Narrative"+(showCodeDetails ? " with Details" : ""));
1000    try {
1001      ResurceWrapperMetaElement resw = new ResurceWrapperMetaElement(er);
1002      BaseWrapperMetaElement base = new BaseWrapperMetaElement(er, null, er.getProperty().getStructure(), er.getProperty().getDefinition());
1003      base.children();
1004      generateByProfile(resw, er.getProperty().getStructure(), base, er.getProperty().getStructure().getSnapshot().getElement(), er.getProperty().getDefinition(), base.children, x, er.fhirType(), showCodeDetails);
1005
1006    } catch (Exception e) {
1007      e.printStackTrace();
1008      x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage());
1009    }
1010    inject(er, x,  NarrativeStatus.GENERATED);
1011    return new XhtmlComposer(XhtmlComposer.XML).compose(x);
1012  }
1013
1014  private boolean generateByProfile(DomainResource r, StructureDefinition profile, boolean showCodeDetails) {
1015    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
1016    x.para().b().tx("Generated Narrative"+(showCodeDetails ? " with Details" : ""));
1017    try {
1018      generateByProfile(r, profile, r, profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile.getSnapshot().getElement(), r.getResourceType().toString()), x, r.getResourceType().toString(), showCodeDetails);
1019    } catch (Exception e) {
1020      e.printStackTrace();
1021      x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage());
1022    }
1023    inject(r, x,  NarrativeStatus.GENERATED);
1024    return true;
1025  }
1026
1027  private String generateByProfile(Element er, StructureDefinition profile, boolean showCodeDetails) throws IOException, org.hl7.fhir.exceptions.FHIRException {
1028    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
1029    x.para().b().tx("Generated Narrative"+(showCodeDetails ? " with Details" : ""));
1030    try {
1031      generateByProfile(er, profile, er, profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile.getSnapshot().getElement(), er.getLocalName()), x, er.getLocalName(), showCodeDetails);
1032    } catch (Exception e) {
1033      e.printStackTrace();
1034      x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage());
1035    }
1036    inject(er, x,  NarrativeStatus.GENERATED);
1037    return new XhtmlComposer(XhtmlComposer.XML).compose(x);
1038  }
1039
1040  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 {
1041
1042    ResurceWrapperElement resw = new ResurceWrapperElement(eres, profile);
1043    BaseWrapperElement base = new BaseWrapperElement(ee, null, profile, profile.getSnapshot().getElement().get(0));
1044    generateByProfile(resw, profile, base, allElements, defn, children, x, path, showCodeDetails);
1045  }
1046
1047
1048  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 {
1049    generateByProfile(new ResourceWrapperDirect(res), profile, new BaseWrapperDirect(e), allElements, defn, children, x, path, showCodeDetails);
1050  }
1051
1052  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 {
1053    if (children.isEmpty()) {
1054      renderLeaf(res, e, defn, x, false, showCodeDetails, readDisplayHints(defn), path);
1055    } else {
1056      for (PropertyWrapper p : splitExtensions(profile, e.children())) {
1057        if (p.hasValues()) {
1058          ElementDefinition child = getElementDefinition(children, path+"."+p.getName(), p);
1059          if (child != null) {
1060            Map<String, String> displayHints = readDisplayHints(child);
1061            if (!exemptFromRendering(child)) {
1062              List<ElementDefinition> grandChildren = getChildrenForPath(allElements, path+"."+p.getName());
1063            filterGrandChildren(grandChildren, path+"."+p.getName(), p);
1064              if (p.getValues().size() > 0 && child != null) {
1065                if (isPrimitive(child)) {
1066                  XhtmlNode para = x.para();
1067                  String name = p.getName();
1068                  if (name.endsWith("[x]"))
1069                    name = name.substring(0, name.length() - 3);
1070                  if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) {
1071                    para.b().addText(name);
1072                    para.tx(": ");
1073                    if (renderAsList(child) && p.getValues().size() > 1) {
1074                      XhtmlNode list = x.ul();
1075                      for (BaseWrapper v : p.getValues())
1076                        renderLeaf(res, v, child, list.li(), false, showCodeDetails, displayHints, path);
1077                    } else {
1078                      boolean first = true;
1079                      for (BaseWrapper v : p.getValues()) {
1080                        if (first)
1081                          first = false;
1082                        else
1083                          para.tx(", ");
1084                        renderLeaf(res, v, child, para, false, showCodeDetails, displayHints, path);
1085                      }
1086                    }
1087                  }
1088                } else if (canDoTable(path, p, grandChildren)) {
1089                  x.addTag(getHeader()).addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName()))));
1090                  XhtmlNode tbl = x.table( "grid");
1091                  XhtmlNode tr = tbl.tr();
1092                  tr.td().tx("-"); // work around problem with empty table rows
1093                  addColumnHeadings(tr, grandChildren);
1094                  for (BaseWrapper v : p.getValues()) {
1095                    if (v != null) {
1096                      tr = tbl.tr();
1097                      tr.td().tx("*"); // work around problem with empty table rows
1098                      addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path);
1099                    }
1100                  }
1101                } else {
1102                  for (BaseWrapper v : p.getValues()) {
1103                    if (v != null) {
1104                      XhtmlNode bq = x.addTag("blockquote");
1105                      bq.para().b().addText(p.getName());
1106                      generateByProfile(res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails);
1107                    }
1108                  }
1109                }
1110              }
1111            }
1112          }
1113        }
1114      }
1115    }
1116  }
1117
1118  private String getHeader() {
1119    int i = 3;
1120    while (i <= headerLevelContext)
1121      i++;
1122    if (i > 6)
1123      i = 6;
1124    return "h"+Integer.toString(i);
1125  }
1126
1127  private void filterGrandChildren(List<ElementDefinition> grandChildren,  String string, PropertyWrapper prop) {
1128        List<ElementDefinition> toRemove = new ArrayList<ElementDefinition>();
1129        toRemove.addAll(grandChildren);
1130        for (BaseWrapper b : prop.getValues()) {
1131        List<ElementDefinition> list = new ArrayList<ElementDefinition>();
1132                for (ElementDefinition ed : toRemove) {
1133                        PropertyWrapper p = b.getChildByName(tail(ed.getPath()));
1134                        if (p != null && p.hasValues())
1135                                list.add(ed);
1136                }
1137                toRemove.removeAll(list);
1138        }
1139        grandChildren.removeAll(toRemove);
1140  }
1141
1142  private List<PropertyWrapper> splitExtensions(StructureDefinition profile, List<PropertyWrapper> children) throws UnsupportedEncodingException, IOException, FHIRException {
1143    List<PropertyWrapper> results = new ArrayList<PropertyWrapper>();
1144    Map<String, PropertyWrapper> map = new HashMap<String, PropertyWrapper>();
1145    for (PropertyWrapper p : children)
1146      if (p.getName().equals("extension") || p.getName().equals("modifierExtension")) {
1147        // we're going to split these up, and create a property for each url
1148        if (p.hasValues()) {
1149          for (BaseWrapper v : p.getValues()) {
1150            Extension ex  = (Extension) v.getBase();
1151            String url = ex.getUrl();
1152            StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
1153            if (p.getName().equals("modifierExtension") && ed == null)
1154              throw new DefinitionException("Unknown modifier extension "+url);
1155            PropertyWrapper pe = map.get(p.getName()+"["+url+"]");
1156            if (pe == null) {
1157              if (ed == null) {
1158                if (url.startsWith("http://hl7.org/fhir"))
1159                  throw new DefinitionException("unknown extension "+url);
1160                // System.out.println("unknown extension "+url);
1161                pe = new PropertyWrapperDirect(new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex));
1162              } else {
1163                ElementDefinition def = ed.getSnapshot().getElement().get(0);
1164                pe = new PropertyWrapperDirect(new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex));
1165                ((PropertyWrapperDirect) pe).wrapped.setStructure(ed);
1166              }
1167              results.add(pe);
1168            } else
1169              pe.getValues().add(v);
1170          }
1171        }
1172      } else
1173        results.add(p);
1174    return results;
1175  }
1176
1177  @SuppressWarnings("rawtypes")
1178  private boolean isDefaultValue(Map<String, String> displayHints, List<BaseWrapper> list) throws UnsupportedEncodingException, IOException, FHIRException {
1179    if (list.size() != 1)
1180      return false;
1181    if (list.get(0).getBase() instanceof PrimitiveType)
1182      return isDefault(displayHints, (PrimitiveType) list.get(0).getBase());
1183    else
1184      return false;
1185  }
1186
1187  private boolean isDefault(Map<String, String> displayHints, PrimitiveType primitiveType) {
1188    String v = primitiveType.asStringValue();
1189    if (!Utilities.noString(v) && displayHints.containsKey("default") && v.equals(displayHints.get("default")))
1190        return true;
1191    return false;
1192  }
1193
1194  private boolean exemptFromRendering(ElementDefinition child) {
1195    if (child == null)
1196      return false;
1197    if ("Composition.subject".equals(child.getPath()))
1198      return true;
1199    if ("Composition.section".equals(child.getPath()))
1200      return true;
1201    return false;
1202  }
1203
1204  private boolean renderAsList(ElementDefinition child) {
1205    if (child.getType().size() == 1) {
1206      String t = child.getType().get(0).getCode();
1207      if (t.equals("Address") || t.equals("Reference"))
1208        return true;
1209    }
1210    return false;
1211  }
1212
1213  private void addColumnHeadings(XhtmlNode tr, List<ElementDefinition> grandChildren) {
1214    for (ElementDefinition e : grandChildren)
1215      tr.td().b().addText(Utilities.capitalize(tail(e.getPath())));
1216  }
1217
1218  private void addColumnValues(ResourceWrapper res, XhtmlNode tr, List<ElementDefinition> grandChildren, BaseWrapper v, boolean showCodeDetails, Map<String, String> displayHints, String path) throws FHIRException, UnsupportedEncodingException, IOException {
1219    for (ElementDefinition e : grandChildren) {
1220      PropertyWrapper p = v.getChildByName(e.getPath().substring(e.getPath().lastIndexOf(".")+1));
1221      if (p == null || p.getValues().size() == 0 || p.getValues().get(0) == null)
1222        tr.td().tx(" ");
1223      else
1224        renderLeaf(res, p.getValues().get(0), e, tr.td(), false, showCodeDetails, displayHints, path);
1225    }
1226  }
1227
1228  private String tail(String path) {
1229    return path.substring(path.lastIndexOf(".")+1);
1230  }
1231
1232  private boolean canDoTable(String path, PropertyWrapper p, List<ElementDefinition> grandChildren) {
1233    for (ElementDefinition e : grandChildren) {
1234      List<PropertyWrapper> values = getValues(path, p, e);
1235      if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e))
1236        return false;
1237    }
1238    return true;
1239  }
1240
1241  private List<PropertyWrapper> getValues(String path, PropertyWrapper p, ElementDefinition e) {
1242    List<PropertyWrapper> res = new ArrayList<PropertyWrapper>();
1243    for (BaseWrapper v : p.getValues()) {
1244      for (PropertyWrapper g : v.children()) {
1245        if ((path+"."+p.getName()+"."+g.getName()).equals(e.getPath()))
1246          res.add(p);
1247      }
1248    }
1249    return res;
1250  }
1251
1252  private boolean canCollapse(ElementDefinition e) {
1253    // we can collapse any data type
1254    return !e.getType().isEmpty();
1255  }
1256
1257  private boolean isPrimitive(ElementDefinition e) {
1258    //we can tell if e is a primitive because it has types
1259    if (e.getType().isEmpty())
1260      return false;
1261    if (e.getType().size() == 1 && isBase(e.getType().get(0).getCode()))
1262      return false;
1263    return true;
1264//    return !e.getType().isEmpty()
1265  }
1266
1267  private boolean isBase(String code) {
1268    return code.equals("Element") || code.equals("BackboneElement");
1269  }
1270
1271  private ElementDefinition getElementDefinition(List<ElementDefinition> elements, String path, PropertyWrapper p) {
1272    for (ElementDefinition element : elements)
1273      if (element.getPath().equals(path))
1274        return element;
1275    if (path.endsWith("\"]") && p.getStructure() != null)
1276      return p.getStructure().getSnapshot().getElement().get(0);
1277    return null;
1278  }
1279
1280  private void renderLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, boolean title, boolean showCodeDetails, Map<String, String> displayHints, String path) throws FHIRException, UnsupportedEncodingException, IOException {
1281    if (ew == null)
1282      return;
1283
1284
1285    Base e = ew.getBase();
1286
1287    if (e instanceof StringType)
1288      x.addText(((StringType) e).getValue());
1289    else if (e instanceof CodeType)
1290      x.addText(((CodeType) e).getValue());
1291    else if (e instanceof IdType)
1292      x.addText(((IdType) e).getValue());
1293    else if (e instanceof Extension)
1294      return;
1295    else if (e instanceof InstantType)
1296      x.addText(((InstantType) e).toHumanDisplay());
1297    else if (e instanceof DateTimeType)
1298      x.addText(((DateTimeType) e).toHumanDisplay());
1299    else if (e instanceof Base64BinaryType)
1300      x.addText(new Base64().encodeAsString(((Base64BinaryType) e).getValue()));
1301    else if (e instanceof org.hl7.fhir.dstu3.model.DateType)
1302      x.addText(((org.hl7.fhir.dstu3.model.DateType) e).toHumanDisplay());
1303    else if (e instanceof Enumeration) {
1304      Object ev = ((Enumeration<?>) e).getValue();
1305                        x.addText(ev == null ? "" : ev.toString()); // todo: look up a display name if there is one
1306    } else if (e instanceof BooleanType)
1307      x.addText(((BooleanType) e).getValue().toString());
1308    else if (e instanceof CodeableConcept) {
1309      renderCodeableConcept((CodeableConcept) e, x, showCodeDetails);
1310    } else if (e instanceof Coding) {
1311      renderCoding((Coding) e, x, showCodeDetails);
1312    } else if (e instanceof Annotation) {
1313      renderAnnotation((Annotation) e, x);
1314    } else if (e instanceof Identifier) {
1315      renderIdentifier((Identifier) e, x);
1316    } else if (e instanceof org.hl7.fhir.dstu3.model.IntegerType) {
1317      x.addText(Integer.toString(((org.hl7.fhir.dstu3.model.IntegerType) e).getValue()));
1318    } else if (e instanceof org.hl7.fhir.dstu3.model.DecimalType) {
1319      x.addText(((org.hl7.fhir.dstu3.model.DecimalType) e).getValue().toString());
1320    } else if (e instanceof HumanName) {
1321      renderHumanName((HumanName) e, x);
1322    } else if (e instanceof SampledData) {
1323      renderSampledData((SampledData) e, x);
1324    } else if (e instanceof Address) {
1325      renderAddress((Address) e, x);
1326    } else if (e instanceof ContactPoint) {
1327      renderContactPoint((ContactPoint) e, x);
1328    } else if (e instanceof UriType) {
1329      renderUri((UriType) e, x);
1330    } else if (e instanceof Timing) {
1331      renderTiming((Timing) e, x);
1332    } else if (e instanceof Range) {
1333      renderRange((Range) e, x);
1334    } else if (e instanceof Quantity) {
1335      renderQuantity((Quantity) e, x, showCodeDetails);
1336    } else if (e instanceof Ratio) {
1337      renderQuantity(((Ratio) e).getNumerator(), x, showCodeDetails);
1338      x.tx("/");
1339      renderQuantity(((Ratio) e).getDenominator(), x, showCodeDetails);
1340    } else if (e instanceof Period) {
1341      Period p = (Period) e;
1342      x.addText(!p.hasStart() ? "??" : p.getStartElement().toHumanDisplay());
1343      x.tx(" --> ");
1344      x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay());
1345    } else if (e instanceof Reference) {
1346      Reference r = (Reference) e;
1347      XhtmlNode c = x;
1348      ResourceWithReference tr = null;
1349      if (r.hasReferenceElement()) {
1350        tr = resolveReference(res, r.getReference());
1351        if (!r.getReference().startsWith("#")) {
1352          if (tr != null && tr.getReference() != null)
1353            c = x.ah(tr.getReference());
1354          else
1355            c = x.ah(r.getReference());
1356        }
1357      }
1358      // what to display: if text is provided, then that. if the reference was resolved, then show the generated narrative
1359      if (r.hasDisplayElement()) {
1360        c.addText(r.getDisplay());
1361        if (tr != null && tr.getResource() != null) {
1362          c.tx(". Generated Summary: ");
1363          generateResourceSummary(c, tr.getResource(), true, r.getReference().startsWith("#"));
1364        }
1365      } else if (tr != null && tr.getResource() != null) {
1366        generateResourceSummary(c, tr.getResource(), r.getReference().startsWith("#"), r.getReference().startsWith("#"));
1367      } else {
1368        c.addText(r.getReference());
1369      }
1370    } else if (e instanceof Resource) {
1371      return;
1372    } else if (e instanceof ElementDefinition) {
1373      x.tx("todo-bundle");
1374    } else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) {
1375      StructureDefinition sd = context.fetchTypeDefinition(e.fhirType());
1376      if (sd == null)
1377        throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet, and no structure found");
1378      else
1379        generateByProfile(res, sd, ew, sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep(),
1380            getChildrenForPath(sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep().getPath()), x, path, showCodeDetails);
1381    }
1382  }
1383
1384  private boolean displayLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException {
1385    if (ew == null)
1386      return false;
1387    Base e = ew.getBase();
1388    if (e == null)
1389      return false;
1390
1391    Map<String, String> displayHints = readDisplayHints(defn);
1392
1393    if (name.endsWith("[x]"))
1394      name = name.substring(0, name.length() - 3);
1395
1396    if (!showCodeDetails && e instanceof PrimitiveType && isDefault(displayHints, ((PrimitiveType) e)))
1397        return false;
1398
1399    if (e instanceof StringType) {
1400      x.addText(name+": "+((StringType) e).getValue());
1401      return true;
1402    } else if (e instanceof CodeType) {
1403      x.addText(name+": "+((CodeType) e).getValue());
1404      return true;
1405    } else if (e instanceof IdType) {
1406      x.addText(name+": "+((IdType) e).getValue());
1407      return true;
1408    } else if (e instanceof UriType) {
1409      x.addText(name+": "+((UriType) e).getValue());
1410      return true;
1411    } else if (e instanceof DateTimeType) {
1412      x.addText(name+": "+((DateTimeType) e).toHumanDisplay());
1413      return true;
1414    } else if (e instanceof InstantType) {
1415      x.addText(name+": "+((InstantType) e).toHumanDisplay());
1416      return true;
1417    } else if (e instanceof Extension) {
1418//      x.tx("Extensions: todo");
1419      return false;
1420    } else if (e instanceof org.hl7.fhir.dstu3.model.DateType) {
1421      x.addText(name+": "+((org.hl7.fhir.dstu3.model.DateType) e).toHumanDisplay());
1422      return true;
1423    } else if (e instanceof Enumeration) {
1424      x.addText(((Enumeration<?>) e).getValue().toString()); // todo: look up a display name if there is one
1425      return true;
1426    } else if (e instanceof BooleanType) {
1427      if (((BooleanType) e).getValue()) {
1428        x.addText(name);
1429          return true;
1430      }
1431    } else if (e instanceof CodeableConcept) {
1432      renderCodeableConcept((CodeableConcept) e, x, showCodeDetails);
1433      return true;
1434    } else if (e instanceof Coding) {
1435      renderCoding((Coding) e, x, showCodeDetails);
1436      return true;
1437    } else if (e instanceof Annotation) {
1438      renderAnnotation((Annotation) e, x, showCodeDetails);
1439      return true;
1440    } else if (e instanceof org.hl7.fhir.dstu3.model.IntegerType) {
1441      x.addText(Integer.toString(((org.hl7.fhir.dstu3.model.IntegerType) e).getValue()));
1442      return true;
1443    } else if (e instanceof org.hl7.fhir.dstu3.model.DecimalType) {
1444      x.addText(((org.hl7.fhir.dstu3.model.DecimalType) e).getValue().toString());
1445      return true;
1446    } else if (e instanceof Identifier) {
1447      renderIdentifier((Identifier) e, x);
1448      return true;
1449    } else if (e instanceof HumanName) {
1450      renderHumanName((HumanName) e, x);
1451      return true;
1452    } else if (e instanceof SampledData) {
1453      renderSampledData((SampledData) e, x);
1454      return true;
1455    } else if (e instanceof Address) {
1456      renderAddress((Address) e, x);
1457      return true;
1458    } else if (e instanceof ContactPoint) {
1459      renderContactPoint((ContactPoint) e, x);
1460      return true;
1461    } else if (e instanceof Timing) {
1462      renderTiming((Timing) e, x);
1463      return true;
1464    } else if (e instanceof Quantity) {
1465      renderQuantity((Quantity) e, x, showCodeDetails);
1466      return true;
1467    } else if (e instanceof Ratio) {
1468      renderQuantity(((Ratio) e).getNumerator(), x, showCodeDetails);
1469      x.tx("/");
1470      renderQuantity(((Ratio) e).getDenominator(), x, showCodeDetails);
1471      return true;
1472    } else if (e instanceof Period) {
1473      Period p = (Period) e;
1474      x.addText(name+": ");
1475      x.addText(!p.hasStart() ? "??" : p.getStartElement().toHumanDisplay());
1476      x.tx(" --> ");
1477      x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay());
1478      return true;
1479    } else if (e instanceof Reference) {
1480      Reference r = (Reference) e;
1481      if (r.hasDisplayElement())
1482        x.addText(r.getDisplay());
1483      else if (r.hasReferenceElement()) {
1484        ResourceWithReference tr = resolveReference(res, r.getReference());
1485        x.addText(tr == null ? r.getReference() : "????"); // getDisplayForReference(tr.getReference()));
1486      } else
1487        x.tx("??");
1488      return true;
1489    } else if (e instanceof Narrative) {
1490      return false;
1491    } else if (e instanceof Resource) {
1492      return false;
1493    } else if (e instanceof ContactDetail) {
1494      return false;
1495    } else if (e instanceof Range) {
1496      return false;
1497    } else if (e instanceof Meta) {
1498      return false;
1499    } else if (e instanceof Dosage) {
1500      return false;
1501    } else if (e instanceof Signature) {
1502      return false;
1503    } else if (e instanceof UsageContext) {
1504      return false;
1505    } else if (e instanceof ElementDefinition) {
1506      return false;
1507    } else if (!(e instanceof Attachment))
1508      throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet");
1509    return false;
1510  }
1511
1512
1513  private Map<String, String> readDisplayHints(ElementDefinition defn) throws DefinitionException {
1514    Map<String, String> hints = new HashMap<String, String>();
1515    if (defn != null) {
1516      String displayHint = ToolingExtensions.getDisplayHint(defn);
1517      if (!Utilities.noString(displayHint)) {
1518        String[] list = displayHint.split(";");
1519        for (String item : list) {
1520          String[] parts = item.split(":");
1521          if (parts.length != 2)
1522            throw new DefinitionException("error reading display hint: '"+displayHint+"'");
1523          hints.put(parts[0].trim(), parts[1].trim());
1524        }
1525      }
1526    }
1527    return hints;
1528  }
1529
1530  public static String displayPeriod(Period p) {
1531    String s = !p.hasStart() ? "??" : p.getStartElement().toHumanDisplay();
1532    s = s + " --> ";
1533    return s + (!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay());
1534  }
1535
1536  private void generateResourceSummary(XhtmlNode x, ResourceWrapper res, boolean textAlready, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException {
1537    if (!textAlready) {
1538      XhtmlNode div = res.getNarrative();
1539      if (div != null) {
1540        if (div.allChildrenAreText())
1541          x.addChildNodes(div.getChildNodes());
1542        if (div.getChildNodes().size() == 1 && div.getChildNodes().get(0).allChildrenAreText())
1543          x.addChildNodes(div.getChildNodes().get(0).getChildNodes());
1544      }
1545      x.tx("Generated Summary: ");
1546    }
1547    String path = res.getName();
1548    StructureDefinition profile = context.fetchResource(StructureDefinition.class, path);
1549    if (profile == null)
1550      x.tx("unknown resource " +path);
1551    else {
1552      boolean firstElement = true;
1553      boolean last = false;
1554      for (PropertyWrapper p : res.children()) {
1555        ElementDefinition child = getElementDefinition(profile.getSnapshot().getElement(), path+"."+p.getName(), p);
1556        if (p.getValues().size() > 0 && p.getValues().get(0) != null && child != null && isPrimitive(child) && includeInSummary(child)) {
1557          if (firstElement)
1558            firstElement = false;
1559          else if (last)
1560            x.tx("; ");
1561          boolean first = true;
1562          last = false;
1563          for (BaseWrapper v : p.getValues()) {
1564            if (first)
1565              first = false;
1566            else if (last)
1567              x.tx(", ");
1568            last = displayLeaf(res, v, child, x, p.getName(), showCodeDetails) || last;
1569          }
1570        }
1571      }
1572    }
1573  }
1574
1575
1576  private boolean includeInSummary(ElementDefinition child) {
1577    if (child.getIsModifier())
1578      return true;
1579    if (child.getMustSupport())
1580      return true;
1581    if (child.getType().size() == 1) {
1582      String t = child.getType().get(0).getCode();
1583      if (t.equals("Address") || t.equals("Contact") || t.equals("Reference") || t.equals("Uri"))
1584        return false;
1585    }
1586    return true;
1587  }
1588
1589  private ResourceWithReference resolveReference(ResourceWrapper res, String url) {
1590    if (url == null)
1591      return null;
1592    if (url.startsWith("#")) {
1593      for (ResourceWrapper r : res.getContained()) {
1594        if (r.getId().equals(url.substring(1)))
1595          return new ResourceWithReference(null, r);
1596      }
1597      return null;
1598    }
1599
1600    Resource ae = context.fetchResource(null, url);
1601    if (ae != null)
1602      return new ResourceWithReference(url, new ResourceWrapperDirect(ae));
1603    else if (resolver != null) {
1604      return resolver.resolve(url);
1605    } else
1606      return null;
1607  }
1608
1609  private void renderCodeableConcept(CodeableConcept cc, XhtmlNode x, boolean showCodeDetails) {
1610    String s = cc.getText();
1611    if (Utilities.noString(s)) {
1612      for (Coding c : cc.getCoding()) {
1613        if (c.hasDisplayElement()) {
1614          s = c.getDisplay();
1615          break;
1616        }
1617      }
1618    }
1619    if (Utilities.noString(s)) {
1620      // still? ok, let's try looking it up
1621      for (Coding c : cc.getCoding()) {
1622        if (c.hasCodeElement() && c.hasSystemElement()) {
1623          s = lookupCode(c.getSystem(), c.getCode());
1624          if (!Utilities.noString(s))
1625            break;
1626        }
1627      }
1628    }
1629
1630    if (Utilities.noString(s)) {
1631      if (cc.getCoding().isEmpty())
1632        s = "";
1633      else
1634        s = cc.getCoding().get(0).getCode();
1635    }
1636
1637    if (showCodeDetails) {
1638      x.addText(s+" ");
1639      XhtmlNode sp = x.span("background: LightGoldenRodYellow", null);
1640      sp.tx("(Details ");
1641      boolean first = true;
1642      for (Coding c : cc.getCoding()) {
1643        if (first) {
1644          sp.tx(": ");
1645          first = false;
1646        } else
1647          sp.tx("; ");
1648        sp.tx("{"+describeSystem(c.getSystem())+" code '"+c.getCode()+"' = '"+lookupCode(c.getSystem(), c.getCode())+(c.hasDisplay() ? "', given as '"+c.getDisplay()+"'}" : ""));
1649      }
1650      sp.tx(")");
1651    } else {
1652
1653    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1654    for (Coding c : cc.getCoding()) {
1655      if (c.hasCodeElement() && c.hasSystemElement()) {
1656        b.append("{"+c.getSystem()+" "+c.getCode()+"}");
1657      }
1658    }
1659
1660    x.span(null, "Codes: "+b.toString()).addText(s);
1661    }
1662  }
1663
1664  private void renderAnnotation(Annotation a, XhtmlNode x, boolean showCodeDetails) throws FHIRException {
1665    StringBuilder s = new StringBuilder();
1666    if (a.hasAuthor()) {
1667      s.append("Author: ");
1668
1669      if (a.hasAuthorReference())
1670        s.append(a.getAuthorReference().getReference());
1671      else if (a.hasAuthorStringType())
1672        s.append(a.getAuthorStringType().getValue());
1673    }
1674
1675
1676    if (a.hasTimeElement()) {
1677      if (s.length() > 0)
1678        s.append("; ");
1679
1680      s.append("Made: ").append(a.getTimeElement().toHumanDisplay());
1681    }
1682
1683    if (a.hasText()) {
1684      if (s.length() > 0)
1685        s.append("; ");
1686
1687      s.append("Annotation: ").append(a.getText());
1688    }
1689
1690    x.addText(s.toString());
1691  }
1692
1693  private void renderCoding(Coding c, XhtmlNode x, boolean showCodeDetails) {
1694    String s = "";
1695    if (c.hasDisplayElement())
1696      s = c.getDisplay();
1697    if (Utilities.noString(s))
1698      s = lookupCode(c.getSystem(), c.getCode());
1699
1700    if (Utilities.noString(s))
1701      s = c.getCode();
1702
1703    if (showCodeDetails) {
1704      x.addText(s+" (Details: "+describeSystem(c.getSystem())+" code "+c.getCode()+" = '"+lookupCode(c.getSystem(), c.getCode())+"', stated as '"+c.getDisplay()+"')");
1705    } else
1706      x.span(null, "{"+c.getSystem()+" "+c.getCode()+"}").addText(s);
1707  }
1708
1709  public static String describeSystem(String system) {
1710    if (system == null)
1711      return "[not stated]";
1712    if (system.equals("http://loinc.org"))
1713      return "LOINC";
1714    if (system.startsWith("http://snomed.info"))
1715      return "SNOMED CT";
1716    if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm"))
1717      return "RxNorm";
1718    if (system.equals("http://hl7.org/fhir/sid/icd-9"))
1719      return "ICD-9";
1720    if (system.equals("http://dicom.nema.org/resources/ontology/DCM"))
1721      return "DICOM";
1722    if (system.equals("http://unitsofmeasure.org"))
1723      return "UCUM";
1724
1725    return system;
1726  }
1727
1728  private String lookupCode(String system, String code) {
1729    ValidationResult t = context.validateCode(system, code, null);
1730
1731    if (t != null && t.getDisplay() != null)
1732        return t.getDisplay();
1733    else
1734      return code;
1735
1736  }
1737
1738  private ConceptDefinitionComponent findCode(String code, List<ConceptDefinitionComponent> list) {
1739    for (ConceptDefinitionComponent t : list) {
1740      if (code.equals(t.getCode()))
1741        return t;
1742      ConceptDefinitionComponent c = findCode(code, t.getConcept());
1743      if (c != null)
1744        return c;
1745    }
1746    return null;
1747  }
1748
1749  public String displayCodeableConcept(CodeableConcept cc) {
1750    String s = cc.getText();
1751    if (Utilities.noString(s)) {
1752      for (Coding c : cc.getCoding()) {
1753        if (c.hasDisplayElement()) {
1754          s = c.getDisplay();
1755          break;
1756        }
1757      }
1758    }
1759    if (Utilities.noString(s)) {
1760      // still? ok, let's try looking it up
1761      for (Coding c : cc.getCoding()) {
1762        if (c.hasCode() && c.hasSystem()) {
1763          s = lookupCode(c.getSystem(), c.getCode());
1764          if (!Utilities.noString(s))
1765            break;
1766        }
1767      }
1768    }
1769
1770    if (Utilities.noString(s)) {
1771      if (cc.getCoding().isEmpty())
1772        s = "";
1773      else
1774        s = cc.getCoding().get(0).getCode();
1775    }
1776    return s;
1777  }
1778
1779  private void renderIdentifier(Identifier ii, XhtmlNode x) {
1780    x.addText(displayIdentifier(ii));
1781  }
1782
1783  private void renderTiming(Timing s, XhtmlNode x) throws FHIRException {
1784    x.addText(displayTiming(s));
1785  }
1786
1787  private void renderQuantity(Quantity q, XhtmlNode x, boolean showCodeDetails) {
1788    if (q.hasComparator())
1789      x.addText(q.getComparator().toCode());
1790    x.addText(q.getValue().toString());
1791    if (q.hasUnit())
1792      x.tx(" "+q.getUnit());
1793    else if (q.hasCode())
1794      x.tx(" "+q.getCode());
1795    if (showCodeDetails && q.hasCode()) {
1796      x.span("background: LightGoldenRodYellow", null).tx(" (Details: "+describeSystem(q.getSystem())+" code "+q.getCode()+" = '"+lookupCode(q.getSystem(), q.getCode())+"')");
1797    }
1798  }
1799
1800  private void renderRange(Range q, XhtmlNode x) {
1801    if (q.hasLow())
1802      x.addText(q.getLow().getValue().toString());
1803    else
1804      x.tx("?");
1805    x.tx("-");
1806    if (q.hasHigh())
1807      x.addText(q.getHigh().getValue().toString());
1808    else
1809      x.tx("?");
1810    if (q.getLow().hasUnit())
1811      x.tx(" "+q.getLow().getUnit());
1812  }
1813
1814  public String displayRange(Range q) {
1815    StringBuilder b = new StringBuilder();
1816    if (q.hasLow())
1817      b.append(q.getLow().getValue().toString());
1818    else
1819      b.append("?");
1820    b.append("-");
1821    if (q.hasHigh())
1822      b.append(q.getHigh().getValue().toString());
1823    else
1824      b.append("?");
1825    if (q.getLow().hasUnit())
1826      b.append(" "+q.getLow().getUnit());
1827    return b.toString();
1828  }
1829
1830  private void renderHumanName(HumanName name, XhtmlNode x) {
1831    x.addText(displayHumanName(name));
1832  }
1833
1834  private void renderAnnotation(Annotation annot, XhtmlNode x) {
1835    x.addText(annot.getText());
1836  }
1837
1838  private void renderAddress(Address address, XhtmlNode x) {
1839    x.addText(displayAddress(address));
1840  }
1841
1842  private void renderContactPoint(ContactPoint contact, XhtmlNode x) {
1843    x.addText(displayContactPoint(contact));
1844  }
1845
1846  private void renderUri(UriType uri, XhtmlNode x) {
1847    x.ah(uri.getValue()).addText(uri.getValue());
1848  }
1849
1850  private void renderSampledData(SampledData sampledData, XhtmlNode x) {
1851    x.addText(displaySampledData(sampledData));
1852  }
1853
1854  private String displaySampledData(SampledData s) {
1855    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1856    if (s.hasOrigin())
1857      b.append("Origin: "+displayQuantity(s.getOrigin()));
1858
1859    if (s.hasPeriod())
1860      b.append("Period: "+s.getPeriod().toString());
1861
1862    if (s.hasFactor())
1863      b.append("Factor: "+s.getFactor().toString());
1864
1865    if (s.hasLowerLimit())
1866      b.append("Lower: "+s.getLowerLimit().toString());
1867
1868    if (s.hasUpperLimit())
1869      b.append("Upper: "+s.getUpperLimit().toString());
1870
1871    if (s.hasDimensions())
1872      b.append("Dimensions: "+s.getDimensions());
1873
1874    if (s.hasData())
1875      b.append("Data: "+s.getData());
1876
1877    return b.toString();
1878  }
1879
1880  private String displayQuantity(Quantity q) {
1881    StringBuilder s = new StringBuilder();
1882
1883    s.append("(system = '").append(describeSystem(q.getSystem()))
1884        .append("' code ").append(q.getCode())
1885        .append(" = '").append(lookupCode(q.getSystem(), q.getCode())).append("')");
1886
1887    return s.toString();
1888  }
1889
1890  private String displayTiming(Timing s) throws FHIRException {
1891    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1892    if (s.hasCode())
1893        b.append("Code: "+displayCodeableConcept(s.getCode()));
1894
1895    if (s.getEvent().size() > 0) {
1896      CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
1897      for (DateTimeType p : s.getEvent()) {
1898        c.append(p.toHumanDisplay());
1899      }
1900      b.append("Events: "+ c.toString());
1901    }
1902
1903    if (s.hasRepeat()) {
1904      TimingRepeatComponent rep = s.getRepeat();
1905      if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasStart())
1906        b.append("Starting "+rep.getBoundsPeriod().getStartElement().toHumanDisplay());
1907      if (rep.hasCount())
1908        b.append("Count "+Integer.toString(rep.getCount())+" times");
1909      if (rep.hasDuration())
1910        b.append("Duration "+rep.getDuration().toPlainString()+displayTimeUnits(rep.getPeriodUnit()));
1911
1912      if (rep.hasWhen()) {
1913        String st = "";
1914        if (rep.hasOffset()) {
1915          st = Integer.toString(rep.getOffset())+"min ";
1916        }
1917        b.append("Do "+st);
1918        for (Enumeration<EventTiming> wh : rep.getWhen())
1919          b.append(displayEventCode(wh.getValue()));
1920      } else {
1921        String st = "";
1922        if (!rep.hasFrequency() || (!rep.hasFrequencyMax() && rep.getFrequency() == 1) )
1923          st = "Once";
1924        else {
1925          st = Integer.toString(rep.getFrequency());
1926          if (rep.hasFrequencyMax())
1927            st = st + "-"+Integer.toString(rep.getFrequency());
1928        }
1929        if (rep.hasPeriod()) {
1930        st = st + " per "+rep.getPeriod().toPlainString();
1931        if (rep.hasPeriodMax())
1932          st = st + "-"+rep.getPeriodMax().toPlainString();
1933                st = st + " "+displayTimeUnits(rep.getPeriodUnit());
1934        }
1935        b.append("Do "+st);
1936      }
1937      if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasEnd())
1938        b.append("Until "+rep.getBoundsPeriod().getEndElement().toHumanDisplay());
1939    }
1940    return b.toString();
1941  }
1942
1943  private String displayEventCode(EventTiming when) {
1944    switch (when) {
1945    case C: return "at meals";
1946    case CD: return "at lunch";
1947    case CM: return "at breakfast";
1948    case CV: return "at dinner";
1949    case AC: return "before meals";
1950    case ACD: return "before lunch";
1951    case ACM: return "before breakfast";
1952    case ACV: return "before dinner";
1953    case HS: return "before sleeping";
1954    case PC: return "after meals";
1955    case PCD: return "after lunch";
1956    case PCM: return "after breakfast";
1957    case PCV: return "after dinner";
1958    case WAKE: return "after waking";
1959    default: return "??";
1960    }
1961  }
1962
1963  private String displayTimeUnits(UnitsOfTime units) {
1964        if (units == null)
1965                return "??";
1966    switch (units) {
1967    case A: return "years";
1968    case D: return "days";
1969    case H: return "hours";
1970    case MIN: return "minutes";
1971    case MO: return "months";
1972    case S: return "seconds";
1973    case WK: return "weeks";
1974    default: return "??";
1975    }
1976  }
1977
1978  public static String displayHumanName(HumanName name) {
1979    StringBuilder s = new StringBuilder();
1980    if (name.hasText())
1981      s.append(name.getText());
1982    else {
1983      for (StringType p : name.getGiven()) {
1984        s.append(p.getValue());
1985        s.append(" ");
1986      }
1987      if (name.hasFamily()) {
1988        s.append(name.getFamily());
1989        s.append(" ");
1990      }
1991    }
1992    if (name.hasUse() && name.getUse() != NameUse.USUAL)
1993      s.append("("+name.getUse().toString()+")");
1994    return s.toString();
1995  }
1996
1997  private String displayAddress(Address address) {
1998    StringBuilder s = new StringBuilder();
1999    if (address.hasText())
2000      s.append(address.getText());
2001    else {
2002      for (StringType p : address.getLine()) {
2003        s.append(p.getValue());
2004        s.append(" ");
2005      }
2006      if (address.hasCity()) {
2007        s.append(address.getCity());
2008        s.append(" ");
2009      }
2010      if (address.hasState()) {
2011        s.append(address.getState());
2012        s.append(" ");
2013      }
2014
2015      if (address.hasPostalCode()) {
2016        s.append(address.getPostalCode());
2017        s.append(" ");
2018      }
2019
2020      if (address.hasCountry()) {
2021        s.append(address.getCountry());
2022        s.append(" ");
2023      }
2024    }
2025    if (address.hasUse())
2026      s.append("("+address.getUse().toString()+")");
2027    return s.toString();
2028  }
2029
2030  public static String displayContactPoint(ContactPoint contact) {
2031    StringBuilder s = new StringBuilder();
2032    s.append(describeSystem(contact.getSystem()));
2033    if (Utilities.noString(contact.getValue()))
2034      s.append("-unknown-");
2035    else
2036      s.append(contact.getValue());
2037    if (contact.hasUse())
2038      s.append("("+contact.getUse().toString()+")");
2039    return s.toString();
2040  }
2041
2042  private static String describeSystem(ContactPointSystem system) {
2043    if (system == null)
2044      return "";
2045    switch (system) {
2046    case PHONE: return "ph: ";
2047    case FAX: return "fax: ";
2048    default:
2049      return "";
2050    }
2051  }
2052
2053  private String displayIdentifier(Identifier ii) {
2054    String s = Utilities.noString(ii.getValue()) ? "??" : ii.getValue();
2055
2056    if (ii.hasType()) {
2057        if (ii.getType().hasText())
2058                s = ii.getType().getText()+" = "+s;
2059        else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasDisplay())
2060                s = ii.getType().getCoding().get(0).getDisplay()+" = "+s;
2061        else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasCode())
2062                s = lookupCode(ii.getType().getCoding().get(0).getSystem(), ii.getType().getCoding().get(0).getCode())+" = "+s;
2063    }
2064
2065    if (ii.hasUse())
2066      s = s + " ("+ii.getUse().toString()+")";
2067    return s;
2068  }
2069
2070  private List<ElementDefinition> getChildrenForPath(List<ElementDefinition> elements, String path) throws DefinitionException {
2071    // do we need to do a name reference substitution?
2072    for (ElementDefinition e : elements) {
2073      if (e.getPath().equals(path) && e.hasContentReference()) {
2074        String ref = e.getContentReference();
2075        ElementDefinition t = null;
2076        // now, resolve the name
2077        for (ElementDefinition e1 : elements) {
2078                if (ref.equals("#"+e1.getId()))
2079                        t = e1;
2080        }
2081        if (t == null)
2082                throw new DefinitionException("Unable to resolve content reference "+ref+" trying to resolve "+path);
2083        path = t.getPath();
2084        break;
2085      }
2086    }
2087
2088    List<ElementDefinition> results = new ArrayList<ElementDefinition>();
2089    for (ElementDefinition e : elements) {
2090      if (e.getPath().startsWith(path+".") && !e.getPath().substring(path.length()+1).contains("."))
2091        results.add(e);
2092    }
2093    return results;
2094  }
2095
2096
2097  public boolean generate(ResourceContext rcontext, ConceptMap cm) throws FHIRFormatError, DefinitionException, IOException {
2098    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
2099    x.h2().addText(cm.getName()+" ("+cm.getUrl()+")");
2100
2101    XhtmlNode p = x.para();
2102    if (cm.hasSource() || cm.hasTarget())
2103      p.tx("Mapping from ");
2104    if (!cm.hasSource())
2105      p.tx("(unspecified)");
2106    else
2107      AddVsRef(rcontext, cm.getSource() instanceof Reference ? ((Reference) cm.getSource()).getReference() : ((UriType) cm.getSource()).asStringValue(), p);
2108    p.tx(" to ");
2109    if (!cm.hasTarget())
2110      p.tx("(unspecified)");
2111    else 
2112      AddVsRef(rcontext, cm.getTarget() instanceof Reference ? ((Reference) cm.getTarget()).getReference() : ((UriType) cm.getTarget()).asStringValue(), p);
2113    
2114    p = x.para();
2115    if (cm.getExperimental())
2116      p.addText(Utilities.capitalize(cm.getStatus().toString())+" (not intended for production usage). ");
2117    else
2118      p.addText(Utilities.capitalize(cm.getStatus().toString())+". ");
2119    p.tx("Published on "+(cm.hasDate() ? cm.getDateElement().toHumanDisplay() : "??")+" by "+cm.getPublisher());
2120    if (!cm.getContact().isEmpty()) {
2121      p.tx(" (");
2122      boolean firsti = true;
2123      for (ContactDetail ci : cm.getContact()) {
2124        if (firsti)
2125          firsti = false;
2126        else
2127          p.tx(", ");
2128        if (ci.hasName())
2129          p.addText(ci.getName()+": ");
2130        boolean first = true;
2131        for (ContactPoint c : ci.getTelecom()) {
2132          if (first)
2133            first = false;
2134          else
2135            p.tx(", ");
2136          addTelecom(p, c);
2137        }
2138      }
2139      p.tx(")");
2140    }
2141    p.tx(". ");
2142    p.addText(cm.getCopyright());
2143    if (!Utilities.noString(cm.getDescription()))
2144      addMarkdown(x, cm.getDescription());
2145
2146    x.br();
2147
2148    for (ConceptMapGroupComponent grp : cm.getGroup()) {
2149      String src = grp.getSource();
2150      boolean comment = false;
2151      boolean ok = true;
2152    Map<String, HashSet<String>> sources = new HashMap<String, HashSet<String>>();
2153    Map<String, HashSet<String>> targets = new HashMap<String, HashSet<String>>();
2154      sources.put("code", new HashSet<String>());
2155    targets.put("code", new HashSet<String>());
2156      SourceElementComponent cc = grp.getElement().get(0);
2157      String dst = grp.getTarget();
2158      sources.get("code").add(grp.getSource());
2159      targets.get("code").add(grp.getTarget());
2160      for (SourceElementComponent ccl : grp.getElement()) {
2161        ok = ok && ccl.getTarget().size() == 1 && ccl.getTarget().get(0).getDependsOn().isEmpty() && ccl.getTarget().get(0).getProduct().isEmpty();
2162        for (TargetElementComponent ccm : ccl.getTarget()) {
2163                comment = comment || !Utilities.noString(ccm.getComment());
2164                for (OtherElementComponent d : ccm.getDependsOn()) {
2165            if (!sources.containsKey(d.getProperty()))
2166              sources.put(d.getProperty(), new HashSet<String>());
2167            sources.get(d.getProperty()).add(d.getSystem());
2168                }
2169                for (OtherElementComponent d : ccm.getProduct()) {
2170            if (!targets.containsKey(d.getProperty()))
2171              targets.put(d.getProperty(), new HashSet<String>());
2172            targets.get(d.getProperty()).add(d.getSystem());
2173            }
2174
2175                }
2176        }
2177
2178      String display;
2179      if (ok) {
2180        // simple
2181        XhtmlNode tbl = x.table( "grid");
2182        XhtmlNode tr = tbl.tr();
2183        tr.td().b().tx("Source Code");
2184        tr.td().b().tx("Equivalence");
2185        tr.td().b().tx("Destination Code");
2186        if (comment)
2187          tr.td().b().tx("Comment");
2188        for (SourceElementComponent ccl : grp.getElement()) {
2189          tr = tbl.tr();
2190          XhtmlNode td = tr.td();
2191          td.addText(ccl.getCode());
2192          display = getDisplayForConcept(grp.getSource(), ccl.getCode());
2193          if (display != null)
2194            td.tx(" ("+display+")");
2195          TargetElementComponent ccm = ccl.getTarget().get(0);
2196          tr.td().addText(!ccm.hasEquivalence() ? "" : ccm.getEquivalence().toCode());
2197          td = tr.td();
2198          td.addText(ccm.getCode());
2199          display = getDisplayForConcept(grp.getTarget(), ccm.getCode());
2200          if (display != null)
2201            td.tx(" ("+display+")");
2202          if (comment)
2203            tr.td().addText(ccm.getComment());
2204        }
2205      } else {
2206        XhtmlNode tbl = x.table( "grid");
2207        XhtmlNode tr = tbl.tr();
2208        XhtmlNode td;
2209        tr.td().colspan(Integer.toString(sources.size())).b().tx("Source Concept");
2210        tr.td().b().tx("Equivalence");
2211        tr.td().colspan(Integer.toString(targets.size())).b().tx("Destination Concept");
2212        if (comment)
2213          tr.td().b().tx("Comment");
2214        tr = tbl.tr();
2215        if (sources.get("code").size() == 1)
2216          tr.td().b().tx("Code "+sources.get("code").toString()+"");
2217        else
2218          tr.td().b().tx("Code");
2219        for (String s : sources.keySet()) {
2220          if (!s.equals("code")) {
2221            if (sources.get(s).size() == 1)
2222              tr.td().b().addText(getDescForConcept(s) +" "+sources.get(s).toString());
2223            else
2224              tr.td().b().addText(getDescForConcept(s));
2225          }
2226        }
2227        tr.td();
2228        if (targets.get("code").size() == 1)
2229          tr.td().b().tx("Code "+targets.get("code").toString());
2230        else
2231          tr.td().b().tx("Code");
2232        for (String s : targets.keySet()) {
2233          if (!s.equals("code")) {
2234            if (targets.get(s).size() == 1)
2235              tr.td().b().addText(getDescForConcept(s) +" "+targets.get(s).toString()+"");
2236            else
2237              tr.td().b().addText(getDescForConcept(s));
2238          }
2239        }
2240        if (comment)
2241          tr.td();
2242
2243        for (SourceElementComponent ccl : grp.getElement()) {
2244          tr = tbl.tr();
2245          td = tr.td();
2246          if (sources.get("code").size() == 1)
2247            td.addText(ccl.getCode());
2248          else
2249            td.addText(grp.getSource()+" / "+ccl.getCode());
2250          display = getDisplayForConcept(grp.getSource(), ccl.getCode());
2251          if (display != null)
2252            td.tx(" ("+display+")");
2253
2254          TargetElementComponent ccm = ccl.getTarget().get(0);
2255          for (String s : sources.keySet()) {
2256            if (!s.equals("code")) {
2257              td = tr.td();
2258              td.addText(getCode(ccm.getDependsOn(), s, sources.get(s).size() != 1));
2259              display = getDisplay(ccm.getDependsOn(), s);
2260              if (display != null)
2261                td.tx(" ("+display+")");
2262            }
2263          }
2264          if (!ccm.hasEquivalence())
2265            tr.td().tx(":"+"("+ConceptMapEquivalence.EQUIVALENT.toCode()+")");
2266          else
2267            tr.td().tx(":"+ccm.getEquivalence().toCode());
2268          td = tr.td();
2269          if (targets.get("code").size() == 1)
2270            td.addText(ccm.getCode());
2271          else
2272            td.addText(grp.getTarget()+" / "+ccm.getCode());
2273          display = getDisplayForConcept(grp.getTarget(), ccm.getCode());
2274          if (display != null)
2275            td.tx(" ("+display+")");
2276
2277          for (String s : targets.keySet()) {
2278            if (!s.equals("code")) {
2279              td = tr.td();
2280              td.addText(getCode(ccm.getProduct(), s, targets.get(s).size() != 1));
2281              display = getDisplay(ccm.getProduct(), s);
2282              if (display != null)
2283                td.tx(" ("+display+")");
2284            }
2285          }
2286          if (comment)
2287            tr.td().addText(ccm.getComment());
2288        }
2289      }
2290    }
2291
2292    inject(cm, x, NarrativeStatus.GENERATED);
2293    return true;
2294  }
2295
2296
2297
2298  private void inject(DomainResource r, XhtmlNode x, NarrativeStatus status) {
2299    if (!x.hasAttribute("xmlns"))
2300      x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2301    if (!r.hasText() || !r.getText().hasDiv() || r.getText().getDiv().getChildNodes().isEmpty()) {
2302      r.setText(new Narrative());
2303      r.getText().setDiv(x);
2304      r.getText().setStatus(status);
2305    } else {
2306      XhtmlNode n = r.getText().getDiv();
2307      n.hr();
2308      n.addChildNodes(x.getChildNodes());
2309    }
2310  }
2311
2312  public Element getNarrative(Element er) {
2313    Element txt = XMLUtil.getNamedChild(er, "text");
2314    if (txt == null)
2315      return null;
2316    return XMLUtil.getNamedChild(txt, "div");
2317  }
2318
2319
2320  private void inject(Element er, XhtmlNode x, NarrativeStatus status) {
2321    if (!x.hasAttribute("xmlns"))
2322      x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2323    Element txt = XMLUtil.getNamedChild(er, "text");
2324    if (txt == null) {
2325      txt = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "text");
2326      Element n = XMLUtil.getFirstChild(er);
2327      while (n != null && (n.getNodeName().equals("id") || n.getNodeName().equals("meta") || n.getNodeName().equals("implicitRules") || n.getNodeName().equals("language")))
2328        n = XMLUtil.getNextSibling(n);
2329      if (n == null)
2330        er.appendChild(txt);
2331      else
2332        er.insertBefore(txt, n);
2333    }
2334    Element st = XMLUtil.getNamedChild(txt, "status");
2335    if (st == null) {
2336      st = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "status");
2337      Element n = XMLUtil.getFirstChild(txt);
2338      if (n == null)
2339        txt.appendChild(st);
2340      else
2341        txt.insertBefore(st, n);
2342    }
2343    st.setAttribute("value", status.toCode());
2344    Element div = XMLUtil.getNamedChild(txt, "div");
2345    if (div == null) {
2346      div = er.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "div");
2347      div.setAttribute("xmlns", FormatUtilities.XHTML_NS);
2348      txt.appendChild(div);
2349    }
2350    if (div.hasChildNodes())
2351      div.appendChild(er.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "hr"));
2352    new XhtmlComposer(XhtmlComposer.XML).compose(div, x);
2353  }
2354
2355  private void inject(org.hl7.fhir.dstu3.elementmodel.Element er, XhtmlNode x, NarrativeStatus status) throws DefinitionException, IOException {
2356    if (!x.hasAttribute("xmlns"))
2357      x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2358    org.hl7.fhir.dstu3.elementmodel.Element txt = er.getNamedChild("text");
2359    if (txt == null) {
2360      txt = new org.hl7.fhir.dstu3.elementmodel.Element("text", er.getProperty().getChild(null, "text"));
2361      int i = 0;
2362      while (i < er.getChildren().size() && (er.getChildren().get(i).getName().equals("id") || er.getChildren().get(i).getName().equals("meta") || er.getChildren().get(i).getName().equals("implicitRules") || er.getChildren().get(i).getName().equals("language")))
2363        i++;
2364      if (i >= er.getChildren().size())
2365        er.getChildren().add(txt);
2366      else
2367        er.getChildren().add(i, txt);
2368    }
2369    org.hl7.fhir.dstu3.elementmodel.Element st = txt.getNamedChild("status");
2370    if (st == null) {
2371      st = new org.hl7.fhir.dstu3.elementmodel.Element("status", txt.getProperty().getChild(null, "status"));
2372      txt.getChildren().add(0, st);
2373    }
2374    st.setValue(status.toCode());
2375    org.hl7.fhir.dstu3.elementmodel.Element div = txt.getNamedChild("div");
2376    if (div == null) {
2377      div = new org.hl7.fhir.dstu3.elementmodel.Element("div", txt.getProperty().getChild(null, "div"));
2378      txt.getChildren().add(div);
2379      div.setValue(new XhtmlComposer(XhtmlComposer.XML).compose(x));
2380    }
2381    div.setXhtml(x);
2382  }
2383
2384  private String getDisplay(List<OtherElementComponent> list, String s) {
2385    for (OtherElementComponent c : list) {
2386      if (s.equals(c.getProperty()))
2387        return getDisplayForConcept(c.getSystem(), c.getCode());
2388    }
2389    return null;
2390  }
2391
2392  private String getDisplayForConcept(String system, String code) {
2393    if (code == null)
2394      return null;
2395    ValidationResult cl = context.validateCode(system, code, null);
2396    return cl == null ? null : cl.getDisplay();
2397  }
2398
2399
2400
2401  private String getDescForConcept(String s) {
2402    if (s.startsWith("http://hl7.org/fhir/v2/element/"))
2403        return "v2 "+s.substring("http://hl7.org/fhir/v2/element/".length());
2404    return s;
2405  }
2406
2407  private String getCode(List<OtherElementComponent> list, String s, boolean withSystem) {
2408    for (OtherElementComponent c : list) {
2409      if (s.equals(c.getProperty()))
2410        if (withSystem)
2411          return c.getSystem()+" / "+c.getCode();
2412        else
2413          return c.getCode();
2414    }
2415    return null;
2416  }
2417
2418  private void addTelecom(XhtmlNode p, ContactPoint c) {
2419    if (c.getSystem() == ContactPointSystem.PHONE) {
2420      p.tx("Phone: "+c.getValue());
2421    } else if (c.getSystem() == ContactPointSystem.FAX) {
2422      p.tx("Fax: "+c.getValue());
2423    } else if (c.getSystem() == ContactPointSystem.EMAIL) {
2424      p.ah( "mailto:"+c.getValue()).addText(c.getValue());
2425    } else if (c.getSystem() == ContactPointSystem.URL) {
2426      if (c.getValue().length() > 30)
2427        p.ah(c.getValue()).addText(c.getValue().substring(0, 30)+"...");
2428      else
2429        p.ah(c.getValue()).addText(c.getValue());
2430    }
2431  }
2432
2433  /**
2434   * This generate is optimised for the FHIR build process itself in as much as it
2435   * generates hyperlinks in the narrative that are only going to be correct for
2436   * the purposes of the build. This is to be reviewed in the future.
2437   *
2438   * @param vs
2439   * @param codeSystems
2440   * @throws IOException
2441   * @throws DefinitionException
2442   * @throws FHIRFormatError
2443   * @throws Exception
2444   */
2445  public boolean generate(ResourceContext rcontext, CodeSystem cs, boolean header) throws FHIRFormatError, DefinitionException, IOException {
2446    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
2447    boolean hasExtensions = false;
2448    hasExtensions = generateDefinition(x, cs, header);
2449    inject(cs, x, hasExtensions ? NarrativeStatus.EXTENSIONS :  NarrativeStatus.GENERATED);
2450    return true;
2451  }
2452
2453  private boolean generateDefinition(XhtmlNode x, CodeSystem cs, boolean header) throws FHIRFormatError, DefinitionException, IOException {
2454    boolean hasExtensions = false;
2455    Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>();
2456//    for (ConceptMap a : context.findMapsForSource(cs.getValueSet())) {
2457//      String url = "";
2458//      ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference());
2459//      if (vsr != null)
2460//        url = (String) vsr.getUserData("filename");
2461//      mymaps.put(a, url);
2462//    }
2463    // also, look in the contained resources for a concept map
2464    for (Resource r : cs.getContained()) {
2465      if (r instanceof ConceptMap) {
2466        ConceptMap cm = (ConceptMap) r;
2467        if (((Reference) cm.getSource()).getReference().equals(cs.getValueSet())) {
2468          String url = "";
2469          ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) cm.getTarget()).getReference());
2470          if (vsr != null)
2471              url = (String) vsr.getUserData("filename");
2472        mymaps.put(cm, url);
2473        }
2474      }
2475    }
2476    List<String> langs = new ArrayList<String>();
2477
2478    if (header) {
2479      XhtmlNode h = x.h2();
2480      h.addText(cs.hasTitle() ? cs.getTitle() : cs.getName());
2481      addMarkdown(x, cs.getDescription());
2482      if (cs.hasCopyright())
2483        generateCopyright(x, cs);
2484    }
2485
2486    generateProperties(x, cs);
2487    generateFilters(x, cs);
2488    hasExtensions = generateCodeSystemContent(x, cs, hasExtensions, mymaps, langs);
2489
2490    return hasExtensions;
2491  }
2492
2493  private void generateFilters(XhtmlNode x, CodeSystem cs) {
2494    if (cs.hasFilter()) {
2495      x.para().b().tx("Filters");
2496      XhtmlNode tbl = x.table("grid");
2497      XhtmlNode tr = tbl.tr();
2498      tr.td().b().tx("Code");
2499      tr.td().b().tx("Description");
2500      tr.td().b().tx("operator");
2501      tr.td().b().tx("Value");
2502      for (CodeSystemFilterComponent f : cs.getFilter()) {
2503        tr = tbl.tr();
2504        tr.td().tx(f.getCode());
2505        tr.td().tx(f.getDescription());
2506        XhtmlNode td = tr.td();
2507        for (Enumeration<org.hl7.fhir.dstu3.model.CodeSystem.FilterOperator> t : f.getOperator())
2508          td.tx(t.asStringValue()+" ");
2509        tr.td().tx(f.getValue());
2510      }
2511    }
2512  }
2513
2514  private void generateProperties(XhtmlNode x, CodeSystem cs) {
2515    if (cs.hasProperty()) {
2516      x.para().b().tx("Properties");
2517      XhtmlNode tbl = x.table("grid");
2518      XhtmlNode tr = tbl.tr();
2519      tr.td().b().tx("Code");
2520      tr.td().b().tx("URL");
2521      tr.td().b().tx("Description");
2522      tr.td().b().tx("Type");
2523      for (PropertyComponent p : cs.getProperty()) {
2524        tr = tbl.tr();
2525        tr.td().tx(p.getCode());
2526        tr.td().tx(p.getUri());
2527        tr.td().tx(p.getDescription());
2528        tr.td().tx(p.hasType() ? p.getType().toCode() : "");
2529      }
2530    }
2531  }
2532
2533  private boolean generateCodeSystemContent(XhtmlNode x, CodeSystem cs, boolean hasExtensions, Map<ConceptMap, String> mymaps, List<String> langs)
2534      throws FHIRFormatError, DefinitionException, IOException {
2535    XhtmlNode p = x.para();
2536    if (cs.getContent() == CodeSystemContentMode.COMPLETE)
2537      p.tx("This code system "+cs.getUrl()+" defines the following codes:");
2538    else if (cs.getContent() == CodeSystemContentMode.EXAMPLE)
2539        p.tx("This code system "+cs.getUrl()+" defines many codes, of which the following are some examples:");
2540    else if (cs.getContent() == CodeSystemContentMode.FRAGMENT )
2541      p.tx("This code system "+cs.getUrl()+" defines many codes, of which the following are a subset:");
2542    else if (cs.getContent() == CodeSystemContentMode.NOTPRESENT ) {
2543      p.tx("This code system "+cs.getUrl()+" defines many codes, but they are not represented here");
2544      return false;
2545    }
2546    XhtmlNode t = x.table( "codes");
2547    boolean commentS = false;
2548    boolean deprecated = false;
2549    boolean display = false;
2550    boolean hierarchy = false;
2551    for (ConceptDefinitionComponent c : cs.getConcept()) {
2552      commentS = commentS || conceptsHaveComments(c);
2553      deprecated = deprecated || conceptsHaveDeprecated(cs, c);
2554      display = display || conceptsHaveDisplay(c);
2555      hierarchy = hierarchy || c.hasConcept();
2556      scanLangs(c, langs);
2557    }
2558    addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, true, commentS, deprecated), mymaps);
2559    for (ConceptDefinitionComponent c : cs.getConcept()) {
2560      hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, commentS, deprecated, mymaps, cs.getUrl(), cs) || hasExtensions;
2561    }
2562    if (langs.size() > 0) {
2563      Collections.sort(langs);
2564      x.para().b().tx("Additional Language Displays");
2565      t = x.table( "codes");
2566      XhtmlNode tr = t.tr();
2567      tr.td().b().tx("Code");
2568      for (String lang : langs)
2569        tr.td().b().addText(describeLang(lang));
2570      for (ConceptDefinitionComponent c : cs.getConcept()) {
2571        addLanguageRow(c, t, langs);
2572      }
2573    }
2574    return hasExtensions;
2575  }
2576
2577  private int countConcepts(List<ConceptDefinitionComponent> list) {
2578    int count = list.size();
2579    for (ConceptDefinitionComponent c : list)
2580      if (c.hasConcept())
2581        count = count + countConcepts(c.getConcept());
2582    return count;
2583  }
2584
2585  private void generateCopyright(XhtmlNode x, CodeSystem cs) {
2586    XhtmlNode p = x.para();
2587    p.b().tx("Copyright Statement:");
2588    smartAddText(p, " " + cs.getCopyright());
2589  }
2590
2591
2592  /**
2593   * This generate is optimised for the FHIR build process itself in as much as it
2594   * generates hyperlinks in the narrative that are only going to be correct for
2595   * the purposes of the build. This is to be reviewed in the future.
2596   *
2597   * @param vs
2598   * @param codeSystems
2599   * @throws FHIRException
2600   * @throws IOException
2601   * @throws Exception
2602   */
2603  public boolean generate(ResourceContext rcontext, ValueSet vs, boolean header) throws FHIRException, IOException {
2604    generate(rcontext, vs, null, header);
2605    return true;
2606  }
2607
2608  public void generate(ResourceContext rcontext, ValueSet vs, ValueSet src, boolean header) throws FHIRException, IOException {
2609    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
2610    boolean hasExtensions;
2611    if (vs.hasExpansion()) {
2612      // for now, we just accept an expansion if there is one
2613      hasExtensions = generateExpansion(x, vs, src, header);
2614    } else {
2615      hasExtensions = generateComposition(rcontext, x, vs, header);
2616    }
2617    inject(vs, x, hasExtensions ? NarrativeStatus.EXTENSIONS :  NarrativeStatus.GENERATED);
2618  }
2619
2620  private Integer countMembership(ValueSet vs) {
2621    int count = 0;
2622    if (vs.hasExpansion())
2623      count = count + conceptCount(vs.getExpansion().getContains());
2624    else {
2625      if (vs.hasCompose()) {
2626        if (vs.getCompose().hasExclude()) {
2627          try {
2628            ValueSetExpansionOutcome vse = context.expandVS(vs, true, false);
2629            count = 0;
2630            count += conceptCount(vse.getValueset().getExpansion().getContains());
2631            return count;
2632          } catch (Exception e) {
2633            return null;
2634          }
2635        }
2636        for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
2637          if (inc.hasFilter())
2638            return null;
2639          if (!inc.hasConcept())
2640            return null;
2641          count = count + inc.getConcept().size();
2642        }
2643      }
2644    }
2645    return count;
2646  }
2647
2648  private int conceptCount(List<ValueSetExpansionContainsComponent> list) {
2649    int count = 0;
2650    for (ValueSetExpansionContainsComponent c : list) {
2651      if (!c.getAbstract())
2652        count++;
2653      count = count + conceptCount(c.getContains());
2654    }
2655    return count;
2656  }
2657
2658  private boolean generateExpansion(XhtmlNode x, ValueSet vs, ValueSet src, boolean header) throws FHIRFormatError, DefinitionException, IOException {
2659    boolean hasExtensions = false;
2660    List<String> langs = new ArrayList<String>();
2661
2662    Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>();
2663//    for (ConceptMap a : context.findMapsForSource(vs.getUrl())) {
2664//      String url = "";
2665//      ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference());
2666//      if (vsr != null)
2667//        url = (String) vsr.getUserData("filename");
2668//      mymaps.put(a, url);
2669//    }
2670
2671    if (header) {
2672      XhtmlNode h = x.addTag(getHeader());
2673      h.tx("Value Set Contents");
2674      if (IsNotFixedExpansion(vs))
2675        addMarkdown(x, vs.getDescription());
2676      if (vs.hasCopyright())
2677        generateCopyright(x, vs);
2678    }
2679    if (ToolingExtensions.hasExtension(vs.getExpansion(), "http://hl7.org/fhir/StructureDefinition/valueset-toocostly"))
2680      x.para().setAttribute("style", "border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(vs.getExpansion().getContains().isEmpty() ? tooCostlyNoteEmpty : tooCostlyNoteNotEmpty );
2681    else {
2682      Integer count = countMembership(vs);
2683      if (count == null)
2684        x.para().tx("This value set does not contain a fixed number of concepts");
2685      else
2686        x.para().tx("This value set contains "+count.toString()+" concepts");
2687    }
2688
2689    generateVersionNotice(x, vs.getExpansion());
2690
2691    CodeSystem allCS = null;
2692    boolean doSystem = true; // checkDoSystem(vs, src);
2693    boolean doDefinition = checkDoDefinition(vs.getExpansion().getContains());
2694    if (doSystem && allFromOneSystem(vs)) {
2695      doSystem = false;
2696      XhtmlNode p = x.para();
2697      p.tx("All codes from system ");
2698      allCS = context.fetchCodeSystem(vs.getExpansion().getContains().get(0).getSystem());
2699      String ref = null;
2700      if (allCS != null)
2701        ref = getCsRef(allCS);
2702      if (ref == null)
2703        p.code(vs.getExpansion().getContains().get(0).getSystem());
2704      else
2705        p.ah(prefix+ref).code(vs.getExpansion().getContains().get(0).getSystem());
2706    }
2707    XhtmlNode t = x.table( "codes");
2708    XhtmlNode tr = t.tr();
2709    tr.td().b().tx("Code");
2710    if (doSystem)
2711      tr.td().b().tx("System");
2712    tr.td().b().tx("Display");
2713    if (doDefinition)
2714      tr.td().b().tx("Definition");
2715
2716    addMapHeaders(tr, mymaps);
2717    for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
2718      addExpansionRowToTable(t, c, 0, doSystem, doDefinition, mymaps, allCS, langs);
2719    }
2720
2721    // now, build observed languages
2722
2723    if (langs.size() > 0) {
2724      Collections.sort(langs);
2725      x.para().b().tx("Additional Language Displays");
2726      t = x.table( "codes");
2727      tr = t.tr();
2728      tr.td().b().tx("Code");
2729      for (String lang : langs)
2730        tr.td().b().addText(describeLang(lang));
2731      for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
2732        addLanguageRow(c, t, langs);
2733      }
2734    }
2735
2736    return hasExtensions;
2737  }
2738
2739  @SuppressWarnings("rawtypes")
2740  private void generateVersionNotice(XhtmlNode x, ValueSetExpansionComponent expansion) {
2741    Map<String, String> versions = new HashMap<String, String>();
2742    boolean firstVersion = true;
2743    for (ValueSetExpansionParameterComponent p : expansion.getParameter()) {
2744      if (p.getName().equals("version")) {
2745        String[] parts = ((PrimitiveType) p.getValue()).asStringValue().split("\\|");
2746        if (parts.length == 2)
2747          versions.put(parts[0], parts[1]);
2748        if (!versions.isEmpty()) {
2749          StringBuilder b = new StringBuilder();
2750          if (firstVersion) {
2751            // the first version
2752            // set the <p> tag and style attribute
2753            x.para().setAttribute("style", "border: black 1px dotted; background-color: #EEEEEE; padding: 8px");
2754                  firstVersion = false;
2755          } else {
2756            // the second (or greater) version
2757            x.br(); // add line break before the version text
2758          }
2759          b.append("Expansion based on ");
2760          boolean firstPart = true;
2761          for (String s : versions.keySet()) {
2762            if (firstPart)
2763              firstPart = false;
2764            else
2765              b.append(", ");
2766            if (!s.equals("http://snomed.info/sct"))
2767              b.append(describeSystem(s)+" version "+versions.get(s));
2768            else {
2769              parts = versions.get(s).split("\\/");
2770              if (parts.length >= 5) {
2771                String m = describeModule(parts[4]);
2772                if (parts.length == 7)
2773                  b.append("SNOMED CT "+m+" edition "+formatSCTDate(parts[6]));
2774                else
2775                  b.append("SNOMED CT "+m+" edition");
2776              } else
2777                b.append(describeSystem(s)+" version "+versions.get(s));
2778            }
2779          }
2780          x.addText(b.toString()); // add the version text
2781        }
2782      }
2783    }
2784  }
2785
2786  private String formatSCTDate(String ds) {
2787    SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
2788    Date date;
2789    try {
2790      date = format.parse(ds);
2791    } catch (ParseException e) {
2792      return ds;
2793    }
2794    return new SimpleDateFormat("dd-MMM yyyy").format(date);
2795  }
2796
2797  private String describeModule(String module) {
2798    if ("900000000000207008".equals(module))
2799      return "International";
2800    if ("731000124108".equals(module))
2801      return "United States";
2802    if ("32506021000036107".equals(module))
2803      return "Australian";
2804    if ("449081005".equals(module))
2805      return "Spanish";
2806    if ("554471000005108".equals(module))
2807      return "Danish";
2808    if ("11000146104".equals(module))
2809      return "Dutch";
2810    if ("45991000052106".equals(module))
2811      return "Swedish";
2812    if ("999000041000000102".equals(module))
2813      return "United Kingdon";
2814    return module;
2815  }
2816
2817  private boolean hasVersionParameter(ValueSetExpansionComponent expansion) {
2818    for (ValueSetExpansionParameterComponent p : expansion.getParameter()) {
2819      if (p.getName().equals("version"))
2820        return true;
2821    }
2822    return false;
2823  }
2824
2825  private void addLanguageRow(ValueSetExpansionContainsComponent c, XhtmlNode t, List<String> langs) {
2826    XhtmlNode tr = t.tr();
2827    tr.td().addText(c.getCode());
2828    for (String lang : langs) {
2829      String d = null;
2830      for (Extension ext : c.getExtension()) {
2831        if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) {
2832          String l = ToolingExtensions.readStringExtension(ext, "lang");
2833          if (lang.equals(l))
2834            d = ToolingExtensions.readStringExtension(ext, "content");;
2835        }
2836      }
2837      tr.td().addText(d == null ? "" : d);
2838    }
2839    for (ValueSetExpansionContainsComponent cc : c.getContains()) {
2840      addLanguageRow(cc, t, langs);
2841    }
2842  }
2843
2844
2845  private String describeLang(String lang) {
2846    ValueSet v = context.fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages");
2847    if (v != null) {
2848      ConceptReferenceComponent l = null;
2849      for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) {
2850        if (cc.getCode().equals(lang))
2851          l = cc;
2852      }
2853      if (l == null) {
2854        if (lang.contains("-"))
2855          lang = lang.substring(0, lang.indexOf("-"));
2856        for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) {
2857          if (cc.getCode().equals(lang) || cc.getCode().startsWith(lang+"-"))
2858            l = cc;
2859        }
2860      }
2861      if (l != null) {
2862        if (lang.contains("-"))
2863          lang = lang.substring(0, lang.indexOf("-"));
2864        String en = l.getDisplay();
2865        String nativelang = null;
2866        for (ConceptReferenceDesignationComponent cd : l.getDesignation()) {
2867          if (cd.getLanguage().equals(lang))
2868            nativelang = cd.getValue();
2869        }
2870        if (nativelang == null)
2871          return en+" ("+lang+")";
2872        else
2873          return nativelang+" ("+en+", "+lang+")";
2874      }
2875    }
2876    return lang;
2877  }
2878
2879
2880  private boolean checkDoDefinition(List<ValueSetExpansionContainsComponent> contains) {
2881    for (ValueSetExpansionContainsComponent c : contains) {
2882      CodeSystem cs = context.fetchCodeSystem(c.getSystem());
2883      if (cs != null)
2884        return true;
2885      if (checkDoDefinition(c.getContains()))
2886        return true;
2887    }
2888    return false;
2889  }
2890
2891
2892  private boolean allFromOneSystem(ValueSet vs) {
2893    if (vs.getExpansion().getContains().isEmpty())
2894      return false;
2895    String system = vs.getExpansion().getContains().get(0).getSystem();
2896    for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
2897      if (!checkSystemMatches(system, cc))
2898        return false;
2899    }
2900    return true;
2901  }
2902
2903
2904  private boolean checkSystemMatches(String system, ValueSetExpansionContainsComponent cc) {
2905    if (!system.equals(cc.getSystem()))
2906      return false;
2907    for (ValueSetExpansionContainsComponent cc1 : cc.getContains()) {
2908      if (!checkSystemMatches(system, cc1))
2909        return false;
2910    }
2911     return true;
2912  }
2913
2914
2915  private boolean checkDoSystem(ValueSet vs, ValueSet src) {
2916    if (src != null)
2917      vs = src;
2918    if (vs.hasCompose())
2919      return true;
2920    return false;
2921  }
2922
2923  private boolean IsNotFixedExpansion(ValueSet vs) {
2924    if (vs.hasCompose())
2925      return false;
2926
2927
2928    // it's not fixed if it has any includes that are not version fixed
2929    for (ConceptSetComponent cc : vs.getCompose().getInclude()) {
2930      if (cc.hasValueSet())
2931        return true;
2932      if (!cc.hasVersion())
2933        return true;
2934    }
2935    return false;
2936  }
2937
2938
2939  private void addLanguageRow(ConceptDefinitionComponent c, XhtmlNode t, List<String> langs) {
2940    XhtmlNode tr = t.tr();
2941    tr.td().addText(c.getCode());
2942    for (String lang : langs) {
2943      ConceptDefinitionDesignationComponent d = null;
2944      for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) {
2945        if (lang.equals(designation.getLanguage()))
2946          d = designation;
2947      }
2948      tr.td().addText(d == null ? "" : d.getValue());
2949    }
2950  }
2951
2952  private void scanLangs(ConceptDefinitionComponent c, List<String> langs) {
2953    for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) {
2954      String lang = designation.getLanguage();
2955      if (langs != null && !langs.contains(lang))
2956        langs.add(lang);
2957    }
2958    for (ConceptDefinitionComponent g : c.getConcept())
2959      scanLangs(g, langs);
2960  }
2961
2962  private void addMapHeaders(XhtmlNode tr, Map<ConceptMap, String> mymaps) throws FHIRFormatError, DefinitionException, IOException {
2963          for (ConceptMap m : mymaps.keySet()) {
2964                XhtmlNode td = tr.td();
2965                XhtmlNode b = td.b();
2966                XhtmlNode a = b.ah(prefix+mymaps.get(m));
2967      a.addText(m.getName());
2968      if (m.hasDescription())
2969        addMarkdown(td, m.getDescription());
2970          }
2971  }
2972
2973        private void smartAddText(XhtmlNode p, String text) {
2974          if (text == null)
2975            return;
2976
2977    String[] lines = text.split("\\r\\n");
2978    for (int i = 0; i < lines.length; i++) {
2979      if (i > 0)
2980        p.br();
2981      p.addText(lines[i]);
2982    }
2983  }
2984
2985  private boolean conceptsHaveComments(ConceptDefinitionComponent c) {
2986    if (ToolingExtensions.hasCSComment(c))
2987      return true;
2988    for (ConceptDefinitionComponent g : c.getConcept())
2989      if (conceptsHaveComments(g))
2990        return true;
2991    return false;
2992  }
2993
2994  private boolean conceptsHaveDisplay(ConceptDefinitionComponent c) {
2995    if (c.hasDisplay())
2996      return true;
2997    for (ConceptDefinitionComponent g : c.getConcept())
2998      if (conceptsHaveDisplay(g))
2999        return true;
3000    return false;
3001  }
3002
3003  private boolean conceptsHaveDeprecated(CodeSystem cs, ConceptDefinitionComponent c) {
3004    if (CodeSystemUtilities.isDeprecated(cs, c))
3005      return true;
3006    for (ConceptDefinitionComponent g : c.getConcept())
3007      if (conceptsHaveDeprecated(cs, g))
3008        return true;
3009    return false;
3010  }
3011
3012  private void generateCopyright(XhtmlNode x, ValueSet vs) {
3013    XhtmlNode p = x.para();
3014    p.b().tx("Copyright Statement:");
3015    smartAddText(p, " " + vs.getCopyright());
3016  }
3017
3018
3019  private XhtmlNode addTableHeaderRowStandard(XhtmlNode t, boolean hasHierarchy, boolean hasDisplay, boolean definitions, boolean comments, boolean deprecated) {
3020    XhtmlNode tr = t.tr();
3021    if (hasHierarchy)
3022      tr.td().b().tx("Lvl");
3023    tr.td().b().tx("Code");
3024    if (hasDisplay)
3025      tr.td().b().tx("Display");
3026    if (definitions)
3027      tr.td().b().tx("Definition");
3028    if (deprecated)
3029      tr.td().b().tx("Deprecated");
3030    if (comments)
3031      tr.td().b().tx("Comments");
3032    return tr;
3033  }
3034
3035  private void addExpansionRowToTable(XhtmlNode t, ValueSetExpansionContainsComponent c, int i, boolean doSystem, boolean doDefinition, Map<ConceptMap, String> mymaps, CodeSystem allCS, List<String> langs) {
3036    XhtmlNode tr = t.tr();
3037    XhtmlNode td = tr.td();
3038
3039    String tgt = makeAnchor(c.getSystem(), c.getCode());
3040    td.an(tgt);
3041
3042    String s = Utilities.padLeft("", '\u00A0', i*2);
3043
3044    td.addText(s);
3045    addCodeToTable(c.getAbstract(), c.getSystem(), c.getCode(), c.getDisplay(), td);
3046    if (doSystem) {
3047      td = tr.td();
3048      td.addText(c.getSystem());
3049    }
3050    td = tr.td();
3051    if (c.hasDisplayElement())
3052      td.addText(c.getDisplay());
3053
3054    if (doDefinition) {
3055      CodeSystem cs = allCS;
3056      if (cs == null)
3057        cs = context.fetchCodeSystem(c.getSystem());
3058      td = tr.td();
3059      if (cs != null)
3060        td.addText(CodeSystemUtilities.getCodeDefinition(cs, c.getCode()));
3061    }
3062    for (ConceptMap m : mymaps.keySet()) {
3063      td = tr.td();
3064      List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m);
3065      boolean first = true;
3066      for (TargetElementComponentWrapper mapping : mappings) {
3067        if (!first)
3068            td.br();
3069        first = false;
3070        XhtmlNode span = td.span(null, mapping.comp.getEquivalence().toString());
3071        span.addText(getCharForEquivalence(mapping.comp));
3072        XhtmlNode a = td.ah(prefix+mymaps.get(m)+"#"+mapping.comp.getCode());
3073        a.addText(mapping.comp.getCode());
3074        if (!Utilities.noString(mapping.comp.getComment()))
3075          td.i().tx("("+mapping.comp.getComment()+")");
3076      }
3077    }
3078    for (Extension ext : c.getExtension()) {
3079      if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) {
3080        String lang = ToolingExtensions.readStringExtension(ext,  "lang");
3081        if (!Utilities.noString(lang) && !langs.contains(lang))
3082          langs.add(lang);
3083      }
3084    }
3085    for (ValueSetExpansionContainsComponent cc : c.getContains()) {
3086      addExpansionRowToTable(t, cc, i+1, doSystem, doDefinition, mymaps, allCS, langs);
3087    }
3088  }
3089
3090  private void addCodeToTable(boolean isAbstract, String system, String code, String display, XhtmlNode td) {
3091    CodeSystem e = context.fetchCodeSystem(system);
3092    if (e == null || e.getContent() != org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode.COMPLETE) {
3093      if (isAbstract)
3094        td.i().setAttribute("title", ABSTRACT_CODE_HINT).addText(code);
3095      else if ("http://snomed.info/sct".equals(system)) {
3096        td.ah("http://browser.ihtsdotools.org/?perspective=full&conceptId1="+code).addText(code);
3097      } else if ("http://loinc.org".equals(system)) {
3098          td.ah(LoincLinker.getLinkForCode(code)).addText(code);
3099      } else        
3100        td.addText(code);
3101    } else {
3102      String href = prefix+getCsRef(e);
3103      if (href.contains("#"))
3104        href = href + "-"+Utilities.nmtokenize(code);
3105      else
3106        href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(code);
3107      if (isAbstract)
3108        td.ah(href).setAttribute("title", ABSTRACT_CODE_HINT).i().addText(code);
3109      else
3110        td.ah(href).addText(code);
3111    }
3112  }
3113
3114  private class TargetElementComponentWrapper {
3115    private ConceptMapGroupComponent group;
3116    private TargetElementComponent comp;
3117    public TargetElementComponentWrapper(ConceptMapGroupComponent group, TargetElementComponent comp) {
3118      super();
3119      this.group = group;
3120      this.comp = comp;
3121    }
3122
3123  }
3124
3125  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) {
3126    boolean hasExtensions = false;
3127    XhtmlNode tr = t.tr();
3128    XhtmlNode td = tr.td();
3129    if (hasHierarchy) {
3130      td.addText(Integer.toString(i+1));
3131      td = tr.td();
3132      String s = Utilities.padLeft("", '\u00A0', i*2);
3133      td.addText(s);
3134    }
3135    td.addText(c.getCode());
3136    XhtmlNode a;
3137    if (c.hasCodeElement()) {
3138      td.an(cs.getId()+"-" + Utilities.nmtokenize(c.getCode()));
3139    }
3140
3141    if (hasDisplay) {
3142      td = tr.td();
3143      if (c.hasDisplayElement())
3144        td.addText(c.getDisplay());
3145    }
3146    td = tr.td();
3147    if (c != null)
3148      smartAddText(td, c.getDefinition());
3149    if (deprecated) {
3150      td = tr.td();
3151      Boolean b = CodeSystemUtilities.isDeprecated(cs, c);
3152      if (b !=  null && b) {
3153        smartAddText(td, "Deprecated");
3154        hasExtensions = true;
3155        if (ToolingExtensions.hasExtension(c, ToolingExtensions.EXT_REPLACED_BY)) {
3156          Coding cc = (Coding) ToolingExtensions.getExtension(c, ToolingExtensions.EXT_REPLACED_BY).getValue();
3157          td.tx(" (replaced by ");
3158          String url = getCodingReference(cc, system);
3159          if (url != null) {
3160            td.ah(url).addText(cc.getCode());
3161            td.tx(": "+cc.getDisplay()+")");
3162          } else
3163            td.addText(cc.getCode()+" '"+cc.getDisplay()+"' in "+cc.getSystem()+")");
3164        }
3165      }
3166    }
3167    if (comment) {
3168      td = tr.td();
3169      String s = ToolingExtensions.getCSComment(c);
3170      if (s != null) {
3171        smartAddText(td, s);
3172        hasExtensions = true;
3173      }
3174    }
3175    for (ConceptMap m : maps.keySet()) {
3176      td = tr.td();
3177      List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m);
3178      boolean first = true;
3179      for (TargetElementComponentWrapper mapping : mappings) {
3180        if (!first)
3181                  td.br();
3182        first = false;
3183        XhtmlNode span = td.span(null, mapping.comp.hasEquivalence() ?  mapping.comp.getEquivalence().toCode() : "");
3184        span.addText(getCharForEquivalence(mapping.comp));
3185        a = td.ah(prefix+maps.get(m)+"#"+makeAnchor(mapping.group.getTarget(), mapping.comp.getCode()));
3186        a.addText(mapping.comp.getCode());
3187        if (!Utilities.noString(mapping.comp.getComment()))
3188          td.i().tx("("+mapping.comp.getComment()+")");
3189      }
3190    }
3191    for (CodeType e : ToolingExtensions.getSubsumes(c)) {
3192      hasExtensions = true;
3193      tr = t.tr();
3194      td = tr.td();
3195      String s = Utilities.padLeft("", '.', i*2);
3196      td.addText(s);
3197      a = td.ah("#"+Utilities.nmtokenize(e.getValue()));
3198      a.addText(c.getCode());
3199    }
3200    for (ConceptDefinitionComponent cc : c.getConcept()) {
3201      hasExtensions = addDefineRowToTable(t, cc, i+1, hasHierarchy, hasDisplay, comment, deprecated, maps, system, cs) || hasExtensions;
3202    }
3203    return hasExtensions;
3204  }
3205
3206
3207  private String makeAnchor(String codeSystem, String code) {
3208    String s = codeSystem+'-'+code;
3209    StringBuilder b = new StringBuilder();
3210    for (char c : s.toCharArray()) {
3211      if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '.')
3212        b.append(c);
3213      else
3214        b.append('-');
3215    }
3216    return b.toString();
3217  }
3218
3219  private String getCodingReference(Coding cc, String system) {
3220    if (cc.getSystem().equals(system))
3221      return "#"+cc.getCode();
3222    if (cc.getSystem().equals("http://snomed.info/sct"))
3223      return "http://snomed.info/sct/"+cc.getCode();
3224    if (cc.getSystem().equals("http://loinc.org"))
3225      return LoincLinker.getLinkForCode(cc.getCode());
3226    return null;
3227  }
3228
3229  private String getCharForEquivalence(TargetElementComponent mapping) {
3230    if (!mapping.hasEquivalence())
3231      return "";
3232          switch (mapping.getEquivalence()) {
3233          case EQUAL : return "=";
3234          case EQUIVALENT : return "~";
3235          case WIDER : return "<";
3236          case NARROWER : return ">";
3237          case INEXACT : return "><";
3238          case UNMATCHED : return "-";
3239          case DISJOINT : return "!=";
3240    case NULL: return null;
3241            default: return "?";
3242          }
3243  }
3244
3245  private List<TargetElementComponentWrapper> findMappingsForCode(String code, ConceptMap map) {
3246    List<TargetElementComponentWrapper> mappings = new ArrayList<TargetElementComponentWrapper>();
3247
3248    for (ConceptMapGroupComponent g : map.getGroup()) {
3249      for (SourceElementComponent c : g.getElement()) {
3250                if (c.getCode().equals(code))
3251          for (TargetElementComponent cc : c.getTarget())
3252            mappings.add(new TargetElementComponentWrapper(g, cc));
3253      }
3254          }
3255          return mappings;
3256  }
3257
3258  private boolean generateComposition(ResourceContext rcontext, XhtmlNode x, ValueSet vs, boolean header) throws FHIRException, IOException {
3259          boolean hasExtensions = false;
3260    List<String> langs = new ArrayList<String>();
3261
3262    if (header) {
3263      XhtmlNode h = x.h2();
3264      h.addText(vs.getName());
3265      addMarkdown(x, vs.getDescription());
3266      if (vs.hasCopyrightElement())
3267        generateCopyright(x, vs);
3268    }
3269    XhtmlNode p = x.para();
3270    p.tx("This value set includes codes from the following code systems:");
3271
3272    XhtmlNode ul = x.ul();
3273    XhtmlNode li;
3274    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
3275      hasExtensions = genInclude(rcontext, ul, inc, "Include", langs) || hasExtensions;
3276    }
3277    for (ConceptSetComponent exc : vs.getCompose().getExclude()) {
3278      hasExtensions = genInclude(rcontext, ul, exc, "Exclude", langs) || hasExtensions;
3279    }
3280
3281    // now, build observed languages
3282
3283    if (langs.size() > 0) {
3284      Collections.sort(langs);
3285      x.para().b().tx("Additional Language Displays");
3286      XhtmlNode t = x.table( "codes");
3287      XhtmlNode tr = t.tr();
3288      tr.td().b().tx("Code");
3289      for (String lang : langs)
3290        tr.td().b().addText(describeLang(lang));
3291      for (ConceptSetComponent c : vs.getCompose().getInclude()) {
3292        for (ConceptReferenceComponent cc : c.getConcept()) {
3293          addLanguageRow(cc, t, langs);
3294        }
3295      }
3296    }
3297
3298    return hasExtensions;
3299  }
3300
3301    private void addLanguageRow(ConceptReferenceComponent c, XhtmlNode t, List<String> langs) {
3302      XhtmlNode tr = t.tr();
3303      tr.td().addText(c.getCode());
3304      for (String lang : langs) {
3305        String d = null;
3306        for (ConceptReferenceDesignationComponent cd : c.getDesignation()) {
3307          String l = cd.getLanguage();
3308          if (lang.equals(l))
3309            d = cd.getValue();
3310        }
3311        tr.td().addText(d == null ? "" : d);
3312      }
3313    }
3314
3315  private void AddVsRef(ResourceContext rcontext, String value, XhtmlNode li) {
3316    Resource res = rcontext == null ? null : rcontext.resolve(value); 
3317    if (res != null && !(res instanceof MetadataResource)) {
3318      li.addText(value);
3319      return;      
3320    }      
3321    MetadataResource vs = (MetadataResource) res;
3322    if (vs == null)
3323                vs = context.fetchResource(ValueSet.class, value);
3324    if (vs == null)
3325                vs = context.fetchResource(StructureDefinition.class, value);
3326//    if (vs == null)
3327        //      vs = context.fetchResource(DataElement.class, value);
3328    if (vs == null)
3329                vs = context.fetchResource(Questionnaire.class, value);
3330    if (vs != null) {
3331      String ref = (String) vs.getUserData("path");
3332      
3333      ref = adjustForPath(ref);
3334      XhtmlNode a = li.ah(ref == null ? "??" : ref.replace("\\", "/"));
3335      a.addText(value);
3336    } else {
3337        CodeSystem cs = context.fetchCodeSystem(value);
3338        if (cs != null) {
3339        String ref = (String) cs.getUserData("path");
3340        ref = adjustForPath(ref);
3341        XhtmlNode a = li.ah(ref == null ? "??" : ref.replace("\\", "/"));
3342        a.addText(value);
3343            } else if (value.equals("http://snomed.info/sct") || value.equals("http://snomed.info/id")) {
3344              XhtmlNode a = li.ah(value);
3345              a.tx("SNOMED-CT");
3346            }
3347            else {
3348              if (value.startsWith("http://hl7.org") && !Utilities.existsInList(value, "http://hl7.org/fhir/sid/icd-10-us"))
3349                System.out.println("Unable to resolve value set "+value);
3350              li.addText(value);
3351    }
3352  }
3353        }
3354
3355  private String adjustForPath(String ref) {
3356    if (prefix == null)
3357      return ref;
3358    else
3359      return prefix+ref;
3360  }
3361
3362  private boolean genInclude(ResourceContext rcontext, XhtmlNode ul, ConceptSetComponent inc, String type, List<String> langs) throws FHIRException {
3363    boolean hasExtensions = false;
3364    XhtmlNode li;
3365    li = ul.li();
3366    CodeSystem e = context.fetchCodeSystem(inc.getSystem());
3367
3368    if (inc.hasSystem()) {
3369      if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
3370        li.addText(type+" all codes defined in ");
3371        addCsRef(inc, li, e);
3372      } else {
3373        if (inc.getConcept().size() > 0) {
3374          li.addText(type+" these codes as defined in ");
3375          addCsRef(inc, li, e);
3376
3377          XhtmlNode t = li.table("none");
3378          boolean hasComments = false;
3379          boolean hasDefinition = false;
3380          for (ConceptReferenceComponent c : inc.getConcept()) {
3381            hasComments = hasComments || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT);
3382            hasDefinition = hasDefinition || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION);
3383          }
3384          if (hasComments || hasDefinition)
3385            hasExtensions = true;
3386          addTableHeaderRowStandard(t, false, true, hasDefinition, hasComments, false);
3387          for (ConceptReferenceComponent c : inc.getConcept()) {
3388            XhtmlNode tr = t.tr();
3389            XhtmlNode td = tr.td();
3390            ConceptDefinitionComponent cc = getConceptForCode(e, c.getCode(), inc);
3391            addCodeToTable(false, inc.getSystem(), c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td);
3392
3393            td = tr.td();
3394            if (!Utilities.noString(c.getDisplay()))
3395              td.addText(c.getDisplay());
3396            else if (cc != null && !Utilities.noString(cc.getDisplay()))
3397              td.addText(cc.getDisplay());
3398
3399            td = tr.td();
3400            if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION))
3401              smartAddText(td, ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_DEFINITION));
3402            else if (cc != null && !Utilities.noString(cc.getDefinition()))
3403              smartAddText(td, cc.getDefinition());
3404
3405            if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT)) {
3406              smartAddText(tr.td(), "Note: "+ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_VS_COMMENT));
3407            }
3408            for (ConceptReferenceDesignationComponent cd : c.getDesignation()) {
3409              if (cd.hasLanguage() && !langs.contains(cd.getLanguage()))
3410                langs.add(cd.getLanguage());
3411            }
3412          }
3413        }
3414        boolean first = true;
3415        for (ConceptSetFilterComponent f : inc.getFilter()) {
3416          if (first) {
3417            li.addText(type+" codes from ");
3418            first = false;
3419          } else
3420            li.tx(" and ");
3421          addCsRef(inc, li, e);
3422          li.tx(" where "+f.getProperty()+" "+describe(f.getOp())+" ");
3423          if (e != null && codeExistsInValueSet(e, f.getValue())) {
3424            String href = prefix+getCsRef(e);
3425            if (href.contains("#"))
3426              href = href + "-"+Utilities.nmtokenize(f.getValue());
3427            else
3428              href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue());
3429            li.ah(href).addText(f.getValue());
3430          } else if ("concept".equals(f.getProperty()) && inc.hasSystem()) {
3431            li.addText(f.getValue());
3432            ValidationResult vr = context.validateCode(inc.getSystem(), f.getValue(), null);
3433            if (vr.isOk()) {
3434              li.tx(" ("+vr.getDisplay()+")");
3435            }
3436          }
3437          else
3438            li.addText(f.getValue());
3439          String disp = ToolingExtensions.getDisplayHint(f);
3440          if (disp != null)
3441            li.tx(" ("+disp+")");
3442        }
3443      }
3444      if (inc.hasValueSet()) {
3445        li.tx(", where the codes are contained in ");
3446        boolean first = true;
3447        for (UriType vs : inc.getValueSet()) {
3448          if (first)
3449            first = false;
3450          else
3451            li.tx(", ");
3452          AddVsRef(rcontext, vs.asStringValue(), li);
3453        }
3454      }
3455    } else {
3456      li = ul.li();
3457      li.tx("Import all the codes that are contained in ");
3458      boolean first = true;
3459      for (UriType vs : inc.getValueSet()) {
3460        if (first)
3461          first = false;
3462        else
3463          li.tx(", ");
3464        AddVsRef(rcontext, vs.asStringValue(), li);
3465      }
3466    }
3467    return hasExtensions;
3468  }
3469
3470  private String describe(FilterOperator op) {
3471    switch (op) {
3472    case EQUAL: return " = ";
3473    case ISA: return " is-a ";
3474    case ISNOTA: return " is-not-a ";
3475    case REGEX: return " matches (by regex) ";
3476                case NULL: return " ?? ";
3477                case IN: return " in ";
3478                case NOTIN: return " not in ";
3479    case DESCENDENTOF: return " descends from ";
3480    case EXISTS: return " exists ";
3481    case GENERALIZES: return " generalizes ";
3482    }
3483    return null;
3484  }
3485
3486  private ConceptDefinitionComponent getConceptForCode(CodeSystem e, String code, ConceptSetComponent inc) {
3487    // first, look in the code systems
3488    if (e == null)
3489    e = context.fetchCodeSystem(inc.getSystem());
3490    if (e != null) {
3491      ConceptDefinitionComponent v = getConceptForCode(e.getConcept(), code);
3492      if (v != null)
3493        return v;
3494    }
3495
3496    if (!context.hasCache()) {
3497      ValueSetExpansionComponent vse;
3498      try {
3499        vse = context.expandVS(inc, false);
3500      } catch (TerminologyServiceException e1) {
3501        return null;
3502      }
3503      if (vse != null) {
3504        ConceptDefinitionComponent v = getConceptForCodeFromExpansion(vse.getContains(), code);
3505      if (v != null)
3506        return v;
3507    }
3508    }
3509
3510    return context.validateCode(inc.getSystem(), code, null).asConceptDefinition();
3511  }
3512
3513
3514
3515  private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> list, String code) {
3516    for (ConceptDefinitionComponent c : list) {
3517    if (code.equals(c.getCode()))
3518      return c;
3519      ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);
3520      if (v != null)
3521        return v;
3522    }
3523    return null;
3524  }
3525
3526  private ConceptDefinitionComponent getConceptForCodeFromExpansion(List<ValueSetExpansionContainsComponent> list, String code) {
3527    for (ValueSetExpansionContainsComponent c : list) {
3528      if (code.equals(c.getCode())) {
3529        ConceptDefinitionComponent res = new ConceptDefinitionComponent();
3530        res.setCode(c.getCode());
3531        res.setDisplay(c.getDisplay());
3532        return res;
3533      }
3534      ConceptDefinitionComponent v = getConceptForCodeFromExpansion(c.getContains(), code);
3535      if (v != null)
3536        return v;
3537    }
3538    return null;
3539  }
3540
3541  private  <T extends Resource> void addCsRef(ConceptSetComponent inc, XhtmlNode li, T cs) {
3542    String ref = null;
3543    if (cs != null) {
3544      ref = (String) cs.getUserData("filename");
3545      if (Utilities.noString(ref))
3546        ref = (String) cs.getUserData("path");
3547    }
3548    String spec = getSpecialReference(inc.getSystem());
3549    if (spec != null) {
3550      XhtmlNode a = li.ah(spec);
3551      a.code(inc.getSystem());
3552    } else if (cs != null && ref != null) {
3553      if (!Utilities.noString(prefix) && ref.startsWith("http://hl7.org/fhir/"))
3554        ref = ref.substring(20)+"/index.html";
3555      else if (!ref.contains(".html"))
3556        ref = ref + ".html";
3557      XhtmlNode a = li.ah(prefix+ref.replace("\\", "/"));
3558      a.code(inc.getSystem());
3559    } else {
3560      li.code(inc.getSystem());
3561    }
3562  }
3563
3564  private String getSpecialReference(String system) {
3565    if ("http://snomed.info/sct".equals(system))
3566      return "http://www.snomed.org/";
3567    if (Utilities.existsInList(system, "http://loinc.org", "http://unitsofmeasure.org", "http://www.nlm.nih.gov/research/umls/rxnorm", "http://ncimeta.nci.nih.gov", "http://fdasis.nlm.nih.gov", 
3568         "http://www.radlex.org", "http://www.whocc.no/atc", "http://dicom.nema.org/resources/ontology/DCM", "http://www.genenames.org", "http://www.ensembl.org", "http://www.ncbi.nlm.nih.gov/nuccore", 
3569         "http://www.ncbi.nlm.nih.gov/clinvar", "http://sequenceontology.org", "http://www.hgvs.org/mutnomen", "http://www.ncbi.nlm.nih.gov/projects/SNP", "http://cancer.sanger.ac.uk/cancergenome/projects/cosmic", 
3570         "http://www.lrg-sequence.org", "http://www.omim.org", "http://www.ncbi.nlm.nih.gov/pubmed", "http://www.pharmgkb.org", "http://clinicaltrials.gov", "http://www.ebi.ac.uk/ipd/imgt/hla/")) 
3571      return system;
3572      
3573    return null;
3574  }
3575
3576  private String getCsRef(String system) {
3577    CodeSystem cs = context.fetchCodeSystem(system);
3578    return getCsRef(cs);
3579  }
3580
3581  private  <T extends Resource> String getCsRef(T cs) {
3582    String ref = (String) cs.getUserData("filename");
3583    if (ref == null)
3584      ref = (String) cs.getUserData("path");
3585    if (ref == null)
3586      return "??.html";
3587    if (!ref.contains(".html"))
3588      ref = ref + ".html";
3589    return ref.replace("\\", "/");
3590  }
3591
3592  private boolean codeExistsInValueSet(CodeSystem cs, String code) {
3593    for (ConceptDefinitionComponent c : cs.getConcept()) {
3594      if (inConcept(code, c))
3595        return true;
3596    }
3597    return false;
3598  }
3599
3600  private boolean inConcept(String code, ConceptDefinitionComponent c) {
3601    if (c.hasCodeElement() && c.getCode().equals(code))
3602      return true;
3603    for (ConceptDefinitionComponent g : c.getConcept()) {
3604      if (inConcept(code, g))
3605        return true;
3606    }
3607    return false;
3608  }
3609
3610  /**
3611   * This generate is optimised for the build tool in that it tracks the source extension.
3612   * But it can be used for any other use.
3613   *
3614   * @param vs
3615   * @param codeSystems
3616   * @throws DefinitionException
3617   * @throws Exception
3618   */
3619  public boolean generate(ResourceContext rcontext, OperationOutcome op) throws DefinitionException {
3620    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
3621    boolean hasSource = false;
3622    boolean success = true;
3623    for (OperationOutcomeIssueComponent i : op.getIssue()) {
3624        success = success && i.getSeverity() == IssueSeverity.INFORMATION;
3625        hasSource = hasSource || ExtensionHelper.hasExtension(i, ToolingExtensions.EXT_ISSUE_SOURCE);
3626    }
3627    if (success)
3628        x.para().tx("All OK");
3629    if (op.getIssue().size() > 0) {
3630                XhtmlNode tbl = x.table("grid"); // on the basis that we'll most likely be rendered using the standard fhir css, but it doesn't really matter
3631                XhtmlNode tr = tbl.tr();
3632                tr.td().b().tx("Severity");
3633                tr.td().b().tx("Location");
3634        tr.td().b().tx("Code");
3635        tr.td().b().tx("Details");
3636        tr.td().b().tx("Diagnostics");
3637                if (hasSource)
3638                        tr.td().b().tx("Source");
3639                for (OperationOutcomeIssueComponent i : op.getIssue()) {
3640                        tr = tbl.tr();
3641                        tr.td().addText(i.getSeverity().toString());
3642                        XhtmlNode td = tr.td();
3643                        boolean d = false;
3644                        for (StringType s : i.getLocation()) {
3645                                if (d)
3646                                        td.tx(", ");
3647                                else
3648                                        d = true;
3649                                td.addText(s.getValue());
3650                        }
3651          tr.td().addText(i.getCode().getDisplay());
3652          tr.td().addText(gen(i.getDetails()));
3653          smartAddText(tr.td(), i.getDiagnostics());
3654                        if (hasSource) {
3655                                Extension ext = ExtensionHelper.getExtension(i, ToolingExtensions.EXT_ISSUE_SOURCE);
3656            tr.td().addText(ext == null ? "" : gen(ext));
3657                        }
3658                }
3659        }
3660    inject(op, x, hasSource ? NarrativeStatus.EXTENSIONS :  NarrativeStatus.GENERATED);
3661    return true;
3662  }
3663
3664
3665  public String genType(Type type) throws DefinitionException {
3666    if (type instanceof Coding)
3667      return gen((Coding) type);
3668    if (type instanceof CodeableConcept)
3669      return displayCodeableConcept((CodeableConcept) type);
3670    if (type instanceof Quantity)
3671      return displayQuantity((Quantity) type);
3672    if (type instanceof Range)
3673      return displayRange((Range) type);
3674    return null;
3675  }
3676        private String gen(Extension extension) throws DefinitionException {
3677                if (extension.getValue() instanceof CodeType)
3678                        return ((CodeType) extension.getValue()).getValue();
3679                if (extension.getValue() instanceof Coding)
3680                        return gen((Coding) extension.getValue());
3681
3682          throw new DefinitionException("Unhandled type "+extension.getValue().getClass().getName());
3683  }
3684
3685        public String gen(CodeableConcept code) {
3686                if (code == null)
3687                return null;
3688                if (code.hasText())
3689                        return code.getText();
3690                if (code.hasCoding())
3691                        return gen(code.getCoding().get(0));
3692                return null;
3693        }
3694
3695        public String gen(Coding code) {
3696          if (code == null)
3697                return null;
3698          if (code.hasDisplayElement())
3699                return code.getDisplay();
3700          if (code.hasCodeElement())
3701                return code.getCode();
3702          return null;
3703  }
3704
3705  public boolean generate(ResourceContext rcontext, StructureDefinition sd, java.util.Set<String> outputTracker) throws EOperationOutcome, FHIRException, IOException {
3706    ProfileUtilities pu = new ProfileUtilities(context, null, pkp);
3707    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
3708    x.addChildNode(pu.generateTable(definitionsTarget, sd, true, destDir, false, sd.getId(), false, corePath, "", false, false, outputTracker));
3709    inject(sd, x, NarrativeStatus.GENERATED);
3710    return true;
3711  }
3712  
3713  public boolean generate(ResourceContext rcontext, ImplementationGuide ig) throws EOperationOutcome, FHIRException, IOException {
3714    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
3715    x.h2().addText(ig.getName());
3716    x.para().tx("The official URL for this implementation guide is: ");
3717    x.pre().tx(ig.getUrl());
3718    addMarkdown(x, ig.getDescription());
3719    inject(ig, x, NarrativeStatus.GENERATED);
3720    return true;
3721  }
3722        public boolean generate(ResourceContext rcontext, OperationDefinition opd) throws EOperationOutcome, FHIRException, IOException {
3723    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
3724    x.h2().addText(opd.getName());
3725    x.para().addText(Utilities.capitalize(opd.getKind().toString())+": "+opd.getName());
3726    x.para().tx("The official URL for this operation definition is: ");
3727    x.pre().tx(opd.getUrl());
3728    addMarkdown(x, opd.getDescription());
3729
3730    if (opd.getSystem())
3731      x.para().tx("URL: [base]/$"+opd.getCode());
3732    for (CodeType c : opd.getResource()) {
3733      if (opd.getType())
3734        x.para().tx("URL: [base]/"+c.getValue()+"/$"+opd.getCode());
3735      if (opd.getInstance())
3736        x.para().tx("URL: [base]/"+c.getValue()+"/[id]/$"+opd.getCode());
3737    }
3738
3739    x.para().tx("Parameters");
3740    XhtmlNode tbl = x.table( "grid");
3741    XhtmlNode tr = tbl.tr();
3742    tr.td().b().tx("Use");
3743    tr.td().b().tx("Name");
3744    tr.td().b().tx("Cardinality");
3745    tr.td().b().tx("Type");
3746    tr.td().b().tx("Binding");
3747    tr.td().b().tx("Documentation");
3748    for (OperationDefinitionParameterComponent p : opd.getParameter()) {
3749      genOpParam(rcontext, tbl, "", p);
3750    }
3751    addMarkdown(x, opd.getComment());
3752    inject(opd, x, NarrativeStatus.GENERATED);
3753    return true;
3754        }
3755
3756        private void genOpParam(ResourceContext rcontext, XhtmlNode tbl, String path, OperationDefinitionParameterComponent p) throws EOperationOutcome, FHIRException, IOException {
3757                XhtmlNode tr;
3758      tr = tbl.tr();
3759      tr.td().addText(p.getUse().toString());
3760                tr.td().addText(path+p.getName());
3761      tr.td().addText(Integer.toString(p.getMin())+".."+p.getMax());
3762      tr.td().addText(p.hasType() ? p.getType() : "");
3763      XhtmlNode td = tr.td();
3764      if (p.hasBinding() && p.getBinding().hasValueSet()) {
3765        if (p.getBinding().getValueSet() instanceof Reference)
3766          AddVsRef(rcontext, p.getBinding().getValueSetReference().getReference(), td);
3767        else
3768          td.ah(p.getBinding().getValueSetUriType().getValue()).tx("External Reference");
3769        td.tx(" ("+p.getBinding().getStrength().getDisplay()+")");
3770      }
3771      addMarkdown(tr.td(), p.getDocumentation());
3772      if (!p.hasType()) {
3773                        for (OperationDefinitionParameterComponent pp : p.getPart()) {
3774                                genOpParam(rcontext, tbl, path+p.getName()+".", pp);
3775        }
3776      }
3777    }
3778
3779        private void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException {
3780          if (text != null) {
3781            // 1. custom FHIR extensions
3782            while (text.contains("[[[")) {
3783              String left = text.substring(0, text.indexOf("[[["));
3784              String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]"));
3785              String right = text.substring(text.indexOf("]]]")+3);
3786              String url = link;
3787              String[] parts = link.split("\\#");
3788              StructureDefinition p = context.fetchResource(StructureDefinition.class, parts[0]);
3789              if (p == null)
3790                p = context.fetchTypeDefinition(parts[0]);
3791              if (p == null)
3792                p = context.fetchResource(StructureDefinition.class, link);
3793              if (p != null) {
3794                url = p.getUserString("path");
3795                if (url == null)
3796                  url = p.getUserString("filename");
3797              } else
3798                throw new DefinitionException("Unable to resolve markdown link "+link);
3799
3800              text = left+"["+link+"]("+url+")"+right;
3801            }
3802
3803            // 2. markdown
3804            String s =  new MarkDownProcessor(Dialect.DARING_FIREBALL).process(Utilities.escapeXml(text), "NarrativeGenerator");
3805            XhtmlParser p = new XhtmlParser();
3806            XhtmlNode m;
3807                try {
3808                        m = p.parse("<div>"+s+"</div>", "div");
3809                } catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
3810                        throw new FHIRFormatError(e.getMessage(), e);
3811                }
3812            x.addChildNodes(m.getChildNodes());
3813          }
3814  }
3815
3816  public boolean generate(ResourceContext rcontext, CompartmentDefinition cpd) {
3817    StringBuilder in = new StringBuilder();
3818    StringBuilder out = new StringBuilder();
3819    for (CompartmentDefinitionResourceComponent cc: cpd.getResource()) {
3820      CommaSeparatedStringBuilder rules = new CommaSeparatedStringBuilder();
3821      if (!cc.hasParam()) {
3822        out.append(" <li><a href=\"").append(cc.getCode().toLowerCase()).append(".html\">").append(cc.getCode()).append("</a></li>\r\n");
3823      } else if (!rules.equals("{def}")) {
3824        for (StringType p : cc.getParam())
3825          rules.append(p.asStringValue());
3826        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");
3827      }
3828    }
3829    XhtmlNode x;
3830    try {
3831      x = new XhtmlParser().parseFragment("<div><p>\r\nThe following resources may be in this compartment:\r\n</p>\r\n" +
3832          "<table class=\"grid\">\r\n"+
3833          " <tr><td><b>Resource</b></td><td><b>Inclusion Criteria</b></td></tr>\r\n"+
3834          in.toString()+
3835          "</table>\r\n"+
3836          "<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" +
3837          "<p>\r\n\r\n</p>\r\n" +
3838          "<p>\r\nThe following resources are never in this compartment:\r\n</p>\r\n" +
3839          "<ul>\r\n"+
3840          out.toString()+
3841          "</ul></div>\r\n");
3842      inject(cpd, x, NarrativeStatus.GENERATED);
3843      return true;
3844    } catch (Exception e) {
3845      e.printStackTrace();
3846      return false;
3847    }
3848  }
3849
3850  public boolean generate(ResourceContext rcontext, CapabilityStatement conf) throws FHIRFormatError, DefinitionException, IOException {
3851    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
3852    x.h2().addText(conf.getName());
3853    addMarkdown(x, conf.getDescription());
3854    if (conf.getRest().size() > 0) {
3855      CapabilityStatementRestComponent rest = conf.getRest().get(0);
3856      XhtmlNode t = x.table(null);
3857      addTableRow(t, "Mode", rest.getMode().toString());
3858      addTableRow(t, "Description", rest.getDocumentation());
3859
3860      addTableRow(t, "Transaction", showOp(rest, SystemRestfulInteraction.TRANSACTION));
3861      addTableRow(t, "System History", showOp(rest, SystemRestfulInteraction.HISTORYSYSTEM));
3862      addTableRow(t, "System Search", showOp(rest, SystemRestfulInteraction.SEARCHSYSTEM));
3863
3864      t = x.table(null);
3865      XhtmlNode tr = t.tr();
3866      tr.th().b().tx("Resource Type");
3867      tr.th().b().tx("Profile");
3868      tr.th().b().tx("Read");
3869      tr.th().b().tx("V-Read");
3870      tr.th().b().tx("Search");
3871      tr.th().b().tx("Update");
3872      tr.th().b().tx("Updates");
3873      tr.th().b().tx("Create");
3874      tr.th().b().tx("Delete");
3875      tr.th().b().tx("History");
3876
3877      for (CapabilityStatementRestResourceComponent r : rest.getResource()) {
3878        tr = t.tr();
3879        tr.td().addText(r.getType());
3880        if (r.hasProfile()) {
3881          tr.td().ah(prefix+r.getProfile().getReference()).addText(r.getProfile().getReference());
3882        }
3883        tr.td().addText(showOp(r, TypeRestfulInteraction.READ));
3884        tr.td().addText(showOp(r, TypeRestfulInteraction.VREAD));
3885        tr.td().addText(showOp(r, TypeRestfulInteraction.SEARCHTYPE));
3886        tr.td().addText(showOp(r, TypeRestfulInteraction.UPDATE));
3887        tr.td().addText(showOp(r, TypeRestfulInteraction.HISTORYINSTANCE));
3888        tr.td().addText(showOp(r, TypeRestfulInteraction.CREATE));
3889        tr.td().addText(showOp(r, TypeRestfulInteraction.DELETE));
3890        tr.td().addText(showOp(r, TypeRestfulInteraction.HISTORYTYPE));
3891      }
3892    }
3893
3894    inject(conf, x, NarrativeStatus.GENERATED);
3895    return true;
3896  }
3897
3898  private String showOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction on) {
3899    for (ResourceInteractionComponent op : r.getInteraction()) {
3900      if (op.getCode() == on)
3901        return "y";
3902    }
3903    return "";
3904  }
3905
3906  private String showOp(CapabilityStatementRestComponent r, SystemRestfulInteraction on) {
3907    for (SystemInteractionComponent op : r.getInteraction()) {
3908      if (op.getCode() == on)
3909        return "y";
3910    }
3911    return "";
3912  }
3913
3914  private void addTableRow(XhtmlNode t, String name, String value) {
3915    XhtmlNode tr = t.tr();
3916    tr.td().addText(name);
3917    tr.td().addText(value);
3918  }
3919
3920  public XhtmlNode generateDocumentNarrative(Bundle feed) {
3921    /*
3922     When the document is presented for human consumption, applications must present the collated narrative portions of the following resources in order:
3923     * The Composition resource
3924     * The Subject resource
3925     * Resources referenced in the section.content
3926     */
3927    XhtmlNode root = new XhtmlNode(NodeType.Element, "div");
3928    Composition comp = (Composition) feed.getEntry().get(0).getResource();
3929    root.addChildNode(comp.getText().getDiv());
3930    Resource subject = ResourceUtilities.getById(feed, null, comp.getSubject().getReference());
3931    if (subject != null && subject instanceof DomainResource) {
3932      root.hr();
3933      root.addChildNode(((DomainResource)subject).getText().getDiv());
3934    }
3935    List<SectionComponent> sections = comp.getSection();
3936    renderSections(feed, root, sections, 1);
3937    return root;
3938  }
3939
3940  private void renderSections(Bundle feed, XhtmlNode node, List<SectionComponent> sections, int level) {
3941    for (SectionComponent section : sections) {
3942      node.hr();
3943      if (section.hasTitleElement())
3944        node.addTag("h"+Integer.toString(level)).addText(section.getTitle());
3945//      else if (section.hasCode())
3946//        node.addTag("h"+Integer.toString(level)).addText(displayCodeableConcept(section.getCode()));
3947
3948//      if (section.hasText()) {
3949//        node.addChildNode(section.getText().getDiv());
3950//      }
3951//
3952//      if (!section.getSection().isEmpty()) {
3953//        renderSections(feed, node.addTag("blockquote"), section.getSection(), level+1);
3954//      }
3955    }
3956  }
3957
3958
3959  public class ObservationNode {
3960    private String ref;
3961    private ResourceWrapper obs;
3962    private List<ObservationNode> contained = new ArrayList<NarrativeGenerator.ObservationNode>();
3963  }
3964
3965  public XhtmlNode generateDiagnosticReport(ResourceWrapper dr) {
3966    XhtmlNode root = new XhtmlNode(NodeType.Element, "div");
3967    XhtmlNode h2 = root.h2();
3968    displayCodeableConcept(h2, getProperty(dr, "code").value());
3969    h2.tx(" ");
3970    PropertyWrapper pw = getProperty(dr, "category");
3971    if (valued(pw)) {
3972      h2.tx("(");
3973      displayCodeableConcept(h2, pw.value());
3974      h2.tx(") ");
3975    }
3976    displayDate(h2, getProperty(dr, "issued").value());
3977
3978    XhtmlNode tbl = root.table( "grid");
3979    XhtmlNode tr = tbl.tr();
3980    XhtmlNode tdl = tr.td();
3981    XhtmlNode tdr = tr.td();
3982    populateSubjectSummary(tdl, getProperty(dr, "subject").value());
3983    tdr.b().tx("Report Details");
3984    tdr.br();
3985    pw = getProperty(dr, "perfomer");
3986    if (valued(pw)) {
3987      tdr.addText(pluralise("Performer", pw.getValues().size())+":");
3988      for (BaseWrapper v : pw.getValues()) {
3989        tdr.tx(" ");
3990        displayReference(tdr, v);
3991      }
3992      tdr.br();
3993    }
3994    pw = getProperty(dr, "identifier");
3995    if (valued(pw)) {
3996      tdr.addText(pluralise("Identifier", pw.getValues().size())+":");
3997      for (BaseWrapper v : pw.getValues()) {
3998        tdr.tx(" ");
3999        displayIdentifier(tdr, v);
4000      }
4001      tdr.br();
4002    }
4003    pw = getProperty(dr, "request");
4004    if (valued(pw)) {
4005      tdr.addText(pluralise("Request", pw.getValues().size())+":");
4006      for (BaseWrapper v : pw.getValues()) {
4007        tdr.tx(" ");
4008        displayReferenceId(tdr, v);
4009      }
4010      tdr.br();
4011    }
4012
4013    pw = getProperty(dr, "result");
4014    if (valued(pw)) {
4015      List<ObservationNode> observations = fetchObservations(pw.getValues());
4016      buildObservationsTable(root, observations);
4017    }
4018
4019    pw = getProperty(dr, "conclusion");
4020    if (valued(pw))
4021      displayText(root.para(), pw.value());
4022
4023    pw = getProperty(dr, "result");
4024    if (valued(pw)) {
4025      XhtmlNode p = root.para();
4026      p.b().tx("Coded Diagnoses :");
4027      for (BaseWrapper v : pw.getValues()) {
4028        tdr.tx(" ");
4029        displayCodeableConcept(tdr, v);
4030      }
4031    }
4032    return root;
4033  }
4034
4035  private void buildObservationsTable(XhtmlNode root, List<ObservationNode> observations) {
4036    XhtmlNode tbl = root.table( "none");
4037    for (ObservationNode o : observations) {
4038      addObservationToTable(tbl, o, 0);
4039    }
4040  }
4041
4042  private void addObservationToTable(XhtmlNode tbl, ObservationNode o, int i) {
4043    XhtmlNode tr = tbl.tr();
4044    if (o.obs == null) {
4045      XhtmlNode td = tr.td().colspan("6");
4046      td.i().tx("This Observation could not be resolved");
4047    } else {
4048      addObservationToTable(tr, o.obs, i);
4049      // todo: contained observations
4050    }
4051    for (ObservationNode c : o.contained) {
4052      addObservationToTable(tbl, c, i+1);
4053    }
4054  }
4055
4056  private void addObservationToTable(XhtmlNode tr, ResourceWrapper obs, int i) {
4057    // TODO Auto-generated method stub
4058
4059    // code (+bodysite)
4060    XhtmlNode td = tr.td();
4061    PropertyWrapper pw = getProperty(obs, "result");
4062    if (valued(pw)) {
4063      displayCodeableConcept(td, pw.value());
4064    }
4065    pw = getProperty(obs, "bodySite");
4066    if (valued(pw)) {
4067      td.tx(" (");
4068      displayCodeableConcept(td, pw.value());
4069      td.tx(")");
4070    }
4071
4072    // value / dataAbsentReason (in red)
4073    td = tr.td();
4074    pw = getProperty(obs, "value[x]");
4075    if (valued(pw)) {
4076      if (pw.getTypeCode().equals("CodeableConcept"))
4077        displayCodeableConcept(td, pw.value());
4078      else if (pw.getTypeCode().equals("string"))
4079        displayText(td, pw.value());
4080      else
4081        td.addText(pw.getTypeCode()+" not rendered yet");
4082    }
4083
4084    // units
4085    td = tr.td();
4086    td.tx("to do");
4087
4088    // reference range
4089    td = tr.td();
4090    td.tx("to do");
4091
4092    // flags (status other than F, interpretation, )
4093    td = tr.td();
4094    td.tx("to do");
4095
4096    // issued if different to DR
4097    td = tr.td();
4098    td.tx("to do");
4099  }
4100
4101  private boolean valued(PropertyWrapper pw) {
4102    return pw != null && pw.hasValues();
4103  }
4104
4105  private void displayText(XhtmlNode c, BaseWrapper v) {
4106    c.addText(v.toString());
4107  }
4108
4109  private String pluralise(String name, int size) {
4110    return size == 1 ? name : name+"s";
4111  }
4112
4113  private void displayIdentifier(XhtmlNode c, BaseWrapper v) {
4114    String hint = "";
4115    PropertyWrapper pw = v.getChildByName("type");
4116    if (valued(pw)) {
4117      hint = genCC(pw.value());
4118    } else {
4119      pw = v.getChildByName("system");
4120      if (valued(pw)) {
4121        hint = pw.value().toString();
4122      }
4123    }
4124    displayText(c.span(null, hint), v.getChildByName("value").value());
4125  }
4126
4127  private String genCoding(BaseWrapper value) {
4128    PropertyWrapper pw = value.getChildByName("display");
4129    if (valued(pw))
4130      return pw.value().toString();
4131    pw = value.getChildByName("code");
4132    if (valued(pw))
4133      return pw.value().toString();
4134    return "";
4135  }
4136
4137  private String genCC(BaseWrapper value) {
4138    PropertyWrapper pw = value.getChildByName("text");
4139    if (valued(pw))
4140      return pw.value().toString();
4141    pw = value.getChildByName("coding");
4142    if (valued(pw))
4143      return genCoding(pw.getValues().get(0));
4144    return "";
4145  }
4146
4147  private void displayReference(XhtmlNode c, BaseWrapper v) {
4148    c.tx("to do");
4149  }
4150
4151
4152  private void displayDate(XhtmlNode c, BaseWrapper baseWrapper) {
4153    c.tx("to do");
4154  }
4155
4156  private void displayCodeableConcept(XhtmlNode c, BaseWrapper property) {
4157    c.tx("to do");
4158  }
4159
4160  private void displayReferenceId(XhtmlNode c, BaseWrapper v) {
4161    c.tx("to do");
4162  }
4163
4164  private PropertyWrapper getProperty(ResourceWrapper res, String name) {
4165    for (PropertyWrapper t : res.children()) {
4166      if (t.getName().equals(name))
4167        return t;
4168    }
4169    return null;
4170  }
4171
4172  private void populateSubjectSummary(XhtmlNode container, BaseWrapper subject) {
4173    ResourceWrapper r = fetchResource(subject);
4174    if (r == null)
4175      container.tx("Unable to get Patient Details");
4176    else if (r.getName().equals("Patient"))
4177      generatePatientSummary(container, r);
4178    else
4179      container.tx("Not done yet");
4180  }
4181
4182  private void generatePatientSummary(XhtmlNode c, ResourceWrapper r) {
4183    c.tx("to do");
4184  }
4185
4186  private ResourceWrapper fetchResource(BaseWrapper subject) {
4187    if (resolver == null)
4188      return null;
4189    String url = subject.getChildByName("reference").value().toString();
4190    ResourceWithReference rr = resolver.resolve(url);
4191    return rr == null ? null : rr.resource;
4192  }
4193
4194  private List<ObservationNode> fetchObservations(List<BaseWrapper> list) {
4195    return new ArrayList<NarrativeGenerator.ObservationNode>();
4196  }
4197
4198  public XhtmlNode renderBundle(Bundle b) throws FHIRException {
4199    if (b.getType() == BundleType.DOCUMENT) {
4200      if (!b.hasEntry() || !(b.getEntryFirstRep().hasResource() && b.getEntryFirstRep().getResource() instanceof Composition))
4201        throw new FHIRException("Invalid document - first entry is not a Composition");
4202      Composition dr = (Composition) b.getEntryFirstRep().getResource();
4203      return dr.getText().getDiv();
4204    } else  {
4205      XhtmlNode root = new XhtmlNode(NodeType.Element, "div");
4206      root.para().addText("Bundle "+b.getId()+" of type "+b.getType().toCode());
4207      int i = 0;
4208      for (BundleEntryComponent be : b.getEntry()) {
4209        root.hr();
4210        root.para().addText("Entry "+Integer.toString(i)+(be.hasFullUrl() ? "Full URL = " + be.getFullUrl() : ""));
4211        if (be.hasRequest())
4212          renderRequest(root, be.getRequest());
4213        if (be.hasSearch())
4214          renderSearch(root, be.getSearch());
4215        if (be.hasResponse())
4216          renderResponse(root, be.getResponse());
4217        if (be.hasResource()) {
4218          root.para().addText("Resource "+be.getResource().fhirType()+":");
4219          if (be.hasResource() && be.getResource() instanceof DomainResource) {
4220            DomainResource dr = (DomainResource) be.getResource();
4221            if ( dr.getText().hasDiv())
4222              root.blockquote().addChildNodes(dr.getText().getDiv().getChildNodes());
4223          }
4224        }
4225      }
4226      return root;
4227    }
4228  }
4229
4230  private void renderSearch(XhtmlNode root, BundleEntrySearchComponent search) {
4231    StringBuilder b = new StringBuilder();
4232    b.append("Search: ");
4233    if (search.hasMode())
4234      b.append("mode = "+search.getMode().toCode());
4235    if (search.hasScore()) {
4236      if (search.hasMode())
4237        b.append(",");
4238      b.append("score = "+search.getScore());
4239    }
4240    root.para().addText(b.toString());    
4241  }
4242
4243  private void renderResponse(XhtmlNode root, BundleEntryResponseComponent response) {
4244    root.para().addText("Request:");
4245    StringBuilder b = new StringBuilder();
4246    b.append(response.getStatus()+"\r\n");
4247    if (response.hasLocation())
4248      b.append("Location: "+response.getLocation()+"\r\n");
4249    if (response.hasEtag())
4250      b.append("E-Tag: "+response.getEtag()+"\r\n");
4251    if (response.hasLastModified())
4252      b.append("LastModified: "+response.getEtag()+"\r\n");
4253    root.pre().addText(b.toString());    
4254  }
4255
4256  private void renderRequest(XhtmlNode root, BundleEntryRequestComponent request) {
4257    root.para().addText("Response:");
4258    StringBuilder b = new StringBuilder();
4259    b.append(request.getMethod()+" "+request.getUrl()+"\r\n");
4260    if (request.hasIfNoneMatch())
4261      b.append("If-None-Match: "+request.getIfNoneMatch()+"\r\n");
4262    if (request.hasIfModifiedSince())
4263      b.append("If-Modified-Since: "+request.getIfModifiedSince()+"\r\n");
4264    if (request.hasIfMatch())
4265      b.append("If-Match: "+request.getIfMatch()+"\r\n");
4266    if (request.hasIfNoneExist())
4267      b.append("If-None-Exist: "+request.getIfNoneExist()+"\r\n");
4268    root.pre().addText(b.toString());    
4269  }
4270
4271  public XhtmlNode renderBundle(org.hl7.fhir.dstu3.elementmodel.Element element) throws FHIRException {
4272    XhtmlNode root = new XhtmlNode(NodeType.Element, "div");
4273    for (Base b : element.listChildrenByName("entry")) {
4274      XhtmlNode c = getHtmlForResource(((org.hl7.fhir.dstu3.elementmodel.Element) b).getNamedChild("resource"));
4275      if (c != null)
4276        root.addChildNodes(c.getChildNodes());
4277      root.hr();
4278    }
4279    return root;
4280  }
4281
4282  private XhtmlNode getHtmlForResource(org.hl7.fhir.dstu3.elementmodel.Element element) {
4283    org.hl7.fhir.dstu3.elementmodel.Element text = element.getNamedChild("text");
4284    if (text == null)
4285      return null;
4286    org.hl7.fhir.dstu3.elementmodel.Element div = text.getNamedChild("div");
4287    if (div == null)
4288      return null;
4289    else
4290      return div.getXhtml();
4291  }
4292
4293  public String getDefinitionsTarget() {
4294    return definitionsTarget;
4295  }
4296
4297  public void setDefinitionsTarget(String definitionsTarget) {
4298    this.definitionsTarget = definitionsTarget;
4299  }
4300
4301  public String getCorePath() {
4302    return corePath;
4303  }
4304
4305  public void setCorePath(String corePath) {
4306    this.corePath = corePath;
4307  }
4308
4309  public String getDestDir() {
4310    return destDir;
4311  }
4312
4313  public void setDestDir(String destDir) {
4314    this.destDir = destDir;
4315  }
4316
4317  public ProfileKnowledgeProvider getPkp() {
4318    return pkp;
4319  }
4320
4321  public void setPkp(ProfileKnowledgeProvider pkp) {
4322    this.pkp = pkp;
4323  }
4324
4325  
4326}