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