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