docs: public String generate(HTMLLinkPolicy links, List<StructureDefinition> structures) {
  
      ST shex_def = tmplt(SHEX_TEMPLATE);
      String start_cmd;
      if(completeModel || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE))
  //            || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE))
        start_cmd = completeModel? tmplt(ALL_START_TEMPLATE).render() :
                tmplt(START_TEMPLATE).add("id", structures.get(0).getId()).render();
      else
        start_cmd = "";
      shex_def.add("header", tmplt(HEADER_TEMPLATE).
              add("start", start_cmd).
              add("fhir", FHIR).
              add("fhirvs", FHIR_VS).render());
  
      Collections.sort(structures, new SortById());
      StringBuilder shapeDefinitions = new StringBuilder();
  
      // For unknown reasons, the list of structures carries duplicates.  We remove them
      // Also, it is possible for the same sd to have multiple hashes...
      uniq_structures = new LinkedList<StructureDefinition>();
      uniq_structure_urls = new HashSet<String>();
      for (StructureDefinition sd : structures) {
        if (!uniq_structure_urls.contains(sd.getUrl())) {
          uniq_structures.add(sd);
          uniq_structure_urls.add(sd.getUrl());
        }
      }
  
  
      for (StructureDefinition sd : uniq_structures) {
        shapeDefinitions.append(genShapeDefinition(sd, true));
      }
  
      shapeDefinitions.append(emitInnerTypes());
      if(doDatatypes) {
        shapeDefinitions.append("\n#---------------------- Data Types -------------------\n");
        while (emittedDatatypes.size() < datatypes.size() ||
                emittedInnerTypes.size() < innerTypes.size()) {
          shapeDefinitions.append(emitDataTypes());
          shapeDefinitions.append(emitInnerTypes());
        }
      }
  
      shapeDefinitions.append("\n#---------------------- Reference Types -------------------\n");
      for(String r: references) {
        shapeDefinitions.append("\n").append(tmplt(TYPED_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n");
        if (!"Resource".equals(r) && !known_resources.contains(r))
          shapeDefinitions.append("\n").append(tmplt(TARGET_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n");
      }
      shex_def.add("shapeDefinitions", shapeDefinitions);
  
      if(completeModel && known_resources.size() > 0) {
        shapeDefinitions.append("\n").append(tmplt(COMPLETE_RESOURCE_TEMPLATE)
                .add("resources", StringUtils.join(known_resources, "> OR\n\t@<")).render());
        List<String> all_entries = new ArrayList<String>();
        for(String kr: known_resources)
          all_entries.add(tmplt(ALL_ENTRY_TEMPLATE).add("id", kr).render());
        shapeDefinitions.append("\n").append(tmplt(ALL_TEMPLATE)
                .add("all_entries", StringUtils.join(all_entries, " OR\n\t")).render());
      }
  
      shapeDefinitions.append("\n#---------------------- Value Sets ------------------------\n");
      for(ValueSet vs: required_value_sets)
        shapeDefinitions.append("\n").append(genValueSet(vs));
      return shex_def.render();
    }
  
  
    /**
     * Emit a ShEx definition for the supplied StructureDefinition
     * @param sd Structure definition to emit
     * @param top_level True means outermost type, False means recursively called
     * @return ShEx definition
     */
    private String genShapeDefinition(StructureDefinition sd, boolean top_level) {
      // xhtml is treated as an atom
      if("xhtml".equals(sd.getName()) || (completeModel && "Resource".equals(sd.getName())))
        return "";
  
      ST shape_defn;
      // Resources are either incomplete items or consist of everything that is defined as a resource (completeModel)
      if("Resource".equals(sd.getName())) {
        shape_defn = tmplt(RESOURCE_SHAPE_TEMPLATE);
        known_resources.add(sd.getName());
      } else {
        shape_defn = tmplt(SHAPE_DEFINITION_TEMPLATE).add("id", sd.getId());
        if (sd.getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE)) {
  //              || sd.getKind().equals(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE)) {
          known_resources.add(sd.getName());
          ST resource_decl = tmplt(RESOURCE_DECL_TEMPLATE).
                  add("id", sd.getId()).
                  add("root", tmplt(ROOT_TEMPLATE));
  //                add("root", top_level ? tmplt(ROOT_TEMPLATE) : "");
          shape_defn.add("resourceDecl", resource_decl.render());
        } else {
          shape_defn.add("resourceDecl", "");
        }
      }
  
      // Generate the defining elements
      List<String> elements = new ArrayList<String>();
  
      // Add the additional entries for special types
      String sdn = sd.getName();
      if (sdn.equals("Coding"))
        elements.add(tmplt(CONCEPT_REFERENCE_TEMPLATE).render());
      else if (sdn.equals("CodeableConcept"))
        elements.add(tmplt(CONCEPT_REFERENCES_TEMPLATE).render());
      else if (sdn.equals("Reference"))
        elements.add(tmplt(RESOURCE_LINK_TEMPLATE).render());
  //    else if (sdn.equals("Extension"))
  //      return tmplt(EXTENSION_TEMPLATE).render();
  
      String root_comment = null;
      for (ElementDefinition ed : sd.getSnapshot().getElement()) {
        if(!ed.getPath().contains("."))
          root_comment = ed.getShort();
        else if (StringUtils.countMatches(ed.getPath(), ".") == 1 && !"0".equals(ed.getMax())) {
          elements.add(genElementDefinition(sd, ed));
        }
      }
      shape_defn.add("elements", StringUtils.join(elements, "\n"));
      shape_defn.add("comment", root_comment == null? " " : "# " + root_comment);
      return shape_defn.render();
    }