001package org.hl7.fhir.r4.utils; 002 003/*- 004 * #%L 005 * org.hl7.fhir.r4 006 * %% 007 * Copyright (C) 2014 - 2019 Health Level 7 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023 024// remember group resolution 025// trace - account for which wasn't transformed in the source 026 027import java.io.IOException; 028import java.util.ArrayList; 029import java.util.EnumSet; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.List; 033import java.util.Map; 034import java.util.Set; 035import java.util.UUID; 036 037import org.apache.commons.lang3.NotImplementedException; 038import org.hl7.fhir.exceptions.DefinitionException; 039import org.hl7.fhir.exceptions.FHIRException; 040import org.hl7.fhir.exceptions.FHIRFormatError; 041import org.hl7.fhir.exceptions.PathEngineException; 042import org.hl7.fhir.r4.conformance.ProfileUtilities; 043import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider; 044import org.hl7.fhir.r4.context.IWorkerContext; 045import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult; 046import org.hl7.fhir.r4.elementmodel.Element; 047import org.hl7.fhir.r4.elementmodel.Property; 048import org.hl7.fhir.r4.model.Base; 049import org.hl7.fhir.r4.model.BooleanType; 050import org.hl7.fhir.r4.model.CanonicalType; 051import org.hl7.fhir.r4.model.CodeType; 052import org.hl7.fhir.r4.model.CodeableConcept; 053import org.hl7.fhir.r4.model.Coding; 054import org.hl7.fhir.r4.model.ConceptMap; 055import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent; 056import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupUnmappedMode; 057import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent; 058import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent; 059import org.hl7.fhir.r4.model.Constants; 060import org.hl7.fhir.r4.model.ContactDetail; 061import org.hl7.fhir.r4.model.ContactPoint; 062import org.hl7.fhir.r4.model.DecimalType; 063import org.hl7.fhir.r4.model.ElementDefinition; 064import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent; 065import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 066import org.hl7.fhir.r4.model.Enumeration; 067import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; 068import org.hl7.fhir.r4.model.Enumerations.FHIRVersion; 069import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 070import org.hl7.fhir.r4.model.ExpressionNode; 071import org.hl7.fhir.r4.model.ExpressionNode.CollectionStatus; 072import org.hl7.fhir.r4.model.IdType; 073import org.hl7.fhir.r4.model.IntegerType; 074import org.hl7.fhir.r4.model.Narrative.NarrativeStatus; 075import org.hl7.fhir.r4.model.PrimitiveType; 076import org.hl7.fhir.r4.model.Reference; 077import org.hl7.fhir.r4.model.Resource; 078import org.hl7.fhir.r4.model.ResourceFactory; 079import org.hl7.fhir.r4.model.StringType; 080import org.hl7.fhir.r4.model.StructureDefinition; 081import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent; 082import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 083import org.hl7.fhir.r4.model.StructureMap; 084import org.hl7.fhir.r4.model.StructureMap.StructureMapContextType; 085import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupComponent; 086import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupInputComponent; 087import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleComponent; 088import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleDependentComponent; 089import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleSourceComponent; 090import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleTargetComponent; 091import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleTargetParameterComponent; 092import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupTypeMode; 093import org.hl7.fhir.r4.model.StructureMap.StructureMapInputMode; 094import org.hl7.fhir.r4.model.StructureMap.StructureMapModelMode; 095import org.hl7.fhir.r4.model.StructureMap.StructureMapSourceListMode; 096import org.hl7.fhir.r4.model.StructureMap.StructureMapStructureComponent; 097import org.hl7.fhir.r4.model.StructureMap.StructureMapTargetListMode; 098import org.hl7.fhir.r4.model.StructureMap.StructureMapTransform; 099import org.hl7.fhir.r4.model.Type; 100import org.hl7.fhir.r4.model.TypeDetails; 101import org.hl7.fhir.r4.model.TypeDetails.ProfiledType; 102import org.hl7.fhir.r4.model.UriType; 103import org.hl7.fhir.r4.model.ValueSet; 104import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 105import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 106import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException; 107import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext; 108import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 109import org.hl7.fhir.utilities.Utilities; 110import org.hl7.fhir.utilities.validation.ValidationMessage; 111import org.hl7.fhir.utilities.xhtml.NodeType; 112import org.hl7.fhir.utilities.xhtml.XhtmlNode; 113 114/** 115 * Services in this class: 116 * 117 * string render(map) - take a structure and convert it to text 118 * map parse(text) - take a text representation and parse it 119 * getTargetType(map) - return the definition for the type to create to hand in 120 * transform(appInfo, source, map, target) - transform from source to target following the map 121 * analyse(appInfo, map) - generate profiles and other analysis artifacts for the targets of the transform 122 * map generateMapFromMappings(StructureDefinition) - build a mapping from a structure definition with loigcal mappings 123 * 124 * @author Grahame Grieve 125 * 126 */ 127public class StructureMapUtilities { 128 129 public class ResolvedGroup { 130 public StructureMapGroupComponent target; 131 public StructureMap targetMap; 132 } 133 public static final String MAP_WHERE_CHECK = "map.where.check"; 134 public static final String MAP_WHERE_LOG = "map.where.log"; 135 public static final String MAP_WHERE_EXPRESSION = "map.where.expression"; 136 public static final String MAP_SEARCH_EXPRESSION = "map.search.expression"; 137 public static final String MAP_EXPRESSION = "map.transform.expression"; 138 private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true; 139 private static final String AUTO_VAR_NAME = "vvv"; 140 141 public interface ITransformerServices { 142 // public boolean validateByValueSet(Coding code, String valuesetId); 143 public void log(String message); // log internal progress 144 public Base createType(Object appInfo, String name) throws FHIRException; 145 public Base createResource(Object appInfo, Base res, boolean atRootofTransform); // an already created resource is provided; this is to identify/store it 146 public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException; 147 // public Coding translate(Coding code) 148 // ValueSet validation operation 149 // Translation operation 150 // Lookup another tree of data 151 // Create an instance tree 152 // Return the correct string format to refer to a tree (input or output) 153 public Base resolveReference(Object appContext, String url) throws FHIRException; 154 public List<Base> performSearch(Object appContext, String url) throws FHIRException; 155 } 156 157 private class FFHIRPathHostServices implements IEvaluationContext{ 158 159 public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { 160 Variables vars = (Variables) appContext; 161 Base res = vars.get(VariableMode.INPUT, name); 162 if (res == null) 163 res = vars.get(VariableMode.OUTPUT, name); 164 return res; 165 } 166 167 @Override 168 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { 169 if (!(appContext instanceof VariablesForProfiling)) 170 throw new Error("Internal Logic Error (wrong type '"+appContext.getClass().getName()+"' in resolveConstantType)"); 171 VariablesForProfiling vars = (VariablesForProfiling) appContext; 172 VariableForProfiling v = vars.get(null, name); 173 if (v == null) 174 throw new PathEngineException("Unknown variable '"+name+"' from variables "+vars.summary()); 175 return v.property.types; 176 } 177 178 @Override 179 public boolean log(String argument, List<Base> focus) { 180 throw new Error("Not Implemented Yet"); 181 } 182 183 @Override 184 public FunctionDetails resolveFunction(String functionName) { 185 return null; // throw new Error("Not Implemented Yet"); 186 } 187 188 @Override 189 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException { 190 throw new Error("Not Implemented Yet"); 191 } 192 193 @Override 194 public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) { 195 throw new Error("Not Implemented Yet"); 196 } 197 198 @Override 199 public Base resolveReference(Object appContext, String url) throws FHIRException { 200 if (services == null) 201 return null; 202 return services.resolveReference(appContext, url); 203 } 204 205 @Override 206 public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { 207 IResourceValidator val = worker.newValidator(); 208 List<ValidationMessage> valerrors = new ArrayList<ValidationMessage>(); 209 if (item instanceof Resource) { 210 val.validate(appContext, valerrors, (Resource) item, url); 211 boolean ok = true; 212 for (ValidationMessage v : valerrors) 213 ok = ok && v.getLevel().isError(); 214 return ok; 215 } 216 throw new NotImplementedException("Not done yet (FFHIRPathHostServices.conformsToProfile), when item is element"); 217 } 218 219 } 220 private IWorkerContext worker; 221 private FHIRPathEngine fpe; 222 private ITransformerServices services; 223 private ProfileKnowledgeProvider pkp; 224 private Map<String, Integer> ids = new HashMap<String, Integer>(); 225 226 public StructureMapUtilities(IWorkerContext worker, ITransformerServices services, ProfileKnowledgeProvider pkp) { 227 super(); 228 this.worker = worker; 229 this.services = services; 230 this.pkp = pkp; 231 fpe = new FHIRPathEngine(worker); 232 fpe.setHostServices(new FFHIRPathHostServices()); 233 } 234 235 public StructureMapUtilities(IWorkerContext worker, ITransformerServices services) { 236 super(); 237 this.worker = worker; 238 this.services = services; 239 fpe = new FHIRPathEngine(worker); 240 fpe.setHostServices(new FFHIRPathHostServices()); 241 } 242 243 public StructureMapUtilities(IWorkerContext worker) { 244 super(); 245 this.worker = worker; 246 fpe = new FHIRPathEngine(worker); 247 fpe.setHostServices(new FFHIRPathHostServices()); 248 } 249 250 public static String render(StructureMap map) { 251 StringBuilder b = new StringBuilder(); 252 b.append("map \""); 253 b.append(map.getUrl()); 254 b.append("\" = \""); 255 b.append(Utilities.escapeJson(map.getName())); 256 b.append("\"\r\n\r\n"); 257 258 renderConceptMaps(b, map); 259 renderUses(b, map); 260 renderImports(b, map); 261 for (StructureMapGroupComponent g : map.getGroup()) 262 renderGroup(b, g); 263 return b.toString(); 264 } 265 266 private static void renderConceptMaps(StringBuilder b, StructureMap map) { 267 for (Resource r : map.getContained()) { 268 if (r instanceof ConceptMap) { 269 produceConceptMap(b, (ConceptMap) r); 270 } 271 } 272 } 273 274 private static void produceConceptMap(StringBuilder b, ConceptMap cm) { 275 b.append("conceptmap \""); 276 b.append(cm.getId()); 277 b.append("\" {\r\n"); 278 Map<String, String> prefixesSrc = new HashMap<String, String>(); 279 Map<String, String> prefixesTgt = new HashMap<String, String>(); 280 char prefix = 's'; 281 for (ConceptMapGroupComponent cg : cm.getGroup()) { 282 if (!prefixesSrc.containsKey(cg.getSource())) { 283 prefixesSrc.put(cg.getSource(), String.valueOf(prefix)); 284 b.append(" prefix "); 285 b.append(prefix); 286 b.append(" = \""); 287 b.append(cg.getSource()); 288 b.append("\"\r\n"); 289 prefix++; 290 } 291 if (!prefixesTgt.containsKey(cg.getTarget())) { 292 prefixesTgt.put(cg.getTarget(), String.valueOf(prefix)); 293 b.append(" prefix "); 294 b.append(prefix); 295 b.append(" = \""); 296 b.append(cg.getTarget()); 297 b.append("\"\r\n"); 298 prefix++; 299 } 300 } 301 b.append("\r\n"); 302 for (ConceptMapGroupComponent cg : cm.getGroup()) { 303 if (cg.hasUnmapped()) { 304 b.append(" unmapped for "); 305 b.append(prefixesSrc.get(cg.getSource())); 306 b.append(" = "); 307 b.append(cg.getUnmapped().getMode().toCode()); 308 b.append("\r\n"); 309 } 310 } 311 312 for (ConceptMapGroupComponent cg : cm.getGroup()) { 313 for (SourceElementComponent ce : cg.getElement()) { 314 b.append(" "); 315 b.append(prefixesSrc.get(cg.getSource())); 316 b.append(":"); 317 if (Utilities.isToken(ce.getCode())) { 318 b.append(ce.getCode()); 319 } else { 320 b.append("\""); 321 b.append(ce.getCode()); 322 b.append("\""); 323 } 324 b.append(" "); 325 b.append(getChar(ce.getTargetFirstRep().getEquivalence())); 326 b.append(" "); 327 b.append(prefixesTgt.get(cg.getTarget())); 328 b.append(":"); 329 if (Utilities.isToken(ce.getTargetFirstRep().getCode())) { 330 b.append(ce.getTargetFirstRep().getCode()); 331 } else { 332 b.append("\""); 333 b.append(ce.getTargetFirstRep().getCode()); 334 b.append("\""); 335 } 336 b.append("\r\n"); 337 } 338 } 339 b.append("}\r\n\r\n"); 340 } 341 342 private static Object getChar(ConceptMapEquivalence equivalence) { 343 switch (equivalence) { 344 case RELATEDTO: return "-"; 345 case EQUAL: return "="; 346 case EQUIVALENT: return "=="; 347 case DISJOINT: return "!="; 348 case UNMATCHED: return "--"; 349 case WIDER: return "<="; 350 case SUBSUMES: return "<-"; 351 case NARROWER: return ">="; 352 case SPECIALIZES: return ">-"; 353 case INEXACT: return "~"; 354 default: return "??"; 355 } 356 } 357 358 private static void renderUses(StringBuilder b, StructureMap map) { 359 for (StructureMapStructureComponent s : map.getStructure()) { 360 b.append("uses \""); 361 b.append(s.getUrl()); 362 b.append("\" "); 363 if (s.hasAlias()) { 364 b.append("alias "); 365 b.append(s.getAlias()); 366 b.append(" "); 367 } 368 b.append("as "); 369 b.append(s.getMode().toCode()); 370 b.append("\r\n"); 371 renderDoco(b, s.getDocumentation()); 372 } 373 if (map.hasStructure()) 374 b.append("\r\n"); 375 } 376 377 private static void renderImports(StringBuilder b, StructureMap map) { 378 for (UriType s : map.getImport()) { 379 b.append("imports \""); 380 b.append(s.getValue()); 381 b.append("\"\r\n"); 382 } 383 if (map.hasImport()) 384 b.append("\r\n"); 385 } 386 387 public static String groupToString(StructureMapGroupComponent g) { 388 StringBuilder b = new StringBuilder(); 389 renderGroup(b, g); 390 return b.toString(); 391 } 392 393 private static void renderGroup(StringBuilder b, StructureMapGroupComponent g) { 394 b.append("group "); 395 b.append(g.getName()); 396 b.append("("); 397 boolean first = true; 398 for (StructureMapGroupInputComponent gi : g.getInput()) { 399 if (first) 400 first = false; 401 else 402 b.append(", "); 403 b.append(gi.getMode().toCode()); 404 b.append(" "); 405 b.append(gi.getName()); 406 if (gi.hasType()) { 407 b.append(" : "); 408 b.append(gi.getType()); 409 } 410 } 411 b.append(")"); 412 if (g.hasExtends()) { 413 b.append(" extends "); 414 b.append(g.getExtends()); 415 } 416 417 switch (g.getTypeMode()) { 418 case TYPES: 419 b.append(" <<types>>"); 420 break; 421 case TYPEANDTYPES: 422 b.append(" <<type+>>"); 423 break; 424 default: // NONE, NULL 425 } 426 b.append(" {\r\n"); 427 for (StructureMapGroupRuleComponent r : g.getRule()) { 428 renderRule(b, r, 2); 429 } 430 b.append("}\r\n\r\n"); 431 } 432 433 public static String ruleToString(StructureMapGroupRuleComponent r) { 434 StringBuilder b = new StringBuilder(); 435 renderRule(b, r, 0); 436 return b.toString(); 437 } 438 439 private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) { 440 for (int i = 0; i < indent; i++) 441 b.append(' '); 442 boolean canBeAbbreviated = checkisSimple(r); 443 444 boolean first = true; 445 for (StructureMapGroupRuleSourceComponent rs : r.getSource()) { 446 if (first) 447 first = false; 448 else 449 b.append(", "); 450 renderSource(b, rs, canBeAbbreviated); 451 } 452 if (r.getTarget().size() > 1) { 453 b.append(" -> "); 454 first = true; 455 for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) { 456 if (first) 457 first = false; 458 else 459 b.append(", "); 460 if (RENDER_MULTIPLE_TARGETS_ONELINE) 461 b.append(' '); 462 else { 463 b.append("\r\n"); 464 for (int i = 0; i < indent+4; i++) 465 b.append(' '); 466 } 467 renderTarget(b, rt, false); 468 } 469 } else if (r.hasTarget()) { 470 b.append(" -> "); 471 renderTarget(b, r.getTarget().get(0), canBeAbbreviated); 472 } 473 if (r.hasRule()) { 474 b.append(" then {\r\n"); 475 renderDoco(b, r.getDocumentation()); 476 for (StructureMapGroupRuleComponent ir : r.getRule()) { 477 renderRule(b, ir, indent+2); 478 } 479 for (int i = 0; i < indent; i++) 480 b.append(' '); 481 b.append("}"); 482 } else { 483 if (r.hasDependent()) { 484 b.append(" then "); 485 first = true; 486 for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) { 487 if (first) 488 first = false; 489 else 490 b.append(", "); 491 b.append(rd.getName()); 492 b.append("("); 493 boolean ifirst = true; 494 for (StringType rdp : rd.getVariable()) { 495 if (ifirst) 496 ifirst = false; 497 else 498 b.append(", "); 499 b.append(rdp.asStringValue()); 500 } 501 b.append(")"); 502 } 503 } 504 } 505 if (r.hasName()) { 506 String n = ntail(r.getName()); 507 if (!n.startsWith("\"")) 508 n = "\""+n+"\""; 509 if (!matchesName(n, r.getSource())) { 510 b.append(" "); 511 b.append(n); 512 } 513 } 514 b.append(";"); 515 renderDoco(b, r.getDocumentation()); 516 b.append("\r\n"); 517 } 518 519 private static boolean matchesName(String n, List<StructureMapGroupRuleSourceComponent> source) { 520 if (source.size() != 1) 521 return false; 522 if (!source.get(0).hasElement()) 523 return false; 524 String s = source.get(0).getElement(); 525 if (n.equals(s) || n.equals("\""+s+"\"")) 526 return true; 527 if (source.get(0).hasType()) { 528 s = source.get(0).getElement()+"-"+source.get(0).getType(); 529 if (n.equals(s) || n.equals("\""+s+"\"")) 530 return true; 531 } 532 return false; 533 } 534 535 private static String ntail(String name) { 536 if (name == null) 537 return null; 538 if (name.startsWith("\"")) { 539 name = name.substring(1); 540 name = name.substring(0, name.length()-1); 541 } 542 return "\""+ (name.contains(".") ? name.substring(name.lastIndexOf(".")+1) : name) + "\""; 543 } 544 545 private static boolean checkisSimple(StructureMapGroupRuleComponent r) { 546 return 547 (r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) && 548 (r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) && 549 (r.getDependent().size() == 0) && (r.getRule().size() == 0) ; 550 } 551 552 public static String sourceToString(StructureMapGroupRuleSourceComponent r) { 553 StringBuilder b = new StringBuilder(); 554 renderSource(b, r, false); 555 return b.toString(); 556 } 557 558 private static void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs, boolean abbreviate) { 559 b.append(rs.getContext()); 560 if (rs.getContext().equals("@search")) { 561 b.append('('); 562 b.append(rs.getElement()); 563 b.append(')'); 564 } else if (rs.hasElement()) { 565 b.append('.'); 566 b.append(rs.getElement()); 567 } 568 if (rs.hasType()) { 569 b.append(" : "); 570 b.append(rs.getType()); 571 if (rs.hasMin()) { 572 b.append(" "); 573 b.append(rs.getMin()); 574 b.append(".."); 575 b.append(rs.getMax()); 576 } 577 } 578 579 if (rs.hasListMode()) { 580 b.append(" "); 581 b.append(rs.getListMode().toCode()); 582 } 583 if (rs.hasDefaultValue()) { 584 b.append(" default "); 585 assert rs.getDefaultValue() instanceof StringType; 586 b.append("\""+Utilities.escapeJson(((StringType) rs.getDefaultValue()).asStringValue())+"\""); 587 } 588 if (!abbreviate && rs.hasVariable()) { 589 b.append(" as "); 590 b.append(rs.getVariable()); 591 } 592 if (rs.hasCondition()) { 593 b.append(" where "); 594 b.append(rs.getCondition()); 595 } 596 if (rs.hasCheck()) { 597 b.append(" check "); 598 b.append(rs.getCheck()); 599 } 600 if (rs.hasLogMessage()) { 601 b.append(" log "); 602 b.append(rs.getLogMessage()); 603 } 604 } 605 606 public static String targetToString(StructureMapGroupRuleTargetComponent rt) { 607 StringBuilder b = new StringBuilder(); 608 renderTarget(b, rt, false); 609 return b.toString(); 610 } 611 612 private static void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt, boolean abbreviate) { 613 if (rt.hasContext()) { 614 if (rt.getContextType() == StructureMapContextType.TYPE) 615 b.append("@"); 616 b.append(rt.getContext()); 617 if (rt.hasElement()) { 618 b.append('.'); 619 b.append(rt.getElement()); 620 } 621 } 622 if (!abbreviate && rt.hasTransform()) { 623 if (rt.hasContext()) 624 b.append(" = "); 625 if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) { 626 renderTransformParam(b, rt.getParameter().get(0)); 627 } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) { 628 b.append("("); 629 b.append("\""+((StringType) rt.getParameter().get(0).getValue()).asStringValue()+"\""); 630 b.append(")"); 631 } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) { 632 b.append(rt.getTransform().toCode()); 633 b.append("("); 634 b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue()); 635 b.append("\""+((StringType) rt.getParameter().get(1).getValue()).asStringValue()+"\""); 636 b.append(")"); 637 } else { 638 b.append(rt.getTransform().toCode()); 639 b.append("("); 640 boolean first = true; 641 for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) { 642 if (first) 643 first = false; 644 else 645 b.append(", "); 646 renderTransformParam(b, rtp); 647 } 648 b.append(")"); 649 } 650 } 651 if (!abbreviate && rt.hasVariable()) { 652 b.append(" as "); 653 b.append(rt.getVariable()); 654 } 655 for (Enumeration<StructureMapTargetListMode> lm : rt.getListMode()) { 656 b.append(" "); 657 b.append(lm.getValue().toCode()); 658 if (lm.getValue() == StructureMapTargetListMode.SHARE) { 659 b.append(" "); 660 b.append(rt.getListRuleId()); 661 } 662 } 663 } 664 665 public static String paramToString(StructureMapGroupRuleTargetParameterComponent rtp) { 666 StringBuilder b = new StringBuilder(); 667 renderTransformParam(b, rtp); 668 return b.toString(); 669 } 670 671 private static void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) { 672 try { 673 if (rtp.hasValueBooleanType()) 674 b.append(rtp.getValueBooleanType().asStringValue()); 675 else if (rtp.hasValueDecimalType()) 676 b.append(rtp.getValueDecimalType().asStringValue()); 677 else if (rtp.hasValueIdType()) 678 b.append(rtp.getValueIdType().asStringValue()); 679 else if (rtp.hasValueDecimalType()) 680 b.append(rtp.getValueDecimalType().asStringValue()); 681 else if (rtp.hasValueIntegerType()) 682 b.append(rtp.getValueIntegerType().asStringValue()); 683 else 684 b.append("'"+Utilities.escapeJava(rtp.getValueStringType().asStringValue())+"'"); 685 } catch (FHIRException e) { 686 e.printStackTrace(); 687 b.append("error!"); 688 } 689 } 690 691 private static void renderDoco(StringBuilder b, String doco) { 692 if (Utilities.noString(doco)) 693 return; 694 b.append(" // "); 695 b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")); 696 } 697 698 public StructureMap parse(String text, String srcName) throws FHIRException { 699 FHIRLexer lexer = new FHIRLexer(text, srcName); 700 if (lexer.done()) 701 throw lexer.error("Map Input cannot be empty"); 702 lexer.skipComments(); 703 lexer.token("map"); 704 StructureMap result = new StructureMap(); 705 result.setUrl(lexer.readConstant("url")); 706 lexer.token("="); 707 result.setName(lexer.readConstant("name")); 708 lexer.skipComments(); 709 710 while (lexer.hasToken("conceptmap")) 711 parseConceptMap(result, lexer); 712 713 while (lexer.hasToken("uses")) 714 parseUses(result, lexer); 715 while (lexer.hasToken("imports")) 716 parseImports(result, lexer); 717 718 while (!lexer.done()) { 719 parseGroup(result, lexer); 720 } 721 722 return result; 723 } 724 725 private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException { 726 lexer.token("conceptmap"); 727 ConceptMap map = new ConceptMap(); 728 String id = lexer.readConstant("map id"); 729 if (id.startsWith("#")) 730 throw lexer.error("Concept Map identifier must start with #"); 731 map.setId(id); 732 map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format 733 result.getContained().add(map); 734 lexer.token("{"); 735 lexer.skipComments(); 736 // lexer.token("source"); 737 // map.setSource(new UriType(lexer.readConstant("source"))); 738 // lexer.token("target"); 739 // map.setSource(new UriType(lexer.readConstant("target"))); 740 Map<String, String> prefixes = new HashMap<String, String>(); 741 while (lexer.hasToken("prefix")) { 742 lexer.token("prefix"); 743 String n = lexer.take(); 744 lexer.token("="); 745 String v = lexer.readConstant("prefix url"); 746 prefixes.put(n, v); 747 } 748 while (lexer.hasToken("unmapped")) { 749 lexer.token("unmapped"); 750 lexer.token("for"); 751 String n = readPrefix(prefixes, lexer); 752 ConceptMapGroupComponent g = getGroup(map, n, null); 753 lexer.token("="); 754 String v = lexer.take(); 755 if (v.equals("provided")) { 756 g.getUnmapped().setMode(ConceptMapGroupUnmappedMode.PROVIDED); 757 } else 758 throw lexer.error("Only unmapped mode PROVIDED is supported at this time"); 759 } 760 while (!lexer.hasToken("}")) { 761 String srcs = readPrefix(prefixes, lexer); 762 lexer.token(":"); 763 String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take(); 764 ConceptMapEquivalence eq = readEquivalence(lexer); 765 String tgts = (eq != ConceptMapEquivalence.UNMATCHED) ? readPrefix(prefixes, lexer) : ""; 766 ConceptMapGroupComponent g = getGroup(map, srcs, tgts); 767 SourceElementComponent e = g.addElement(); 768 e.setCode(sc); 769 if (e.getCode().startsWith("\"")) 770 e.setCode(lexer.processConstant(e.getCode())); 771 TargetElementComponent tgt = e.addTarget(); 772 if (eq != ConceptMapEquivalence.EQUIVALENT) 773 tgt.setEquivalence(eq); 774 if (tgt.getEquivalence() != ConceptMapEquivalence.UNMATCHED) { 775 lexer.token(":"); 776 tgt.setCode(lexer.take()); 777 if (tgt.getCode().startsWith("\"")) 778 tgt.setCode(lexer.processConstant(tgt.getCode())); 779 } 780 if (lexer.hasComment()) 781 tgt.setComment(lexer.take().substring(2).trim()); 782 } 783 lexer.token("}"); 784 } 785 786 787 private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) { 788 for (ConceptMapGroupComponent grp : map.getGroup()) { 789 if (grp.getSource().equals(srcs)) 790 if (!grp.hasTarget() || tgts == null || tgts.equals(grp.getTarget())) { 791 if (!grp.hasTarget() && tgts != null) 792 grp.setTarget(tgts); 793 return grp; 794 } 795 } 796 ConceptMapGroupComponent grp = map.addGroup(); 797 grp.setSource(srcs); 798 grp.setTarget(tgts); 799 return grp; 800 } 801 802 803 private String readPrefix(Map<String, String> prefixes, FHIRLexer lexer) throws FHIRLexerException { 804 String prefix = lexer.take(); 805 if (!prefixes.containsKey(prefix)) 806 throw lexer.error("Unknown prefix '"+prefix+"'"); 807 return prefixes.get(prefix); 808 } 809 810 811 private ConceptMapEquivalence readEquivalence(FHIRLexer lexer) throws FHIRLexerException { 812 String token = lexer.take(); 813 if (token.equals("-")) 814 return ConceptMapEquivalence.RELATEDTO; 815 if (token.equals("=")) 816 return ConceptMapEquivalence.EQUAL; 817 if (token.equals("==")) 818 return ConceptMapEquivalence.EQUIVALENT; 819 if (token.equals("!=")) 820 return ConceptMapEquivalence.DISJOINT; 821 if (token.equals("--")) 822 return ConceptMapEquivalence.UNMATCHED; 823 if (token.equals("<=")) 824 return ConceptMapEquivalence.WIDER; 825 if (token.equals("<-")) 826 return ConceptMapEquivalence.SUBSUMES; 827 if (token.equals(">=")) 828 return ConceptMapEquivalence.NARROWER; 829 if (token.equals(">-")) 830 return ConceptMapEquivalence.SPECIALIZES; 831 if (token.equals("~")) 832 return ConceptMapEquivalence.INEXACT; 833 throw lexer.error("Unknown equivalence token '"+token+"'"); 834 } 835 836 837 private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException { 838 lexer.token("uses"); 839 StructureMapStructureComponent st = result.addStructure(); 840 st.setUrl(lexer.readConstant("url")); 841 if (lexer.hasToken("alias")) { 842 lexer.token("alias"); 843 st.setAlias(lexer.take()); 844 } 845 lexer.token("as"); 846 st.setMode(StructureMapModelMode.fromCode(lexer.take())); 847 lexer.skipToken(";"); 848 if (lexer.hasComment()) { 849 st.setDocumentation(lexer.take().substring(2).trim()); 850 } 851 lexer.skipComments(); 852 } 853 854 private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException { 855 lexer.token("imports"); 856 result.addImport(lexer.readConstant("url")); 857 lexer.skipToken(";"); 858 if (lexer.hasComment()) { 859 lexer.next(); 860 } 861 lexer.skipComments(); 862 } 863 864 private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException { 865 lexer.token("group"); 866 StructureMapGroupComponent group = result.addGroup(); 867 boolean newFmt = false; 868 if (lexer.hasToken("for")) { 869 lexer.token("for"); 870 if ("type".equals(lexer.getCurrent())) { 871 lexer.token("type"); 872 lexer.token("+"); 873 lexer.token("types"); 874 group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); 875 } else { 876 lexer.token("types"); 877 group.setTypeMode(StructureMapGroupTypeMode.TYPES); 878 } 879 } else 880 group.setTypeMode(StructureMapGroupTypeMode.NONE); 881 group.setName(lexer.take()); 882 if (lexer.hasToken("(")) { 883 newFmt = true; 884 lexer.take(); 885 while (!lexer.hasToken(")")) { 886 parseInput(group, lexer, true); 887 if (lexer.hasToken(",")) 888 lexer.token(","); 889 } 890 lexer.take(); 891 } 892 if (lexer.hasToken("extends")) { 893 lexer.next(); 894 group.setExtends(lexer.take()); 895 } 896 if (newFmt) { 897 group.setTypeMode(StructureMapGroupTypeMode.NONE); 898 if (lexer.hasToken("<")) { 899 lexer.token("<"); 900 lexer.token("<"); 901 if (lexer.hasToken("types")) { 902 group.setTypeMode(StructureMapGroupTypeMode.TYPES); 903 lexer.token("types"); 904 } else { 905 lexer.token("type"); 906 lexer.token("+"); 907 group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); 908 } 909 lexer.token(">"); 910 lexer.token(">"); 911 } 912 lexer.token("{"); 913 } 914 lexer.skipComments(); 915 if (newFmt) { 916 while (!lexer.hasToken("}")) { 917 if (lexer.done()) 918 throw lexer.error("premature termination expecting 'endgroup'"); 919 parseRule(result, group.getRule(), lexer, true); 920 } 921 } else { 922 while (lexer.hasToken("input")) 923 parseInput(group, lexer, false); 924 while (!lexer.hasToken("endgroup")) { 925 if (lexer.done()) 926 throw lexer.error("premature termination expecting 'endgroup'"); 927 parseRule(result, group.getRule(), lexer, false); 928 } 929 } 930 lexer.next(); 931 if (newFmt && lexer.hasToken(";")) 932 lexer.next(); 933 lexer.skipComments(); 934 } 935 936 private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer, boolean newFmt) throws FHIRException { 937 StructureMapGroupInputComponent input = group.addInput(); 938 if (newFmt) { 939 input.setMode(StructureMapInputMode.fromCode(lexer.take())); 940 } else 941 lexer.token("input"); 942 input.setName(lexer.take()); 943 if (lexer.hasToken(":")) { 944 lexer.token(":"); 945 input.setType(lexer.take()); 946 } 947 if (!newFmt) { 948 lexer.token("as"); 949 input.setMode(StructureMapInputMode.fromCode(lexer.take())); 950 if (lexer.hasComment()) { 951 input.setDocumentation(lexer.take().substring(2).trim()); 952 } 953 lexer.skipToken(";"); 954 lexer.skipComments(); 955 } 956 } 957 958 private void parseRule(StructureMap map, List<StructureMapGroupRuleComponent> list, FHIRLexer lexer, boolean newFmt) throws FHIRException { 959 StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); 960 list.add(rule); 961 if (!newFmt) { 962 rule.setName(lexer.takeDottedToken()); 963 lexer.token(":"); 964 lexer.token("for"); 965 } 966 boolean done = false; 967 while (!done) { 968 parseSource(rule, lexer); 969 done = !lexer.hasToken(","); 970 if (!done) 971 lexer.next(); 972 } 973 if ((newFmt && lexer.hasToken("->")) || (!newFmt && lexer.hasToken("make"))) { 974 lexer.token(newFmt ? "->" : "make"); 975 done = false; 976 while (!done) { 977 parseTarget(rule, lexer); 978 done = !lexer.hasToken(","); 979 if (!done) 980 lexer.next(); 981 } 982 } 983 if (lexer.hasToken("then")) { 984 lexer.token("then"); 985 if (lexer.hasToken("{")) { 986 lexer.token("{"); 987 if (lexer.hasComment()) { 988 rule.setDocumentation(lexer.take().substring(2).trim()); 989 } 990 lexer.skipComments(); 991 while (!lexer.hasToken("}")) { 992 if (lexer.done()) 993 throw lexer.error("premature termination expecting '}' in nested group"); 994 parseRule(map, rule.getRule(), lexer, newFmt); 995 } 996 lexer.token("}"); 997 } else { 998 done = false; 999 while (!done) { 1000 parseRuleReference(rule, lexer); 1001 done = !lexer.hasToken(","); 1002 if (!done) 1003 lexer.next(); 1004 } 1005 } 1006 } else if (lexer.hasComment()) { 1007 rule.setDocumentation(lexer.take().substring(2).trim()); 1008 } 1009 if (isSimpleSyntax(rule)) { 1010 rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME); 1011 rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME); 1012 rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created 1013 // no dependencies - imply what is to be done based on types 1014 } 1015 if (newFmt) { 1016 if (lexer.isConstant()) { 1017 rule.setName(lexer.take()); 1018 } else { 1019 if (rule.getSource().size() != 1 || !rule.getSourceFirstRep().hasElement()) 1020 throw lexer.error("Complex rules must have an explicit name"); 1021 if (rule.getSourceFirstRep().hasType()) 1022 rule.setName(rule.getSourceFirstRep().getElement()+"-"+rule.getSourceFirstRep().getType()); 1023 else 1024 rule.setName(rule.getSourceFirstRep().getElement()); 1025 } 1026 lexer.token(";"); 1027 } 1028 lexer.skipComments(); 1029 } 1030 1031 private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) { 1032 return 1033 (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) && 1034 (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) && 1035 (rule.getDependent().size() == 0 && rule.getRule().size() == 0); 1036 } 1037 1038 private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException { 1039 StructureMapGroupRuleDependentComponent ref = rule.addDependent(); 1040 ref.setName(lexer.take()); 1041 lexer.token("("); 1042 boolean done = false; 1043 while (!done) { 1044 ref.addVariable(lexer.take()); 1045 done = !lexer.hasToken(","); 1046 if (!done) 1047 lexer.next(); 1048 } 1049 lexer.token(")"); 1050 } 1051 1052 private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { 1053 StructureMapGroupRuleSourceComponent source = rule.addSource(); 1054 source.setContext(lexer.take()); 1055 if (source.getContext().equals("search") && lexer.hasToken("(")) { 1056 source.setContext("@search"); 1057 lexer.take(); 1058 ExpressionNode node = fpe.parse(lexer); 1059 source.setUserData(MAP_SEARCH_EXPRESSION, node); 1060 source.setElement(node.toString()); 1061 lexer.token(")"); 1062 } else if (lexer.hasToken(".")) { 1063 lexer.token("."); 1064 source.setElement(lexer.take()); 1065 } 1066 if (lexer.hasToken(":")) { 1067 // type and cardinality 1068 lexer.token(":"); 1069 source.setType(lexer.takeDottedToken()); 1070 if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) { 1071 source.setMin(lexer.takeInt()); 1072 lexer.token(".."); 1073 source.setMax(lexer.take()); 1074 } 1075 } 1076 if (lexer.hasToken("default")) { 1077 lexer.token("default"); 1078 source.setDefaultValue(new StringType(lexer.readConstant("default value"))); 1079 } 1080 if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one")) 1081 source.setListMode(StructureMapSourceListMode.fromCode(lexer.take())); 1082 1083 if (lexer.hasToken("as")) { 1084 lexer.take(); 1085 source.setVariable(lexer.take()); 1086 } 1087 if (lexer.hasToken("where")) { 1088 lexer.take(); 1089 ExpressionNode node = fpe.parse(lexer); 1090 source.setUserData(MAP_WHERE_EXPRESSION, node); 1091 source.setCondition(node.toString()); 1092 } 1093 if (lexer.hasToken("check")) { 1094 lexer.take(); 1095 ExpressionNode node = fpe.parse(lexer); 1096 source.setUserData(MAP_WHERE_CHECK, node); 1097 source.setCheck(node.toString()); 1098 } 1099 if (lexer.hasToken("log")) { 1100 lexer.take(); 1101 ExpressionNode node = fpe.parse(lexer); 1102 source.setUserData(MAP_WHERE_CHECK, node); 1103 source.setLogMessage(node.toString()); 1104 } 1105 } 1106 1107 private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { 1108 StructureMapGroupRuleTargetComponent target = rule.addTarget(); 1109 String start = lexer.take(); 1110 if (lexer.hasToken(".")) { 1111 target.setContext(start); 1112 target.setContextType(StructureMapContextType.VARIABLE); 1113 start = null; 1114 lexer.token("."); 1115 target.setElement(lexer.take()); 1116 } 1117 String name; 1118 boolean isConstant = false; 1119 if (lexer.hasToken("=")) { 1120 if (start != null) 1121 target.setContext(start); 1122 lexer.token("="); 1123 isConstant = lexer.isConstant(); 1124 name = lexer.take(); 1125 } else 1126 name = start; 1127 1128 if ("(".equals(name)) { 1129 // inline fluentpath expression 1130 target.setTransform(StructureMapTransform.EVALUATE); 1131 ExpressionNode node = fpe.parse(lexer); 1132 target.setUserData(MAP_EXPRESSION, node); 1133 target.addParameter().setValue(new StringType(node.toString())); 1134 lexer.token(")"); 1135 } else if (lexer.hasToken("(")) { 1136 target.setTransform(StructureMapTransform.fromCode(name)); 1137 lexer.token("("); 1138 if (target.getTransform() == StructureMapTransform.EVALUATE) { 1139 parseParameter(target, lexer); 1140 lexer.token(","); 1141 ExpressionNode node = fpe.parse(lexer); 1142 target.setUserData(MAP_EXPRESSION, node); 1143 target.addParameter().setValue(new StringType(node.toString())); 1144 } else { 1145 while (!lexer.hasToken(")")) { 1146 parseParameter(target, lexer); 1147 if (!lexer.hasToken(")")) 1148 lexer.token(","); 1149 } 1150 } 1151 lexer.token(")"); 1152 } else if (name != null) { 1153 target.setTransform(StructureMapTransform.COPY); 1154 if (!isConstant) { 1155 String id = name; 1156 while (lexer.hasToken(".")) { 1157 id = id + lexer.take() + lexer.take(); 1158 } 1159 target.addParameter().setValue(new IdType(id)); 1160 } 1161 else 1162 target.addParameter().setValue(readConstant(name, lexer)); 1163 } 1164 if (lexer.hasToken("as")) { 1165 lexer.take(); 1166 target.setVariable(lexer.take()); 1167 } 1168 while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) { 1169 if (lexer.getCurrent().equals("share")) { 1170 target.addListMode(StructureMapTargetListMode.SHARE); 1171 lexer.next(); 1172 target.setListRuleId(lexer.take()); 1173 } else if (lexer.getCurrent().equals("first")) 1174 target.addListMode(StructureMapTargetListMode.FIRST); 1175 else 1176 target.addListMode(StructureMapTargetListMode.LAST); 1177 lexer.next(); 1178 } 1179 } 1180 1181 1182 private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError { 1183 if (!lexer.isConstant()) { 1184 target.addParameter().setValue(new IdType(lexer.take())); 1185 } else if (lexer.isStringConstant()) 1186 target.addParameter().setValue(new StringType(lexer.readConstant("??"))); 1187 else { 1188 target.addParameter().setValue(readConstant(lexer.take(), lexer)); 1189 } 1190 } 1191 1192 private Type readConstant(String s, FHIRLexer lexer) throws FHIRLexerException { 1193 if (Utilities.isInteger(s)) 1194 return new IntegerType(s); 1195 else if (Utilities.isDecimal(s)) 1196 return new DecimalType(s); 1197 else if (Utilities.existsInList(s, "true", "false")) 1198 return new BooleanType(s.equals("true")); 1199 else 1200 return new StringType(lexer.processConstant(s)); 1201 } 1202 1203 public StructureDefinition getTargetType(StructureMap map) throws FHIRException { 1204 boolean found = false; 1205 StructureDefinition res = null; 1206 for (StructureMapStructureComponent uses : map.getStructure()) { 1207 if (uses.getMode() == StructureMapModelMode.TARGET) { 1208 if (found) 1209 throw new FHIRException("Multiple targets found in map "+map.getUrl()); 1210 found = true; 1211 res = worker.fetchResource(StructureDefinition.class, uses.getUrl()); 1212 if (res == null) 1213 throw new FHIRException("Unable to find "+uses.getUrl()+" referenced from map "+map.getUrl()); 1214 } 1215 } 1216 if (res == null) 1217 throw new FHIRException("No targets found in map "+map.getUrl()); 1218 return res; 1219 } 1220 1221 public enum VariableMode { 1222 INPUT, OUTPUT 1223 } 1224 1225 public class Variable { 1226 private VariableMode mode; 1227 private String name; 1228 private Base object; 1229 public Variable(VariableMode mode, String name, Base object) { 1230 super(); 1231 this.mode = mode; 1232 this.name = name; 1233 this.object = object; 1234 } 1235 public VariableMode getMode() { 1236 return mode; 1237 } 1238 public String getName() { 1239 return name; 1240 } 1241 public Base getObject() { 1242 return object; 1243 } 1244 public String summary() { 1245 if (object == null) 1246 return null; 1247 else if (object instanceof PrimitiveType) 1248 return name+": \""+((PrimitiveType) object).asStringValue()+'"'; 1249 else 1250 return name+": ("+object.fhirType()+")"; 1251 } 1252 } 1253 1254 public class Variables { 1255 private List<Variable> list = new ArrayList<Variable>(); 1256 1257 public void add(VariableMode mode, String name, Base object) { 1258 Variable vv = null; 1259 for (Variable v : list) 1260 if ((v.mode == mode) && v.getName().equals(name)) 1261 vv = v; 1262 if (vv != null) 1263 list.remove(vv); 1264 list.add(new Variable(mode, name, object)); 1265 } 1266 1267 public Variables copy() { 1268 Variables result = new Variables(); 1269 result.list.addAll(list); 1270 return result; 1271 } 1272 1273 public Base get(VariableMode mode, String name) { 1274 for (Variable v : list) 1275 if ((v.mode == mode) && v.getName().equals(name)) 1276 return v.getObject(); 1277 return null; 1278 } 1279 1280 public String summary() { 1281 CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 1282 CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); 1283 for (Variable v : list) 1284 if (v.mode == VariableMode.INPUT) 1285 s.append(v.summary()); 1286 else 1287 t.append(v.summary()); 1288 return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; 1289 } 1290 } 1291 1292 public class TransformContext { 1293 private Object appInfo; 1294 1295 public TransformContext(Object appInfo) { 1296 super(); 1297 this.appInfo = appInfo; 1298 } 1299 1300 public Object getAppInfo() { 1301 return appInfo; 1302 } 1303 1304 } 1305 1306 private void log(String cnt) { 1307 if (services != null) 1308 services.log(cnt); 1309 else 1310 System.out.println(cnt); 1311 } 1312 1313 /** 1314 * Given an item, return all the children that conform to the pattern described in name 1315 * 1316 * Possible patterns: 1317 * - a simple name (which may be the base of a name with [] e.g. value[x]) 1318 * - a name with a type replacement e.g. valueCodeableConcept 1319 * - * which means all children 1320 * - ** which means all descendents 1321 * 1322 * @param item 1323 * @param name 1324 * @param result 1325 * @throws FHIRException 1326 */ 1327 protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException { 1328 for (Base v : item.listChildrenByName(name, true)) 1329 if (v != null) 1330 result.add(v); 1331 } 1332 1333 public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException { 1334 TransformContext context = new TransformContext(appInfo); 1335 log("Start Transform "+map.getUrl()); 1336 StructureMapGroupComponent g = map.getGroup().get(0); 1337 1338 Variables vars = new Variables(); 1339 vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source); 1340 if (target != null) 1341 vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target); 1342 1343 executeGroup("", context, map, vars, g, true); 1344 if (target instanceof Element) 1345 ((Element) target).sort(); 1346 } 1347 1348 private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException { 1349 String name = null; 1350 for (StructureMapGroupInputComponent inp : g.getInput()) { 1351 if (inp.getMode() == mode) 1352 if (name != null) 1353 throw new DefinitionException("This engine does not support multiple source inputs"); 1354 else 1355 name = inp.getName(); 1356 } 1357 return name == null ? def : name; 1358 } 1359 1360 private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, boolean atRoot) throws FHIRException { 1361 log(indent+"Group : "+group.getName()+"; vars = "+vars.summary()); 1362 // todo: check inputs 1363 if (group.hasExtends()) { 1364 ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends()); 1365 executeGroup(indent+" ", context, rg.targetMap, vars, rg.target, false); 1366 } 1367 1368 for (StructureMapGroupRuleComponent r : group.getRule()) { 1369 executeRule(indent+" ", context, map, vars, group, r, atRoot); 1370 } 1371 } 1372 1373 private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, boolean atRoot) throws FHIRException { 1374 log(indent+"rule : "+rule.getName()+"; vars = "+vars.summary()); 1375 Variables srcVars = vars.copy(); 1376 if (rule.getSource().size() != 1) 1377 throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet"); 1378 List<Variables> source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0), map.getUrl(), indent); 1379 if (source != null) { 1380 for (Variables v : source) { 1381 for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { 1382 processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot); 1383 } 1384 if (rule.hasRule()) { 1385 for (StructureMapGroupRuleComponent childrule : rule.getRule()) { 1386 executeRule(indent +" ", context, map, v, group, childrule, false); 1387 } 1388 } else if (rule.hasDependent()) { 1389 for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { 1390 executeDependency(indent+" ", context, map, v, group, dependent); 1391 } 1392 } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) { 1393 // simple inferred, map by type 1394 System.out.println(v.summary()); 1395 Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable()); 1396 Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable()); 1397 String srcType = src.fhirType(); 1398 String tgtType = tgt.fhirType(); 1399 ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType); 1400 Variables vdef = new Variables(); 1401 vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src); 1402 vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt); 1403 executeGroup(indent+" ", context, defGroup.targetMap, vdef, defGroup.target, false); 1404 } 1405 } 1406 } 1407 } 1408 1409 private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException { 1410 ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName()); 1411 1412 if (rg.target.getInput().size() != dependent.getVariable().size()) { 1413 throw new FHIRException("Rule '"+dependent.getName()+"' has "+Integer.toString(rg.target.getInput().size())+" but the invocation has "+Integer.toString(dependent.getVariable().size())+" variables"); 1414 } 1415 Variables v = new Variables(); 1416 for (int i = 0; i < rg.target.getInput().size(); i++) { 1417 StructureMapGroupInputComponent input = rg.target.getInput().get(i); 1418 StringType rdp = dependent.getVariable().get(i); 1419 String var = rdp.asStringValue(); 1420 VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT; 1421 Base vv = vin.get(mode, var); 1422 if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient 1423 vv = vin.get(VariableMode.OUTPUT, var); 1424 if (vv == null) 1425 throw new FHIRException("Rule '"+dependent.getName()+"' "+mode.toString()+" variable '"+input.getName()+"' named as '"+var+"' has no value (vars = "+vin.summary()+")"); 1426 v.add(mode, input.getName(), vv); 1427 } 1428 executeGroup(indent+" ", context, rg.targetMap, v, rg.target, false); 1429 } 1430 1431 private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException { 1432 String type = base.fhirType(); 1433 String kn = "type^"+type; 1434 if (source.hasUserData(kn)) 1435 return source.getUserString(kn); 1436 1437 ResolvedGroup res = new ResolvedGroup(); 1438 res.targetMap = null; 1439 res.target = null; 1440 for (StructureMapGroupComponent grp : map.getGroup()) { 1441 if (matchesByType(map, grp, type)) { 1442 if (res.targetMap == null) { 1443 res.targetMap = map; 1444 res.target = grp; 1445 } else 1446 throw new FHIRException("Multiple possible matches looking for default rule for '"+type+"'"); 1447 } 1448 } 1449 if (res.targetMap != null) { 1450 String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); 1451 source.setUserData(kn, result); 1452 return result; 1453 } 1454 1455 for (UriType imp : map.getImport()) { 1456 List<StructureMap> impMapList = findMatchingMaps(imp.getValue()); 1457 if (impMapList.size() == 0) 1458 throw new FHIRException("Unable to find map(s) for "+imp.getValue()); 1459 for (StructureMap impMap : impMapList) { 1460 if (!impMap.getUrl().equals(map.getUrl())) { 1461 for (StructureMapGroupComponent grp : impMap.getGroup()) { 1462 if (matchesByType(impMap, grp, type)) { 1463 if (res.targetMap == null) { 1464 res.targetMap = impMap; 1465 res.target = grp; 1466 } else 1467 throw new FHIRException("Multiple possible matches for default rule for '"+type+"' in "+res.targetMap.getUrl()+" ("+res.target.getName()+") and "+impMap.getUrl()+" ("+grp.getName()+")"); 1468 } 1469 } 1470 } 1471 } 1472 } 1473 if (res.target == null) 1474 throw new FHIRException("No matches found for default rule for '"+type+"' from "+map.getUrl()); 1475 String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2... 1476 source.setUserData(kn, result); 1477 return result; 1478 } 1479 1480 private List<StructureMap> findMatchingMaps(String value) { 1481 List<StructureMap> res = new ArrayList<StructureMap>(); 1482 if (value.contains("*")) { 1483 for (StructureMap sm : worker.listTransforms()) { 1484 if (urlMatches(value, sm.getUrl())) { 1485 res.add(sm); 1486 } 1487 } 1488 } else { 1489 StructureMap sm = worker.getTransform(value); 1490 if (sm != null) 1491 res.add(sm); 1492 } 1493 Set<String> check = new HashSet<String>(); 1494 for (StructureMap sm : res) { 1495 if (check.contains(sm.getUrl())) 1496 throw new Error("duplicate"); 1497 else 1498 check.add(sm.getUrl()); 1499 } 1500 return res; 1501 } 1502 1503 private boolean urlMatches(String mask, String url) { 1504 return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*")+1)) ; 1505 } 1506 1507 private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException { 1508 String kn = "types^"+srcType+":"+tgtType; 1509 if (source.hasUserData(kn)) 1510 return (ResolvedGroup) source.getUserData(kn); 1511 1512 ResolvedGroup res = new ResolvedGroup(); 1513 res.targetMap = null; 1514 res.target = null; 1515 for (StructureMapGroupComponent grp : map.getGroup()) { 1516 if (matchesByType(map, grp, srcType, tgtType)) { 1517 if (res.targetMap == null) { 1518 res.targetMap = map; 1519 res.target = grp; 1520 } else 1521 throw new FHIRException("Multiple possible matches looking for rule for '"+srcType+"/"+tgtType+"', from rule '"+ruleid+"'"); 1522 } 1523 } 1524 if (res.targetMap != null) { 1525 source.setUserData(kn, res); 1526 return res; 1527 } 1528 1529 for (UriType imp : map.getImport()) { 1530 List<StructureMap> impMapList = findMatchingMaps(imp.getValue()); 1531 if (impMapList.size() == 0) 1532 throw new FHIRException("Unable to find map(s) for "+imp.getValue()); 1533 for (StructureMap impMap : impMapList) { 1534 if (!impMap.getUrl().equals(map.getUrl())) { 1535 for (StructureMapGroupComponent grp : impMap.getGroup()) { 1536 if (matchesByType(impMap, grp, srcType, tgtType)) { 1537 if (res.targetMap == null) { 1538 res.targetMap = impMap; 1539 res.target = grp; 1540 } else 1541 throw new FHIRException("Multiple possible matches for rule for '"+srcType+"/"+tgtType+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()+", from rule '"+ruleid+"'"); 1542 } 1543 } 1544 } 1545 } 1546 } 1547 if (res.target == null) 1548 throw new FHIRException("No matches found for rule for '"+srcType+" to "+tgtType+"' from "+map.getUrl()+", from rule '"+ruleid+"'"); 1549 source.setUserData(kn, res); 1550 return res; 1551 } 1552 1553 1554 private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException { 1555 if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES) 1556 return false; 1557 if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) 1558 return false; 1559 return matchesType(map, type, grp.getInput().get(0).getType()); 1560 } 1561 1562 private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException { 1563 if (grp.getTypeMode() == StructureMapGroupTypeMode.NONE) 1564 return false; 1565 if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) 1566 return false; 1567 if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType()) 1568 return false; 1569 return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType()); 1570 } 1571 1572 private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException { 1573 // check the aliases 1574 for (StructureMapStructureComponent imp : map.getStructure()) { 1575 if (imp.hasAlias() && statedType.equals(imp.getAlias())) { 1576 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); 1577 if (sd != null) 1578 statedType = sd.getType(); 1579 break; 1580 } 1581 } 1582 1583 if (Utilities.isAbsoluteUrl(actualType)) { 1584 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, actualType); 1585 if (sd != null) 1586 actualType = sd.getType(); 1587 } 1588 if (Utilities.isAbsoluteUrl(statedType)) { 1589 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, statedType); 1590 if (sd != null) 1591 statedType = sd.getType(); 1592 } 1593 return actualType.equals(statedType); 1594 } 1595 1596 private String getActualType(StructureMap map, String statedType) throws FHIRException { 1597 // check the aliases 1598 for (StructureMapStructureComponent imp : map.getStructure()) { 1599 if (imp.hasAlias() && statedType.equals(imp.getAlias())) { 1600 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); 1601 if (sd == null) 1602 throw new FHIRException("Unable to resolve structure "+imp.getUrl()); 1603 return sd.getId(); // should be sd.getType(), but R2... 1604 } 1605 } 1606 return statedType; 1607 } 1608 1609 1610 private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException { 1611 String kn = "ref^"+name; 1612 if (source.hasUserData(kn)) 1613 return (ResolvedGroup) source.getUserData(kn); 1614 1615 ResolvedGroup res = new ResolvedGroup(); 1616 res.targetMap = null; 1617 res.target = null; 1618 for (StructureMapGroupComponent grp : map.getGroup()) { 1619 if (grp.getName().equals(name)) { 1620 if (res.targetMap == null) { 1621 res.targetMap = map; 1622 res.target = grp; 1623 } else 1624 throw new FHIRException("Multiple possible matches for rule '"+name+"'"); 1625 } 1626 } 1627 if (res.targetMap != null) { 1628 source.setUserData(kn, res); 1629 return res; 1630 } 1631 1632 for (UriType imp : map.getImport()) { 1633 List<StructureMap> impMapList = findMatchingMaps(imp.getValue()); 1634 if (impMapList.size() == 0) 1635 throw new FHIRException("Unable to find map(s) for "+imp.getValue()); 1636 for (StructureMap impMap : impMapList) { 1637 if (!impMap.getUrl().equals(map.getUrl())) { 1638 for (StructureMapGroupComponent grp : impMap.getGroup()) { 1639 if (grp.getName().equals(name)) { 1640 if (res.targetMap == null) { 1641 res.targetMap = impMap; 1642 res.target = grp; 1643 } else 1644 throw new FHIRException("Multiple possible matches for rule group '"+name+"' in "+ 1645 res.targetMap.getUrl()+"#"+res.target.getName()+" and "+ 1646 impMap.getUrl()+"#"+grp.getName()); 1647 } 1648 } 1649 } 1650 } 1651 } 1652 if (res.target == null) 1653 throw new FHIRException("No matches found for rule '"+name+"'. Reference found in "+map.getUrl()); 1654 source.setUserData(kn, res); 1655 return res; 1656 } 1657 1658 private List<Variables> processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src, String pathForErrors, String indent) throws FHIRException { 1659 List<Base> items; 1660 if (src.getContext().equals("@search")) { 1661 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION); 1662 if (expr == null) { 1663 expr = fpe.parse(src.getElement()); 1664 src.setUserData(MAP_SEARCH_EXPRESSION, expr); 1665 } 1666 String search = fpe.evaluateToString(vars, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly 1667 items = services.performSearch(context.appInfo, search); 1668 } else { 1669 items = new ArrayList<Base>(); 1670 Base b = vars.get(VariableMode.INPUT, src.getContext()); 1671 if (b == null) 1672 throw new FHIRException("Unknown input variable "+src.getContext()+" in "+pathForErrors+" rule "+ruleId+" (vars = "+vars.summary()+")"); 1673 1674 if (!src.hasElement()) 1675 items.add(b); 1676 else { 1677 getChildrenByName(b, src.getElement(), items); 1678 if (items.size() == 0 && src.hasDefaultValue()) 1679 items.add(src.getDefaultValue()); 1680 } 1681 } 1682 1683 if (src.hasType()) { 1684 List<Base> remove = new ArrayList<Base>(); 1685 for (Base item : items) { 1686 if (item != null && !isType(item, src.getType())) { 1687 remove.add(item); 1688 } 1689 } 1690 items.removeAll(remove); 1691 } 1692 1693 if (src.hasCondition()) { 1694 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION); 1695 if (expr == null) { 1696 expr = fpe.parse(src.getCondition()); 1697 // fpe.check(context.appInfo, ??, ??, expr) 1698 src.setUserData(MAP_WHERE_EXPRESSION, expr); 1699 } 1700 List<Base> remove = new ArrayList<Base>(); 1701 for (Base item : items) { 1702 if (!fpe.evaluateToBoolean(vars, null, item, expr)) { 1703 log(indent+" condition ["+src.getCondition()+"] for "+item.toString()+" : false"); 1704 remove.add(item); 1705 } else 1706 log(indent+" condition ["+src.getCondition()+"] for "+item.toString()+" : true"); 1707 } 1708 items.removeAll(remove); 1709 } 1710 1711 if (src.hasCheck()) { 1712 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK); 1713 if (expr == null) { 1714 expr = fpe.parse(src.getCheck()); 1715 // fpe.check(context.appInfo, ??, ??, expr) 1716 src.setUserData(MAP_WHERE_CHECK, expr); 1717 } 1718 List<Base> remove = new ArrayList<Base>(); 1719 for (Base item : items) { 1720 if (!fpe.evaluateToBoolean(vars, null, item, expr)) 1721 throw new FHIRException("Rule \""+ruleId+"\": Check condition failed"); 1722 } 1723 } 1724 1725 if (src.hasLogMessage()) { 1726 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_LOG); 1727 if (expr == null) { 1728 expr = fpe.parse(src.getLogMessage()); 1729 // fpe.check(context.appInfo, ??, ??, expr) 1730 src.setUserData(MAP_WHERE_LOG, expr); 1731 } 1732 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1733 for (Base item : items) 1734 b.appendIfNotNull(fpe.evaluateToString(vars, null, item, expr)); 1735 if (b.length() > 0) 1736 services.log(b.toString()); 1737 } 1738 1739 1740 if (src.hasListMode() && !items.isEmpty()) { 1741 switch (src.getListMode()) { 1742 case FIRST: 1743 Base bt = items.get(0); 1744 items.clear(); 1745 items.add(bt); 1746 break; 1747 case NOTFIRST: 1748 if (items.size() > 0) 1749 items.remove(0); 1750 break; 1751 case LAST: 1752 bt = items.get(items.size()-1); 1753 items.clear(); 1754 items.add(bt); 1755 break; 1756 case NOTLAST: 1757 if (items.size() > 0) 1758 items.remove(items.size()-1); 1759 break; 1760 case ONLYONE: 1761 if (items.size() > 1) 1762 throw new FHIRException("Rule \""+ruleId+"\": Check condition failed: the collection has more than one item"); 1763 break; 1764 case NULL: 1765 } 1766 } 1767 List<Variables> result = new ArrayList<Variables>(); 1768 for (Base r : items) { 1769 Variables v = vars.copy(); 1770 if (src.hasVariable()) 1771 v.add(VariableMode.INPUT, src.getVariable(), r); 1772 result.add(v); 1773 } 1774 return result; 1775 } 1776 1777 1778 private boolean isType(Base item, String type) { 1779 if (type.equals(item.fhirType())) 1780 return true; 1781 return false; 1782 } 1783 1784 private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot) throws FHIRException { 1785 Base dest = null; 1786 if (tgt.hasContext()) { 1787 dest = vars.get(VariableMode.OUTPUT, tgt.getContext()); 1788 if (dest == null) 1789 throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext()); 1790 if (!tgt.hasElement()) 1791 throw new FHIRException("Rule \""+ruleId+"\": Not supported yet"); 1792 } 1793 Base v = null; 1794 if (tgt.hasTransform()) { 1795 v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar, atRoot); 1796 if (v != null && dest != null) 1797 v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value 1798 } else if (dest != null) 1799 v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); 1800 if (tgt.hasVariable() && v != null) 1801 vars.add(VariableMode.OUTPUT, tgt.getVariable(), v); 1802 } 1803 1804 private Base runTransform(String ruleId, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar, boolean root) throws FHIRException { 1805 try { 1806 switch (tgt.getTransform()) { 1807 case CREATE : 1808 String tn; 1809 if (tgt.getParameter().isEmpty()) { 1810 // we have to work out the type. First, we see if there is a single type for the target. If there is, we use that 1811 String[] types = dest.getTypesForProperty(element.hashCode(), element); 1812 if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource")) 1813 tn = types[0]; 1814 else if (srcVar != null) { 1815 tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types); 1816 } else 1817 throw new Error("Cannot determine type implicitly because there is no single input variable"); 1818 } else { 1819 tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()); 1820 // ok, now we resolve the type name against the import statements 1821 for (StructureMapStructureComponent uses : map.getStructure()) { 1822 if (uses.getMode() == StructureMapModelMode.TARGET && uses.hasAlias() && tn.equals(uses.getAlias())) { 1823 tn = uses.getUrl(); 1824 break; 1825 } 1826 } 1827 } 1828 Base res = services != null ? services.createType(context.getAppInfo(), tn) : ResourceFactory.createResourceOrType(tn); 1829 if (res.isResource() && !res.fhirType().equals("Parameters")) { 1830// res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase()); 1831 if (services != null) 1832 res = services.createResource(context.getAppInfo(), res, root); 1833 } 1834 if (tgt.hasUserData("profile")) 1835 res.setUserData("profile", tgt.getUserData("profile")); 1836 return res; 1837 case COPY : 1838 return getParam(vars, tgt.getParameter().get(0)); 1839 case EVALUATE : 1840 ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); 1841 if (expr == null) { 1842 expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); 1843 tgt.setUserData(MAP_WHERE_EXPRESSION, expr); 1844 } 1845 List<Base> v = fpe.evaluate(vars, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr); 1846 if (v.size() == 0) 1847 return null; 1848 else if (v.size() != 1) 1849 throw new FHIRException("Rule \""+ruleId+"\": Evaluation of "+expr.toString()+" returned "+Integer.toString(v.size())+" objects"); 1850 else 1851 return v.get(0); 1852 1853 case TRUNCATE : 1854 String src = getParamString(vars, tgt.getParameter().get(0)); 1855 String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()); 1856 if (Utilities.isInteger(len)) { 1857 int l = Integer.parseInt(len); 1858 if (src.length() > l) 1859 src = src.substring(0, l); 1860 } 1861 return new StringType(src); 1862 case ESCAPE : 1863 throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); 1864 case CAST : 1865 src = getParamString(vars, tgt.getParameter().get(0)); 1866 if (tgt.getParameter().size() == 1) 1867 throw new FHIRException("Implicit type parameters on cast not yet supported"); 1868 String t = getParamString(vars, tgt.getParameter().get(1)); 1869 if (t.equals("string")) 1870 return new StringType(src); 1871 else 1872 throw new FHIRException("cast to "+t+" not yet supported"); 1873 case APPEND : 1874 StringBuilder sb = new StringBuilder(getParamString(vars, tgt.getParameter().get(0))); 1875 for (int i = 1; i < tgt.getParameter().size(); i++) 1876 sb.append(getParamString(vars, tgt.getParameter().get(i))); 1877 return new StringType(sb.toString()); 1878 case TRANSLATE : 1879 return translate(context, map, vars, tgt.getParameter()); 1880 case REFERENCE : 1881 Base b = getParam(vars, tgt.getParameter().get(0)); 1882 if (b == null) 1883 throw new FHIRException("Rule \""+ruleId+"\": Unable to find parameter "+((IdType) tgt.getParameter().get(0).getValue()).asStringValue()); 1884 if (!b.isResource()) 1885 throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType()); 1886 else { 1887 String id = b.getIdBase(); 1888 if (id == null) { 1889 id = UUID.randomUUID().toString().toLowerCase(); 1890 b.setIdBase(id); 1891 } 1892 return new Reference().setReference(b.fhirType()+"/"+id); 1893 } 1894 case DATEOP : 1895 throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); 1896 case UUID : 1897 return new IdType(UUID.randomUUID().toString()); 1898 case POINTER : 1899 b = getParam(vars, tgt.getParameter().get(0)); 1900 if (b instanceof Resource) 1901 return new UriType("urn:uuid:"+((Resource) b).getId()); 1902 else 1903 throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType()); 1904 case CC: 1905 CodeableConcept cc = new CodeableConcept(); 1906 cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()))); 1907 return cc; 1908 case C: 1909 Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); 1910 return c; 1911 default: 1912 throw new Error("Rule \""+ruleId+"\": Transform Unknown: "+tgt.getTransform().toCode()); 1913 } 1914 } catch (Exception e) { 1915 throw new FHIRException("Exception executing transform "+tgt.toString()+" on Rule \""+ruleId+"\": "+e.getMessage(), e); 1916 } 1917 } 1918 1919 1920 private Coding buildCoding(String uri, String code) throws FHIRException { 1921 // if we can get this as a valueSet, we will 1922 String system = null; 1923 String display = null; 1924 ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri); 1925 if (vs != null) { 1926 ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false); 1927 if (vse.getError() != null) 1928 throw new FHIRException(vse.getError()); 1929 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1930 for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) { 1931 if (t.hasCode()) 1932 b.append(t.getCode()); 1933 if (code.equals(t.getCode()) && t.hasSystem()) { 1934 system = t.getSystem(); 1935 display = t.getDisplay(); 1936 break; 1937 } 1938 if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) { 1939 system = t.getSystem(); 1940 display = t.getDisplay(); 1941 break; 1942 } 1943 } 1944 if (system == null) 1945 throw new FHIRException("The code '"+code+"' is not in the value set '"+uri+"' (valid codes: "+b.toString()+"; also checked displays)"); 1946 } else 1947 system = uri; 1948 ValidationResult vr = worker.validateCode(system, code, null); 1949 if (vr != null && vr.getDisplay() != null) 1950 display = vr.getDisplay(); 1951 return new Coding().setSystem(system).setCode(code).setDisplay(display); 1952 } 1953 1954 1955 private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException { 1956 Base b = getParam(vars, parameter); 1957 if (b == null) 1958 throw new FHIRException("Unable to find a value for "+parameter.toString()+". Context: "+message); 1959 if (!b.hasPrimitiveValue()) 1960 throw new FHIRException("Found a value for "+parameter.toString()+", but it has a type of "+b.fhirType()+" and cannot be treated as a string. Context: "+message); 1961 return b.primitiveValue(); 1962 } 1963 1964 private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { 1965 Base b = getParam(vars, parameter); 1966 if (b == null || !b.hasPrimitiveValue()) 1967 return null; 1968 return b.primitiveValue(); 1969 } 1970 1971 1972 private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { 1973 Type p = parameter.getValue(); 1974 if (!(p instanceof IdType)) 1975 return p; 1976 else { 1977 String n = ((IdType) p).asStringValue(); 1978 Base b = vars.get(VariableMode.INPUT, n); 1979 if (b == null) 1980 b = vars.get(VariableMode.OUTPUT, n); 1981 if (b == null) 1982 throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")"); 1983 return b; 1984 } 1985 } 1986 1987 1988 private Base translate(TransformContext context, StructureMap map, Variables vars, List<StructureMapGroupRuleTargetParameterComponent> parameter) throws FHIRException { 1989 Base src = getParam(vars, parameter.get(0)); 1990 String id = getParamString(vars, parameter.get(1)); 1991 String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null; 1992 return translate(context, map, src, id, fld); 1993 } 1994 1995 private class SourceElementComponentWrapper { 1996 private ConceptMapGroupComponent group; 1997 private SourceElementComponent comp; 1998 public SourceElementComponentWrapper(ConceptMapGroupComponent group, SourceElementComponent comp) { 1999 super(); 2000 this.group = group; 2001 this.comp = comp; 2002 } 2003 } 2004 public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException { 2005 Coding src = new Coding(); 2006 if (source.isPrimitive()) { 2007 src.setCode(source.primitiveValue()); 2008 } else if ("Coding".equals(source.fhirType())) { 2009 Base[] b = source.getProperty("system".hashCode(), "system", true); 2010 if (b.length == 1) 2011 src.setSystem(b[0].primitiveValue()); 2012 b = source.getProperty("code".hashCode(), "code", true); 2013 if (b.length == 1) 2014 src.setCode(b[0].primitiveValue()); 2015 } else if ("CE".equals(source.fhirType())) { 2016 Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true); 2017 if (b.length == 1) 2018 src.setSystem(b[0].primitiveValue()); 2019 b = source.getProperty("code".hashCode(), "code", true); 2020 if (b.length == 1) 2021 src.setCode(b[0].primitiveValue()); 2022 } else 2023 throw new FHIRException("Unable to translate source "+source.fhirType()); 2024 2025 String su = conceptMapUrl; 2026 if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) { 2027 String uri = worker.oid2Uri(src.getCode()); 2028 if (uri == null) 2029 uri = "urn:oid:"+src.getCode(); 2030 if ("uri".equals(fieldToReturn)) 2031 return new UriType(uri); 2032 else 2033 throw new FHIRException("Error in return code"); 2034 } else { 2035 ConceptMap cmap = null; 2036 if (conceptMapUrl.startsWith("#")) { 2037 for (Resource r : map.getContained()) { 2038 if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(conceptMapUrl.substring(1))) { 2039 cmap = (ConceptMap) r; 2040 su = map.getUrl()+"#"+conceptMapUrl; 2041 } 2042 } 2043 if (cmap == null) 2044 throw new FHIRException("Unable to translate - cannot find map "+conceptMapUrl); 2045 } else { 2046 if (conceptMapUrl.contains("#")) { 2047 String[] p = conceptMapUrl.split("\\#"); 2048 StructureMap mapU = worker.fetchResource(StructureMap.class, p[0]); 2049 for (Resource r : mapU.getContained()) { 2050 if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(p[1])) { 2051 cmap = (ConceptMap) r; 2052 su = conceptMapUrl; 2053 } 2054 } 2055 } 2056 if (cmap == null) 2057 cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl); 2058 } 2059 Coding outcome = null; 2060 boolean done = false; 2061 String message = null; 2062 if (cmap == null) { 2063 if (services == null) 2064 message = "No map found for "+conceptMapUrl; 2065 else { 2066 outcome = services.translate(context.appInfo, src, conceptMapUrl); 2067 done = true; 2068 } 2069 } else { 2070 List<SourceElementComponentWrapper> list = new ArrayList<SourceElementComponentWrapper>(); 2071 for (ConceptMapGroupComponent g : cmap.getGroup()) { 2072 for (SourceElementComponent e : g.getElement()) { 2073 if (!src.hasSystem() && src.getCode().equals(e.getCode())) 2074 list.add(new SourceElementComponentWrapper(g, e)); 2075 else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode())) 2076 list.add(new SourceElementComponentWrapper(g, e)); 2077 } 2078 } 2079 if (list.size() == 0) 2080 done = true; 2081 else if (list.get(0).comp.getTarget().size() == 0) 2082 message = "Concept map "+su+" found no translation for "+src.getCode(); 2083 else { 2084 for (TargetElementComponent tgt : list.get(0).comp.getTarget()) { 2085 if (tgt.getEquivalence() == null || EnumSet.of( ConceptMapEquivalence.EQUAL , ConceptMapEquivalence.RELATEDTO , ConceptMapEquivalence.EQUIVALENT, ConceptMapEquivalence.WIDER).contains(tgt.getEquivalence())) { 2086 if (done) { 2087 message = "Concept map "+su+" found multiple matches for "+src.getCode(); 2088 done = false; 2089 } else { 2090 done = true; 2091 outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).group.getTarget()); 2092 } 2093 } else if (tgt.getEquivalence() == ConceptMapEquivalence.UNMATCHED) { 2094 done = true; 2095 } 2096 } 2097 if (!done) 2098 message = "Concept map "+su+" found no usable translation for "+src.getCode(); 2099 } 2100 } 2101 if (!done) 2102 throw new FHIRException(message); 2103 if (outcome == null) 2104 return null; 2105 if ("code".equals(fieldToReturn)) 2106 return new CodeType(outcome.getCode()); 2107 else 2108 return outcome; 2109 } 2110 } 2111 2112 2113 public class PropertyWithType { 2114 private String path; 2115 private Property baseProperty; 2116 private Property profileProperty; 2117 private TypeDetails types; 2118 public PropertyWithType(String path, Property baseProperty, Property profileProperty, TypeDetails types) { 2119 super(); 2120 this.baseProperty = baseProperty; 2121 this.profileProperty = profileProperty; 2122 this.path = path; 2123 this.types = types; 2124 } 2125 2126 public TypeDetails getTypes() { 2127 return types; 2128 } 2129 public String getPath() { 2130 return path; 2131 } 2132 2133 public Property getBaseProperty() { 2134 return baseProperty; 2135 } 2136 2137 public void setBaseProperty(Property baseProperty) { 2138 this.baseProperty = baseProperty; 2139 } 2140 2141 public Property getProfileProperty() { 2142 return profileProperty; 2143 } 2144 2145 public void setProfileProperty(Property profileProperty) { 2146 this.profileProperty = profileProperty; 2147 } 2148 2149 public String summary() { 2150 return path; 2151 } 2152 2153 } 2154 2155 public class VariableForProfiling { 2156 private VariableMode mode; 2157 private String name; 2158 private PropertyWithType property; 2159 2160 public VariableForProfiling(VariableMode mode, String name, PropertyWithType property) { 2161 super(); 2162 this.mode = mode; 2163 this.name = name; 2164 this.property = property; 2165 } 2166 public VariableMode getMode() { 2167 return mode; 2168 } 2169 public String getName() { 2170 return name; 2171 } 2172 public PropertyWithType getProperty() { 2173 return property; 2174 } 2175 public String summary() { 2176 return name+": "+property.summary(); 2177 } 2178 } 2179 2180 public class VariablesForProfiling { 2181 private List<VariableForProfiling> list = new ArrayList<VariableForProfiling>(); 2182 private boolean optional; 2183 private boolean repeating; 2184 2185 public VariablesForProfiling(boolean optional, boolean repeating) { 2186 this.optional = optional; 2187 this.repeating = repeating; 2188 } 2189 2190 public void add(VariableMode mode, String name, String path, Property property, TypeDetails types) { 2191 add(mode, name, new PropertyWithType(path, property, null, types)); 2192 } 2193 2194 public void add(VariableMode mode, String name, String path, Property baseProperty, Property profileProperty, TypeDetails types) { 2195 add(mode, name, new PropertyWithType(path, baseProperty, profileProperty, types)); 2196 } 2197 2198 public void add(VariableMode mode, String name, PropertyWithType property) { 2199 VariableForProfiling vv = null; 2200 for (VariableForProfiling v : list) 2201 if ((v.mode == mode) && v.getName().equals(name)) 2202 vv = v; 2203 if (vv != null) 2204 list.remove(vv); 2205 list.add(new VariableForProfiling(mode, name, property)); 2206 } 2207 2208 public VariablesForProfiling copy(boolean optional, boolean repeating) { 2209 VariablesForProfiling result = new VariablesForProfiling(optional, repeating); 2210 result.list.addAll(list); 2211 return result; 2212 } 2213 2214 public VariablesForProfiling copy() { 2215 VariablesForProfiling result = new VariablesForProfiling(optional, repeating); 2216 result.list.addAll(list); 2217 return result; 2218 } 2219 2220 public VariableForProfiling get(VariableMode mode, String name) { 2221 if (mode == null) { 2222 for (VariableForProfiling v : list) 2223 if ((v.mode == VariableMode.OUTPUT) && v.getName().equals(name)) 2224 return v; 2225 for (VariableForProfiling v : list) 2226 if ((v.mode == VariableMode.INPUT) && v.getName().equals(name)) 2227 return v; 2228 } 2229 for (VariableForProfiling v : list) 2230 if ((v.mode == mode) && v.getName().equals(name)) 2231 return v; 2232 return null; 2233 } 2234 2235 public String summary() { 2236 CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 2237 CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); 2238 for (VariableForProfiling v : list) 2239 if (v.mode == VariableMode.INPUT) 2240 s.append(v.summary()); 2241 else 2242 t.append(v.summary()); 2243 return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; 2244 } 2245 } 2246 2247 public class StructureMapAnalysis { 2248 private List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2249 private XhtmlNode summary; 2250 public List<StructureDefinition> getProfiles() { 2251 return profiles; 2252 } 2253 public XhtmlNode getSummary() { 2254 return summary; 2255 } 2256 2257 } 2258 2259 /** 2260 * Given a structure map, return a set of analyses on it. 2261 * 2262 * Returned: 2263 * - a list or profiles for what it will create. First profile is the target 2264 * - a table with a summary (in xhtml) for easy human undertanding of the mapping 2265 * 2266 * 2267 * @param appInfo 2268 * @param map 2269 * @return 2270 * @throws Exception 2271 */ 2272 public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws Exception { 2273 ids.clear(); 2274 StructureMapAnalysis result = new StructureMapAnalysis(); 2275 TransformContext context = new TransformContext(appInfo); 2276 VariablesForProfiling vars = new VariablesForProfiling(false, false); 2277 StructureMapGroupComponent start = map.getGroup().get(0); 2278 for (StructureMapGroupInputComponent t : start.getInput()) { 2279 PropertyWithType ti = resolveType(map, t.getType(), t.getMode()); 2280 if (t.getMode() == StructureMapInputMode.SOURCE) 2281 vars.add(VariableMode.INPUT, t.getName(), ti); 2282 else 2283 vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start)); 2284 } 2285 2286 result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid"); 2287 XhtmlNode tr = result.summary.addTag("tr"); 2288 tr.addTag("td").addTag("b").addText("Source"); 2289 tr.addTag("td").addTag("b").addText("Target"); 2290 2291 log("Start Profiling Transform "+map.getUrl()); 2292 analyseGroup("", context, map, vars, start, result); 2293 ProfileUtilities pu = new ProfileUtilities(worker, null, pkp); 2294 for (StructureDefinition sd : result.getProfiles()) 2295 pu.cleanUpDifferential(sd); 2296 return result; 2297 } 2298 2299 2300 private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws Exception { 2301 log(indent+"Analyse Group : "+group.getName()); 2302 // todo: extends 2303 // todo: check inputs 2304 XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title"); 2305 XhtmlNode xs = tr.addTag("td"); 2306 XhtmlNode xt = tr.addTag("td"); 2307 for (StructureMapGroupInputComponent inp : group.getInput()) { 2308 if (inp.getMode() == StructureMapInputMode.SOURCE) 2309 noteInput(vars, inp, VariableMode.INPUT, xs); 2310 if (inp.getMode() == StructureMapInputMode.TARGET) 2311 noteInput(vars, inp, VariableMode.OUTPUT, xt); 2312 } 2313 for (StructureMapGroupRuleComponent r : group.getRule()) { 2314 analyseRule(indent+" ", context, map, vars, group, r, result); 2315 } 2316 } 2317 2318 2319 private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) { 2320 VariableForProfiling v = vars.get(mode, inp.getName()); 2321 if (v != null) 2322 xs.addText("Input: "+v.property.getPath()); 2323 } 2324 2325 private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws Exception { 2326 log(indent+"Analyse rule : "+rule.getName()); 2327 XhtmlNode tr = result.summary.addTag("tr"); 2328 XhtmlNode xs = tr.addTag("td"); 2329 XhtmlNode xt = tr.addTag("td"); 2330 2331 VariablesForProfiling srcVars = vars.copy(); 2332 if (rule.getSource().size() != 1) 2333 throw new Exception("Rule \""+rule.getName()+"\": not handled yet"); 2334 VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs); 2335 2336 TargetWriter tw = new TargetWriter(); 2337 for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { 2338 analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName()); 2339 } 2340 tw.commit(xt); 2341 2342 for (StructureMapGroupRuleComponent childrule : rule.getRule()) { 2343 analyseRule(indent+" ", context, map, source, group, childrule, result); 2344 } 2345// for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { 2346// executeDependency(indent+" ", context, map, v, group, dependent); // do we need group here? 2347// } 2348 } 2349 2350 public class StringPair { 2351 private String var; 2352 private String desc; 2353 public StringPair(String var, String desc) { 2354 super(); 2355 this.var = var; 2356 this.desc = desc; 2357 } 2358 public String getVar() { 2359 return var; 2360 } 2361 public String getDesc() { 2362 return desc; 2363 } 2364 } 2365 public class TargetWriter { 2366 private Map<String, String> newResources = new HashMap<String, String>(); 2367 private List<StringPair> assignments = new ArrayList<StringPair>(); 2368 private List<StringPair> keyProps = new ArrayList<StringPair>(); 2369 private CommaSeparatedStringBuilder txt = new CommaSeparatedStringBuilder(); 2370 2371 public void newResource(String var, String name) { 2372 newResources.put(var, name); 2373 txt.append("new "+name); 2374 } 2375 2376 public void valueAssignment(String context, String desc) { 2377 assignments.add(new StringPair(context, desc)); 2378 txt.append(desc); 2379 } 2380 2381 public void keyAssignment(String context, String desc) { 2382 keyProps.add(new StringPair(context, desc)); 2383 txt.append(desc); 2384 } 2385 public void commit(XhtmlNode xt) { 2386 if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 1 && newResources.containsKey(keyProps.get(0).getVar()) ) { 2387 xt.addText("new "+assignments.get(0).desc+" ("+keyProps.get(0).desc.substring(keyProps.get(0).desc.indexOf(".")+1)+")"); 2388 } else if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 0) { 2389 xt.addText("new "+assignments.get(0).desc); 2390 } else { 2391 xt.addText(txt.toString()); 2392 } 2393 } 2394 } 2395 2396 private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws Exception { 2397 VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext()); 2398 if (var == null) 2399 throw new FHIRException("Rule \""+ruleId+"\": Unknown input variable "+src.getContext()); 2400 PropertyWithType prop = var.getProperty(); 2401 2402 boolean optional = false; 2403 boolean repeating = false; 2404 2405 if (src.hasCondition()) { 2406 optional = true; 2407 } 2408 2409 if (src.hasElement()) { 2410 Property element = prop.getBaseProperty().getChild(prop.types.getType(), src.getElement()); 2411 if (element == null) 2412 throw new Exception("Rule \""+ruleId+"\": Unknown element name "+src.getElement()); 2413 if (element.getDefinition().getMin() == 0) 2414 optional = true; 2415 if (element.getDefinition().getMax().equals("*")) 2416 repeating = true; 2417 VariablesForProfiling result = vars.copy(optional, repeating); 2418 TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON); 2419 for (TypeRefComponent tr : element.getDefinition().getType()) { 2420 if (!tr.hasCode()) 2421 throw new Error("Rule \""+ruleId+"\": Element has no type"); 2422 ProfiledType pt = new ProfiledType(tr.getCode()); 2423 if (tr.hasProfile()) 2424 pt.addProfiles(tr.getProfile()); 2425 if (element.getDefinition().hasBinding()) 2426 pt.addBinding(element.getDefinition().getBinding()); 2427 type.addType(pt); 2428 } 2429 td.addText(prop.getPath()+"."+src.getElement()); 2430 if (src.hasVariable()) 2431 result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath()+"."+src.getElement(), element, null, type)); 2432 return result; 2433 } else { 2434 td.addText(prop.getPath()); // ditto! 2435 return vars.copy(optional, repeating); 2436 } 2437 } 2438 2439 2440 private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List<StructureDefinition> profiles, String sliceName) throws Exception { 2441 VariableForProfiling var = null; 2442 if (tgt.hasContext()) { 2443 var = vars.get(VariableMode.OUTPUT, tgt.getContext()); 2444 if (var == null) 2445 throw new Exception("Rule \""+ruleId+"\": target context not known: "+tgt.getContext()); 2446 if (!tgt.hasElement()) 2447 throw new Exception("Rule \""+ruleId+"\": Not supported yet"); 2448 } 2449 2450 2451 TypeDetails type = null; 2452 if (tgt.hasTransform()) { 2453 type = analyseTransform(context, map, tgt, var, vars); 2454 // profiling: dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); 2455 } else { 2456 Property vp = var.property.baseProperty.getChild(tgt.getElement(), tgt.getElement()); 2457 if (vp == null) 2458 throw new Exception("Unknown Property "+tgt.getElement()+" on "+var.property.path); 2459 2460 type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement())); 2461 } 2462 2463 if (tgt.getTransform() == StructureMapTransform.CREATE) { 2464 String s = getParamString(vars, tgt.getParameter().get(0)); 2465 if (worker.getResourceNames().contains(s)) 2466 tw.newResource(tgt.getVariable(), s); 2467 } else { 2468 boolean mapsSrc = false; 2469 for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { 2470 Type pr = p.getValue(); 2471 if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv)) 2472 mapsSrc = true; 2473 } 2474 if (mapsSrc) { 2475 if (var == null) 2476 throw new Error("Rule \""+ruleId+"\": Attempt to assign with no context"); 2477 tw.valueAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+getTransformSuffix(tgt.getTransform())); 2478 } else if (tgt.hasContext()) { 2479 if (isSignificantElement(var.property, tgt.getElement())) { 2480 String td = ""; 2481 if (td != null) 2482 tw.keyAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+" = "+td); 2483 } 2484 } 2485 } 2486 Type fixed = generateFixedValue(tgt); 2487 2488 PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt); 2489 if (tgt.hasVariable()) 2490 if (tgt.hasElement()) 2491 vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); 2492 else 2493 vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); 2494 } 2495 2496 private Type generateFixedValue(StructureMapGroupRuleTargetComponent tgt) { 2497 if (!allParametersFixed(tgt)) 2498 return null; 2499 if (!tgt.hasTransform()) 2500 return null; 2501 switch (tgt.getTransform()) { 2502 case COPY: return tgt.getParameter().get(0).getValue(); 2503 case TRUNCATE: return null; 2504 //case ESCAPE: 2505 //case CAST: 2506 //case APPEND: 2507 case TRANSLATE: return null; 2508 //case DATEOP, 2509 //case UUID, 2510 //case POINTER, 2511 //case EVALUATE, 2512 case CC: 2513 CodeableConcept cc = new CodeableConcept(); 2514 cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue())); 2515 return cc; 2516 case C: 2517 return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()); 2518 case QTY: return null; 2519 //case ID, 2520 //case CP, 2521 default: 2522 return null; 2523 } 2524 } 2525 2526 @SuppressWarnings("rawtypes") 2527 private Coding buildCoding(Type value1, Type value2) { 2528 return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()) ; 2529 } 2530 2531 private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) { 2532 for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { 2533 Type pr = p.getValue(); 2534 if (pr instanceof IdType) 2535 return false; 2536 } 2537 return true; 2538 } 2539 2540 2541 2542 private boolean isSignificantElement(PropertyWithType property, String element) { 2543 if ("Observation".equals(property.getPath())) 2544 return "code".equals(element); 2545 else if ("Bundle".equals(property.getPath())) 2546 return "type".equals(element); 2547 else 2548 return false; 2549 } 2550 2551 private String getTransformSuffix(StructureMapTransform transform) { 2552 switch (transform) { 2553 case COPY: return ""; 2554 case TRUNCATE: return " (truncated)"; 2555 //case ESCAPE: 2556 //case CAST: 2557 //case APPEND: 2558 case TRANSLATE: return " (translated)"; 2559 //case DATEOP, 2560 //case UUID, 2561 //case POINTER, 2562 //case EVALUATE, 2563 case CC: return " (--> CodeableConcept)"; 2564 case C: return " (--> Coding)"; 2565 case QTY: return " (--> Quantity)"; 2566 //case ID, 2567 //case CP, 2568 default: 2569 return " {??)"; 2570 } 2571 } 2572 2573 private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List<StructureDefinition> profiles, String sliceName, Type fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException { 2574 if (var == null) { 2575 assert (Utilities.noString(element)); 2576 // 1. start the new structure definition 2577 StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType()); 2578 if (sdn == null) 2579 throw new FHIRException("Unable to find definition for "+type.getType()); 2580 ElementDefinition edn = sdn.getSnapshot().getElementFirstRep(); 2581 PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt); 2582 2583// // 2. hook it into the base bundle 2584// if (type.getType().startsWith("http://hl7.org/fhir/StructureDefinition/") && worker.getResourceNames().contains(type.getType().substring(40))) { 2585// StructureDefinition sd = var.getProperty().profileProperty.getStructure(); 2586// ElementDefinition ed = sd.getDifferential().addElement(); 2587// ed.setPath("Bundle.entry"); 2588// ed.setName(sliceName); 2589// ed.setMax("1"); // well, it is for now... 2590// ed = sd.getDifferential().addElement(); 2591// ed.setPath("Bundle.entry.fullUrl"); 2592// ed.setMin(1); 2593// ed = sd.getDifferential().addElement(); 2594// ed.setPath("Bundle.entry.resource"); 2595// ed.setMin(1); 2596// ed.addType().setCode(pn.getProfileProperty().getStructure().getType()).setProfile(pn.getProfileProperty().getStructure().getUrl()); 2597// } 2598 return pn; 2599 } else { 2600 assert (!Utilities.noString(element)); 2601 Property pvb = var.getProperty().getBaseProperty(); 2602 Property pvd = var.getProperty().getProfileProperty(); 2603 Property pc = pvb.getChild(element, var.property.types); 2604 if (pc == null) 2605 throw new DefinitionException("Unable to find a definition for "+pvb.getDefinition().getPath()+"."+element); 2606 2607 // the profile structure definition (derived) 2608 StructureDefinition sd = var.getProperty().profileProperty.getStructure(); 2609 ElementDefinition ednew = sd.getDifferential().addElement(); 2610 ednew.setPath(var.getProperty().profileProperty.getDefinition().getPath()+"."+pc.getName()); 2611 ednew.setUserData("slice-name", sliceName); 2612 ednew.setFixed(fixed); 2613 for (ProfiledType pt : type.getProfiledTypes()) { 2614 if (pt.hasBindings()) 2615 ednew.setBinding(pt.getBindings().get(0)); 2616 if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2617 String t = pt.getUri().substring(40); 2618 t = checkType(t, pc, pt.getProfiles()); 2619 if (t != null) { 2620 if (pt.hasProfiles()) { 2621 for (String p : pt.getProfiles()) 2622 if (t.equals("Reference")) 2623 ednew.getType(t).addTargetProfile(p); 2624 else 2625 ednew.getType(t).addProfile(p); 2626 } else 2627 ednew.getType(t); 2628 } 2629 } 2630 } 2631 2632 return new PropertyWithType(var.property.path+"."+element, pc, new Property(worker, ednew, sd), type); 2633 } 2634 } 2635 2636 2637 2638 private String checkType(String t, Property pvb, List<String> profiles) throws FHIRException { 2639 if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile())) 2640 return null; 2641 for (TypeRefComponent tr : pvb.getDefinition().getType()) { 2642 if (isCompatibleType(t, tr.getCode())) 2643 return tr.getCode(); // note what is returned - the base type, not the inferred mapping type 2644 } 2645 throw new FHIRException("The type "+t+" is not compatible with the allowed types for "+pvb.getDefinition().getPath()); 2646 } 2647 2648 private boolean profilesMatch(List<String> profiles, List<CanonicalType> profile) { 2649 return profiles == null || profiles.size() == 0 || profile.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile.get(0).getValue())); 2650 } 2651 2652 private boolean isCompatibleType(String t, String code) { 2653 if (t.equals(code)) 2654 return true; 2655 if (t.equals("string")) { 2656 StructureDefinition sd = worker.fetchTypeDefinition(code); 2657 if (sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string")) 2658 return true; 2659 } 2660 return false; 2661 } 2662 2663 private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException { 2664 switch (tgt.getTransform()) { 2665 case CREATE : 2666 String p = getParamString(vars, tgt.getParameter().get(0)); 2667 return new TypeDetails(CollectionStatus.SINGLETON, p); 2668 case COPY : 2669 return getParam(vars, tgt.getParameter().get(0)); 2670 case EVALUATE : 2671 ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); 2672 if (expr == null) { 2673 expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size()-1))); 2674 tgt.setUserData(MAP_WHERE_EXPRESSION, expr); 2675 } 2676 return fpe.check(vars, null, expr); 2677 2678////case TRUNCATE : 2679//// String src = getParamString(vars, tgt.getParameter().get(0)); 2680//// String len = getParamString(vars, tgt.getParameter().get(1)); 2681//// if (Utilities.isInteger(len)) { 2682//// int l = Integer.parseInt(len); 2683//// if (src.length() > l) 2684//// src = src.substring(0, l); 2685//// } 2686//// return new StringType(src); 2687////case ESCAPE : 2688//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 2689////case CAST : 2690//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 2691////case APPEND : 2692//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 2693 case TRANSLATE : 2694 return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept"); 2695 case CC: 2696 ProfiledType res = new ProfiledType("CodeableConcept"); 2697 if (tgt.getParameter().size() >= 2 && isParamId(vars, tgt.getParameter().get(1))) { 2698 TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).property.types; 2699 if (td != null && td.hasBinding()) 2700 // todo: do we need to check that there's no implicit translation her? I don't think we do... 2701 res.addBinding(td.getBinding()); 2702 } 2703 return new TypeDetails(CollectionStatus.SINGLETON, res); 2704 case C: 2705 return new TypeDetails(CollectionStatus.SINGLETON, "Coding"); 2706 case QTY: 2707 return new TypeDetails(CollectionStatus.SINGLETON, "Quantity"); 2708 case REFERENCE : 2709 VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep())); 2710 if (vrs == null) 2711 throw new FHIRException("Unable to resolve variable \""+getParamId(vars, tgt.getParameterFirstRep())+"\""); 2712 String profile = vrs.property.getProfileProperty().getStructure().getUrl(); 2713 TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON); 2714 td.addType("Reference", profile); 2715 return td; 2716////case DATEOP : 2717//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 2718////case UUID : 2719//// return new IdType(UUID.randomUUID().toString()); 2720////case POINTER : 2721//// Base b = getParam(vars, tgt.getParameter().get(0)); 2722//// if (b instanceof Resource) 2723//// return new UriType("urn:uuid:"+((Resource) b).getId()); 2724//// else 2725//// throw new FHIRException("Transform engine cannot point at an element of type "+b.fhirType()); 2726 default: 2727 throw new Error("Transform Unknown or not handled yet: "+tgt.getTransform().toCode()); 2728 } 2729 } 2730 private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { 2731 Type p = parameter.getValue(); 2732 if (p == null || p instanceof IdType) 2733 return null; 2734 if (!p.hasPrimitiveValue()) 2735 return null; 2736 return p.primitiveValue(); 2737 } 2738 2739 private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { 2740 Type p = parameter.getValue(); 2741 if (p == null || !(p instanceof IdType)) 2742 return null; 2743 return p.primitiveValue(); 2744 } 2745 2746 private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { 2747 Type p = parameter.getValue(); 2748 if (p == null || !(p instanceof IdType)) 2749 return false; 2750 return vars.get(null, p.primitiveValue()) != null; 2751 } 2752 2753 private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { 2754 Type p = parameter.getValue(); 2755 if (!(p instanceof IdType)) 2756 return new TypeDetails(CollectionStatus.SINGLETON, ProfileUtilities.sdNs(p.fhirType(), worker.getOverrideVersionNs())); 2757 else { 2758 String n = ((IdType) p).asStringValue(); 2759 VariableForProfiling b = vars.get(VariableMode.INPUT, n); 2760 if (b == null) 2761 b = vars.get(VariableMode.OUTPUT, n); 2762 if (b == null) 2763 throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")"); 2764 return b.getProperty().getTypes(); 2765 } 2766 } 2767 2768 private PropertyWithType createProfile(StructureMap map, List<StructureDefinition> profiles, PropertyWithType prop, String sliceName, Base ctxt) throws FHIRException { 2769 if (prop.getBaseProperty().getDefinition().getPath().contains(".")) 2770 throw new DefinitionException("Unable to process entry point"); 2771 2772 String type = prop.getBaseProperty().getDefinition().getPath(); 2773 String suffix = ""; 2774 if (ids.containsKey(type)) { 2775 int id = ids.get(type); 2776 id++; 2777 ids.put(type, id); 2778 suffix = "-"+Integer.toString(id); 2779 } else 2780 ids.put(type, 0); 2781 2782 StructureDefinition profile = new StructureDefinition(); 2783 profiles.add(profile); 2784 profile.setDerivation(TypeDerivationRule.CONSTRAINT); 2785 profile.setType(type); 2786 profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl()); 2787 profile.setName("Profile for "+profile.getType()+" for "+sliceName); 2788 profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition")+"-"+profile.getType()+suffix); 2789 ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform 2790 profile.setId(map.getId()+"-"+profile.getType()+suffix); 2791 profile.setStatus(map.getStatus()); 2792 profile.setExperimental(map.getExperimental()); 2793 profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation"); 2794 for (ContactDetail c : map.getContact()) { 2795 ContactDetail p = profile.addContact(); 2796 p.setName(c.getName()); 2797 for (ContactPoint cc : c.getTelecom()) 2798 p.addTelecom(cc); 2799 } 2800 profile.setDate(map.getDate()); 2801 profile.setCopyright(map.getCopyright()); 2802 profile.setFhirVersion(FHIRVersion.fromCode(Constants.VERSION)); 2803 profile.setKind(prop.getBaseProperty().getStructure().getKind()); 2804 profile.setAbstract(false); 2805 ElementDefinition ed = profile.getDifferential().addElement(); 2806 ed.setPath(profile.getType()); 2807 prop.profileProperty = new Property(worker, ed, profile); 2808 return prop; 2809 } 2810 2811 private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws Exception { 2812 for (StructureMapStructureComponent imp : map.getStructure()) { 2813 if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) || 2814 (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) { 2815 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); 2816 if (sd == null) 2817 throw new Exception("Import "+imp.getUrl()+" cannot be resolved"); 2818 if (sd.getId().equals(type)) { 2819 return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl())); 2820 } 2821 } 2822 } 2823 throw new Exception("Unable to find structure definition for "+type+" in imports"); 2824 } 2825 2826 2827 public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException { 2828 String id = getLogicalMappingId(sd); 2829 if (id == null) 2830 return null; 2831 String prefix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_PREFIX); 2832 String suffix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_SUFFIX); 2833 if (prefix == null || suffix == null) 2834 return null; 2835 // we build this by text. Any element that has a mapping, we put it's mappings inside it.... 2836 StringBuilder b = new StringBuilder(); 2837 b.append(prefix); 2838 2839 ElementDefinition root = sd.getSnapshot().getElementFirstRep(); 2840 String m = getMapping(root, id); 2841 if (m != null) 2842 b.append(m+"\r\n"); 2843 addChildMappings(b, id, "", sd, root, false); 2844 b.append("\r\n"); 2845 b.append(suffix); 2846 b.append("\r\n"); 2847 StructureMap map = parse(b.toString(), sd.getUrl()); 2848 map.setId(tail(map.getUrl())); 2849 if (!map.hasStatus()) 2850 map.setStatus(PublicationStatus.DRAFT); 2851 map.getText().setStatus(NarrativeStatus.GENERATED); 2852 map.getText().setDiv(new XhtmlNode(NodeType.Element, "div")); 2853 map.getText().getDiv().addTag("pre").addText(render(map)); 2854 return map; 2855 } 2856 2857 2858 private String tail(String url) { 2859 return url.substring(url.lastIndexOf("/")+1); 2860 } 2861 2862 2863 private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException { 2864 boolean first = true; 2865 List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed); 2866 for (ElementDefinition child : children) { 2867 if (first && inner) { 2868 b.append(" then {\r\n"); 2869 first = false; 2870 } 2871 String map = getMapping(child, id); 2872 if (map != null) { 2873 b.append(indent+" "+child.getPath()+": "+map); 2874 addChildMappings(b, id, indent+" ", sd, child, true); 2875 b.append("\r\n"); 2876 } 2877 } 2878 if (!first && inner) 2879 b.append(indent+"}"); 2880 2881 } 2882 2883 2884 private String getMapping(ElementDefinition ed, String id) { 2885 for (ElementDefinitionMappingComponent map : ed.getMapping()) 2886 if (id.equals(map.getIdentity())) 2887 return map.getMap(); 2888 return null; 2889 } 2890 2891 2892 private String getLogicalMappingId(StructureDefinition sd) { 2893 String id = null; 2894 for (StructureDefinitionMappingComponent map : sd.getMapping()) { 2895 if ("http://hl7.org/fhir/logical".equals(map.getUri())) 2896 return map.getIdentity(); 2897 } 2898 return null; 2899 } 2900 2901}