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