001package org.hl7.fhir.r4.conformance;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Collections;
006import java.util.Comparator;
007import java.util.HashSet;
008import java.util.LinkedList;
009import java.util.List;
010
011import org.apache.commons.lang3.StringUtils;
012import org.apache.commons.lang3.tuple.ImmutablePair;
013import org.apache.commons.lang3.tuple.Pair;
014import org.hl7.fhir.exceptions.FHIRException;
015import org.hl7.fhir.r4.context.IWorkerContext;
016import org.hl7.fhir.r4.elementmodel.TurtleParser;
017import org.hl7.fhir.r4.model.DomainResource;
018import org.hl7.fhir.r4.model.ElementDefinition;
019import org.hl7.fhir.r4.model.Enumerations;
020import org.hl7.fhir.r4.model.Reference;
021import org.hl7.fhir.r4.model.Resource;
022import org.hl7.fhir.r4.model.StructureDefinition;
023import org.hl7.fhir.r4.model.Type;
024import org.hl7.fhir.r4.model.UriType;
025import org.hl7.fhir.r4.model.ValueSet;
026import org.hl7.fhir.r4.terminologies.ValueSetExpander;
027import org.hl7.fhir.r4.utils.ToolingExtensions;
028import org.stringtemplate.v4.ST;
029
030public class ShExGenerator {
031
032  public enum HTMLLinkPolicy {
033    NONE, EXTERNAL, INTERNAL
034  }
035  public boolean doDatatypes = true;                 // add data types
036  public boolean withComments = true;                // include comments
037  public boolean completeModel = false;              // doing complete build (fhir.shex)
038
039
040  private static String SHEX_TEMPLATE = "$header$\n\n" +
041          "$shapeDefinitions$";
042
043  // A header is a list of prefixes, a base declaration and a start node
044  private static String FHIR = "http://hl7.org/fhir/";
045  private static String FHIR_VS = FHIR + "ValueSet/";
046  private static String HEADER_TEMPLATE =
047          "PREFIX fhir: <$fhir$> \n" +
048                  "PREFIX fhirvs: <$fhirvs$>\n" +
049                  "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> \n" +
050                  "BASE <http://hl7.org/fhir/shape/>\n$start$";
051
052  // Start template for single (open) entry
053  private static String START_TEMPLATE = "\n\nstart=@<$id$> AND {fhir:nodeRole [fhir:treeRoot]}\n";
054
055  // Start template for complete (closed) model
056  private static String ALL_START_TEMPLATE = "\n\nstart=@<All>\n";
057
058  private static String ALL_TEMPLATE = "\n<All> $all_entries$\n";
059
060  private static String ALL_ENTRY_TEMPLATE = "(NOT { fhir:nodeRole [fhir:treeRoot] ; a [fhir:$id$] } OR @<$id$>)";
061
062
063  // Shape Definition
064  //      the shape name
065  //      an optional resource declaration (type + treeRoot)
066  //      the list of element declarations
067  //      an optional index element (for appearances inside ordered lists)
068  private static String SHAPE_DEFINITION_TEMPLATE =
069          "$comment$\n<$id$> CLOSED {\n    $resourceDecl$" +
070                  "\n    $elements$" +
071                  "\n    fhir:index xsd:integer?                 # Relative position in a list\n}\n";
072
073  // Resource Definition
074  //      an open shape of type Resource.  Used when completeModel = false.
075  private static String RESOURCE_SHAPE_TEMPLATE =
076          "$comment$\n<Resource> {a .+;" +
077                  "\n    $elements$" +
078                  "\n    fhir:index xsd:integer?" +
079                  "\n}\n";
080
081  // If we have knowledge of all of the possible resources available to us (completeModel = true), we can build
082  // a model of all possible resources.
083  private static String COMPLETE_RESOURCE_TEMPLATE =
084          "<Resource>  @<$resources$>" +
085                  "\n\n";
086
087  // Resource Declaration
088  //      a type node
089  //      an optional treeRoot declaration (identifies the entry point)
090  private static String RESOURCE_DECL_TEMPLATE = "\na [fhir:$id$];$root$";
091
092  // Root Declaration.
093  private static String ROOT_TEMPLATE = "\nfhir:nodeRole [fhir:treeRoot]?;";
094
095  // Element
096  //    a predicate, type and cardinality triple
097  private static String ELEMENT_TEMPLATE = "$id$$defn$$card$$comment$";
098  private static int COMMENT_COL = 40;
099  private static int MAX_CHARS = 35;
100  private static int MIN_COMMENT_SEP = 2;
101
102  // Inner Shape Definition
103  private static String INNER_SHAPE_TEMPLATE = "($comment$\n    $defn$\n)$card$";
104
105  // Simple Element
106  //    a shape reference
107  private static String SIMPLE_ELEMENT_DEFN_TEMPLATE = "@<$typ$>$vsdef$";
108
109  // Value Set Element
110  private static String VALUESET_DEFN_TEMPLATE = " AND\n\t{fhir:value @$vsn$}";
111
112  // Fixed Value Template
113  private static String FIXED_VALUE_TEMPLATE = " AND\n\t{fhir:value [\"$val$\"]}";
114
115  // A primitive element definition
116  //    the actual type reference
117  private static String PRIMITIVE_ELEMENT_DEFN_TEMPLATE = "$typ$$facets$";
118
119  // Facets
120  private static String MINVALUE_TEMPLATE = " MININCLUSIVE $val$";
121  private static String MAXVALUE_TEMPLATE = " MAXINCLUSIVE $val$";
122  private static String MAXLENGTH_TEMPLATE = " MAXLENGTH $val$";
123  private static String PATTERN_TEMPLATE = " PATTERN \"$val$\"";
124
125  // A choice of alternative shape definitions
126  //  rendered as an inner anonymous shape
127  private static String ALTERNATIVE_SHAPES_TEMPLATE = "fhir:$id$$comment$\n(   $altEntries$\n)$card$";
128
129  // A typed reference definition
130  private static String REFERENCE_DEFN_TEMPLATE = "@<$ref$Reference>";
131
132  // What we emit for an xhtml
133  private static String XHTML_TYPE_TEMPLATE = "xsd:string";
134
135  // Additional type for Coding
136  private static String CONCEPT_REFERENCE_TEMPLATE = "a NONLITERAL?;";
137
138  // Additional type for CodedConcept
139  private static String CONCEPT_REFERENCES_TEMPLATE = "a NONLITERAL*;";
140
141  // Untyped resource has the extra link entry
142  private static String RESOURCE_LINK_TEMPLATE = "fhir:link IRI?;";
143
144  // Extension template
145  // No longer used -- we emit the actual definition
146//  private static String EXTENSION_TEMPLATE = "<Extension> {fhir:extension @<Extension>*;" +
147//          "\n    fhir:index xsd:integer?" +
148//          "\n}\n";
149
150  // A typed reference -- a fhir:uri with an optional type and the possibility of a resolvable shape
151  private static String TYPED_REFERENCE_TEMPLATE = "\n<$refType$Reference> CLOSED {" +
152          "\n    fhir:Element.id @<id>?;" +
153          "\n    fhir:Element.extension @<Extension>*;" +
154          "\n    fhir:link @<$refType$> OR CLOSED {a [fhir:$refType$]}?;" +
155          "\n    fhir:Reference.reference @<string>?;" +
156          "\n    fhir:Reference.display @<string>?;" +
157          "\n    fhir:index xsd:integer?" +
158          "\n}";
159
160  private static String TARGET_REFERENCE_TEMPLATE = "\n<$refType$> {" +
161          "\n    a [fhir:$refType$];" +
162          "\n    fhir:nodeRole [fhir:treeRoot]?" +
163          "\n}";
164
165  // A value set definition
166  private static String VALUE_SET_DEFINITION = "# $comment$\n$vsuri$$val_list$\n";
167
168
169  /**
170   * this makes internal metadata services available to the generator - retrieving structure definitions, and value set expansion etc
171   */
172  private IWorkerContext context;
173
174  /**
175   * innerTypes -- inner complex types.  Currently flattened in ShEx (doesn't have to be, btw)
176   * emittedInnerTypes -- set of inner types that have been generated
177   * datatypes, emittedDatatypes -- types used in the definition, types that have been generated
178   * references -- Reference types (Patient, Specimen, etc)
179   * uniq_structures -- set of structures on the to be generated list...
180   * doDataTypes -- whether or not to emit the data types.
181   */
182  private HashSet<Pair<StructureDefinition, ElementDefinition>> innerTypes, emittedInnerTypes;
183  private HashSet<String> datatypes, emittedDatatypes;
184  private HashSet<String> references;
185  private LinkedList<StructureDefinition> uniq_structures;
186  private HashSet<String> uniq_structure_urls;
187  private HashSet<ValueSet> required_value_sets;
188  private HashSet<String> known_resources;          // Used when generating a full definition
189
190  public ShExGenerator(IWorkerContext context) {
191    super();
192    this.context = context;
193    innerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>();
194    emittedInnerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>();
195    datatypes = new HashSet<String>();
196    emittedDatatypes = new HashSet<String>();
197    references = new HashSet<String>();
198    required_value_sets = new HashSet<ValueSet>();
199    known_resources = new HashSet<String>();
200  }
201
202  public String generate(HTMLLinkPolicy links, StructureDefinition structure) {
203    List<StructureDefinition> list = new ArrayList<StructureDefinition>();
204    list.add(structure);
205    innerTypes.clear();
206    emittedInnerTypes.clear();
207    datatypes.clear();
208    emittedDatatypes.clear();
209    references.clear();
210    required_value_sets.clear();
211    known_resources.clear();
212    return generate(links, list);
213  }
214
215  public class SortById implements Comparator<StructureDefinition> {
216
217    @Override
218    public int compare(StructureDefinition arg0, StructureDefinition arg1) {
219      return arg0.getId().compareTo(arg1.getId());
220    }
221
222  }
223
224  private ST tmplt(String template) {
225    return new ST(template, '$', '$');
226  }
227
228  /**
229   * this is called externally to generate a set of structures to a single ShEx file
230   * generally, it will be called with a single structure, or a long list of structures (all of them)
231   *
232   * @param links HTML link rendering policy
233   * @param structures list of structure definitions to render
234   * @return ShEx definition of structures
235   */
236  public String generate(HTMLLinkPolicy links, List<StructureDefinition> structures) {
237
238    ST shex_def = tmplt(SHEX_TEMPLATE);
239    String start_cmd;
240    if(completeModel || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE))
241//            || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE))
242      start_cmd = completeModel? tmplt(ALL_START_TEMPLATE).render() :
243              tmplt(START_TEMPLATE).add("id", structures.get(0).getId()).render();
244    else
245      start_cmd = "";
246    shex_def.add("header", tmplt(HEADER_TEMPLATE).
247            add("start", start_cmd).
248            add("fhir", FHIR).
249            add("fhirvs", FHIR_VS).render());
250
251    Collections.sort(structures, new SortById());
252    StringBuilder shapeDefinitions = new StringBuilder();
253
254    // For unknown reasons, the list of structures carries duplicates.  We remove them
255    // Also, it is possible for the same sd to have multiple hashes...
256    uniq_structures = new LinkedList<StructureDefinition>();
257    uniq_structure_urls = new HashSet<String>();
258    for (StructureDefinition sd : structures) {
259      if (!uniq_structure_urls.contains(sd.getUrl())) {
260        uniq_structures.add(sd);
261        uniq_structure_urls.add(sd.getUrl());
262      }
263    }
264
265
266    for (StructureDefinition sd : uniq_structures) {
267      shapeDefinitions.append(genShapeDefinition(sd, true));
268    }
269
270    shapeDefinitions.append(emitInnerTypes());
271    if(doDatatypes) {
272      shapeDefinitions.append("\n#---------------------- Data Types -------------------\n");
273      while (emittedDatatypes.size() < datatypes.size() ||
274              emittedInnerTypes.size() < innerTypes.size()) {
275        shapeDefinitions.append(emitDataTypes());
276        shapeDefinitions.append(emitInnerTypes());
277      }
278    }
279
280    shapeDefinitions.append("\n#---------------------- Reference Types -------------------\n");
281    for(String r: references) {
282      shapeDefinitions.append("\n").append(tmplt(TYPED_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n");
283      if (!"Resource".equals(r) && !known_resources.contains(r))
284        shapeDefinitions.append("\n").append(tmplt(TARGET_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n");
285    }
286    shex_def.add("shapeDefinitions", shapeDefinitions);
287
288    if(completeModel && known_resources.size() > 0) {
289      shapeDefinitions.append("\n").append(tmplt(COMPLETE_RESOURCE_TEMPLATE)
290              .add("resources", StringUtils.join(known_resources, "> OR\n\t@<")).render());
291      List<String> all_entries = new ArrayList<String>();
292      for(String kr: known_resources)
293        all_entries.add(tmplt(ALL_ENTRY_TEMPLATE).add("id", kr).render());
294      shapeDefinitions.append("\n").append(tmplt(ALL_TEMPLATE)
295              .add("all_entries", StringUtils.join(all_entries, " OR\n\t")).render());
296    }
297
298    shapeDefinitions.append("\n#---------------------- Value Sets ------------------------\n");
299    for(ValueSet vs: required_value_sets)
300      shapeDefinitions.append("\n").append(genValueSet(vs));
301    return shex_def.render();
302  }
303
304
305  /**
306   * Emit a ShEx definition for the supplied StructureDefinition
307   * @param sd Structure definition to emit
308   * @param top_level True means outermost type, False means recursively called
309   * @return ShEx definition
310   */
311  private String genShapeDefinition(StructureDefinition sd, boolean top_level) {
312    // xhtml is treated as an atom
313    if("xhtml".equals(sd.getName()) || (completeModel && "Resource".equals(sd.getName())))
314      return "";
315
316    ST shape_defn;
317    // Resources are either incomplete items or consist of everything that is defined as a resource (completeModel)
318    if("Resource".equals(sd.getName())) {
319      shape_defn = tmplt(RESOURCE_SHAPE_TEMPLATE);
320      known_resources.add(sd.getName());
321    } else {
322      shape_defn = tmplt(SHAPE_DEFINITION_TEMPLATE).add("id", sd.getId());
323      if (sd.getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE)) {
324//              || sd.getKind().equals(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE)) {
325        known_resources.add(sd.getName());
326        ST resource_decl = tmplt(RESOURCE_DECL_TEMPLATE).
327                add("id", sd.getId()).
328                add("root", tmplt(ROOT_TEMPLATE));
329//                add("root", top_level ? tmplt(ROOT_TEMPLATE) : "");
330        shape_defn.add("resourceDecl", resource_decl.render());
331      } else {
332        shape_defn.add("resourceDecl", "");
333      }
334    }
335
336    // Generate the defining elements
337    List<String> elements = new ArrayList<String>();
338
339    // Add the additional entries for special types
340    String sdn = sd.getName();
341    if (sdn.equals("Coding"))
342      elements.add(tmplt(CONCEPT_REFERENCE_TEMPLATE).render());
343    else if (sdn.equals("CodeableConcept"))
344      elements.add(tmplt(CONCEPT_REFERENCES_TEMPLATE).render());
345    else if (sdn.equals("Reference"))
346      elements.add(tmplt(RESOURCE_LINK_TEMPLATE).render());
347//    else if (sdn.equals("Extension"))
348//      return tmplt(EXTENSION_TEMPLATE).render();
349
350    String root_comment = null;
351    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
352      if(!ed.getPath().contains("."))
353        root_comment = ed.getShort();
354      else if (StringUtils.countMatches(ed.getPath(), ".") == 1 && !"0".equals(ed.getMax())) {
355        elements.add(genElementDefinition(sd, ed));
356      }
357    }
358    shape_defn.add("elements", StringUtils.join(elements, "\n"));
359    shape_defn.add("comment", root_comment == null? " " : "# " + root_comment);
360    return shape_defn.render();
361  }
362
363
364  /**
365   * Generate a flattened definition for the inner types
366   * @return stringified inner type definitions
367   */
368  private String emitInnerTypes() {
369    StringBuilder itDefs = new StringBuilder();
370    while(emittedInnerTypes.size() < innerTypes.size()) {
371      for (Pair<StructureDefinition, ElementDefinition> it : new HashSet<Pair<StructureDefinition, ElementDefinition>>(innerTypes)) {
372        if (!emittedInnerTypes.contains(it)) {
373          itDefs.append("\n").append(genInnerTypeDef(it.getLeft(), it.getRight()));
374          emittedInnerTypes.add(it);
375        }
376      }
377    }
378    return itDefs.toString();
379  }
380
381  /**
382   * Generate a shape definition for the current set of datatypes
383   * @return stringified data type definitions
384   */
385  private String emitDataTypes() {
386    StringBuilder dtDefs = new StringBuilder();
387    while (emittedDatatypes.size() < datatypes.size()) {
388      for (String dt : new HashSet<String>(datatypes)) {
389        if (!emittedDatatypes.contains(dt)) {
390          StructureDefinition sd = context.fetchResource(StructureDefinition.class,
391              ProfileUtilities.sdNs(dt, null));
392          // TODO: Figure out why the line below doesn't work
393          // if (sd != null && !uniq_structures.contains(sd))
394          if(sd != null && !uniq_structure_urls.contains(sd.getUrl()))
395            dtDefs.append("\n").append(genShapeDefinition(sd, false));
396          emittedDatatypes.add(dt);
397        }
398      }
399    }
400    return dtDefs.toString();
401  }
402
403  private ArrayList<String> split_text(String text, int max_col) {
404    int pos = 0;
405    ArrayList<String> rval = new ArrayList<String>();
406    if (text.length() <= max_col) {
407      rval.add(text);
408    } else {
409      String[] words = text.split(" ");
410      int word_idx = 0;
411      while(word_idx < words.length) {
412        StringBuilder accum = new StringBuilder();
413        while (word_idx < words.length && accum.length() + words[word_idx].length() < max_col)
414          accum.append(words[word_idx++] + " ");
415        if (accum.length() == 0) {
416          accum.append(words[word_idx].substring(0, max_col - 3) + "-");
417          words[word_idx] = words[word_idx].substring(max_col - 3);
418        }
419        rval.add(accum.toString());
420        accum = new StringBuilder();
421      }
422    }
423    return rval;
424  }
425
426  private void addComment(ST tmplt, ElementDefinition ed) {
427    if(withComments && ed.hasShort() && !ed.getId().startsWith("Extension.")) {
428      int nspaces;
429      char[] sep;
430      nspaces = Integer.max(COMMENT_COL - tmplt.add("comment", "#").render().indexOf('#'), MIN_COMMENT_SEP);
431      tmplt.remove("comment");
432      sep = new char[nspaces];
433      Arrays.fill(sep, ' ');
434      ArrayList<String> comment_lines = split_text(ed.getShort().replace("\n", " "), MAX_CHARS);
435      StringBuilder comment = new StringBuilder("# ");
436      char[] indent = new char[COMMENT_COL];
437      Arrays.fill(indent, ' ');
438      for(int i = 0; i < comment_lines.size();) {
439        comment.append(comment_lines.get(i++));
440        if(i < comment_lines.size())
441          comment.append("\n" + new String(indent) + "# ");
442      }
443      tmplt.add("comment", new String(sep) + comment.toString());
444    } else {
445      tmplt.add("comment", " ");
446    }
447  }
448
449
450  /**
451   * Generate a ShEx element definition
452   * @param sd Containing structure definition
453   * @param ed Containing element definition
454   * @return ShEx definition
455   */
456  private String genElementDefinition(StructureDefinition sd, ElementDefinition ed) {
457    String id = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();
458    String shortId = id.substring(id.lastIndexOf(".") + 1);
459    String defn;
460    ST element_def;
461    String card = ("*".equals(ed.getMax()) ? (ed.getMin() == 0 ? "*" : "+") : (ed.getMin() == 0 ? "?" : "")) + ";";
462
463    if(id.endsWith("[x]")) {
464      element_def = ed.getType().size() > 1? tmplt(INNER_SHAPE_TEMPLATE) : tmplt(ELEMENT_TEMPLATE);
465      element_def.add("id", "");
466    } else {
467      element_def = tmplt(ELEMENT_TEMPLATE);
468      element_def.add("id", "fhir:" + (id.charAt(0) == id.toLowerCase().charAt(0)? shortId : id) + " ");
469    }
470
471    List<ElementDefinition> children = ProfileUtilities.getChildList(sd, ed);
472    if (children.size() > 0) {
473      innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed));
474      defn = simpleElement(sd, ed, id);
475    } else if(id.endsWith("[x]")) {
476      defn = genChoiceTypes(sd, ed, id);
477    }
478    else if (ed.getType().size() == 1) {
479      // Single entry
480      defn = genTypeRef(sd, ed, id, ed.getType().get(0));
481    } else if (ed.getContentReference() != null) {
482      // Reference to another element
483      String ref = ed.getContentReference();
484      if(!ref.startsWith("#"))
485        throw new AssertionError("Not equipped to deal with absolute path references: " + ref);
486      String refPath = null;
487      for(ElementDefinition ed1: sd.getSnapshot().getElement()) {
488        if(ed1.getId() != null && ed1.getId().equals(ref.substring(1))) {
489          refPath = ed1.getPath();
490          break;
491        }
492      }
493      if(refPath == null)
494        throw new AssertionError("Reference path not found: " + ref);
495      // String typ = id.substring(0, id.indexOf(".") + 1) + ed.getContentReference().substring(1);
496      defn = simpleElement(sd, ed, refPath);
497    } else if(id.endsWith("[x]")) {
498      defn = genChoiceTypes(sd, ed, id);
499    } else {
500      // TODO: Refactoring required here
501      element_def = genAlternativeTypes(ed, id, shortId);
502      element_def.add("id", id.charAt(0) == id.toLowerCase().charAt(0)? shortId : id);
503      element_def.add("card", card);
504      addComment(element_def, ed);
505      return element_def.render();
506    }
507    element_def.add("defn", defn);
508    element_def.add("card", card);
509    addComment(element_def, ed);
510    return element_def.render();
511  }
512
513  /**
514   * Generate a type reference and optional value set definition
515   * @param sd Containing StructureDefinition
516   * @param ed Element being defined
517   * @param typ Element type
518   * @return Type definition
519   */
520  private String simpleElement(StructureDefinition sd, ElementDefinition ed, String typ) {
521    String addldef = "";
522    ElementDefinition.ElementDefinitionBindingComponent binding = ed.getBinding();
523    if(binding.hasStrength() && binding.getStrength() == Enumerations.BindingStrength.REQUIRED && "code".equals(typ)) {
524      ValueSet vs = resolveBindingReference(sd, binding.getValueSet());
525      if (vs != null) {
526        addldef = tmplt(VALUESET_DEFN_TEMPLATE).add("vsn", vsprefix(vs.getUrl())).render();
527        required_value_sets.add(vs);
528      }
529    }
530    // TODO: check whether value sets and fixed are mutually exclusive
531    if(ed.hasFixed()) {
532      addldef = tmplt(FIXED_VALUE_TEMPLATE).add("val", ed.getFixed().primitiveValue()).render();
533    }
534    return tmplt(SIMPLE_ELEMENT_DEFN_TEMPLATE).add("typ", typ).add("vsdef", addldef).render();
535  }
536
537  private String vsprefix(String uri) {
538    if(uri.startsWith(FHIR_VS))
539      return "fhirvs:" + uri.replace(FHIR_VS, "");
540    return "<" + uri + ">";
541  }
542
543  /**
544   * Generate a type reference
545   * @param sd Containing structure definition
546   * @param ed Containing element definition
547   * @param id Element id
548   * @param typ Element type
549   * @return Type reference string
550   */
551  private String genTypeRef(StructureDefinition sd, ElementDefinition ed, String id, ElementDefinition.TypeRefComponent typ) {
552
553    if(typ.hasProfile()) {
554      if(typ.getCode().equals("Reference"))
555        return genReference("", typ);
556      else if(ProfileUtilities.getChildList(sd, ed).size() > 0) {
557        // inline anonymous type - give it a name and factor it out
558        innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed));
559        return simpleElement(sd, ed, id);
560      }
561      else {
562        String ref = getTypeName(typ);
563        datatypes.add(ref);
564        return simpleElement(sd, ed, ref);
565      }
566
567    } else if (typ.getCodeElement().getExtensionsByUrl(ToolingExtensions.EXT_RDF_TYPE).size() > 0) {
568      String xt = null;
569      try {
570        xt = typ.getCodeElement().getExtensionString(ToolingExtensions.EXT_RDF_TYPE);
571      } catch (FHIRException e) {
572        e.printStackTrace();
573      }
574      // TODO: Remove the next line when the type of token gets switched to string
575      // TODO: Add a rdf-type entry for valueInteger to xsd:integer (instead of int)
576      ST td_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE).add("typ",
577              xt.replace("xsd:token", "xsd:string").replace("xsd:int", "xsd:integer"));
578      StringBuilder facets =  new StringBuilder();
579      if(ed.hasMinValue()) {
580        Type mv = ed.getMinValue();
581        facets.append(tmplt(MINVALUE_TEMPLATE).add("val", TurtleParser.ttlLiteral(mv.primitiveValue(), mv.fhirType())).render());
582      }
583      if(ed.hasMaxValue()) {
584        Type mv = ed.getMaxValue();
585        facets.append(tmplt(MAXVALUE_TEMPLATE).add("val", TurtleParser.ttlLiteral(mv.primitiveValue(), mv.fhirType())).render());
586      }
587      if(ed.hasMaxLength()) {
588        int ml = ed.getMaxLength();
589        facets.append(tmplt(MAXLENGTH_TEMPLATE).add("val", ml).render());
590      }
591      if(ed.hasPattern()) {
592        Type pat = ed.getPattern();
593        facets.append(tmplt(PATTERN_TEMPLATE).add("val",pat.primitiveValue()).render());
594      }
595      td_entry.add("facets", facets.toString());
596      return td_entry.render();
597
598    } else if (typ.getCode() == null) {
599      ST primitive_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE);
600      primitive_entry.add("typ", "xsd:string");
601      return primitive_entry.render();
602
603    } else if(typ.getCode().equals("xhtml")) {
604      return tmplt(XHTML_TYPE_TEMPLATE).render();
605    } else {
606      datatypes.add(typ.getCode());
607      return simpleElement(sd, ed, typ.getCode());
608    }
609  }
610
611  /**
612   * Generate a set of alternative shapes
613   * @param ed Containing element definition
614   * @param id Element definition identifier
615   * @param shortId id to use in the actual definition
616   * @return ShEx list of alternative anonymous shapes separated by "OR"
617   */
618  private ST genAlternativeTypes(ElementDefinition ed, String id, String shortId) {
619    ST shex_alt = tmplt(ALTERNATIVE_SHAPES_TEMPLATE);
620    List<String> altEntries = new ArrayList<String>();
621
622
623    for(ElementDefinition.TypeRefComponent typ : ed.getType())  {
624      altEntries.add(genAltEntry(id, typ));
625    }
626    shex_alt.add("altEntries", StringUtils.join(altEntries, " OR\n    "));
627    return shex_alt;
628  }
629
630
631
632  /**
633   * Generate an alternative shape for a reference
634   * @param id reference name
635   * @param typ shape type
636   * @return ShEx equivalent
637   */
638  private String genAltEntry(String id, ElementDefinition.TypeRefComponent typ) {
639    if(!typ.getCode().equals("Reference"))
640      throw new AssertionError("We do not handle " + typ.getCode() + " alternatives");
641
642    return genReference(id, typ);
643  }
644
645  /**
646   * Generate a list of type choices for a "name[x]" style id
647   * @param sd Structure containing ed
648   * @param ed element definition
649   * @param id choice identifier
650   * @return ShEx fragment for the set of choices
651   */
652  private String genChoiceTypes(StructureDefinition sd, ElementDefinition ed, String id) {
653    List<String> choiceEntries = new ArrayList<String>();
654    String base = id.replace("[x]", "");
655
656    for(ElementDefinition.TypeRefComponent typ : ed.getType())
657      choiceEntries.add(genChoiceEntry(sd, ed, id, base, typ));
658
659    return StringUtils.join(choiceEntries, " |\n");
660  }
661
662  /**
663   * Generate an entry in a choice list
664   * @param base base identifier
665   * @param typ type/discriminant
666   * @return ShEx fragment for choice entry
667   */
668  private String genChoiceEntry(StructureDefinition sd, ElementDefinition ed, String id, String base, ElementDefinition.TypeRefComponent typ) {
669    ST shex_choice_entry = tmplt(ELEMENT_TEMPLATE);
670
671    String ext = typ.getCode();
672    shex_choice_entry.add("id", "fhir:" + base+Character.toUpperCase(ext.charAt(0)) + ext.substring(1) + " ");
673    shex_choice_entry.add("card", "");
674    shex_choice_entry.add("defn", genTypeRef(sd, ed, id, typ));
675    shex_choice_entry.add("comment", " ");
676    return shex_choice_entry.render();
677  }
678
679  /**
680   * Generate a definition for a referenced element
681   * @param sd Containing structure definition
682   * @param ed Inner element
683   * @return ShEx representation of element reference
684   */
685  private String genInnerTypeDef(StructureDefinition sd, ElementDefinition ed) {
686    String path = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();;
687    ST element_reference = tmplt(SHAPE_DEFINITION_TEMPLATE);
688    element_reference.add("resourceDecl", "");  // Not a resource
689    element_reference.add("id", path);
690    String comment = ed.getShort();
691    element_reference.add("comment", comment == null? " " : "# " + comment);
692
693    List<String> elements = new ArrayList<String>();
694    for (ElementDefinition child: ProfileUtilities.getChildList(sd, path, null))
695      elements.add(genElementDefinition(sd, child));
696
697    element_reference.add("elements", StringUtils.join(elements, "\n"));
698    return element_reference.render();
699  }
700
701  /**
702   * Generate a reference to a resource
703   * @param id attribute identifier
704   * @param typ possible reference types
705   * @return string that represents the result
706   */
707  private String genReference(String id, ElementDefinition.TypeRefComponent typ) {
708    ST shex_ref = tmplt(REFERENCE_DEFN_TEMPLATE);
709
710    String ref = getTypeName(typ);
711    shex_ref.add("id", id);
712    shex_ref.add("ref", ref);
713    references.add(ref);
714    return shex_ref.render();
715  }
716
717  /**
718   * Return the type name for typ
719   * @param typ type to get name for
720   * @return name
721   */
722  private String getTypeName(ElementDefinition.TypeRefComponent typ) {
723    // TODO: This is brittle. There has to be a utility to do this...
724    if (typ.hasTargetProfile()) {
725      String[] els = typ.getTargetProfile().get(0).getValue().split("/");
726      return els[els.length - 1];
727    } else if (typ.hasProfile()) {
728      String[] els = typ.getProfile().get(0).getValue().split("/");
729      return els[els.length - 1];
730    } else {
731      return typ.getCode();
732    }
733  }
734
735  private String genValueSet(ValueSet vs) {
736    ST vsd = tmplt(VALUE_SET_DEFINITION).add("vsuri", vsprefix(vs.getUrl())).add("comment", vs.getDescription());
737    ValueSetExpander.ValueSetExpansionOutcome vse = context.expandVS(vs, true, false);
738    List<String> valid_codes = new ArrayList<String>();
739    if(vse != null &&
740            vse.getValueset() != null &&
741            vse.getValueset().hasExpansion() &&
742            vse.getValueset().getExpansion().hasContains()) {
743      for(ValueSet.ValueSetExpansionContainsComponent vsec : vse.getValueset().getExpansion().getContains())
744        valid_codes.add("\"" + vsec.getCode() + "\"");
745    }
746    return vsd.add("val_list", valid_codes.size() > 0? " [" + StringUtils.join(valid_codes, " ") + ']' : " EXTERNAL").render();
747  }
748
749
750  // TODO: find a utility that implements this
751  private ValueSet resolveBindingReference(DomainResource ctxt, String reference) {
752    try {
753      return context.fetchResource(ValueSet.class, reference);
754    } catch (Throwable e) {
755      return null;
756    }
757  }
758}