001package org.hl7.fhir.r5.conformance;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033
034import java.util.*;
035import java.util.regex.Matcher;
036import java.util.regex.Pattern;
037
038import lombok.extern.slf4j.Slf4j;
039import org.apache.commons.lang3.StringUtils;
040import org.apache.commons.lang3.tuple.ImmutablePair;
041import org.apache.commons.lang3.tuple.Pair;
042import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
043import org.hl7.fhir.r5.context.IWorkerContext;
044import org.hl7.fhir.r5.fhirpath.ExpressionNode;
045import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
046import org.hl7.fhir.r5.model.*;
047import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
048import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
049import org.stringtemplate.v4.ST;
050
051@MarkedToMoveToAdjunctPackage
052@Slf4j
053public class ShExGenerator {
054
055  public static class ShExComparator implements Comparator<String> {
056    @Override
057    public int compare(String o1, String o2) {
058        String substring1 = StringUtils.substringBetween(o1, "fhirvs:", " ");
059        String substring2 = StringUtils.substringBetween(o2, "fhirvs:", " ");
060        return (substring1 == null ? "" : substring1)
061          .compareTo(substring2 == null ? "" : substring2);
062    }
063  }
064
065  public enum HTMLLinkPolicy {
066    NONE, EXTERNAL, INTERNAL
067  }
068
069  public enum ConstraintTranslationPolicy {
070    ALL, // Translate all Extensions found; Default (or when no policy defined)
071    GENERIC_ONLY, // Translate all Extensions except constraints with context-of-use
072    CONTEXT_OF_USE_ONLY  // Translate only Extensions with context-of-use
073  }
074
075  public boolean doDatatypes = false;                 // add data types
076  public boolean withComments = true;                // include comments
077  public boolean completeModel = false;              // doing complete build (fhir.shex)
078
079  @Deprecated
080  public boolean debugMode = false;     // Used for Debugging and testing the code
081
082  public boolean processConstraints = false;   // set to false - to skip processing constraints
083
084  public ConstraintTranslationPolicy constraintPolicy = ConstraintTranslationPolicy.ALL;
085
086  private static String SHEX_TEMPLATE =
087      "$header$\n" +
088      "$imports$\n" +
089      "$shapeDefinitions$";
090
091  // A header is a list of prefixes, a base declaration and a start node
092  private static String FHIR = "http://hl7.org/fhir/";
093  private static String FHIR_VS = FHIR + "ValueSet/";
094  private static String HEADER_TEMPLATE =
095    "PREFIX fhir: <$fhir$> \n" +
096      "PREFIX fhirvs: <$fhirvs$>\n" +
097      "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> \n" +
098      "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> \n";
099      //"BASE <http://hl7.org/fhir/shape/>\n";
100
101  private static String IMPORT_TEMPLATE = "IMPORT <$import$$fileExt$>\n";
102
103  // Start template for single (open) entry
104  private static String START_TEMPLATE = "\n\nstart=@<$id$> AND {fhir:nodeRole [fhir:treeRoot]}\n";
105
106  // Start template for complete (closed) model
107  private static String ALL_START_TEMPLATE = "\n\nstart=@<All>\n";
108
109  private static String ALL_TEMPLATE = "\n<All> $all_entries$\n";
110
111  private static String ALL_ENTRY_TEMPLATE = "(NOT { fhir:nodeRole [fhir:treeRoot] ; a [fhir:$id$] } OR @<$id$>)";
112
113
114  // Shape Definition
115  //      the shape name
116  //      an optional resource declaration (type + treeRoot)
117  //      the list of element declarations
118  //      an optional index element (for appearances inside ordered lists)
119  private static String SHAPE_DEFINITION_TEMPLATE =
120    "$comment$\n<$id$> CLOSED { $fhirType$ " +
121      "\n    $resourceDecl$" +
122      "\n    $elements$" +
123      "\n    $contextOfUse$" +
124      "\n} $constraints$ \n";
125
126  // Base DataTypes
127  private List<String> baseDataTypes = Arrays.asList(
128    "DataType",
129    "PrimitiveType",
130    "BackboneElement"
131  );
132
133  private List<String> shortIdException = Arrays.asList(
134    "base64Binary", "boolean",
135    "date", "dateTime", "decimal", "instant", "integer64",
136    "integer", "string", "time", "uri"
137  );
138
139  private List<String> mappedFunctions = Arrays.asList(
140    "empty",
141    "exists",
142    "hasValue",
143    "matches",
144    "contains",
145    //"toString",
146    "is"
147    //"where"
148  );
149
150  private static String ONE_OR_MORE_PREFIX = "OneOrMore_";
151  private static String ONE_OR_MORE_CHOICES = "_One-Or-More-Choices_";
152  private static String ONE_OR_MORE_TEMPLATE =
153    "\n$comment$\n<$oomType$> CLOSED {" +
154      "\n    rdf:first @<$origType$> $restriction$ ;" +
155      "\n    rdf:rest [rdf:nil] OR @<$oomType$> " +
156      "\n}\n";
157
158  // Resource Definition
159  //      an open shape of type Resource.  Used when completeModel = false.
160  private static String RESOURCE_SHAPE_TEMPLATE =
161    "$comment$\n<Resource> {" +
162      "\n    $elements$" +
163      "\n    $contextOfUse$" +
164      "\n} $constraints$ \n";
165
166  // If we have knowledge of all of the possible resources available to us (completeModel = true), we can build
167  // a model of all possible resources.
168  private static String COMPLETE_RESOURCE_TEMPLATE =
169    "<Resource>  @<$resources$>" +
170      "\n\n";
171
172  // Resource Declaration
173  //      a type node
174  //      an optional treeRoot declaration (identifies the entry point)
175  private static String RESOURCE_DECL_TEMPLATE = "\na [fhir:$id$]?;$root$";
176
177  // Root Declaration.
178  private static String ROOT_TEMPLATE = "\nfhir:nodeRole [fhir:treeRoot]?;\n";
179
180  // Element
181  //    a predicate, type and cardinality triple
182  private static String ELEMENT_TEMPLATE = "$id$$defn$$card$$comment$";
183  private static int COMMENT_COL = 40;
184  private static int MAX_CHARS = 35;
185  private static int MIN_COMMENT_SEP = 2;
186
187  // Inner Shape Definition
188  private static String INNER_SHAPE_TEMPLATE = "($comment$\n    $defn$\n)$card$";
189
190  // Simple Element
191  //    a shape reference
192  private static String SIMPLE_ELEMENT_DEFN_TEMPLATE = "@<$typ$>$vsdef$";
193
194  // Value Set Element
195  private static String VALUESET_DEFN_TEMPLATE = " AND\n\t{fhir:v @$vsn$}";
196
197  // Fixed Value Template
198  private static String FIXED_VALUE_TEMPLATE = " AND\n\t{fhir:value [\"$val$\"]}";
199
200  // A primitive element definition
201  //    the actual type reference
202  private static String PRIMITIVE_ELEMENT_DEFN_TEMPLATE = "$typ$$facets$";
203
204  // Facets
205  private static String MINVALUE_TEMPLATE = " MININCLUSIVE $val$";
206  private static String MAXVALUE_TEMPLATE = " MAXINCLUSIVE $val$";
207  private static String MAXLENGTH_TEMPLATE = " MAXLENGTH $val$";
208  private static String PATTERN_TEMPLATE = " PATTERN \"$val$\"";
209
210  // A choice of alternative shape definitions
211  //  rendered as an inner anonymous shape
212  private static String ALTERNATIVE_SHAPES_TEMPLATE = "fhir:$id$$comment$\n(   $altEntries$\n)$card$";
213
214  // A typed reference definition
215  private static String REFERENCE_DEFN_TEMPLATE = "@<$ref$Reference>";
216
217  // What we emit for an xhtml
218  private static String XHTML_TYPE_TEMPLATE = "xsd:string";
219
220  // Additional type for Coding
221  private static String CONCEPT_REFERENCE_TEMPLATE = "a NONLITERAL?;";
222
223  // Additional type for CodedConcept
224  private static String CONCEPT_REFERENCES_TEMPLATE = "a NONLITERAL*;";
225
226  // Untyped resource has the extra link entry
227  private static String RESOURCE_LINK_TEMPLATE = "fhir:link IRI?;";
228
229  // Extension template
230  // No longer used -- we emit the actual definition
231//  private static String EXTENSION_TEMPLATE = "<Extension> {fhir:extension @<Extension>*;" +
232//          "\n    fhir:index xsd:integer?" +
233//          "\n}\n";
234
235  // A typed reference -- a fhir:uri with an optional type and the possibility of a resolvable shape
236  private static String TYPED_REFERENCE_TEMPLATE = "\n<$refType$Reference> CLOSED {" +
237    "\n    fhir:Element.id @<id>?;" +
238    "\n    fhir:Element.extension @<Extension>*;" +
239    "\n    fhir:link @<$refType$> OR CLOSED {a [fhir:$refType$]}?;" +
240    "\n    fhir:Reference.reference @<string>?;" +
241    "\n    fhir:Reference.display @<string>?;" +
242    // "\n    fhir:index xsd:integer?" +
243    "\n}";
244
245  private static String TARGET_REFERENCE_TEMPLATE = "\n<$refType$> {" +
246    "\n    a [fhir:$refType$];" +
247    "\n    fhir:nodeRole [fhir:treeRoot]?" +
248    "\n}";
249
250  // A value set definition
251  private static String VALUE_SET_DEFINITION = "# $comment$\n$vsuri$$val_list$\n";
252
253
254  /**
255   * this makes internal metadata services available to the generator - retrieving structure definitions, and value set expansion etc
256   */
257  private IWorkerContext context;
258  private ProfileUtilities profileUtilities;
259
260  /**
261   * innerTypes -- inner complex types.  Currently flattened in ShEx (doesn't have to be, btw)
262   * emittedInnerTypes -- set of inner types that have been generated
263   * datatypes, emittedDatatypes -- types used in the definition, types that have been generated
264   * references -- Reference types (Patient, Specimen, etc)
265   * uniq_structures -- set of structures on the to be generated list...
266   * doDataTypes -- whether or not to emit the data types.
267   */
268  private HashSet<Pair<StructureDefinition, ElementDefinition>> innerTypes, emittedInnerTypes;
269
270  private List<String> oneOrMoreTypes;
271
272  private List<String> constraintsList;
273
274  private List<String> unMappedFunctions;
275
276  private HashSet<String> datatypes, emittedDatatypes;
277  private HashSet<String> references;
278  private LinkedList<StructureDefinition> uniq_structures;
279  private HashSet<String> uniq_structure_urls;
280  private HashSet<ValueSet> required_value_sets;
281  private HashSet<String> known_resources;          // Used when generating a full definition
282
283  // List of URLs of Excluded Structure Definitions from ShEx Schema generation.
284  private List<String> excludedSDUrls;
285
286  // List of URLs of selected Structure Definitions of Extensions from ShEx Schema generation.
287  // Extensions are Structure Definitions with type as "Extension".
288  private List<StructureDefinition> selectedExtensions;
289  private List<String> selectedExtensionUrls;
290
291  private List<String> imports;
292  private FHIRPathEngine fpe;
293
294  public ShExGenerator(IWorkerContext context) {
295    super();
296    this.context = context;
297    profileUtilities = new ProfileUtilities(context, null, null);
298    innerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>();
299    oneOrMoreTypes = new ArrayList<String>();
300    constraintsList = new ArrayList<String>();
301    unMappedFunctions = new ArrayList<String>();
302    emittedInnerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>();
303    datatypes = new HashSet<String>();
304    emittedDatatypes = new HashSet<String>();
305    references = new HashSet<String>();
306    required_value_sets = new HashSet<ValueSet>();
307    known_resources = new HashSet<String>();
308    excludedSDUrls = new ArrayList<String>();
309    selectedExtensions = new ArrayList<StructureDefinition>();
310    selectedExtensionUrls = new ArrayList<String>();
311    imports = new ArrayList<String>();
312
313    fpe = new FHIRPathEngine(context);
314  }
315
316  public String generate(HTMLLinkPolicy links, StructureDefinition structure) {
317    List<StructureDefinition> list = new ArrayList<StructureDefinition>();
318    list.add(structure);
319    innerTypes.clear();
320    oneOrMoreTypes.clear();
321    constraintsList.clear();
322    unMappedFunctions.clear();
323    emittedInnerTypes.clear();
324    datatypes.clear();
325    emittedDatatypes.clear();
326    references.clear();
327    required_value_sets.clear();
328    known_resources.clear();
329    imports.clear();
330    return generate(links, list);
331  }
332
333  public List<String> getExcludedStructureDefinitionUrls(){
334    return this.excludedSDUrls;
335  }
336
337  public void setExcludedStructureDefinitionUrls(List<String> excludedSDs){
338    this.excludedSDUrls = excludedSDs;
339  }
340
341  public List<StructureDefinition> getSelectedExtensions(){
342    return this.selectedExtensions;
343  }
344
345  public void setSelectedExtension(List<StructureDefinition> selectedExtensions){
346    this.selectedExtensions = selectedExtensions;
347
348    selectedExtensionUrls.clear();
349
350    for (StructureDefinition eSD : selectedExtensions){
351      if (!selectedExtensionUrls.contains(eSD.getUrl()))
352        selectedExtensionUrls.add(eSD.getUrl());
353    }
354  }
355
356  public class SortById implements Comparator<StructureDefinition> {
357
358    @Override
359    public int compare(StructureDefinition arg0, StructureDefinition arg1) {
360      return arg0.getId().compareTo(arg1.getId());
361    }
362
363  }
364
365  private ST tmplt(String template) {
366    return new ST(template, '$', '$');
367  }
368
369  /**
370   * this is called externally to generate a set of structures to a single ShEx file
371   * generally, it will be called with a single structure, or a long list of structures (all of them)
372   *
373   * @param links HTML link rendering policy
374   * @param structures list of structure definitions to render
375   * @return ShEx definition of structures
376   */
377  public String generate(HTMLLinkPolicy links, List<StructureDefinition> structures, List<String> excludedSDUrls) {
378    this.excludedSDUrls = excludedSDUrls;
379
380    if ((structures != null )&&(this.selectedExtensions != null)){
381      structures.addAll(this.selectedExtensions);
382    }
383
384    return generate(links, structures);
385  }
386
387  /**
388   * this is called externally to generate a set of structures to a single ShEx file
389   * generally, it will be called with a single structure, or a long list of structures (all of them)
390   *
391   * @param links HTML link rendering policy
392   * @param structures list of structure definitions to render
393   * @return ShEx definition of structures
394   */
395  public String generate(HTMLLinkPolicy links, List<StructureDefinition> structures) {
396    ST shex_def = tmplt(SHEX_TEMPLATE);
397    String start_cmd;
398    if(completeModel || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE))
399      start_cmd = completeModel? tmplt(ALL_START_TEMPLATE).render() :
400        tmplt(START_TEMPLATE).add("id", structures.get(0).getId()).render();
401    else
402      start_cmd = "";
403
404    shex_def.add("header",
405      tmplt(HEADER_TEMPLATE).
406        add("fhir", FHIR).
407        add("fhirvs", FHIR_VS).render());
408
409    Collections.sort(structures, new SortById());
410    StringBuilder shapeDefinitions = new StringBuilder();
411
412    // For unknown reasons, the list of structures carries duplicates.
413    // We remove them.  Also, it is possible for the same sd to have multiple hashes...
414    uniq_structures = new LinkedList<StructureDefinition>();
415    uniq_structure_urls = new HashSet<String>();
416    StringBuffer allStructures = new StringBuffer("");
417    for (StructureDefinition sd : structures) {
418      // Exclusion Criteria...
419      if ((excludedSDUrls != null) &&
420        (excludedSDUrls.contains(sd.getUrl()))) {
421        log.trace("SKIPPED Generating ShEx for " + sd.getName() + "  [ " + sd.getUrl() + " ] !");
422        log.trace("Reason: It is in excluded list of structures.");
423        continue;
424      }
425
426      if ("Extension".equals(sd.getType())) {
427        if ((!this.selectedExtensionUrls.isEmpty()) && (!this.selectedExtensionUrls.contains(sd.getUrl()))) {
428          log.trace("SKIPPED Generating ShEx for " + sd.getName() + "  [ " + sd.getUrl() + " ] !");
429          log.trace("Reason: It is NOT included in the list of selected extensions.");
430          continue;
431        }
432
433        if ((this.constraintPolicy == ConstraintTranslationPolicy.GENERIC_ONLY) && (sd.hasContext())) {
434          log.trace("SKIPPED Generating ShEx for " + sd.getName() + "  [ " + sd.getUrl() + " ] !");
435          log.trace("Reason: ConstraintTranslationPolicy is set to GENERIC_ONLY, and this Structure has Context of Use.");
436          continue;
437        }
438
439        if ((this.constraintPolicy == ConstraintTranslationPolicy.CONTEXT_OF_USE_ONLY) && (!sd.hasContext())) {
440          log.trace("SKIPPED Generating ShEx for " + sd.getName() + "  [ " + sd.getUrl() + " ] !");
441          log.trace("Reason: ConstraintTranslationPolicy is set to CONTEXT_OF_USE_ONLY, and this Structure has no Context of Use.");
442          continue;
443        }
444      }
445
446      if (!uniq_structure_urls.contains(sd.getUrl())) {
447        uniq_structures.add(sd);
448        uniq_structure_urls.add(sd.getUrl());
449      }
450    }
451
452    for (StructureDefinition sd : uniq_structures) {
453      log.trace(" ---- Generating ShEx for : " + sd.getName() + "  [ " + sd.getUrl() + " ] ...");
454      String shapeDefinitionStr = genShapeDefinition(sd, true);
455
456      if (!shapeDefinitionStr.isEmpty()) {
457        shapeDefinitions.append(shapeDefinitionStr);
458      } else {
459        log.trace(" ---- WARNING! EMPTY/No ShEx SCHEMA Body generated for : " + sd.getName() + "  [ " + sd.getUrl() + " ].\n" +
460              "This might not be an issue, if this resource is normative base or a meta resource");
461        shapeDefinitions.append("<" + sd.getName() + "> CLOSED {\n}");
462      }
463    }
464      shapeDefinitions.append(emitInnerTypes());
465
466      // If data types are to be put in the same file
467      if (doDatatypes) {
468        shapeDefinitions.append("\n#---------------------- Data Types -------------------\n");
469        while (emittedDatatypes.size() < datatypes.size() ||
470          emittedInnerTypes.size() < innerTypes.size()) {
471          shapeDefinitions.append(emitDataTypes());
472          // As process data types, it may introduce some more inner types, so we repeat the call here.
473          shapeDefinitions.append(emitInnerTypes());
474        }
475      }
476
477      if (oneOrMoreTypes.size() > 0) {
478        shapeDefinitions.append("\n#---------------------- Cardinality Types (OneOrMore) -------------------\n");
479        oneOrMoreTypes.forEach((String oomType) -> {
480          shapeDefinitions.append(getOneOrMoreType(oomType));
481        });
482      }
483
484      if (references.size() > 0) {
485        shapeDefinitions.append("\n#---------------------- Reference Types -------------------\n");
486        for (String r : references) {
487          shapeDefinitions.append("\n").append(tmplt(TYPED_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n");
488          if (!"Resource".equals(r) && !known_resources.contains(r))
489            shapeDefinitions.append("\n").append(tmplt(TARGET_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n");
490        }
491      }
492
493      if (completeModel && known_resources.size() > 0) {
494        shapeDefinitions.append("\n").append(tmplt(COMPLETE_RESOURCE_TEMPLATE)
495          .add("resources", StringUtils.join(known_resources, "> OR\n\t@<")).render());
496        List<String> all_entries = new ArrayList<String>();
497        for (String kr : known_resources)
498          all_entries.add(tmplt(ALL_ENTRY_TEMPLATE).add("id", kr).render());
499        shapeDefinitions.append("\n").append(tmplt(ALL_TEMPLATE)
500          .add("all_entries", StringUtils.join(all_entries, " OR\n\t")).render());
501      }
502
503      if (required_value_sets.size() > 0) {
504        shapeDefinitions.append("\n#---------------------- Value Sets ------------------------\n");
505        List<String> sortedVS = new ArrayList<String>();
506        for (ValueSet vs : required_value_sets)
507          sortedVS.add(genValueSet(vs));
508
509        Collections.sort(sortedVS, new ShExComparator());
510
511        for (String svs : sortedVS)
512          shapeDefinitions.append("\n").append(svs);
513      }
514
515      if ((unMappedFunctions != null) && (!unMappedFunctions.isEmpty())) {
516        log.debug("------------------------- Unmapped Functions ---------------------");
517        for (String um : unMappedFunctions) {
518          log.debug(um);
519        }
520      }
521
522      allStructures.append(shapeDefinitions + "\n");
523
524    StringBuffer allImports = new StringBuffer("");
525    if (!imports.isEmpty()) {
526      uniq_structures.forEach((StructureDefinition sdstruct) -> {
527          imports.removeIf(s -> s.contains(sdstruct.getName()));
528      });
529
530      imports.sort(Comparator.comparingInt(String::length));
531      imports.forEach((String imp) -> {
532        ST import_def = tmplt(IMPORT_TEMPLATE);
533        import_def.add("import", imp);
534        import_def.add("fileExt", ".shex");
535        allImports.append(import_def.render());
536      });
537    }
538
539    allImports.append(start_cmd);
540    shex_def.add("imports", allImports);
541    shex_def.add("shapeDefinitions", allStructures.toString());
542
543    return shex_def.render();
544  }
545
546  private String getBaseTypeName(StructureDefinition sd){
547    if (sd == null)
548      return null;
549
550    String bd = null;
551    if (sd.hasBaseDefinition()) {
552      bd = sd.getBaseDefinitionNoVersion();
553      String[] els = bd.split("/");
554      bd = els[els.length - 1];
555    }
556
557    return bd;
558  }
559
560  private String getBaseTypeName(ElementDefinition ed){
561    if (ed == null)
562      return null;
563
564    return (ed.getType().size() > 0)? (ed.getType().get(0).getCode()) : null;
565  }
566
567  private String getExtendedType(StructureDefinition sd){
568    String bd = getBaseTypeName(sd);
569    String sId = sd.getId();
570
571    if (bd!=null) {
572      addImport("<" + bd + ">");
573      sId += "> EXTENDS @<" + bd;
574    }
575
576    return sId;
577  }
578
579  private String getExtendedType(ElementDefinition ed){
580    String bd = getBaseTypeName(ed);
581    //if (bd != null  && !baseDataTypes.contains(bd)) {
582    if (bd!=null) {
583      addImport("<" + bd + ">");
584      bd = "> EXTENDS @<" + bd;
585    }
586    return bd;
587  }
588
589  /**
590   * Emit a ShEx definition for the supplied StructureDefinition
591   * @param sd Structure definition to emit
592   * @param top_level True means outermost type, False means recursively called
593   * @return ShEx definition
594   */
595  private String genShapeDefinition(StructureDefinition sd, boolean top_level) {
596    // xhtml is treated as an atom
597    if("xhtml".equals(sd.getName()) || (completeModel && "Resource".equals(sd.getName())))
598      return "";
599
600    ST shape_defn;
601    // Resources are either incomplete items or consist of everything that is defined as a resource (completeModel)
602    //    if (sd.getName().equals("ActivityDefinition")){
603    //      debug("ActivityDefinition found");
604    //    }
605      if("Resource".equals(sd.getName())) {
606        shape_defn = tmplt(RESOURCE_SHAPE_TEMPLATE);
607        known_resources.add(sd.getName());
608        } else {
609        shape_defn = tmplt(SHAPE_DEFINITION_TEMPLATE).add("id", getExtendedType(sd));
610        known_resources.add(sd.getName());
611
612        if (baseDataTypes.contains(sd.getType())) {
613          shape_defn.add("resourceDecl", "\n");
614        } else {
615          if ("Element".equals(sd.getName()))
616            shape_defn.add("resourceDecl", tmplt(ROOT_TEMPLATE).render());
617          else {
618            String rootTmpl = tmplt(ROOT_TEMPLATE).render();
619            String btn = getBaseTypeName(sd);
620            if ((baseDataTypes.contains(btn))||
621              (shortIdException.contains(btn)))
622              rootTmpl = "\n";
623
624            ST resource_decl = tmplt(RESOURCE_DECL_TEMPLATE).
625              add("id", sd.getId()).
626              add("root", rootTmpl);
627
628            shape_defn.add("resourceDecl", resource_decl.render());
629          }
630        }
631      }
632      shape_defn.add("fhirType", " ");
633
634    // Generate the defining elements
635    List<String> elements = new ArrayList<String>();
636
637    // Add the additional entries for special types
638    String sdn = sd.getName();
639    if (sdn.equals("Coding"))
640      elements.add(tmplt(CONCEPT_REFERENCE_TEMPLATE).render());
641    else if (sdn.equals("CodeableConcept"))
642      elements.add(tmplt(CONCEPT_REFERENCES_TEMPLATE).render());
643    else if (sdn.equals("Reference"))
644      elements.add(tmplt(RESOURCE_LINK_TEMPLATE).render());
645
646    String root_comment = null;
647
648    constraintsList.clear();
649    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
650      if(!ed.getPath().contains("."))
651        root_comment = ed.getShort();
652      else if (
653        (StringUtils.countMatches(ed.getPath(), ".") == 1 && !"0".equals(ed.getMax()))
654          && (ed.hasBase()
655          && (
656          ed.getBase().getPath().startsWith(sdn)
657            || (ed.getBase().getPath().startsWith("Extension"))
658            || (ed.getBase().getPath().startsWith("Element.extension")&&(ed.hasSliceName()))
659        )
660        )
661      ){
662        String elementDefinition = genElementDefinition(sd, ed);
663
664        boolean isInnerType = false;
665        if (isInInnerTypes(ed)){
666          //debug("This element is already in innerTypes:" + ed.getPath());
667          isInnerType = true;
668        }
669
670        if (processConstraints) {
671          // Process constraints
672          for (ElementDefinition.ElementDefinitionConstraintComponent constraint : ed.getConstraint()) {
673            String sdType = sd.getType();
674            String cstype = constraint.getSource();
675            if ((!cstype.isEmpty()) && (cstype.indexOf("/") != -1)) {
676              String[] els = cstype.split("/");
677              cstype = els[els.length - 1];
678            }
679
680            String id = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();
681            String shortId = id.substring(id.lastIndexOf(".") + 1);
682            if ((ed.hasContentReference() && (!ed.hasType())) || (id.equals(sd.getName() + "." + shortId))) {
683              if ((sdType.equals(cstype)) || baseDataTypes.contains(sdType)) {
684                if (!isInnerType) {
685                  log.debug("\n        Key: " + constraint.getKey() + " SD type: " + sd.getType() + " Element: " + ed.getPath() + " Constraint Source: " + constraint.getSource() + " Constraint:" + constraint.getExpression());
686                  String transl = translateConstraint(sd, ed, constraint);
687                  if (transl.isEmpty() || constraintsList.contains(transl))
688                    continue;
689                  constraintsList.add(transl);
690                }
691              }
692            }
693          }
694        }
695        elements.add(elementDefinition);
696      }
697      else {
698        List<ElementDefinition> children = profileUtilities.getChildList(sd, ed);
699        if (children.size() > 0) {
700          for (ElementDefinition child : children) {
701            if (child.getPath().startsWith(ed.getPath()))
702              innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed));
703          }
704        }
705      }
706    }
707
708    if (processConstraints) {
709      // Constraints for differential to cover constraints on SD itself without any elements of its own
710      for (ElementDefinition ded : sd.getDifferential().getElement()) {
711        // Process constraints
712        for (ElementDefinition.ElementDefinitionConstraintComponent dconstraint : ded.getConstraint()) {
713          String sdType = sd.getType();
714
715          String id = ded.hasBase() ? ded.getBase().getPath() : ded.getPath();
716          String shortId = id.substring(id.lastIndexOf(".") + 1);
717
718          if (!isInInnerTypes(ded)) {
719            log.debug("\n        Key: " + dconstraint.getKey() + " SD type: " + sd.getType() + " Element: " + ded.getPath() + " Constraint Source: " + dconstraint.getSource() + " Constraint:" + dconstraint.getExpression());
720            String dtransl = translateConstraint(sd, ded, dconstraint);
721            if (dtransl.isEmpty() || constraintsList.contains(dtransl))
722              continue;
723            constraintsList.add(dtransl);
724          }
725        }
726      }
727    }
728
729    shape_defn.add("elements", StringUtils.join(elements, "\n"));
730    shape_defn.add("comment", root_comment == null? " " : "# " + root_comment);
731
732    String constraintStr = "";
733
734    if (!constraintsList.isEmpty()) {
735      constraintStr = "AND (\n\n" + StringUtils.join(constraintsList, "\n\n) AND (\n\n") + "\n\n)\n";
736    }
737
738    shape_defn.add("constraints", constraintStr);
739
740    String contextOfUseStr = "";
741    ArrayList<String> contextOfUse = new ArrayList<String>();
742    if (!sd.getContext().isEmpty()) {
743      for (StructureDefinition.StructureDefinitionContextComponent uc : sd.getContext()) {
744        if (!uc.getExpression().isEmpty()) {
745          String toStore = uc.getExpression();
746          log.debug("CONTEXT-OF-USE FOUND: " + toStore);
747          if (toStore.indexOf("http") != -1) {
748            log.debug("\t\tWARNING: CONTEXT-OF-USE SKIPPED as it has 'http' in it, might be a URL, instead of '.' delimited string");
749            continue;  // some erroneous context of use may use a URL; ignore them
750          }
751          String[] backRefs = toStore.split("\\.");
752          toStore = "a [fhir:" + backRefs[0] + "]";
753          for (int i = 1; i < backRefs.length; i++)
754            toStore = "^fhir:" + backRefs[i] + " {" + toStore + "}";
755
756          if (!contextOfUse.contains(toStore)) {
757            contextOfUse.add(toStore);
758          }
759        }
760      }
761
762      if (!contextOfUse.isEmpty()) {
763        if (contextOfUse.size() > 1)
764          contextOfUseStr = "^fhir:extension { " + StringUtils.join(contextOfUse, "} OR \n      {") + "}\n";
765        else
766          contextOfUseStr = "^fhir:extension { " + contextOfUse.get(0) + "}\n";
767      }
768    }
769
770    shape_defn.add("contextOfUse", contextOfUseStr);
771
772    return shape_defn.render();
773  }
774
775  /**
776   * @param ed
777   * @param constraint
778   * @return
779   */
780  private String translateConstraint(StructureDefinition sd, ElementDefinition ed, ElementDefinition.ElementDefinitionConstraintComponent constraint){
781    String translated = "";
782
783    if (constraint != null) {
784    //if (false) {
785      String ce = constraint.getExpression();
786      String constItem = "FHIR-SD-Path:" + ed.getPath() + " Expression: " + ce;
787      try {
788        translated = "# Constraint UniqueKey:" + constraint.getKey() + "\n# Human readable:" + constraint.getHuman() + "\n\n# Constraint: " + constraint.getExpression() + "\n# ShEx:\n";
789
790        ExpressionNode expr = fpe.parse(ce);
791        String shexConstraint = processExpressionNode(sd, ed, expr, false, 0);
792        shexConstraint = shexConstraint.replaceAll("CALLER", "");
793        log.debug("        Parsed to ShEx Constraint:" + shexConstraint);
794        if (!shexConstraint.isEmpty())
795          translated += "\n" + shexConstraint;
796
797        log.debug("        TRANSLATED\t"+ed.getPath()+"\t"+constraint.getHuman()+"\t"+constraint.getExpression()+"\t"+shexConstraint);
798
799      } catch (Exception e) {
800        //String message = "        FAILED to parse the constraint from Structure Definition: " + constItem + " [ " + e.getMessage() + " ]";
801        String message = "        FAILED to parse the constraint from Structure Definition: " + constItem;
802        e.printStackTrace();
803
804        translated = "";
805        log.debug(message);
806      }
807    }
808    return commentUnmapped(translated);
809  }
810
811  /**
812   * @param node
813   * @param quote
814   * @return
815   */
816  private String processExpressionNode(StructureDefinition sd, ElementDefinition ed, ExpressionNode node, boolean quote, int depth) {
817    if (node == null)
818      return "";
819    boolean toQuote  = quote;
820
821    String innerShEx = "";
822
823    if (node.getInner() != null){
824        innerShEx = processExpressionNode(sd, ed, node.getInner(), quote, depth + 1);
825    }
826
827    String translatedShEx = "";
828
829    boolean treatBothOpsSame = false;
830    // Figure out if there are any operations defined on this node
831    String ops = "";
832    String endOps = "";
833    if (node.getOperation() != null) {
834      String opName = node.getOperation().name();
835      switch (opName) {
836        case "Or":
837          ops = " OR ";
838          break;
839        case "Union":
840          //ops = " | ";
841          ops = " ";
842          break;
843        case "In" :
844        case "Equals":
845        case "Contains":
846          if (!node.getOpNext().getKind().equals(ExpressionNode.Kind.Name)) {
847            ops = " { fhir:v [";
848            endOps = "] } ";
849            toQuote = true;
850          } else {
851            String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind();
852            addUnmappedFunction(opName);
853            ops = TBD(opName);
854          }
855          break;
856        case "NotEquals":
857          if (!node.getOpNext().getKind().equals(ExpressionNode.Kind.Name)) {
858            ops = " [fhir:v  . -";
859            endOps = "] ";
860            toQuote = true;
861          } else {
862            String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind();
863            addUnmappedFunction(opName);
864            ops = TBD(opName);
865          }
866          break;
867        case "Greater":
868          if (node.getOpNext().getKind().equals(ExpressionNode.Kind.Constant)) {
869            ops = " { fhir:v MinExclusive ";
870            endOps = " } ";
871          } else {
872            String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind();
873            addUnmappedFunction(opName);
874            ops = TBD(opName);
875          }
876          break;
877        case "GreaterOrEqual":
878          if (node.getOpNext().getKind().equals(ExpressionNode.Kind.Constant)) {
879            ops = " { fhir:v MinInclusive ";
880            endOps = " } ";
881          } else {
882            String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind();
883            addUnmappedFunction(opName);
884            ops = TBD(opName);
885          }
886          break;
887        case "Less":
888        case "LessThan":
889          if (node.getOpNext().getKind().equals(ExpressionNode.Kind.Constant)) {
890            ops = " { fhir:v MaxExclusive ";
891            endOps = " } ";
892          } else {
893            String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind();
894            addUnmappedFunction(opName);
895            ops = TBD(opName);
896          }
897          break;
898        case "LessOrEqual":
899          if (node.getOpNext().getKind().equals(ExpressionNode.Kind.Constant)) {
900            ops = " { fhir:v MaxInclusive ";
901            endOps = " } ";
902          } else {
903            String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind();
904            addUnmappedFunction(opName);
905            ops = TBD(opName);
906          }
907          break;
908        case "And":
909        //case "Implies" :
910        ops = " AND ";
911        break;
912        case "As":
913        case "Is":
914          ops = " a ";
915          break;
916        //case "Xor":
917          //ops = " XOR ";  // Although I implemented a routine for XOR, but that needs more testing.
918         // break;
919        default:
920          String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind();
921          if (!unMappedFunctions.contains(toStore))
922            unMappedFunctions.add(toStore);
923
924          ops = TBD(opName);
925      }
926    }
927
928    // Functions
929    String fExp = "";
930    String pExp = "";
931    boolean isFunctionCall = false;
932    ExpressionNode.Kind kind = node.getKind();
933    if (kind == ExpressionNode.Kind.Function) {
934      String funcName = node.getName();
935      if (!mappedFunctions.contains(funcName)) {
936        if (node.parameterCount() == 0) {
937          if ("not".equals(funcName))
938            fExp = " NOT { CALLER }";
939          else {
940            fExp = " " + TBD(funcName) + "( CALLER )";
941            addUnmappedFunction(node.getFunction().toCode());
942            String toStore = addUnmappedFunction(node.getFunction().toCode());
943          }
944        }
945      }
946
947      if ("".equals(fExp)) {
948        switch (funcName) {
949          case "empty":
950            //fExp = " .?";
951            fExp = " NOT { CALLER {fhir:v .} } " ;
952            break;
953          case "exists":
954          case "hasValue":
955            fExp = " .";
956            break;
957          case "matches":
958            ops = " { fhir:v /";
959            endOps = "/ } ";
960            break;
961          //case "where": // 'where' just states an assertion
962           // ops = "{ ";
963           // endOps = " }";
964           // break;
965          case "contains":
966            ops = " { fhir:v [";
967            endOps = "] } ";
968            toQuote = true;
969            break;
970          //case "toString":  // skip this function call because values gets stringitize anyway
971           // pExp = "";
972           // break;
973          case "is":
974            ops = " { a [";
975            endOps = "] } ";
976            break;
977          default:
978            fExp = TBD(node.getFunction().toCode());
979            String toStore = addUnmappedFunction(node.getFunction().toCode());
980        }
981
982        if (node.parameterCount() > 0) {
983          for (ExpressionNode pen : node.getParameters()) {
984            if (!"".equals(pExp))
985              pExp += ", ";
986            pExp += processExpressionNode(sd, ed, pen, quote, depth);
987            isFunctionCall = true;
988          }
989        }
990      }
991
992      if (isFunctionCall) {
993        if (!mappedFunctions.contains(funcName))
994          translatedShEx += fExp + "(" + pExp + ")";
995        else {
996          translatedShEx += ops + pExp + endOps;
997          ops = "";
998          endOps = "";
999        }
1000      }
1001      else
1002        translatedShEx += fExp;
1003
1004      translatedShEx = positionParts(innerShEx, translatedShEx,
1005        getNextOps(ops , processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame),
1006        depth, false);
1007
1008    } else  if (kind == ExpressionNode.Kind.Name) {
1009      String mT = (depth == 0)? "fhir:" + node.getName() : node.getName();
1010      translatedShEx += positionParts(innerShEx, mT,
1011        getNextOps(ops, processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame),
1012        depth, true);
1013      //if (depth == 0)
1014       // translatedShEx = commentUnmapped(translatedShEx);
1015    }else if (kind == ExpressionNode.Kind.Group) {
1016      translatedShEx += positionParts(innerShEx, processExpressionNode(sd, ed, node.getGroup(), toQuote, depth),
1017        getNextOps(ops , processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame),
1018        depth, true);
1019      //if (depth == 0)
1020       // translatedShEx = commentUnmapped(translatedShEx);
1021    } else if (kind == ExpressionNode.Kind.Constant) {
1022      Base constantB = node.getConstant();
1023      boolean toQt = (constantB instanceof StringType) || (!constantB.isPrimitive());
1024      String constantV = constantB.primitiveValue();
1025
1026      if (constantV.startsWith("%")) {
1027        try {
1028          // Evaluate the expression, this resolves unknowns in the value.
1029          List<Base> evaluated = fpe.evaluate(null, sd, sd, ed, node);
1030
1031          if (!evaluated.isEmpty())
1032            constantV = evaluated.get(0).primitiveValue();
1033        }
1034        catch (Exception e) {
1035          log.debug("Failed to evaluate constant expression: " + constantV);
1036        }
1037      }
1038
1039      translatedShEx += positionParts(innerShEx, quoteThis(constantV, toQt),
1040        getNextOps(ops , processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame),
1041        depth, false);
1042      //translatedShEx += positionParts(innerShEx, node.getConstant().primitiveValue(), ops + processExpressionNode(node.getOpNext(), toQuote, 0) + endOps, depth);
1043    } else if (kind == ExpressionNode.Kind.Unary) {
1044      translatedShEx += positionParts(innerShEx, node.getName(),
1045        getNextOps(ops,processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame),
1046        depth, false);
1047    } else {
1048      translatedShEx += positionParts(innerShEx, node.toString(),
1049        getNextOps(ops, processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame),
1050        depth, false);
1051    }
1052
1053    return translatedShEx;
1054  }
1055
1056  private String commentUnmapped(String text) {
1057
1058    String pre = "";
1059    String token = "NNNNN";
1060    String temp = text;
1061    if ((text != null)&&(text.indexOf("SHEX_") != -1)) {
1062      pre = "\n# This constraint does not have mapping to a ShEx construct yet.";
1063      temp = text.replaceAll("\n", token);
1064      while (temp.indexOf("SHEX_") != -1) {
1065        pre += "\n# Unmapped construct found: " + StringUtils.substringBetween(temp, "SHEX_", "_SHEX");
1066        temp = temp.replaceFirst("SHEX_", " ").replaceFirst("_SHEX", " ");
1067      }
1068
1069      pre += "\n# ";
1070    }
1071
1072    if (temp.indexOf(token) != -1) {
1073      temp = "#" + temp;
1074      temp = temp.replaceAll(token, "\n#");
1075      temp = temp.replaceAll("##", "#");
1076      temp = temp.replaceAll("# #", "#");
1077      temp = temp.replaceAll("# #", "#");
1078      temp += "\n{}";
1079    }
1080
1081    return pre + temp;
1082  }
1083
1084  private String addUnmappedFunction(String func) {
1085    String toStore = "UNMAPPED_FUNCTION_" + func;
1086    if (!unMappedFunctions.contains(toStore))
1087      unMappedFunctions.add(toStore);
1088
1089    return toStore;
1090  }
1091
1092  private String getNextOps(String startOp, String opNext, String endOp, boolean treatBothOps){
1093    if (treatBothOps)
1094      return startOp + opNext + " " + endOp + opNext;
1095
1096    return startOp + opNext + endOp;
1097  }
1098
1099  private String positionParts(String funCall, String mainTxt, String nextText, int depth, boolean complete){
1100    if (funCall.indexOf("CALLER") != -1) {
1101      if (depth == 0) {
1102        String toReturn = funCall.replaceFirst("CALLER", mainTxt);
1103        if (complete)
1104          toReturn =  toReturn ;
1105
1106        toReturn = postProcessing(toReturn, nextText);
1107        return toReturn.replaceAll("CALLER", "");
1108      }
1109      else{
1110        String mT = (mainTxt != null) ? mainTxt.trim() : "";
1111        String dR = (mT.startsWith(".") || mT.startsWith("{") || mT.startsWith("[")) ? "" : ".";
1112        return  postProcessing(funCall.replaceFirst("CALLER", Matcher.quoteReplacement("CALLER" + dR + mT )), nextText) ;
1113      }
1114    }
1115
1116    String fc = funCall;
1117    if (fc.startsWith("fhir:"))
1118      fc = "." + fc.substring("fhir:".length());
1119
1120    if ((depth == 0)&&(complete)) {
1121      if (mainTxt.startsWith("fhir:")) {
1122        if ("".equals(funCall)) {
1123          return "{ " + postProcessing(mainTxt, nextText) + " }";
1124        }
1125
1126        if ((fc!= null)&&(!fc.isEmpty())) {
1127          String fT = fc.trim();
1128          String dR = (fT.startsWith(".") || fT.startsWith("{") || fT.startsWith("[")) ? "" : ".";
1129          fc = dR + fc;
1130        }
1131
1132        return "{" + postProcessing(mainTxt + fc, nextText) + "}";
1133      }
1134
1135      if (mainTxt.startsWith("'"))
1136          mainTxt = mainTxt;
1137      else
1138          mainTxt = "(" + mainTxt + ")";
1139
1140      if ("".equals(funCall)) {
1141        return postProcessing(mainTxt, nextText);
1142      }
1143    }
1144
1145    if ((fc!= null)&&(!fc.isEmpty())) {
1146      String fT = fc.trim();
1147      String dR = (fT.startsWith(".") || fT.startsWith("{") || fT.startsWith("[")) ? "" : ".";
1148      fc = dR + fc;
1149    }
1150    return postProcessing(mainTxt + fc,  nextText);
1151  }
1152
1153  private String postProcessing(String p, String q){
1154    String qp = q;
1155    if ((q != null)&&(q.trim().startsWith("XOR"))){
1156      qp = q.split("XOR")[1];
1157
1158      // because p xor q = ( p and not q) OR (not p and q)
1159      //return "(" + p + " AND NOT " + qp + ") OR ( NOT " + p + " AND " + qp + ")";
1160      return "{" + p + "} AND NOT {" + qp + "} OR { NOT {" + p + "} AND " + qp + "} ";
1161    }
1162
1163    return p + qp;
1164  }
1165
1166  /**
1167   * @param str
1168   * @return
1169   */
1170  private String TBD(String str){
1171    return " SHEX_" + str + "_SHEX ";
1172  }
1173
1174  /**
1175   * @param str
1176   * @param quote
1177   * @return
1178   */
1179  private String quoteThis(String str, boolean quote){
1180
1181    if (quote)
1182      return "'" + str + "'";
1183
1184    return str;
1185  }
1186
1187  /**
1188   * Generate a flattened definition for the inner types
1189   * @return stringified inner type definitions
1190   */
1191  private String emitInnerTypes() {
1192    StringBuilder itDefs = new StringBuilder();
1193    while(emittedInnerTypes.size() < innerTypes.size()) {
1194      for (Pair<StructureDefinition, ElementDefinition> it : new HashSet<Pair<StructureDefinition, ElementDefinition>>(innerTypes)) {
1195        if ((!emittedInnerTypes.contains(it))
1196          // && (it.getRight().hasBase() && it.getRight().getBase().getPath().startsWith(it.getLeft().getName()))
1197        ){
1198          itDefs.append("\n").append(genInnerTypeDef(it.getLeft(), it.getRight()));
1199          emittedInnerTypes.add(it);
1200        }
1201      }
1202    }
1203    return itDefs.toString();
1204  }
1205
1206  /**
1207   * @param ed
1208   * @return
1209   */
1210  private boolean  isInInnerTypes(ElementDefinition ed) {
1211
1212    if (this.innerTypes.isEmpty())
1213      return false;
1214
1215    for (Iterator<Pair<StructureDefinition, ElementDefinition>> itr = this.innerTypes.iterator(); itr.hasNext(); )
1216      if (itr.next().getRight() == ed)
1217        return true;
1218
1219    return false;
1220  }
1221
1222  /**
1223   * Generate a shape definition for the current set of datatypes
1224   * @return stringified data type definitions
1225   */
1226  private String emitDataTypes() {
1227    StringBuilder dtDefs = new StringBuilder();
1228    while (emittedDatatypes.size() < datatypes.size()) {
1229      for (String dt : new HashSet<String>(datatypes)) {
1230        if (!emittedDatatypes.contains(dt)) {
1231          StructureDefinition sd = context.fetchResource(StructureDefinition.class,
1232            ProfileUtilities.sdNs(dt, null));
1233          // TODO: Figure out why the line below doesn't work
1234          // if (sd != null && !uniq_structures.contains(sd))
1235          if(sd != null && !uniq_structure_urls.contains(sd.getUrl()))
1236            dtDefs.append("\n").append(genShapeDefinition(sd, false));
1237          emittedDatatypes.add(dt);
1238        }
1239      }
1240    }
1241    return dtDefs.toString();
1242  }
1243
1244  /**
1245   * @param text
1246   * @param max_col
1247   * @return
1248   */
1249  private ArrayList<String> split_text(String text, int max_col) {
1250    int pos = 0;
1251    ArrayList<String> rval = new ArrayList<String>();
1252    if (text.length() <= max_col) {
1253      rval.add(text);
1254    } else {
1255      String[] words = text.split(" ");
1256      int word_idx = 0;
1257      while(word_idx < words.length) {
1258        StringBuilder accum = new StringBuilder();
1259        while (word_idx < words.length && accum.length() + words[word_idx].length() < max_col)
1260          accum.append(words[word_idx++] + " ");
1261        if (accum.length() == 0) {
1262          accum.append(words[word_idx].substring(0, max_col - 3) + "-");
1263          words[word_idx] = words[word_idx].substring(max_col - 3);
1264        }
1265        rval.add(accum.toString());
1266        accum = new StringBuilder();
1267      }
1268    }
1269    return rval;
1270  }
1271
1272  /**
1273   * @param tmplt
1274   * @param ed
1275   */
1276  private void addComment(ST tmplt, ElementDefinition ed) {
1277    if(withComments && ed.hasShort() && !ed.getId().startsWith("Extension.")) {
1278      int nspaces;
1279      char[] sep;
1280      nspaces = Integer.max(COMMENT_COL - tmplt.add("comment", "#").render().indexOf('#'), MIN_COMMENT_SEP);
1281      tmplt.remove("comment");
1282      sep = new char[nspaces];
1283      Arrays.fill(sep, ' ');
1284      ArrayList<String> comment_lines = split_text(ed.getShort().replace("\n", " "), MAX_CHARS);
1285      StringBuilder comment = new StringBuilder("# ");
1286      char[] indent = new char[COMMENT_COL];
1287      Arrays.fill(indent, ' ');
1288      for(int i = 0; i < comment_lines.size();) {
1289        comment.append(comment_lines.get(i++));
1290        if(i < comment_lines.size())
1291          comment.append("\n" + new String(indent) + "# ");
1292      }
1293      tmplt.add("comment", new String(sep) + comment.toString());
1294    } else {
1295      tmplt.add("comment", " ");
1296    }
1297  }
1298
1299
1300  /**
1301   * Generate a ShEx element definition
1302   * @param sd Containing structure definition
1303   * @param ed Containing element definition
1304   * @return ShEx definition
1305   */
1306  private String genElementDefinition(StructureDefinition sd,
1307                                      ElementDefinition ed) {
1308    String id = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();
1309
1310    String shortId = id;
1311    String typ = id;
1312
1313    if (ed.getType().size() > 0)
1314      typ = ed.getType().get(0).getCode();
1315
1316    if (id.equals("Element.extension") && ed.hasSliceName()) {
1317      shortId = ed.getSliceName();
1318    }
1319    else
1320      shortId = id.substring(id.lastIndexOf(".") + 1);
1321
1322    if ((ed.getType().size() > 0) &&
1323      (ed.getType().get(0).getCode().startsWith(Constants.NS_SYSTEM_TYPE))) {
1324
1325      if (changeShortName(sd, ed)) {
1326        log.debug("VALUE NAME CHANGED to v from " + shortId + " for " + sd.getName() + ":" + ed.getPath());
1327        shortId = "v";
1328      }
1329
1330      typ = ed.getType().get(0).getWorkingCode();
1331    }
1332
1333    String defn = "";
1334    ST element_def;
1335    String card = ("*".equals(ed.getMax()) ? (ed.getMin() == 0 ? "*" : "+") : (ed.getMin() == 0 ? "?" : "")) + ";";
1336
1337    element_def = tmplt(ELEMENT_TEMPLATE);
1338    if (id.endsWith("[x]")) {
1339      element_def.add("id", "fhir:" + shortId.replace("[x]", ""));
1340    } else {
1341      element_def.add("id", "fhir:" + shortId + " ");
1342    }
1343
1344    List<ElementDefinition> children = profileUtilities.getChildList(sd, ed);
1345    if (children.size() > 0) {
1346      String parentPath = sd.getName();
1347      if ((ed.hasContentReference() && (!ed.hasType())) || (!id.equals(parentPath + "." + shortId))) {
1348        //debug("Not Adding innerType:" + id + " to " + sd.getName());
1349      } else
1350        innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed));
1351    }
1352
1353    if ("BackboneElement".equals(typ))
1354      typ = id;
1355
1356    defn = simpleElement(sd, ed, typ);
1357
1358    String refChoices = "";
1359
1360    if (id.endsWith("[x]")) {
1361      //defn = " (" + genChoiceTypes(sd, ed, shortId) + ")";
1362      defn = " " + genChoiceTypes(sd, ed, shortId) + " ";
1363      //defn += " AND { rdf:type IRI } ";
1364    } else {
1365      if (ed.getType().size() == 1) {
1366        // Single entry
1367        if ((defn.isEmpty())||(typ.equals(sd.getName())))
1368          defn = genTypeRef(sd, ed, id, ed.getType().get(0));
1369      } else if (ed.getContentReference() != null) {
1370        // Reference to another element
1371        String ref = ed.getContentReference();
1372        if (!ref.startsWith("#"))
1373          throw new AssertionError("Not equipped to deal with absolute path references: " + ref);
1374        String refPath = null;
1375        for (ElementDefinition ed1 : sd.getSnapshot().getElement()) {
1376          if (ed1.getId() != null && ed1.getId().equals(ref.substring(1))) {
1377            refPath = ed1.getPath();
1378            break;
1379          }
1380        }
1381        if (refPath == null)
1382          throw new AssertionError("Reference path not found: " + ref);
1383
1384        defn = simpleElement(sd, ed, refPath);
1385      }
1386
1387
1388      List<String> refValues = new ArrayList<String>();
1389      if (ed.hasType() && (ed.getType().get(0).getWorkingCode().equals("Reference"))) {
1390        if (ed.getType().get(0).hasTargetProfile()) {
1391
1392          ed.getType().get(0).getTargetProfile().forEach((CanonicalType tps) -> {
1393            String els[] = tps.getValue().split("/");
1394            refValues.add(els[els.length - 1]);
1395          });
1396        }
1397      }
1398
1399      if (!refValues.isEmpty()) {
1400        Collections.sort(refValues);
1401        refChoices = StringUtils.join(refValues, "_OR_");
1402      }
1403    }
1404
1405    // Adding OneOrMore as prefix to the reference type if cardinality is 1..* or 0..*
1406    if (card.startsWith("*") || card.startsWith("+")) {
1407      card = card.replace("+", "");
1408      card = card.replace("*", "?");
1409      defn = defn.replace("<", "<" + ONE_OR_MORE_PREFIX);
1410
1411      String defnToStore = defn;
1412      if (!refChoices.isEmpty()) {
1413        defnToStore = defn.replace(">", ONE_OR_MORE_CHOICES + refChoices + ">");
1414        defn = defn.replace(">", "_" + refChoices + ">");
1415      }
1416
1417      defnToStore = StringUtils.substringBetween(defnToStore, "<", ">");
1418      if (!oneOrMoreTypes.contains(defnToStore))
1419        oneOrMoreTypes.add(defnToStore);
1420    } else {
1421      if (!refChoices.isEmpty()) {
1422        defn += " AND {fhir:link \n\t\t\t@<" +
1423          refChoices.replaceAll("_OR_", "> OR \n\t\t\t@<") + "> ? }";
1424      }
1425    }
1426
1427    element_def.add("defn", defn);
1428    addImport(defn);
1429    element_def.add("card", card);
1430    addComment(element_def, ed);
1431
1432    return element_def.render();
1433  }
1434
1435  private boolean changeShortName(StructureDefinition sd, ElementDefinition ed){
1436      return shortIdException.contains(sd.getName());
1437  }
1438
1439  private void addImport(String typeDefn) {
1440      if ((typeDefn != null) && (!typeDefn.isEmpty())) {
1441  //        String importType = StringUtils.substringBetween(typeDefn, "<", ">");
1442  //        if ((importType.indexOf(ONE_OR_MORE_PREFIX) == -1) &&
1443  //          (!imports.contains(importType)))
1444  //            imports.add(importType);
1445  //    }
1446        Pattern p = Pattern.compile("<([^\\s>/]+)");
1447        Matcher m = p.matcher(typeDefn);
1448        while (m.find()) {
1449          String tag = m.group(1);
1450
1451          if (tag.indexOf(ONE_OR_MORE_PREFIX) != -1) {
1452            tag = tag.substring(ONE_OR_MORE_PREFIX.length());
1453          }
1454
1455          if ((tag.indexOf("_") != -1)||(tag.indexOf("_OR_") != -1))
1456            continue;
1457
1458           if (!imports.contains(tag))
1459            imports.add(tag);
1460        }
1461      }
1462  }
1463  private List<ElementDefinition> getChildren(StructureDefinition derived, ElementDefinition element) {
1464    List<ElementDefinition> elements = derived.getSnapshot().getElement();
1465    int index = elements.indexOf(element) + 1;
1466    String path = element.getPath()+".";
1467    List<ElementDefinition> list = new ArrayList<>();
1468    while (index < elements.size()) {
1469      ElementDefinition e = elements.get(index);
1470      String p = e.getPath();
1471      if (p.startsWith(path) && !e.hasSliceName()) {
1472        if (!p.substring(path.length()).contains(".")) {
1473          list.add(e);
1474        }
1475        index++;
1476      } else  {
1477        break;
1478      }
1479    }
1480    return list;
1481  }
1482
1483  /**
1484   * Generate a type reference and optional value set definition
1485   * @param sd Containing StructureDefinition
1486   * @param ed Element being defined
1487   * @param typ Element type
1488   * @return Type definition
1489   */
1490  private String simpleElement(StructureDefinition sd, ElementDefinition ed, String typ) {
1491    String addldef = "";
1492    ElementDefinition.ElementDefinitionBindingComponent binding = ed.getBinding();
1493    if(binding.hasStrength() && binding.getStrength() == Enumerations.BindingStrength.REQUIRED && "code".equals(typ)) {
1494      ValueSet vs = resolveBindingReference(sd, binding.getValueSet());
1495      if (vs != null) {
1496        addldef = tmplt(VALUESET_DEFN_TEMPLATE).add("vsn", vsprefix(vs.getUrl())).render();
1497        required_value_sets.add(vs);
1498      }
1499    }
1500    // TODO: check whether value sets and fixed are mutually exclusive
1501    if(ed.hasFixed()) {
1502      addldef = tmplt(FIXED_VALUE_TEMPLATE).add("val", ed.getFixed().primitiveValue()).render();
1503    }
1504    return tmplt(SIMPLE_ELEMENT_DEFN_TEMPLATE).add("typ", typ).add("vsdef", addldef).render();
1505  }
1506
1507  private String vsprefix(String uri) {
1508    if(uri.startsWith(FHIR_VS))
1509      return "fhirvs:" + uri.replace(FHIR_VS, "");
1510    return "<" + uri + ">";
1511  }
1512
1513  /**
1514   * Generate a type reference
1515   * @param sd Containing structure definition
1516   * @param ed Containing element definition
1517   * @param id Element id
1518   * @param typ Element type
1519   * @return Type reference string
1520   */
1521  private String genTypeRef(StructureDefinition sd, ElementDefinition ed, String id, ElementDefinition.TypeRefComponent typ) {
1522
1523    if(typ.hasProfile()) {
1524      if(typ.getWorkingCode().equals("Reference"))
1525        return genReference("", typ);
1526      else if(profileUtilities.getChildList(sd, ed).size() > 0) {
1527        // inline anonymous type - give it a name and factor it out
1528        innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed));
1529        return simpleElement(sd, ed, id);
1530      }
1531      else {
1532        String ref = getTypeName(typ);
1533        datatypes.add(ref);
1534        return simpleElement(sd, ed, ref);
1535      }
1536
1537    } else if (typ.getCode().startsWith(Constants.NS_SYSTEM_TYPE)) {
1538      String xt = getShexCode(typ.getWorkingCode());
1539
1540      // TODO: Remove the next line when the type of token gets switched to string
1541      // TODO: Add a rdf-type entry for valueInteger to xsd:integer (instead of int)
1542      ST td_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE).add("typ", xt);
1543      StringBuilder facets =  new StringBuilder();
1544      if(ed.hasMinValue()) {
1545        DataType mv = ed.getMinValue();
1546        facets.append(tmplt(MINVALUE_TEMPLATE).add("val", mv.primitiveValue()).render());
1547      }
1548      if(ed.hasMaxValue()) {
1549        DataType mv = ed.getMaxValue();
1550        facets.append(tmplt(MAXVALUE_TEMPLATE).add("val", mv.primitiveValue()).render());
1551      }
1552      if(ed.hasMaxLength()) {
1553        int ml = ed.getMaxLength();
1554        facets.append(tmplt(MAXLENGTH_TEMPLATE).add("val", ml).render());
1555      }
1556      if(ed.hasPattern()) {
1557        DataType pat = ed.getPattern();
1558        facets.append(tmplt(PATTERN_TEMPLATE).add("val",pat.primitiveValue()).render());
1559      }
1560      td_entry.add("facets", facets.toString());
1561      return td_entry.render();
1562
1563    } else if (typ.getWorkingCode() == null) {
1564      ST primitive_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE);
1565      primitive_entry.add("typ", "xsd:string");
1566      return primitive_entry.render();
1567
1568    } else if(typ.getWorkingCode().equals("xhtml")) {
1569      return tmplt(XHTML_TYPE_TEMPLATE).render();
1570    } else {
1571      datatypes.add(typ.getWorkingCode());
1572      return simpleElement(sd, ed, typ.getWorkingCode());
1573    }
1574  }
1575
1576  /**
1577   * @param c
1578   * @return
1579   */
1580  private String getShexCode(String c) {
1581    switch (c) {
1582      case "boolean" :
1583        return "xsd:boolean";
1584      case "integer" :
1585        return "xsd:int";
1586      case "integer64" :
1587        return "xsd:long";
1588      case "decimal" :
1589        return "xsd:decimal OR xsd:double";
1590      case "base64Binary" :
1591        return "xsd:base64Binary";
1592      case "instant" :
1593        return "xsd:dateTime";
1594      case "string" :
1595        return "xsd:string";
1596      case "uri" :
1597        return "xsd:anyURI";
1598      case "date" :
1599        return "xsd:gYear OR xsd:gYearMonth OR xsd:date";
1600      case "dateTime" :
1601        return "xsd:gYear OR xsd:gYearMonth OR xsd:date OR xsd:dateTime";
1602      case "time" :
1603        return "xsd:time";
1604      case "code" :
1605        return "xsd:token";
1606      case "oid" :
1607        return "xsd:anyURI";
1608      case "uuid" :
1609        return "xsd:anyURI";
1610      case "url" :
1611        return "xsd:anyURI";
1612      case "canonical" :
1613        return "xsd:anyURI";
1614      case "id" :
1615        return "xsd:string";
1616      case "unsignedInt" :
1617        return "xsd:nonNegativeInteger";
1618      case "positiveInt" :
1619        return "xsd:positiveInteger";
1620      case "markdown" :
1621        return "xsd:string";
1622    }
1623    throw new Error("Not implemented yet");
1624
1625  }
1626
1627  /**
1628   * Generate a set of alternative shapes
1629   * @param ed Containing element definition
1630   * @param id Element definition identifier
1631   * @param shortId id to use in the actual definition
1632   * @return ShEx list of alternative anonymous shapes separated by "OR"
1633   */
1634  private ST genAlternativeTypes(ElementDefinition ed, String id, String shortId) {
1635    ST shex_alt = tmplt(ALTERNATIVE_SHAPES_TEMPLATE);
1636    List<String> altEntries = new ArrayList<String>();
1637
1638
1639    for(ElementDefinition.TypeRefComponent typ : ed.getType())  {
1640      altEntries.add(genAltEntry(id, typ));
1641    }
1642    shex_alt.add("altEntries", StringUtils.join(altEntries, " OR \n    "));
1643    return shex_alt;
1644  }
1645
1646
1647
1648  /**
1649   * Generate an alternative shape for a reference
1650   * @param id reference name
1651   * @param typ shape type
1652   * @return ShEx equivalent
1653   */
1654  private String genAltEntry(String id, ElementDefinition.TypeRefComponent typ) {
1655    if(!typ.getWorkingCode().equals("Reference"))
1656      throw new AssertionError("We do not handle " + typ.getWorkingCode() + " alternatives");
1657
1658    return genReference(id, typ);
1659  }
1660
1661  /**
1662   * Generate a list of type choices for a "name[x]" style id
1663   * @param sd Structure containing ed
1664   * @param ed element definition
1665   * @param id choice identifier
1666   * @return ShEx fragment for the set of choices
1667   */
1668  private String genChoiceTypes(StructureDefinition sd,
1669                                ElementDefinition ed,
1670                                String id) {
1671    List<String> choiceEntries = new ArrayList<String>();
1672    List<String> refValues = new ArrayList<String>();
1673    String base = id.replace("[x]", "");
1674
1675    for (ElementDefinition.TypeRefComponent typ : ed.getType()) {
1676      String entry = genChoiceEntry(sd, ed, base, typ);
1677      refValues.clear();
1678      if (typ.hasTargetProfile()) {
1679        typ.getTargetProfile().forEach((CanonicalType tps) -> {
1680          String els[] = tps.getValue().split("/");
1681          refValues.add("@<" + els[els.length - 1] + ">");
1682        });
1683      }
1684
1685      if (!refValues.isEmpty())
1686        choiceEntries.add("(" + entry + " AND {fhir:link " + StringUtils.join(refValues, " OR \n\t\t\t ") + " }) ");
1687      else
1688        choiceEntries.add(entry);
1689    }
1690    return StringUtils.join(choiceEntries, " OR \n\t\t\t");
1691  }
1692
1693  /**
1694   * Generate an entry in a choice list
1695   * @param id base identifier
1696   * @param typ type/discriminant
1697   * @return ShEx fragment for choice entry
1698   */
1699  private String genChoiceEntry(StructureDefinition sd,
1700                                ElementDefinition ed,
1701                                String id,
1702                                ElementDefinition.TypeRefComponent typ) {
1703    ST shex_choice_entry = tmplt(ELEMENT_TEMPLATE);
1704
1705    String ext = typ.getWorkingCode();
1706    // shex_choice_entry.add("id", "fhir:" + base+Character.toUpperCase(ext.charAt(0)) + ext.substring(1) + " ");
1707    shex_choice_entry.add("id", "");
1708    shex_choice_entry.add("card", "");
1709    String typeDefn = genTypeRef(sd, ed, id, typ);
1710    shex_choice_entry.add("defn", typeDefn);
1711    addImport(typeDefn);
1712    shex_choice_entry.add("comment", " ");
1713    return shex_choice_entry.render();
1714  }
1715
1716  /**
1717   * @param oneOrMoreType
1718   * @return
1719   */
1720  private String getOneOrMoreType(String oneOrMoreType) {
1721    if ((oneOrMoreType == null)||(oneOrMoreTypes.isEmpty()))
1722      return "";
1723
1724    ST one_or_more_type = tmplt(ONE_OR_MORE_TEMPLATE);
1725    String oomType = oneOrMoreType;
1726    String origType = oneOrMoreType;
1727    String restriction = "";
1728    if (oneOrMoreType.indexOf(ONE_OR_MORE_CHOICES) != -1) {
1729      oomType = oneOrMoreType.replaceAll(ONE_OR_MORE_CHOICES, "_");
1730      origType = oneOrMoreType.split(ONE_OR_MORE_CHOICES)[0];
1731      restriction = "AND {fhir:link \n\t\t\t@<";
1732
1733      String choices = oneOrMoreType.split(ONE_OR_MORE_CHOICES)[1];
1734      restriction += choices.replaceAll("_OR_", "> OR \n\t\t\t@<") + "> }";
1735    }
1736
1737    origType = origType.replaceAll(ONE_OR_MORE_PREFIX, "");
1738
1739    one_or_more_type.add("oomType", oomType);
1740    one_or_more_type.add("origType", origType);
1741    one_or_more_type.add("restriction", restriction);
1742    addImport(origType);
1743    addImport(restriction);
1744    one_or_more_type.add("comment", "");
1745    return one_or_more_type.render();
1746  }
1747
1748  /**
1749   * Generate a definition for a referenced element
1750   * @param sd Containing structure definition
1751   * @param ed Inner element
1752   * @return ShEx representation of element reference
1753   */
1754  private String genInnerTypeDef(StructureDefinition sd, ElementDefinition ed) {
1755    String path = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();
1756    ST element_reference = tmplt(SHAPE_DEFINITION_TEMPLATE);
1757    element_reference.add("resourceDecl", "");  // Not a resource
1758    element_reference.add("id", path + getExtendedType(ed));
1759    element_reference.add("fhirType", " ");
1760    String comment = ed.getShort();
1761    element_reference.add("comment", comment == null? " " : "# " + comment);
1762
1763    List<String> elements = new ArrayList<String>();
1764    for (ElementDefinition child: profileUtilities.getChildList(sd, path, null))
1765      if (child.hasBase() && child.getBase().getPath().startsWith(sd.getName())) {
1766        String elementDefinition = genElementDefinition(sd, child);
1767        elements.add(elementDefinition);
1768      }
1769
1770    element_reference.add("elements", StringUtils.join(elements, "\n"));
1771
1772    List<String> innerConstraintsList = new ArrayList<String>();
1773
1774    if (processConstraints) {
1775      // Process constraints
1776      for (ElementDefinition.ElementDefinitionConstraintComponent constraint : ed.getConstraint()) {
1777        String sdType = sd.getType();
1778        String cstype = constraint.getSource();
1779        if ((cstype != null) && (cstype.indexOf("/") != -1)) {
1780          String[] els = cstype.split("/");
1781          cstype = els[els.length - 1];
1782        }
1783
1784        String id = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();
1785        String shortId = id.substring(id.lastIndexOf(".") + 1);
1786        if ((ed.hasContentReference() && (!ed.hasType())) || (id.equals(sd.getName() + "." + shortId))) {
1787          if ((sdType.equals(cstype)) || baseDataTypes.contains(sdType)) {
1788            //if (!isInInnerTypes(ed)) {
1789            log.debug("\n        (INNER ED) Key: " + constraint.getKey() + " SD type: " + sd.getType() + " Element: " + ed.getPath() + " Constraint Source: " + constraint.getSource() + " Constraint:" + constraint.getExpression());
1790            String transl = translateConstraint(sd, ed, constraint);
1791            if (transl.isEmpty() || innerConstraintsList.contains(transl))
1792              continue;
1793            innerConstraintsList.add(transl);
1794            //}
1795          }
1796        }
1797      }
1798    }
1799
1800    String constraintStr = "";
1801
1802    if (!innerConstraintsList.isEmpty()) {
1803      constraintStr = "AND (\n\n" + StringUtils.join(constraintsList, "\n\n) AND (\n\n") + "\n\n)\n";
1804    }
1805
1806    element_reference.add("constraints", constraintStr);
1807
1808    // TODO: See if we need to process contexts
1809    element_reference.add("contextOfUse", "");
1810
1811    return element_reference.render();
1812  }
1813
1814  /**
1815   * Generate a reference to a resource
1816   * @param id attribute identifier
1817   * @param typ possible reference types
1818   * @return string that represents the result
1819   */
1820  private String genReference(String id, ElementDefinition.TypeRefComponent typ) {
1821    ST shex_ref = tmplt(REFERENCE_DEFN_TEMPLATE);
1822
1823    String ref = getTypeName(typ);
1824    shex_ref.add("id", id);
1825    shex_ref.add("ref", ref);
1826    references.add(ref);
1827    return shex_ref.render();
1828  }
1829
1830  /**
1831   * Return the type name for typ
1832   * @param typ type to get name for
1833   * @return name
1834   */
1835  private String getTypeName(ElementDefinition.TypeRefComponent typ) {
1836    // TODO: This is brittle. There has to be a utility to do this...
1837    if (typ.hasTargetProfile()) {
1838      String[] els = typ.getTargetProfile().get(0).getValue().split("/");
1839      return els[els.length - 1];
1840    } else if (typ.hasProfile()) {
1841      String[] els = typ.getProfile().get(0).getValue().split("/");
1842      return els[els.length - 1];
1843    } else {
1844      return typ.getWorkingCode();
1845    }
1846  }
1847
1848  /**
1849   * @param vs
1850   * @return
1851   */
1852  private String genValueSet(ValueSet vs) {
1853    ST vsd = tmplt(VALUE_SET_DEFINITION).add("vsuri", vsprefix(vs.getUrl())).add("comment", vs.getDescription());
1854    ValueSetExpansionOutcome vse = context.expandVS(vs, true, false);
1855    List<String> valid_codes = new ArrayList<String>();
1856    if(vse != null &&
1857      vse.getValueset() != null &&
1858      vse.getValueset().hasExpansion() &&
1859      vse.getValueset().getExpansion().hasContains()) {
1860      for(ValueSet.ValueSetExpansionContainsComponent vsec : vse.getValueset().getExpansion().getContains())
1861        valid_codes.add("\"" + vsec.getCode() + "\"");
1862    }
1863    return vsd.add("val_list", valid_codes.size() > 0? " [" + StringUtils.join(valid_codes, " ") + ']' : " xsd:string #EXTERNAL").render();
1864  }
1865
1866
1867  /**
1868   * @param ctxt
1869   * @param reference
1870   * @return
1871   */
1872  // TODO: find a utility that implements this
1873  private ValueSet resolveBindingReference(DomainResource ctxt, String reference) {
1874    try {
1875      return context.fetchResource(ValueSet.class, reference);
1876    } catch (Throwable e) {
1877      return null;
1878    }
1879  }
1880}