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