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