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.*; 035import java.util.regex.Matcher; 036import java.util.regex.Pattern; 037 038import org.apache.commons.lang3.StringUtils; 039import org.apache.commons.lang3.tuple.ImmutablePair; 040import org.apache.commons.lang3.tuple.Pair; 041import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 042import org.hl7.fhir.r5.context.IWorkerContext; 043import org.hl7.fhir.r5.model.*; 044import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 045import org.hl7.fhir.r5.utils.FHIRPathEngine; 046import org.stringtemplate.v4.ST; 047 048public class ShExGenerator { 049 050 public enum HTMLLinkPolicy { 051 NONE, EXTERNAL, INTERNAL 052 } 053 054 public enum ConstraintTranslationPolicy { 055 ALL, // Translate all Extensions found; Default (or when no policy defined) 056 GENERIC_ONLY, // Translate all Extensions except constraints with context-of-use 057 CONTEXT_OF_USE_ONLY // Translate only Extensions with context-of-use 058 } 059 060 public boolean doDatatypes = false; // add data types 061 public boolean withComments = true; // include comments 062 public boolean completeModel = false; // doing complete build (fhir.shex) 063 064 public boolean debugMode = false; // Used for Debugging and testing the code 065 066 public boolean processConstraints = false; // set to false - to skip processing constraints 067 068 public ConstraintTranslationPolicy constraintPolicy = ConstraintTranslationPolicy.ALL; 069 070 private static String SHEX_TEMPLATE = 071 "$header$\n" + 072 "$imports$\n" + 073 "$shapeDefinitions$"; 074 075 // A header is a list of prefixes, a base declaration and a start node 076 private static String FHIR = "http://hl7.org/fhir/"; 077 private static String FHIR_VS = FHIR + "ValueSet/"; 078 private static String HEADER_TEMPLATE = 079 "PREFIX fhir: <$fhir$> \n" + 080 "PREFIX fhirvs: <$fhirvs$>\n" + 081 "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> \n" + 082 "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> \n"; 083 //"BASE <http://hl7.org/fhir/shape/>\n"; 084 085 private static String IMPORT_TEMPLATE = "IMPORT <$import$$fileExt$>\n"; 086 087 // Start template for single (open) entry 088 private static String START_TEMPLATE = "\n\nstart=@<$id$> AND {fhir:nodeRole [fhir:treeRoot]}\n"; 089 090 // Start template for complete (closed) model 091 private static String ALL_START_TEMPLATE = "\n\nstart=@<All>\n"; 092 093 private static String ALL_TEMPLATE = "\n<All> $all_entries$\n"; 094 095 private static String ALL_ENTRY_TEMPLATE = "(NOT { fhir:nodeRole [fhir:treeRoot] ; a [fhir:$id$] } OR @<$id$>)"; 096 097 098 // Shape Definition 099 // the shape name 100 // an optional resource declaration (type + treeRoot) 101 // the list of element declarations 102 // an optional index element (for appearances inside ordered lists) 103 private static String SHAPE_DEFINITION_TEMPLATE = 104 "$comment$\n<$id$> CLOSED { $fhirType$ " + 105 "\n $resourceDecl$" + 106 "\n $elements$" + 107 "\n $contextOfUse$" + 108 "\n} $constraints$ \n"; 109 110 // Base DataTypes 111 private List<String> baseDataTypes = Arrays.asList( 112 "DataType", 113 "PrimitiveType", 114 "BackboneElement" 115 ); 116 117 private List<String> shortIdException = Arrays.asList( 118 "base64Binary", "boolean", 119 "date", "dateTime", "decimal", "instant", "integer64", 120 "integer", "string", "time", "uri" 121 ); 122 123 private List<String> mappedFunctions = Arrays.asList( 124 "empty", 125 "exists", 126 "hasValue", 127 "matches", 128 "contains", 129 //"toString", 130 "is" 131 //"where" 132 ); 133 134 private static String ONE_OR_MORE_PREFIX = "OneOrMore_"; 135 private static String ONE_OR_MORE_CHOICES = "_One-Or-More-Choices_"; 136 private static String ONE_OR_MORE_TEMPLATE = 137 "\n$comment$\n<$oomType$> CLOSED {" + 138 "\n rdf:first @<$origType$> $restriction$ ;" + 139 "\n rdf:rest [rdf:nil] OR @<$oomType$> " + 140 "\n}\n"; 141 142 // Resource Definition 143 // an open shape of type Resource. Used when completeModel = false. 144 private static String RESOURCE_SHAPE_TEMPLATE = 145 "$comment$\n<Resource> {" + 146 "\n $elements$" + 147 "\n $contextOfUse$" + 148 "\n} $constraints$ \n"; 149 150 // If we have knowledge of all of the possible resources available to us (completeModel = true), we can build 151 // a model of all possible resources. 152 private static String COMPLETE_RESOURCE_TEMPLATE = 153 "<Resource> @<$resources$>" + 154 "\n\n"; 155 156 // Resource Declaration 157 // a type node 158 // an optional treeRoot declaration (identifies the entry point) 159 private static String RESOURCE_DECL_TEMPLATE = "\na [fhir:$id$]?;$root$"; 160 161 // Root Declaration. 162 private static String ROOT_TEMPLATE = "\nfhir:nodeRole [fhir:treeRoot]?;\n"; 163 164 // Element 165 // a predicate, type and cardinality triple 166 private static String ELEMENT_TEMPLATE = "$id$$defn$$card$$comment$"; 167 private static int COMMENT_COL = 40; 168 private static int MAX_CHARS = 35; 169 private static int MIN_COMMENT_SEP = 2; 170 171 // Inner Shape Definition 172 private static String INNER_SHAPE_TEMPLATE = "($comment$\n $defn$\n)$card$"; 173 174 // Simple Element 175 // a shape reference 176 private static String SIMPLE_ELEMENT_DEFN_TEMPLATE = "@<$typ$>$vsdef$"; 177 178 // Value Set Element 179 private static String VALUESET_DEFN_TEMPLATE = " AND\n\t{fhir:v @$vsn$}"; 180 181 // Fixed Value Template 182 private static String FIXED_VALUE_TEMPLATE = " AND\n\t{fhir:value [\"$val$\"]}"; 183 184 // A primitive element definition 185 // the actual type reference 186 private static String PRIMITIVE_ELEMENT_DEFN_TEMPLATE = "$typ$$facets$"; 187 188 // Facets 189 private static String MINVALUE_TEMPLATE = " MININCLUSIVE $val$"; 190 private static String MAXVALUE_TEMPLATE = " MAXINCLUSIVE $val$"; 191 private static String MAXLENGTH_TEMPLATE = " MAXLENGTH $val$"; 192 private static String PATTERN_TEMPLATE = " PATTERN \"$val$\""; 193 194 // A choice of alternative shape definitions 195 // rendered as an inner anonymous shape 196 private static String ALTERNATIVE_SHAPES_TEMPLATE = "fhir:$id$$comment$\n( $altEntries$\n)$card$"; 197 198 // A typed reference definition 199 private static String REFERENCE_DEFN_TEMPLATE = "@<$ref$Reference>"; 200 201 // What we emit for an xhtml 202 private static String XHTML_TYPE_TEMPLATE = "xsd:string"; 203 204 // Additional type for Coding 205 private static String CONCEPT_REFERENCE_TEMPLATE = "a NONLITERAL?;"; 206 207 // Additional type for CodedConcept 208 private static String CONCEPT_REFERENCES_TEMPLATE = "a NONLITERAL*;"; 209 210 // Untyped resource has the extra link entry 211 private static String RESOURCE_LINK_TEMPLATE = "fhir:link IRI?;"; 212 213 // Extension template 214 // No longer used -- we emit the actual definition 215// private static String EXTENSION_TEMPLATE = "<Extension> {fhir:extension @<Extension>*;" + 216// "\n fhir:index xsd:integer?" + 217// "\n}\n"; 218 219 // A typed reference -- a fhir:uri with an optional type and the possibility of a resolvable shape 220 private static String TYPED_REFERENCE_TEMPLATE = "\n<$refType$Reference> CLOSED {" + 221 "\n fhir:Element.id @<id>?;" + 222 "\n fhir:Element.extension @<Extension>*;" + 223 "\n fhir:link @<$refType$> OR CLOSED {a [fhir:$refType$]}?;" + 224 "\n fhir:Reference.reference @<string>?;" + 225 "\n fhir:Reference.display @<string>?;" + 226 // "\n fhir:index xsd:integer?" + 227 "\n}"; 228 229 private static String TARGET_REFERENCE_TEMPLATE = "\n<$refType$> {" + 230 "\n a [fhir:$refType$];" + 231 "\n fhir:nodeRole [fhir:treeRoot]?" + 232 "\n}"; 233 234 // A value set definition 235 private static String VALUE_SET_DEFINITION = "# $comment$\n$vsuri$$val_list$\n"; 236 237 238 /** 239 * this makes internal metadata services available to the generator - retrieving structure definitions, and value set expansion etc 240 */ 241 private IWorkerContext context; 242 private ProfileUtilities profileUtilities; 243 244 /** 245 * innerTypes -- inner complex types. Currently flattened in ShEx (doesn't have to be, btw) 246 * emittedInnerTypes -- set of inner types that have been generated 247 * datatypes, emittedDatatypes -- types used in the definition, types that have been generated 248 * references -- Reference types (Patient, Specimen, etc) 249 * uniq_structures -- set of structures on the to be generated list... 250 * doDataTypes -- whether or not to emit the data types. 251 */ 252 private HashSet<Pair<StructureDefinition, ElementDefinition>> innerTypes, emittedInnerTypes; 253 254 private List<String> oneOrMoreTypes; 255 256 private List<String> constraintsList; 257 258 private List<String> unMappedFunctions; 259 260 private HashSet<String> datatypes, emittedDatatypes; 261 private HashSet<String> references; 262 private LinkedList<StructureDefinition> uniq_structures; 263 private HashSet<String> uniq_structure_urls; 264 private HashSet<ValueSet> required_value_sets; 265 private HashSet<String> known_resources; // Used when generating a full definition 266 267 // List of URLs of Excluded Structure Definitions from ShEx Schema generation. 268 private List<String> excludedSDUrls; 269 270 // List of URLs of selected Structure Definitions of Extensions from ShEx Schema generation. 271 // Extensions are Structure Definitions with type as "Extension". 272 private List<StructureDefinition> selectedExtensions; 273 private List<String> selectedExtensionUrls; 274 275 private List<String> imports; 276 private FHIRPathEngine fpe; 277 278 public ShExGenerator(IWorkerContext context) { 279 super(); 280 this.context = context; 281 profileUtilities = new ProfileUtilities(context, null, null); 282 innerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>(); 283 oneOrMoreTypes = new ArrayList<String>(); 284 constraintsList = new ArrayList<String>(); 285 unMappedFunctions = new ArrayList<String>(); 286 emittedInnerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>(); 287 datatypes = new HashSet<String>(); 288 emittedDatatypes = new HashSet<String>(); 289 references = new HashSet<String>(); 290 required_value_sets = new HashSet<ValueSet>(); 291 known_resources = new HashSet<String>(); 292 excludedSDUrls = new ArrayList<String>(); 293 selectedExtensions = new ArrayList<StructureDefinition>(); 294 selectedExtensionUrls = new ArrayList<String>(); 295 imports = new ArrayList<String>(); 296 297 fpe = new FHIRPathEngine(context); 298 } 299 300 public String generate(HTMLLinkPolicy links, StructureDefinition structure) { 301 List<StructureDefinition> list = new ArrayList<StructureDefinition>(); 302 list.add(structure); 303 innerTypes.clear(); 304 oneOrMoreTypes.clear(); 305 constraintsList.clear(); 306 unMappedFunctions.clear(); 307 emittedInnerTypes.clear(); 308 datatypes.clear(); 309 emittedDatatypes.clear(); 310 references.clear(); 311 required_value_sets.clear(); 312 known_resources.clear(); 313 imports.clear(); 314 return generate(links, list); 315 } 316 317 public List<String> getExcludedStructureDefinitionUrls(){ 318 return this.excludedSDUrls; 319 } 320 321 public void setExcludedStructureDefinitionUrls(List<String> excludedSDs){ 322 this.excludedSDUrls = excludedSDs; 323 } 324 325 public List<StructureDefinition> getSelectedExtensions(){ 326 return this.selectedExtensions; 327 } 328 329 public void setSelectedExtension(List<StructureDefinition> selectedExtensions){ 330 this.selectedExtensions = selectedExtensions; 331 332 selectedExtensionUrls.clear(); 333 334 for (StructureDefinition eSD : selectedExtensions){ 335 if (!selectedExtensionUrls.contains(eSD.getUrl())) 336 selectedExtensionUrls.add(eSD.getUrl()); 337 } 338 } 339 340 public class SortById implements Comparator<StructureDefinition> { 341 342 @Override 343 public int compare(StructureDefinition arg0, StructureDefinition arg1) { 344 return arg0.getId().compareTo(arg1.getId()); 345 } 346 347 } 348 349 private ST tmplt(String template) { 350 return new ST(template, '$', '$'); 351 } 352 353 /** 354 * this is called externally to generate a set of structures to a single ShEx file 355 * generally, it will be called with a single structure, or a long list of structures (all of them) 356 * 357 * @param links HTML link rendering policy 358 * @param structures list of structure definitions to render 359 * @return ShEx definition of structures 360 */ 361 public String generate(HTMLLinkPolicy links, List<StructureDefinition> structures, List<String> excludedSDUrls) { 362 this.excludedSDUrls = excludedSDUrls; 363 364 if ((structures != null )&&(this.selectedExtensions != null)){ 365 structures.addAll(this.selectedExtensions); 366 } 367 368 return generate(links, structures); 369 } 370 371 /** 372 * this is called externally to generate a set of structures to a single ShEx file 373 * generally, it will be called with a single structure, or a long list of structures (all of them) 374 * 375 * @param links HTML link rendering policy 376 * @param structures list of structure definitions to render 377 * @return ShEx definition of structures 378 */ 379 public String generate(HTMLLinkPolicy links, List<StructureDefinition> structures) { 380 ST shex_def = tmplt(SHEX_TEMPLATE); 381 String start_cmd; 382 if(completeModel || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE)) 383 start_cmd = completeModel? tmplt(ALL_START_TEMPLATE).render() : 384 tmplt(START_TEMPLATE).add("id", structures.get(0).getId()).render(); 385 else 386 start_cmd = ""; 387 388 shex_def.add("header", 389 tmplt(HEADER_TEMPLATE). 390 add("fhir", FHIR). 391 add("fhirvs", FHIR_VS).render()); 392 393 Collections.sort(structures, new SortById()); 394 StringBuilder shapeDefinitions = new StringBuilder(); 395 396 // For unknown reasons, the list of structures carries duplicates. 397 // We remove them. Also, it is possible for the same sd to have multiple hashes... 398 uniq_structures = new LinkedList<StructureDefinition>(); 399 uniq_structure_urls = new HashSet<String>(); 400 StringBuffer allStructures = new StringBuffer(""); 401 for (StructureDefinition sd : structures) { 402 // Exclusion Criteria... 403 if ((excludedSDUrls != null) && 404 (excludedSDUrls.contains(sd.getUrl()))) { 405 printBuildMessage("SKIPPED Generating ShEx for " + sd.getName() + " [ " + sd.getUrl() + " ] !"); 406 printBuildMessage("Reason: It is in excluded list of structures."); 407 continue; 408 } 409 410 if ("Extension".equals(sd.getType())) { 411 if ((!this.selectedExtensionUrls.isEmpty()) && (!this.selectedExtensionUrls.contains(sd.getUrl()))) { 412 printBuildMessage("SKIPPED Generating ShEx for " + sd.getName() + " [ " + sd.getUrl() + " ] !"); 413 printBuildMessage("Reason: It is NOT included in the list of selected extensions."); 414 continue; 415 } 416 417 if ((this.constraintPolicy == ConstraintTranslationPolicy.GENERIC_ONLY) && (sd.hasContext())) { 418 printBuildMessage("SKIPPED Generating ShEx for " + sd.getName() + " [ " + sd.getUrl() + " ] !"); 419 printBuildMessage("Reason: ConstraintTranslationPolicy is set to GENERIC_ONLY, and this Structure has Context of Use."); 420 continue; 421 } 422 423 if ((this.constraintPolicy == ConstraintTranslationPolicy.CONTEXT_OF_USE_ONLY) && (!sd.hasContext())) { 424 printBuildMessage("SKIPPED Generating ShEx for " + sd.getName() + " [ " + sd.getUrl() + " ] !"); 425 printBuildMessage("Reason: ConstraintTranslationPolicy is set to CONTEXT_OF_USE_ONLY, and this Structure has no Context of Use."); 426 continue; 427 } 428 } 429 430 if (!uniq_structure_urls.contains(sd.getUrl())) { 431 uniq_structures.add(sd); 432 uniq_structure_urls.add(sd.getUrl()); 433 } 434 } 435 436 for (StructureDefinition sd : uniq_structures) { 437 printBuildMessage(" ---- Generating ShEx for : " + sd.getName() + " [ " + sd.getUrl() + " ] ..."); 438 String shapeDefinitionStr = genShapeDefinition(sd, true); 439 440 if (!shapeDefinitionStr.isEmpty()) { 441 shapeDefinitions.append(shapeDefinitionStr); 442 } else { 443 printBuildMessage(" ---- WARNING! EMPTY/No ShEx SCHEMA Body generated for : " + sd.getName() + " [ " + sd.getUrl() + " ].\n" + 444 "This might not be an issue, if this resource is normative base or a meta resource"); 445 shapeDefinitions.append("<" + sd.getName() + "> CLOSED {\n}"); 446 } 447 } 448 shapeDefinitions.append(emitInnerTypes()); 449 450 // If data types are to be put in the same file 451 if (doDatatypes) { 452 shapeDefinitions.append("\n#---------------------- Data Types -------------------\n"); 453 while (emittedDatatypes.size() < datatypes.size() || 454 emittedInnerTypes.size() < innerTypes.size()) { 455 shapeDefinitions.append(emitDataTypes()); 456 // As process data types, it may introduce some more inner types, so we repeat the call here. 457 shapeDefinitions.append(emitInnerTypes()); 458 } 459 } 460 461 if (oneOrMoreTypes.size() > 0) { 462 shapeDefinitions.append("\n#---------------------- Cardinality Types (OneOrMore) -------------------\n"); 463 oneOrMoreTypes.forEach((String oomType) -> { 464 shapeDefinitions.append(getOneOrMoreType(oomType)); 465 }); 466 } 467 468 if (references.size() > 0) { 469 shapeDefinitions.append("\n#---------------------- Reference Types -------------------\n"); 470 for (String r : references) { 471 shapeDefinitions.append("\n").append(tmplt(TYPED_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n"); 472 if (!"Resource".equals(r) && !known_resources.contains(r)) 473 shapeDefinitions.append("\n").append(tmplt(TARGET_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n"); 474 } 475 } 476 477 if (completeModel && known_resources.size() > 0) { 478 shapeDefinitions.append("\n").append(tmplt(COMPLETE_RESOURCE_TEMPLATE) 479 .add("resources", StringUtils.join(known_resources, "> OR\n\t@<")).render()); 480 List<String> all_entries = new ArrayList<String>(); 481 for (String kr : known_resources) 482 all_entries.add(tmplt(ALL_ENTRY_TEMPLATE).add("id", kr).render()); 483 shapeDefinitions.append("\n").append(tmplt(ALL_TEMPLATE) 484 .add("all_entries", StringUtils.join(all_entries, " OR\n\t")).render()); 485 } 486 487 if (required_value_sets.size() > 0) { 488 shapeDefinitions.append("\n#---------------------- Value Sets ------------------------\n"); 489 List<String> sortedVS = new ArrayList<String>(); 490 for (ValueSet vs : required_value_sets) 491 sortedVS.add(genValueSet(vs)); 492 493 Collections.sort(sortedVS, new Comparator<String>() { 494 @Override 495 public int compare(String o1, String o2) { 496 try { 497 return StringUtils.substringBetween(o1, "fhirvs:", " ") 498 .compareTo(StringUtils.substringBetween(o2, "fhirvs:", " ")); 499 } 500 catch(Exception e){ 501 debug("SORT COMPARISON FAILED BETWEEN \n\t\t" + o1 + "\n\t\t and \n\t\t" + o2); 502 debug(e.getMessage()); 503 return 0; 504 } 505 } 506 }); 507 508 for (String svs : sortedVS) 509 shapeDefinitions.append("\n").append(svs); 510 } 511 512 if ((unMappedFunctions != null) && (!unMappedFunctions.isEmpty())) { 513 debug("------------------------- Unmapped Functions ---------------------"); 514 for (String um : unMappedFunctions) { 515 debug(um); 516 } 517 } 518 519 allStructures.append(shapeDefinitions + "\n"); 520 521 StringBuffer allImports = new StringBuffer(""); 522 if (!imports.isEmpty()) { 523 uniq_structures.forEach((StructureDefinition sdstruct) -> { 524 imports.removeIf(s -> s.contains(sdstruct.getName())); 525 }); 526 527 imports.sort(Comparator.comparingInt(String::length)); 528 imports.forEach((String imp) -> { 529 ST import_def = tmplt(IMPORT_TEMPLATE); 530 import_def.add("import", imp); 531 import_def.add("fileExt", ".shex"); 532 allImports.append(import_def.render()); 533 }); 534 } 535 536 allImports.append(start_cmd); 537 shex_def.add("imports", allImports); 538 shex_def.add("shapeDefinitions", allStructures.toString()); 539 540 return shex_def.render(); 541 } 542 543 private String getBaseTypeName(StructureDefinition sd){ 544 if (sd == null) 545 return null; 546 547 String bd = null; 548 if (sd.hasBaseDefinition()) { 549 bd = sd.getBaseDefinition(); 550 String[] els = bd.split("/"); 551 bd = els[els.length - 1]; 552 } 553 554 return bd; 555 } 556 557 private String getBaseTypeName(ElementDefinition ed){ 558 if (ed == null) 559 return null; 560 561 return (ed.getType().size() > 0)? (ed.getType().get(0).getCode()) : null; 562 } 563 564 private String getExtendedType(StructureDefinition sd){ 565 String bd = getBaseTypeName(sd); 566 String sId = sd.getId(); 567 568 if (bd!=null) { 569 addImport("<" + bd + ">"); 570 sId += "> EXTENDS @<" + bd; 571 } 572 573 return sId; 574 } 575 576 private String getExtendedType(ElementDefinition ed){ 577 String bd = getBaseTypeName(ed); 578 //if (bd != null && !baseDataTypes.contains(bd)) { 579 if (bd!=null) { 580 addImport("<" + bd + ">"); 581 bd = "> EXTENDS @<" + bd; 582 } 583 return bd; 584 } 585 586 /** 587 * Emit a ShEx definition for the supplied StructureDefinition 588 * @param sd Structure definition to emit 589 * @param top_level True means outermost type, False means recursively called 590 * @return ShEx definition 591 */ 592 private String genShapeDefinition(StructureDefinition sd, boolean top_level) { 593 // xhtml is treated as an atom 594 if("xhtml".equals(sd.getName()) || (completeModel && "Resource".equals(sd.getName()))) 595 return ""; 596 597 ST shape_defn; 598 // Resources are either incomplete items or consist of everything that is defined as a resource (completeModel) 599 // if (sd.getName().equals("ActivityDefinition")){ 600 // debug("ActivityDefinition found"); 601 // } 602 if("Resource".equals(sd.getName())) { 603 shape_defn = tmplt(RESOURCE_SHAPE_TEMPLATE); 604 known_resources.add(sd.getName()); 605 } else { 606 shape_defn = tmplt(SHAPE_DEFINITION_TEMPLATE).add("id", getExtendedType(sd)); 607 known_resources.add(sd.getName()); 608 609 if (baseDataTypes.contains(sd.getType())) { 610 shape_defn.add("resourceDecl", "\n"); 611 } else { 612 if ("Element".equals(sd.getName())) 613 shape_defn.add("resourceDecl", tmplt(ROOT_TEMPLATE).render()); 614 else { 615 String rootTmpl = tmplt(ROOT_TEMPLATE).render(); 616 String btn = getBaseTypeName(sd); 617 if ((baseDataTypes.contains(btn))|| 618 (shortIdException.contains(btn))) 619 rootTmpl = "\n"; 620 621 ST resource_decl = tmplt(RESOURCE_DECL_TEMPLATE). 622 add("id", sd.getId()). 623 add("root", rootTmpl); 624 625 shape_defn.add("resourceDecl", resource_decl.render()); 626 } 627 } 628 } 629 shape_defn.add("fhirType", " "); 630 631 // Generate the defining elements 632 List<String> elements = new ArrayList<String>(); 633 634 // Add the additional entries for special types 635 String sdn = sd.getName(); 636 if (sdn.equals("Coding")) 637 elements.add(tmplt(CONCEPT_REFERENCE_TEMPLATE).render()); 638 else if (sdn.equals("CodeableConcept")) 639 elements.add(tmplt(CONCEPT_REFERENCES_TEMPLATE).render()); 640 else if (sdn.equals("Reference")) 641 elements.add(tmplt(RESOURCE_LINK_TEMPLATE).render()); 642 643 String root_comment = null; 644 645 constraintsList.clear(); 646 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 647 if(!ed.getPath().contains(".")) 648 root_comment = ed.getShort(); 649 else if ( 650 (StringUtils.countMatches(ed.getPath(), ".") == 1 && !"0".equals(ed.getMax())) 651 && (ed.hasBase() 652 && ( 653 ed.getBase().getPath().startsWith(sdn) 654 || (ed.getBase().getPath().startsWith("Extension")) 655 || (ed.getBase().getPath().startsWith("Element.extension")&&(ed.hasSliceName())) 656 ) 657 ) 658 ){ 659 String elementDefinition = genElementDefinition(sd, ed); 660 661 boolean isInnerType = false; 662 if (isInInnerTypes(ed)){ 663 //debug("This element is already in innerTypes:" + ed.getPath()); 664 isInnerType = true; 665 } 666 667 if (processConstraints) { 668 // Process constraints 669 for (ElementDefinition.ElementDefinitionConstraintComponent constraint : ed.getConstraint()) { 670 String sdType = sd.getType(); 671 String cstype = constraint.getSource(); 672 if ((!cstype.isEmpty()) && (cstype.indexOf("/") != -1)) { 673 String[] els = cstype.split("/"); 674 cstype = els[els.length - 1]; 675 } 676 677 String id = ed.hasBase() ? ed.getBase().getPath() : ed.getPath(); 678 String shortId = id.substring(id.lastIndexOf(".") + 1); 679 if ((ed.hasContentReference() && (!ed.hasType())) || (id.equals(sd.getName() + "." + shortId))) { 680 if ((sdType.equals(cstype)) || baseDataTypes.contains(sdType)) { 681 if (!isInnerType) { 682 debug("\n Key: " + constraint.getKey() + " SD type: " + sd.getType() + " Element: " + ed.getPath() + " Constraint Source: " + constraint.getSource() + " Constraint:" + constraint.getExpression()); 683 String transl = translateConstraint(sd, ed, constraint); 684 if (transl.isEmpty() || constraintsList.contains(transl)) 685 continue; 686 constraintsList.add(transl); 687 } 688 } 689 } 690 } 691 } 692 elements.add(elementDefinition); 693 } 694 else { 695 List<ElementDefinition> children = profileUtilities.getChildList(sd, ed); 696 if (children.size() > 0) { 697 for (ElementDefinition child : children) { 698 if (child.getPath().startsWith(ed.getPath())) 699 innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed)); 700 } 701 } 702 } 703 } 704 705 if (processConstraints) { 706 // Constraints for differential to cover constraints on SD itself without any elements of its own 707 for (ElementDefinition ded : sd.getDifferential().getElement()) { 708 // Process constraints 709 for (ElementDefinition.ElementDefinitionConstraintComponent dconstraint : ded.getConstraint()) { 710 String sdType = sd.getType(); 711 712 String id = ded.hasBase() ? ded.getBase().getPath() : ded.getPath(); 713 String shortId = id.substring(id.lastIndexOf(".") + 1); 714 715 if (!isInInnerTypes(ded)) { 716 debug("\n Key: " + dconstraint.getKey() + " SD type: " + sd.getType() + " Element: " + ded.getPath() + " Constraint Source: " + dconstraint.getSource() + " Constraint:" + dconstraint.getExpression()); 717 String dtransl = translateConstraint(sd, ded, dconstraint); 718 if (dtransl.isEmpty() || constraintsList.contains(dtransl)) 719 continue; 720 constraintsList.add(dtransl); 721 } 722 } 723 } 724 } 725 726 shape_defn.add("elements", StringUtils.join(elements, "\n")); 727 shape_defn.add("comment", root_comment == null? " " : "# " + root_comment); 728 729 String constraintStr = ""; 730 731 if (!constraintsList.isEmpty()) { 732 constraintStr = "AND (\n\n" + StringUtils.join(constraintsList, "\n\n) AND (\n\n") + "\n\n)\n"; 733 } 734 735 shape_defn.add("constraints", constraintStr); 736 737 String contextOfUseStr = ""; 738 ArrayList<String> contextOfUse = new ArrayList<String>(); 739 if (!sd.getContext().isEmpty()) { 740 for (StructureDefinition.StructureDefinitionContextComponent uc : sd.getContext()) { 741 if (!uc.getExpression().isEmpty()) { 742 String toStore = uc.getExpression(); 743 debug("CONTEXT-OF-USE FOUND: " + toStore); 744 if (toStore.indexOf("http") != -1) { 745 debug("\t\tWARNING: CONTEXT-OF-USE SKIPPED as it has 'http' in it, might be a URL, instead of '.' delimited string"); 746 continue; // some erroneous context of use may use a URL; ignore them 747 } 748 String[] backRefs = toStore.split("\\."); 749 toStore = "a [fhir:" + backRefs[0] + "]"; 750 for (int i = 1; i < backRefs.length; i++) 751 toStore = "^fhir:" + backRefs[i] + " {" + toStore + "}"; 752 753 if (!contextOfUse.contains(toStore)) { 754 contextOfUse.add(toStore); 755 } 756 } 757 } 758 759 if (!contextOfUse.isEmpty()) { 760 if (contextOfUse.size() > 1) 761 contextOfUseStr = "^fhir:extension { " + StringUtils.join(contextOfUse, "} OR \n {") + "}\n"; 762 else 763 contextOfUseStr = "^fhir:extension { " + contextOfUse.get(0) + "}\n"; 764 } 765 } 766 767 shape_defn.add("contextOfUse", contextOfUseStr); 768 769 return shape_defn.render(); 770 } 771 772 /** 773 * @param ed 774 * @param constraint 775 * @return 776 */ 777 private String translateConstraint(StructureDefinition sd, ElementDefinition ed, ElementDefinition.ElementDefinitionConstraintComponent constraint){ 778 String translated = ""; 779 780 if (constraint != null) { 781 //if (false) { 782 String ce = constraint.getExpression(); 783 String constItem = "FHIR-SD-Path:" + ed.getPath() + " Expression: " + ce; 784 try { 785 translated = "# Constraint UniqueKey:" + constraint.getKey() + "\n# Human readable:" + constraint.getHuman() + "\n\n# Constraint: " + constraint.getExpression() + "\n# ShEx:\n"; 786 787 ExpressionNode expr = fpe.parse(ce); 788 String shexConstraint = processExpressionNode(sd, ed, expr, false, 0); 789 shexConstraint = shexConstraint.replaceAll("CALLER", ""); 790 debug(" Parsed to ShEx Constraint:" + shexConstraint); 791 if (!shexConstraint.isEmpty()) 792 translated += "\n" + shexConstraint; 793 794 debug(" TRANSLATED\t"+ed.getPath()+"\t"+constraint.getHuman()+"\t"+constraint.getExpression()+"\t"+shexConstraint); 795 796 } catch (Exception e) { 797 //String message = " FAILED to parse the constraint from Structure Definition: " + constItem + " [ " + e.getMessage() + " ]"; 798 String message = " FAILED to parse the constraint from Structure Definition: " + constItem; 799 e.printStackTrace(); 800 801 translated = ""; 802 debug(message); 803 } 804 } 805 return commentUnmapped(translated); 806 } 807 808 /** 809 * @param node 810 * @param quote 811 * @return 812 */ 813 private String processExpressionNode(StructureDefinition sd, ElementDefinition ed, ExpressionNode node, boolean quote, int depth) { 814 if (node == null) 815 return ""; 816 boolean toQuote = quote; 817 818 String innerShEx = ""; 819 820 if (node.getInner() != null){ 821 innerShEx = processExpressionNode(sd, ed, node.getInner(), quote, depth + 1); 822 } 823 824 String translatedShEx = ""; 825 826 boolean treatBothOpsSame = false; 827 // Figure out if there are any operations defined on this node 828 String ops = ""; 829 String endOps = ""; 830 if (node.getOperation() != null) { 831 String opName = node.getOperation().name(); 832 switch (opName) { 833 case "Or": 834 ops = " OR "; 835 break; 836 case "Union": 837 //ops = " | "; 838 ops = " "; 839 break; 840 case "In" : 841 case "Equals": 842 case "Contains": 843 if (!node.getOpNext().getKind().equals(ExpressionNode.Kind.Name)) { 844 ops = " { fhir:v ["; 845 endOps = "] } "; 846 toQuote = true; 847 } else { 848 String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind(); 849 addUnmappedFunction(opName); 850 ops = TBD(opName); 851 } 852 break; 853 case "NotEquals": 854 if (!node.getOpNext().getKind().equals(ExpressionNode.Kind.Name)) { 855 ops = " [fhir:v . -"; 856 endOps = "] "; 857 toQuote = true; 858 } else { 859 String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind(); 860 addUnmappedFunction(opName); 861 ops = TBD(opName); 862 } 863 break; 864 case "Greater": 865 if (node.getOpNext().getKind().equals(ExpressionNode.Kind.Constant)) { 866 ops = " { fhir:v MinExclusive "; 867 endOps = " } "; 868 } else { 869 String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind(); 870 addUnmappedFunction(opName); 871 ops = TBD(opName); 872 } 873 break; 874 case "GreaterOrEqual": 875 if (node.getOpNext().getKind().equals(ExpressionNode.Kind.Constant)) { 876 ops = " { fhir:v MinInclusive "; 877 endOps = " } "; 878 } else { 879 String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind(); 880 addUnmappedFunction(opName); 881 ops = TBD(opName); 882 } 883 break; 884 case "Less": 885 case "LessThan": 886 if (node.getOpNext().getKind().equals(ExpressionNode.Kind.Constant)) { 887 ops = " { fhir:v MaxExclusive "; 888 endOps = " } "; 889 } else { 890 String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind(); 891 addUnmappedFunction(opName); 892 ops = TBD(opName); 893 } 894 break; 895 case "LessOrEqual": 896 if (node.getOpNext().getKind().equals(ExpressionNode.Kind.Constant)) { 897 ops = " { fhir:v MaxInclusive "; 898 endOps = " } "; 899 } else { 900 String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind(); 901 addUnmappedFunction(opName); 902 ops = TBD(opName); 903 } 904 break; 905 case "And": 906 //case "Implies" : 907 ops = " AND "; 908 break; 909 case "As": 910 case "Is": 911 ops = " a "; 912 break; 913 //case "Xor": 914 //ops = " XOR "; // Although I implemented a routine for XOR, but that needs more testing. 915 // break; 916 default: 917 String toStore = "UNMAPPED_OPERATOR_" + opName + " in Node type: " + node.getKind(); 918 if (!unMappedFunctions.contains(toStore)) 919 unMappedFunctions.add(toStore); 920 921 ops = TBD(opName); 922 } 923 } 924 925 // Functions 926 String fExp = ""; 927 String pExp = ""; 928 boolean isFunctionCall = false; 929 ExpressionNode.Kind kind = node.getKind(); 930 if (kind == ExpressionNode.Kind.Function) { 931 String funcName = node.getName(); 932 if (!mappedFunctions.contains(funcName)) { 933 if (node.parameterCount() == 0) { 934 if ("not".equals(funcName)) 935 fExp = " NOT { CALLER }"; 936 else { 937 fExp = " " + TBD(funcName) + "( CALLER )"; 938 addUnmappedFunction(node.getFunction().toCode()); 939 String toStore = addUnmappedFunction(node.getFunction().toCode()); 940 } 941 } 942 } 943 944 if ("".equals(fExp)) { 945 switch (funcName) { 946 case "empty": 947 //fExp = " .?"; 948 fExp = " NOT { CALLER {fhir:v .} } " ; 949 break; 950 case "exists": 951 case "hasValue": 952 fExp = " ."; 953 break; 954 case "matches": 955 ops = " { fhir:v /"; 956 endOps = "/ } "; 957 break; 958 //case "where": // 'where' just states an assertion 959 // ops = "{ "; 960 // endOps = " }"; 961 // break; 962 case "contains": 963 ops = " { fhir:v ["; 964 endOps = "] } "; 965 toQuote = true; 966 break; 967 //case "toString": // skip this function call because values gets stringitize anyway 968 // pExp = ""; 969 // break; 970 case "is": 971 ops = " { a ["; 972 endOps = "] } "; 973 break; 974 default: 975 fExp = TBD(node.getFunction().toCode()); 976 String toStore = addUnmappedFunction(node.getFunction().toCode()); 977 } 978 979 if (node.parameterCount() > 0) { 980 for (ExpressionNode pen : node.getParameters()) { 981 if (!"".equals(pExp)) 982 pExp += ", "; 983 pExp += processExpressionNode(sd, ed, pen, quote, depth); 984 isFunctionCall = true; 985 } 986 } 987 } 988 989 if (isFunctionCall) { 990 if (!mappedFunctions.contains(funcName)) 991 translatedShEx += fExp + "(" + pExp + ")"; 992 else { 993 translatedShEx += ops + pExp + endOps; 994 ops = ""; 995 endOps = ""; 996 } 997 } 998 else 999 translatedShEx += fExp; 1000 1001 translatedShEx = positionParts(innerShEx, translatedShEx, 1002 getNextOps(ops , processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame), 1003 depth, false); 1004 1005 } else if (kind == ExpressionNode.Kind.Name) { 1006 String mT = (depth == 0)? "fhir:" + node.getName() : node.getName(); 1007 translatedShEx += positionParts(innerShEx, mT, 1008 getNextOps(ops, processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame), 1009 depth, true); 1010 //if (depth == 0) 1011 // translatedShEx = commentUnmapped(translatedShEx); 1012 }else if (kind == ExpressionNode.Kind.Group) { 1013 translatedShEx += positionParts(innerShEx, processExpressionNode(sd, ed, node.getGroup(), toQuote, depth), 1014 getNextOps(ops , processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame), 1015 depth, true); 1016 //if (depth == 0) 1017 // translatedShEx = commentUnmapped(translatedShEx); 1018 } else if (kind == ExpressionNode.Kind.Constant) { 1019 Base constantB = node.getConstant(); 1020 boolean toQt = (constantB instanceof StringType) || (!constantB.isPrimitive()); 1021 String constantV = constantB.primitiveValue(); 1022 1023 if (constantV.startsWith("%")) { 1024 try { 1025 // Evaluate the expression, this resolves unknowns in the value. 1026 List<Base> evaluated = fpe.evaluate(null, sd, sd, ed, node); 1027 1028 if (!evaluated.isEmpty()) 1029 constantV = evaluated.get(0).primitiveValue(); 1030 } 1031 catch (Exception e) { 1032 debug("Failed to evaluate constant expression: " + constantV); 1033 } 1034 } 1035 1036 translatedShEx += positionParts(innerShEx, quoteThis(constantV, toQt), 1037 getNextOps(ops , processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame), 1038 depth, false); 1039 //translatedShEx += positionParts(innerShEx, node.getConstant().primitiveValue(), ops + processExpressionNode(node.getOpNext(), toQuote, 0) + endOps, depth); 1040 } else if (kind == ExpressionNode.Kind.Unary) { 1041 translatedShEx += positionParts(innerShEx, node.getName(), 1042 getNextOps(ops,processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame), 1043 depth, false); 1044 } else { 1045 translatedShEx += positionParts(innerShEx, node.toString(), 1046 getNextOps(ops, processExpressionNode(sd, ed, node.getOpNext(), toQuote, depth), endOps, treatBothOpsSame), 1047 depth, false); 1048 } 1049 1050 return translatedShEx; 1051 } 1052 1053 private String commentUnmapped(String text) { 1054 1055 String pre = ""; 1056 String token = "NNNNN"; 1057 String temp = text; 1058 if ((text != null)&&(text.indexOf("SHEX_") != -1)) { 1059 pre = "\n# This constraint does not have mapping to a ShEx construct yet."; 1060 temp = text.replaceAll("\n", token); 1061 while (temp.indexOf("SHEX_") != -1) { 1062 pre += "\n# Unmapped construct found: " + StringUtils.substringBetween(temp, "SHEX_", "_SHEX"); 1063 temp = temp.replaceFirst("SHEX_", " ").replaceFirst("_SHEX", " "); 1064 } 1065 1066 pre += "\n# "; 1067 } 1068 1069 if (temp.indexOf(token) != -1) { 1070 temp = "#" + temp; 1071 temp = temp.replaceAll(token, "\n#"); 1072 temp = temp.replaceAll("##", "#"); 1073 temp = temp.replaceAll("# #", "#"); 1074 temp = temp.replaceAll("# #", "#"); 1075 temp += "\n{}"; 1076 } 1077 1078 return pre + temp; 1079 } 1080 1081 private String addUnmappedFunction(String func) { 1082 String toStore = "UNMAPPED_FUNCTION_" + func; 1083 if (!unMappedFunctions.contains(toStore)) 1084 unMappedFunctions.add(toStore); 1085 1086 return toStore; 1087 } 1088 1089 private String getNextOps(String startOp, String opNext, String endOp, boolean treatBothOps){ 1090 if (treatBothOps) 1091 return startOp + opNext + " " + endOp + opNext; 1092 1093 return startOp + opNext + endOp; 1094 } 1095 1096 private String positionParts(String funCall, String mainTxt, String nextText, int depth, boolean complete){ 1097 if (funCall.indexOf("CALLER") != -1) { 1098 if (depth == 0) { 1099 String toReturn = funCall.replaceFirst("CALLER", mainTxt); 1100 if (complete) 1101 toReturn = toReturn ; 1102 1103 toReturn = postProcessing(toReturn, nextText); 1104 return toReturn.replaceAll("CALLER", ""); 1105 } 1106 else{ 1107 String mT = (mainTxt != null) ? mainTxt.trim() : ""; 1108 String dR = (mT.startsWith(".") || mT.startsWith("{") || mT.startsWith("[")) ? "" : "."; 1109 return postProcessing(funCall.replaceFirst("CALLER", Matcher.quoteReplacement("CALLER" + dR + mT )), nextText) ; 1110 } 1111 } 1112 1113 String fc = funCall; 1114 if (fc.startsWith("fhir:")) 1115 fc = "." + fc.substring("fhir:".length()); 1116 1117 if ((depth == 0)&&(complete)) { 1118 if (mainTxt.startsWith("fhir:")) { 1119 if ("".equals(funCall)) { 1120 return "{ " + postProcessing(mainTxt, nextText) + " }"; 1121 } 1122 1123 if ((fc!= null)&&(!fc.isEmpty())) { 1124 String fT = fc.trim(); 1125 String dR = (fT.startsWith(".") || fT.startsWith("{") || fT.startsWith("[")) ? "" : "."; 1126 fc = dR + fc; 1127 } 1128 1129 return "{" + postProcessing(mainTxt + fc, nextText) + "}"; 1130 } 1131 1132 if (mainTxt.startsWith("'")) 1133 mainTxt = mainTxt; 1134 else 1135 mainTxt = "(" + mainTxt + ")"; 1136 1137 if ("".equals(funCall)) { 1138 return postProcessing(mainTxt, nextText); 1139 } 1140 } 1141 1142 if ((fc!= null)&&(!fc.isEmpty())) { 1143 String fT = fc.trim(); 1144 String dR = (fT.startsWith(".") || fT.startsWith("{") || fT.startsWith("[")) ? "" : "."; 1145 fc = dR + fc; 1146 } 1147 return postProcessing(mainTxt + fc, nextText); 1148 } 1149 1150 private String postProcessing(String p, String q){ 1151 String qp = q; 1152 if ((q != null)&&(q.trim().startsWith("XOR"))){ 1153 qp = q.split("XOR")[1]; 1154 1155 // because p xor q = ( p and not q) OR (not p and q) 1156 //return "(" + p + " AND NOT " + qp + ") OR ( NOT " + p + " AND " + qp + ")"; 1157 return "{" + p + "} AND NOT {" + qp + "} OR { NOT {" + p + "} AND " + qp + "} "; 1158 } 1159 1160 return p + qp; 1161 } 1162 1163 /** 1164 * @param str 1165 * @return 1166 */ 1167 private String TBD(String str){ 1168 return " SHEX_" + str + "_SHEX "; 1169 } 1170 1171 /** 1172 * @param str 1173 * @param quote 1174 * @return 1175 */ 1176 private String quoteThis(String str, boolean quote){ 1177 1178 if (quote) 1179 return "'" + str + "'"; 1180 1181 return str; 1182 } 1183 1184 /** 1185 * Generate a flattened definition for the inner types 1186 * @return stringified inner type definitions 1187 */ 1188 private String emitInnerTypes() { 1189 StringBuilder itDefs = new StringBuilder(); 1190 while(emittedInnerTypes.size() < innerTypes.size()) { 1191 for (Pair<StructureDefinition, ElementDefinition> it : new HashSet<Pair<StructureDefinition, ElementDefinition>>(innerTypes)) { 1192 if ((!emittedInnerTypes.contains(it)) 1193 // && (it.getRight().hasBase() && it.getRight().getBase().getPath().startsWith(it.getLeft().getName())) 1194 ){ 1195 itDefs.append("\n").append(genInnerTypeDef(it.getLeft(), it.getRight())); 1196 emittedInnerTypes.add(it); 1197 } 1198 } 1199 } 1200 return itDefs.toString(); 1201 } 1202 1203 /** 1204 * @param ed 1205 * @return 1206 */ 1207 private boolean isInInnerTypes(ElementDefinition ed) { 1208 1209 if (this.innerTypes.isEmpty()) 1210 return false; 1211 1212 for (Iterator<Pair<StructureDefinition, ElementDefinition>> itr = this.innerTypes.iterator(); itr.hasNext(); ) 1213 if (itr.next().getRight() == ed) 1214 return true; 1215 1216 return false; 1217 } 1218 1219 /** 1220 * Generate a shape definition for the current set of datatypes 1221 * @return stringified data type definitions 1222 */ 1223 private String emitDataTypes() { 1224 StringBuilder dtDefs = new StringBuilder(); 1225 while (emittedDatatypes.size() < datatypes.size()) { 1226 for (String dt : new HashSet<String>(datatypes)) { 1227 if (!emittedDatatypes.contains(dt)) { 1228 StructureDefinition sd = context.fetchResource(StructureDefinition.class, 1229 ProfileUtilities.sdNs(dt, null)); 1230 // TODO: Figure out why the line below doesn't work 1231 // if (sd != null && !uniq_structures.contains(sd)) 1232 if(sd != null && !uniq_structure_urls.contains(sd.getUrl())) 1233 dtDefs.append("\n").append(genShapeDefinition(sd, false)); 1234 emittedDatatypes.add(dt); 1235 } 1236 } 1237 } 1238 return dtDefs.toString(); 1239 } 1240 1241 /** 1242 * @param text 1243 * @param max_col 1244 * @return 1245 */ 1246 private ArrayList<String> split_text(String text, int max_col) { 1247 int pos = 0; 1248 ArrayList<String> rval = new ArrayList<String>(); 1249 if (text.length() <= max_col) { 1250 rval.add(text); 1251 } else { 1252 String[] words = text.split(" "); 1253 int word_idx = 0; 1254 while(word_idx < words.length) { 1255 StringBuilder accum = new StringBuilder(); 1256 while (word_idx < words.length && accum.length() + words[word_idx].length() < max_col) 1257 accum.append(words[word_idx++] + " "); 1258 if (accum.length() == 0) { 1259 accum.append(words[word_idx].substring(0, max_col - 3) + "-"); 1260 words[word_idx] = words[word_idx].substring(max_col - 3); 1261 } 1262 rval.add(accum.toString()); 1263 accum = new StringBuilder(); 1264 } 1265 } 1266 return rval; 1267 } 1268 1269 /** 1270 * @param tmplt 1271 * @param ed 1272 */ 1273 private void addComment(ST tmplt, ElementDefinition ed) { 1274 if(withComments && ed.hasShort() && !ed.getId().startsWith("Extension.")) { 1275 int nspaces; 1276 char[] sep; 1277 nspaces = Integer.max(COMMENT_COL - tmplt.add("comment", "#").render().indexOf('#'), MIN_COMMENT_SEP); 1278 tmplt.remove("comment"); 1279 sep = new char[nspaces]; 1280 Arrays.fill(sep, ' '); 1281 ArrayList<String> comment_lines = split_text(ed.getShort().replace("\n", " "), MAX_CHARS); 1282 StringBuilder comment = new StringBuilder("# "); 1283 char[] indent = new char[COMMENT_COL]; 1284 Arrays.fill(indent, ' '); 1285 for(int i = 0; i < comment_lines.size();) { 1286 comment.append(comment_lines.get(i++)); 1287 if(i < comment_lines.size()) 1288 comment.append("\n" + new String(indent) + "# "); 1289 } 1290 tmplt.add("comment", new String(sep) + comment.toString()); 1291 } else { 1292 tmplt.add("comment", " "); 1293 } 1294 } 1295 1296 1297 /** 1298 * Generate a ShEx element definition 1299 * @param sd Containing structure definition 1300 * @param ed Containing element definition 1301 * @return ShEx definition 1302 */ 1303 private String genElementDefinition(StructureDefinition sd, 1304 ElementDefinition ed) { 1305 String id = ed.hasBase() ? ed.getBase().getPath() : ed.getPath(); 1306 1307 String shortId = id; 1308 String typ = id; 1309 1310 if (ed.getType().size() > 0) 1311 typ = ed.getType().get(0).getCode(); 1312 1313 if (id.equals("Element.extension") && ed.hasSliceName()) { 1314 shortId = ed.getSliceName(); 1315 } 1316 else 1317 shortId = id.substring(id.lastIndexOf(".") + 1); 1318 1319 if ((ed.getType().size() > 0) && 1320 (ed.getType().get(0).getCode().startsWith(Constants.NS_SYSTEM_TYPE))) { 1321 1322 if (changeShortName(sd, ed)) { 1323 debug("VALUE NAME CHANGED to v from " + shortId + " for " + sd.getName() + ":" + ed.getPath()); 1324 shortId = "v"; 1325 } 1326 1327 typ = ed.getType().get(0).getWorkingCode(); 1328 } 1329 1330 String defn = ""; 1331 ST element_def; 1332 String card = ("*".equals(ed.getMax()) ? (ed.getMin() == 0 ? "*" : "+") : (ed.getMin() == 0 ? "?" : "")) + ";"; 1333 1334 element_def = tmplt(ELEMENT_TEMPLATE); 1335 if (id.endsWith("[x]")) { 1336 element_def.add("id", "fhir:" + shortId.replace("[x]", "")); 1337 } else { 1338 element_def.add("id", "fhir:" + shortId + " "); 1339 } 1340 1341 List<ElementDefinition> children = profileUtilities.getChildList(sd, ed); 1342 if (children.size() > 0) { 1343 String parentPath = sd.getName(); 1344 if ((ed.hasContentReference() && (!ed.hasType())) || (!id.equals(parentPath + "." + shortId))) { 1345 //debug("Not Adding innerType:" + id + " to " + sd.getName()); 1346 } else 1347 innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed)); 1348 } 1349 1350 if ("BackboneElement".equals(typ)) 1351 typ = id; 1352 1353 defn = simpleElement(sd, ed, typ); 1354 1355 String refChoices = ""; 1356 1357 if (id.endsWith("[x]")) { 1358 //defn = " (" + genChoiceTypes(sd, ed, shortId) + ")"; 1359 defn = " " + genChoiceTypes(sd, ed, shortId) + " "; 1360 //defn += " AND { rdf:type IRI } "; 1361 } else { 1362 if (ed.getType().size() == 1) { 1363 // Single entry 1364 if ((defn.isEmpty())||(typ.equals(sd.getName()))) 1365 defn = genTypeRef(sd, ed, id, ed.getType().get(0)); 1366 } else if (ed.getContentReference() != null) { 1367 // Reference to another element 1368 String ref = ed.getContentReference(); 1369 if (!ref.startsWith("#")) 1370 throw new AssertionError("Not equipped to deal with absolute path references: " + ref); 1371 String refPath = null; 1372 for (ElementDefinition ed1 : sd.getSnapshot().getElement()) { 1373 if (ed1.getId() != null && ed1.getId().equals(ref.substring(1))) { 1374 refPath = ed1.getPath(); 1375 break; 1376 } 1377 } 1378 if (refPath == null) 1379 throw new AssertionError("Reference path not found: " + ref); 1380 1381 defn = simpleElement(sd, ed, refPath); 1382 } 1383 1384 1385 List<String> refValues = new ArrayList<String>(); 1386 if (ed.hasType() && (ed.getType().get(0).getWorkingCode().equals("Reference"))) { 1387 if (ed.getType().get(0).hasTargetProfile()) { 1388 1389 ed.getType().get(0).getTargetProfile().forEach((CanonicalType tps) -> { 1390 String els[] = tps.getValue().split("/"); 1391 refValues.add(els[els.length - 1]); 1392 }); 1393 } 1394 } 1395 1396 if (!refValues.isEmpty()) { 1397 Collections.sort(refValues); 1398 refChoices = StringUtils.join(refValues, "_OR_"); 1399 } 1400 } 1401 1402 // Adding OneOrMore as prefix to the reference type if cardinality is 1..* or 0..* 1403 if (card.startsWith("*") || card.startsWith("+")) { 1404 card = card.replace("+", ""); 1405 card = card.replace("*", "?"); 1406 defn = defn.replace("<", "<" + ONE_OR_MORE_PREFIX); 1407 1408 String defnToStore = defn; 1409 if (!refChoices.isEmpty()) { 1410 defnToStore = defn.replace(">", ONE_OR_MORE_CHOICES + refChoices + ">"); 1411 defn = defn.replace(">", "_" + refChoices + ">"); 1412 } 1413 1414 defnToStore = StringUtils.substringBetween(defnToStore, "<", ">"); 1415 if (!oneOrMoreTypes.contains(defnToStore)) 1416 oneOrMoreTypes.add(defnToStore); 1417 } else { 1418 if (!refChoices.isEmpty()) { 1419 defn += " AND {fhir:link \n\t\t\t@<" + 1420 refChoices.replaceAll("_OR_", "> OR \n\t\t\t@<") + "> ? }"; 1421 } 1422 } 1423 1424 element_def.add("defn", defn); 1425 addImport(defn); 1426 element_def.add("card", card); 1427 addComment(element_def, ed); 1428 1429 return element_def.render(); 1430 } 1431 1432 private boolean changeShortName(StructureDefinition sd, ElementDefinition ed){ 1433 return shortIdException.contains(sd.getName()); 1434 } 1435 1436 private void addImport(String typeDefn) { 1437 if ((typeDefn != null) && (!typeDefn.isEmpty())) { 1438 // String importType = StringUtils.substringBetween(typeDefn, "<", ">"); 1439 // if ((importType.indexOf(ONE_OR_MORE_PREFIX) == -1) && 1440 // (!imports.contains(importType))) 1441 // imports.add(importType); 1442 // } 1443 Pattern p = Pattern.compile("<([^\\s>/]+)"); 1444 Matcher m = p.matcher(typeDefn); 1445 while (m.find()) { 1446 String tag = m.group(1); 1447 //System.out.println("FOUND IMPORT: " + tag); 1448 if (tag.indexOf(ONE_OR_MORE_PREFIX) != -1) { 1449 tag = tag.substring(ONE_OR_MORE_PREFIX.length()); 1450 } 1451 1452 if ((tag.indexOf("_") != -1)||(tag.indexOf("_OR_") != -1)) 1453 continue; 1454 1455 if (!imports.contains(tag)) 1456 imports.add(tag); 1457 } 1458 } 1459 } 1460 private List<ElementDefinition> getChildren(StructureDefinition derived, ElementDefinition element) { 1461 List<ElementDefinition> elements = derived.getSnapshot().getElement(); 1462 int index = elements.indexOf(element) + 1; 1463 String path = element.getPath()+"."; 1464 List<ElementDefinition> list = new ArrayList<>(); 1465 while (index < elements.size()) { 1466 ElementDefinition e = elements.get(index); 1467 String p = e.getPath(); 1468 if (p.startsWith(path) && !e.hasSliceName()) { 1469 if (!p.substring(path.length()).contains(".")) { 1470 list.add(e); 1471 } 1472 index++; 1473 } else { 1474 break; 1475 } 1476 } 1477 return list; 1478 } 1479 1480 /** 1481 * Generate a type reference and optional value set definition 1482 * @param sd Containing StructureDefinition 1483 * @param ed Element being defined 1484 * @param typ Element type 1485 * @return Type definition 1486 */ 1487 private String simpleElement(StructureDefinition sd, ElementDefinition ed, String typ) { 1488 String addldef = ""; 1489 ElementDefinition.ElementDefinitionBindingComponent binding = ed.getBinding(); 1490 if(binding.hasStrength() && binding.getStrength() == Enumerations.BindingStrength.REQUIRED && "code".equals(typ)) { 1491 ValueSet vs = resolveBindingReference(sd, binding.getValueSet()); 1492 if (vs != null) { 1493 addldef = tmplt(VALUESET_DEFN_TEMPLATE).add("vsn", vsprefix(vs.getUrl())).render(); 1494 required_value_sets.add(vs); 1495 } 1496 } 1497 // TODO: check whether value sets and fixed are mutually exclusive 1498 if(ed.hasFixed()) { 1499 addldef = tmplt(FIXED_VALUE_TEMPLATE).add("val", ed.getFixed().primitiveValue()).render(); 1500 } 1501 return tmplt(SIMPLE_ELEMENT_DEFN_TEMPLATE).add("typ", typ).add("vsdef", addldef).render(); 1502 } 1503 1504 private String vsprefix(String uri) { 1505 if(uri.startsWith(FHIR_VS)) 1506 return "fhirvs:" + uri.replace(FHIR_VS, ""); 1507 return "<" + uri + ">"; 1508 } 1509 1510 /** 1511 * Generate a type reference 1512 * @param sd Containing structure definition 1513 * @param ed Containing element definition 1514 * @param id Element id 1515 * @param typ Element type 1516 * @return Type reference string 1517 */ 1518 private String genTypeRef(StructureDefinition sd, ElementDefinition ed, String id, ElementDefinition.TypeRefComponent typ) { 1519 1520 if(typ.hasProfile()) { 1521 if(typ.getWorkingCode().equals("Reference")) 1522 return genReference("", typ); 1523 else if(profileUtilities.getChildList(sd, ed).size() > 0) { 1524 // inline anonymous type - give it a name and factor it out 1525 innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed)); 1526 return simpleElement(sd, ed, id); 1527 } 1528 else { 1529 String ref = getTypeName(typ); 1530 datatypes.add(ref); 1531 return simpleElement(sd, ed, ref); 1532 } 1533 1534 } else if (typ.getCode().startsWith(Constants.NS_SYSTEM_TYPE)) { 1535 String xt = getShexCode(typ.getWorkingCode()); 1536 1537 // TODO: Remove the next line when the type of token gets switched to string 1538 // TODO: Add a rdf-type entry for valueInteger to xsd:integer (instead of int) 1539 ST td_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE).add("typ", xt); 1540 StringBuilder facets = new StringBuilder(); 1541 if(ed.hasMinValue()) { 1542 DataType mv = ed.getMinValue(); 1543 facets.append(tmplt(MINVALUE_TEMPLATE).add("val", mv.primitiveValue()).render()); 1544 } 1545 if(ed.hasMaxValue()) { 1546 DataType mv = ed.getMaxValue(); 1547 facets.append(tmplt(MAXVALUE_TEMPLATE).add("val", mv.primitiveValue()).render()); 1548 } 1549 if(ed.hasMaxLength()) { 1550 int ml = ed.getMaxLength(); 1551 facets.append(tmplt(MAXLENGTH_TEMPLATE).add("val", ml).render()); 1552 } 1553 if(ed.hasPattern()) { 1554 DataType pat = ed.getPattern(); 1555 facets.append(tmplt(PATTERN_TEMPLATE).add("val",pat.primitiveValue()).render()); 1556 } 1557 td_entry.add("facets", facets.toString()); 1558 return td_entry.render(); 1559 1560 } else if (typ.getWorkingCode() == null) { 1561 ST primitive_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE); 1562 primitive_entry.add("typ", "xsd:string"); 1563 return primitive_entry.render(); 1564 1565 } else if(typ.getWorkingCode().equals("xhtml")) { 1566 return tmplt(XHTML_TYPE_TEMPLATE).render(); 1567 } else { 1568 datatypes.add(typ.getWorkingCode()); 1569 return simpleElement(sd, ed, typ.getWorkingCode()); 1570 } 1571 } 1572 1573 /** 1574 * @param c 1575 * @return 1576 */ 1577 private String getShexCode(String c) { 1578 switch (c) { 1579 case "boolean" : 1580 return "xsd:boolean"; 1581 case "integer" : 1582 return "xsd:int"; 1583 case "integer64" : 1584 return "xsd:long"; 1585 case "decimal" : 1586 return "xsd:decimal OR xsd:double"; 1587 case "base64Binary" : 1588 return "xsd:base64Binary"; 1589 case "instant" : 1590 return "xsd:dateTime"; 1591 case "string" : 1592 return "xsd:string"; 1593 case "uri" : 1594 return "xsd:anyURI"; 1595 case "date" : 1596 return "xsd:gYear OR xsd:gYearMonth OR xsd:date"; 1597 case "dateTime" : 1598 return "xsd:gYear OR xsd:gYearMonth OR xsd:date OR xsd:dateTime"; 1599 case "time" : 1600 return "xsd:time"; 1601 case "code" : 1602 return "xsd:token"; 1603 case "oid" : 1604 return "xsd:anyURI"; 1605 case "uuid" : 1606 return "xsd:anyURI"; 1607 case "url" : 1608 return "xsd:anyURI"; 1609 case "canonical" : 1610 return "xsd:anyURI"; 1611 case "id" : 1612 return "xsd:string"; 1613 case "unsignedInt" : 1614 return "xsd:nonNegativeInteger"; 1615 case "positiveInt" : 1616 return "xsd:positiveInteger"; 1617 case "markdown" : 1618 return "xsd:string"; 1619 } 1620 throw new Error("Not implemented yet"); 1621 1622 } 1623 1624 /** 1625 * Generate a set of alternative shapes 1626 * @param ed Containing element definition 1627 * @param id Element definition identifier 1628 * @param shortId id to use in the actual definition 1629 * @return ShEx list of alternative anonymous shapes separated by "OR" 1630 */ 1631 private ST genAlternativeTypes(ElementDefinition ed, String id, String shortId) { 1632 ST shex_alt = tmplt(ALTERNATIVE_SHAPES_TEMPLATE); 1633 List<String> altEntries = new ArrayList<String>(); 1634 1635 1636 for(ElementDefinition.TypeRefComponent typ : ed.getType()) { 1637 altEntries.add(genAltEntry(id, typ)); 1638 } 1639 shex_alt.add("altEntries", StringUtils.join(altEntries, " OR \n ")); 1640 return shex_alt; 1641 } 1642 1643 1644 1645 /** 1646 * Generate an alternative shape for a reference 1647 * @param id reference name 1648 * @param typ shape type 1649 * @return ShEx equivalent 1650 */ 1651 private String genAltEntry(String id, ElementDefinition.TypeRefComponent typ) { 1652 if(!typ.getWorkingCode().equals("Reference")) 1653 throw new AssertionError("We do not handle " + typ.getWorkingCode() + " alternatives"); 1654 1655 return genReference(id, typ); 1656 } 1657 1658 /** 1659 * Generate a list of type choices for a "name[x]" style id 1660 * @param sd Structure containing ed 1661 * @param ed element definition 1662 * @param id choice identifier 1663 * @return ShEx fragment for the set of choices 1664 */ 1665 private String genChoiceTypes(StructureDefinition sd, 1666 ElementDefinition ed, 1667 String id) { 1668 List<String> choiceEntries = new ArrayList<String>(); 1669 List<String> refValues = new ArrayList<String>(); 1670 String base = id.replace("[x]", ""); 1671 1672 for (ElementDefinition.TypeRefComponent typ : ed.getType()) { 1673 String entry = genChoiceEntry(sd, ed, base, typ); 1674 refValues.clear(); 1675 if (typ.hasTargetProfile()) { 1676 typ.getTargetProfile().forEach((CanonicalType tps) -> { 1677 String els[] = tps.getValue().split("/"); 1678 refValues.add("@<" + els[els.length - 1] + ">"); 1679 }); 1680 } 1681 1682 if (!refValues.isEmpty()) 1683 choiceEntries.add("(" + entry + " AND {fhir:link " + StringUtils.join(refValues, " OR \n\t\t\t ") + " }) "); 1684 else 1685 choiceEntries.add(entry); 1686 } 1687 return StringUtils.join(choiceEntries, " OR \n\t\t\t"); 1688 } 1689 1690 /** 1691 * Generate an entry in a choice list 1692 * @param id base identifier 1693 * @param typ type/discriminant 1694 * @return ShEx fragment for choice entry 1695 */ 1696 private String genChoiceEntry(StructureDefinition sd, 1697 ElementDefinition ed, 1698 String id, 1699 ElementDefinition.TypeRefComponent typ) { 1700 ST shex_choice_entry = tmplt(ELEMENT_TEMPLATE); 1701 1702 String ext = typ.getWorkingCode(); 1703 // shex_choice_entry.add("id", "fhir:" + base+Character.toUpperCase(ext.charAt(0)) + ext.substring(1) + " "); 1704 shex_choice_entry.add("id", ""); 1705 shex_choice_entry.add("card", ""); 1706 String typeDefn = genTypeRef(sd, ed, id, typ); 1707 shex_choice_entry.add("defn", typeDefn); 1708 addImport(typeDefn); 1709 shex_choice_entry.add("comment", " "); 1710 return shex_choice_entry.render(); 1711 } 1712 1713 /** 1714 * @param oneOrMoreType 1715 * @return 1716 */ 1717 private String getOneOrMoreType(String oneOrMoreType) { 1718 if ((oneOrMoreType == null)||(oneOrMoreTypes.isEmpty())) 1719 return ""; 1720 1721 ST one_or_more_type = tmplt(ONE_OR_MORE_TEMPLATE); 1722 String oomType = oneOrMoreType; 1723 String origType = oneOrMoreType; 1724 String restriction = ""; 1725 if (oneOrMoreType.indexOf(ONE_OR_MORE_CHOICES) != -1) { 1726 oomType = oneOrMoreType.replaceAll(ONE_OR_MORE_CHOICES, "_"); 1727 origType = oneOrMoreType.split(ONE_OR_MORE_CHOICES)[0]; 1728 restriction = "AND {fhir:link \n\t\t\t@<"; 1729 1730 String choices = oneOrMoreType.split(ONE_OR_MORE_CHOICES)[1]; 1731 restriction += choices.replaceAll("_OR_", "> OR \n\t\t\t@<") + "> }"; 1732 } 1733 1734 origType = origType.replaceAll(ONE_OR_MORE_PREFIX, ""); 1735 1736 one_or_more_type.add("oomType", oomType); 1737 one_or_more_type.add("origType", origType); 1738 one_or_more_type.add("restriction", restriction); 1739 addImport(origType); 1740 addImport(restriction); 1741 one_or_more_type.add("comment", ""); 1742 return one_or_more_type.render(); 1743 } 1744 1745 /** 1746 * Generate a definition for a referenced element 1747 * @param sd Containing structure definition 1748 * @param ed Inner element 1749 * @return ShEx representation of element reference 1750 */ 1751 private String genInnerTypeDef(StructureDefinition sd, ElementDefinition ed) { 1752 String path = ed.hasBase() ? ed.getBase().getPath() : ed.getPath(); 1753 ST element_reference = tmplt(SHAPE_DEFINITION_TEMPLATE); 1754 element_reference.add("resourceDecl", ""); // Not a resource 1755 element_reference.add("id", path + getExtendedType(ed)); 1756 element_reference.add("fhirType", " "); 1757 String comment = ed.getShort(); 1758 element_reference.add("comment", comment == null? " " : "# " + comment); 1759 1760 List<String> elements = new ArrayList<String>(); 1761 for (ElementDefinition child: profileUtilities.getChildList(sd, path, null)) 1762 if (child.hasBase() && child.getBase().getPath().startsWith(sd.getName())) { 1763 String elementDefinition = genElementDefinition(sd, child); 1764 elements.add(elementDefinition); 1765 } 1766 1767 element_reference.add("elements", StringUtils.join(elements, "\n")); 1768 1769 List<String> innerConstraintsList = new ArrayList<String>(); 1770 1771 if (processConstraints) { 1772 // Process constraints 1773 for (ElementDefinition.ElementDefinitionConstraintComponent constraint : ed.getConstraint()) { 1774 String sdType = sd.getType(); 1775 String cstype = constraint.getSource(); 1776 if ((cstype != null) && (cstype.indexOf("/") != -1)) { 1777 String[] els = cstype.split("/"); 1778 cstype = els[els.length - 1]; 1779 } 1780 1781 String id = ed.hasBase() ? ed.getBase().getPath() : ed.getPath(); 1782 String shortId = id.substring(id.lastIndexOf(".") + 1); 1783 if ((ed.hasContentReference() && (!ed.hasType())) || (id.equals(sd.getName() + "." + shortId))) { 1784 if ((sdType.equals(cstype)) || baseDataTypes.contains(sdType)) { 1785 //if (!isInInnerTypes(ed)) { 1786 debug("\n (INNER ED) Key: " + constraint.getKey() + " SD type: " + sd.getType() + " Element: " + ed.getPath() + " Constraint Source: " + constraint.getSource() + " Constraint:" + constraint.getExpression()); 1787 String transl = translateConstraint(sd, ed, constraint); 1788 if (transl.isEmpty() || innerConstraintsList.contains(transl)) 1789 continue; 1790 innerConstraintsList.add(transl); 1791 //} 1792 } 1793 } 1794 } 1795 } 1796 1797 String constraintStr = ""; 1798 1799 if (!innerConstraintsList.isEmpty()) { 1800 constraintStr = "AND (\n\n" + StringUtils.join(constraintsList, "\n\n) AND (\n\n") + "\n\n)\n"; 1801 } 1802 1803 element_reference.add("constraints", constraintStr); 1804 1805 // TODO: See if we need to process contexts 1806 element_reference.add("contextOfUse", ""); 1807 1808 return element_reference.render(); 1809 } 1810 1811 /** 1812 * Generate a reference to a resource 1813 * @param id attribute identifier 1814 * @param typ possible reference types 1815 * @return string that represents the result 1816 */ 1817 private String genReference(String id, ElementDefinition.TypeRefComponent typ) { 1818 ST shex_ref = tmplt(REFERENCE_DEFN_TEMPLATE); 1819 1820 String ref = getTypeName(typ); 1821 shex_ref.add("id", id); 1822 shex_ref.add("ref", ref); 1823 references.add(ref); 1824 return shex_ref.render(); 1825 } 1826 1827 /** 1828 * Return the type name for typ 1829 * @param typ type to get name for 1830 * @return name 1831 */ 1832 private String getTypeName(ElementDefinition.TypeRefComponent typ) { 1833 // TODO: This is brittle. There has to be a utility to do this... 1834 if (typ.hasTargetProfile()) { 1835 String[] els = typ.getTargetProfile().get(0).getValue().split("/"); 1836 return els[els.length - 1]; 1837 } else if (typ.hasProfile()) { 1838 String[] els = typ.getProfile().get(0).getValue().split("/"); 1839 return els[els.length - 1]; 1840 } else { 1841 return typ.getWorkingCode(); 1842 } 1843 } 1844 1845 /** 1846 * @param vs 1847 * @return 1848 */ 1849 private String genValueSet(ValueSet vs) { 1850 ST vsd = tmplt(VALUE_SET_DEFINITION).add("vsuri", vsprefix(vs.getUrl())).add("comment", vs.getDescription()); 1851 ValueSetExpansionOutcome vse = context.expandVS(vs, true, false); 1852 List<String> valid_codes = new ArrayList<String>(); 1853 if(vse != null && 1854 vse.getValueset() != null && 1855 vse.getValueset().hasExpansion() && 1856 vse.getValueset().getExpansion().hasContains()) { 1857 for(ValueSet.ValueSetExpansionContainsComponent vsec : vse.getValueset().getExpansion().getContains()) 1858 valid_codes.add("\"" + vsec.getCode() + "\""); 1859 } 1860 return vsd.add("val_list", valid_codes.size() > 0? " [" + StringUtils.join(valid_codes, " ") + ']' : " xsd:string #EXTERNAL").render(); 1861 } 1862 1863 1864 /** 1865 * @param ctxt 1866 * @param reference 1867 * @return 1868 */ 1869 // TODO: find a utility that implements this 1870 private ValueSet resolveBindingReference(DomainResource ctxt, String reference) { 1871 try { 1872 return context.fetchResource(ValueSet.class, reference); 1873 } catch (Throwable e) { 1874 return null; 1875 } 1876 } 1877 1878 private void debug(String message) { 1879 if (this.debugMode) 1880 System.out.println(message); 1881 } 1882 1883 private void printBuildMessage(String message){ 1884 // System.out.println("ShExGenerator: " + message); 1885 } 1886}