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