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