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