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