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