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