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