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