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