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}