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