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