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