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