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