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