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