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