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