2 // Copyright (c) Microsoft Corporation. All rights reserved.
5 namespace Microsoft.Activities.Presentation.Xaml
8 using System.Activities;
9 using System.Activities.Debugger;
10 using System.Activities.Debugger.Symbol;
11 using System.Activities.Presentation;
12 using System.Activities.Presentation.ViewState;
13 using System.Activities.Presentation.Xaml;
14 using System.Activities.XamlIntegration;
15 using System.Collections;
16 using System.Collections.Generic;
17 using System.Diagnostics;
18 using System.Globalization;
20 using System.Runtime.Versioning;
23 internal class WorkflowDesignerXamlHelper
25 private IWorkflowDesignerXamlHelperExecutionContext executionContext;
27 private XamlMember dynamicActivityPropertyNameMember;
29 private XamlMember dynamicActivityPropertyValueMember;
31 internal WorkflowDesignerXamlHelper(IWorkflowDesignerXamlHelperExecutionContext executionContext)
33 this.executionContext = executionContext;
34 SharedFx.Assert(this.executionContext != null, "this.executionContext != null");
35 SharedFx.Assert(this.executionContext.XamlSchemaContext != null, "this.executionContext.XamlSchemaContext != null");
37 this.dynamicActivityPropertyNameMember = new XamlMember(typeof(DynamicActivityProperty).GetProperty("Name"), this.XamlSchemaContext);
38 this.dynamicActivityPropertyValueMember = new XamlMember(typeof(DynamicActivityProperty).GetProperty("Value"), this.XamlSchemaContext);
41 private delegate void SourceLocationFoundCallback(object obj, SourceLocation sourceLocation);
43 internal enum DeserializationMode
49 public FrameworkName FrameworkName
51 get { return this.executionContext.FrameworkName; }
54 public WorkflowDesignerXamlSchemaContext XamlSchemaContext
56 get { return this.executionContext.XamlSchemaContext; }
59 public ViewStateIdManager IdManager
61 get { return this.executionContext.IdManager; }
64 public WorkflowSymbol LastWorkflowSymbol
68 return this.executionContext.LastWorkflowSymbol;
73 this.executionContext.LastWorkflowSymbol = value;
77 public string LocalAssemblyName
79 get { return this.executionContext.LocalAssemblyName; }
82 public void OnSerializationCompleted(Dictionary<object, object> sourceLocationObjectToModelItemObjectMapping)
84 this.executionContext.OnSerializationCompleted(sourceLocationObjectToModelItemObjectMapping);
87 public void OnBeforeDeserialize()
89 this.executionContext.OnBeforeDeserialize();
92 public void OnSourceLocationFound(object target, SourceLocation sourceLocation)
94 this.executionContext.OnSourceLocationFound(target, sourceLocation);
97 public void OnAfterDeserialize(Dictionary<string, SourceLocation> viewStateDataSourceLocationMapping)
99 this.executionContext.OnAfterDeserialize(viewStateDataSourceLocationMapping);
102 // Get root Activity. Currently only handle when the object is ActivityBuilder or Activity.
103 // May return null if it does not know how to get the root activity.
104 internal static Activity GetRootWorkflowElement(object rootModelObject)
106 SharedFx.Assert(rootModelObject != null, "Cannot pass null as rootModelObject");
107 Activity rootWorkflowElement;
108 IDebuggableWorkflowTree debuggableWorkflowTree = rootModelObject as IDebuggableWorkflowTree;
109 if (debuggableWorkflowTree != null)
111 rootWorkflowElement = debuggableWorkflowTree.GetWorkflowRoot();
116 rootWorkflowElement = rootModelObject as Activity;
119 return rootWorkflowElement;
122 internal static Activity GetRootElementForSymbol(object rootInstance, Activity documentRootElement)
124 ActivityBuilder activityBuilder = rootInstance as ActivityBuilder;
125 if (activityBuilder != null)
127 documentRootElement = activityBuilder.ConvertToDynamicActivity();
130 return documentRootElement;
133 // Copy the root namespaces from a reader to a writer.
134 // DesignTimeXamlWriter follows proper XAML convention by omitting the assembly name from
135 // clr-namespaces in the local assembly. However, VB Expressions aren't local-assembly-aware,
136 // and require an assembly name. So for every clr-namespace with no assembly name, we add an
137 // additional namespace record with an assembly name, to support VB.
138 // We only do this at the root level, since the designer only writes out namespaces at the root level.
139 internal void CopyNamespacesAndAddLocalAssembly(System.Xaml.XamlReader activityBuilderReader, System.Xaml.XamlWriter objectWriter)
141 // Designer loads alwas provide line info
142 IXamlLineInfo lineInfo = (IXamlLineInfo)activityBuilderReader;
143 IXamlLineInfoConsumer lineInfoConsumer = (IXamlLineInfoConsumer)objectWriter;
144 HashSet<string> definedPrefixes = new HashSet<string>();
145 List<NamespaceDeclaration> localAsmNamespaces = null;
147 while (activityBuilderReader.Read())
149 lineInfoConsumer.SetLineInfo(lineInfo.LineNumber, lineInfo.LinePosition);
151 if (activityBuilderReader.NodeType == XamlNodeType.NamespaceDeclaration)
153 definedPrefixes.Add(activityBuilderReader.Namespace.Prefix);
154 if (this.XamlSchemaContext.IsClrNamespaceWithNoAssembly(activityBuilderReader.Namespace.Namespace))
156 if (localAsmNamespaces == null)
158 localAsmNamespaces = new List<NamespaceDeclaration>();
161 localAsmNamespaces.Add(activityBuilderReader.Namespace);
164 objectWriter.WriteNode(activityBuilderReader);
168 if (localAsmNamespaces != null)
170 foreach (NamespaceDeclaration ns in localAsmNamespaces)
172 string prefix = null;
177 prefix = ns.Prefix + i.ToString(CultureInfo.InvariantCulture);
179 while (definedPrefixes.Contains(prefix));
180 string fullNs = this.XamlSchemaContext.AddLocalAssembly(ns.Namespace);
181 objectWriter.WriteNamespace(new NamespaceDeclaration(fullNs, prefix));
182 definedPrefixes.Add(prefix);
186 objectWriter.WriteNode(activityBuilderReader);
192 internal string SerializeToString(object obj, string fileName)
194 FrameworkName targetFramework = this.FrameworkName;
196 string sourceFile = null;
197 Activity rootWorkflowElement = GetRootWorkflowElement(obj);
198 Dictionary<int, object> modelItemObjectSequence = null;
200 // If the current target is 4.5 or Higher, let us not write the filename attribute as DebugSymbol eliminates the need for it.
201 // We will serialize without the Filename by removing it from the element and adding it back after serialization
202 if (targetFramework.Is45OrHigher())
204 if (AttachablePropertyServices.TryGetProperty<string>(rootWorkflowElement, XamlDebuggerXmlReader.FileNameName, out sourceFile))
206 AttachablePropertyServices.RemoveProperty(rootWorkflowElement, XamlDebuggerXmlReader.FileNameName);
210 TextWriter textWriter = new StringWriter(CultureInfo.InvariantCulture);
212 WorkflowDesignerXamlSchemaContext schemaContext = obj is ActivityBuilder ? this.XamlSchemaContext : new WorkflowDesignerXamlSchemaContext(null);
214 bool shouldWriteDebugSymbol = true;
215 if (targetFramework.IsLessThan45())
217 shouldWriteDebugSymbol = false;
220 System.Xaml.XamlReader outerReader;
221 XamlObjectReaderWithSequence innerReader;
226 new DesignTimeXamlWriter(textWriter, schemaContext, shouldWriteDebugSymbol),
227 delegate(DesignTimeXamlWriter designTimeXamlWriter)
230 ActivityXamlServices.CreateBuilderWriter(designTimeXamlWriter),
231 delegate(System.Xaml.XamlWriter activityBuilderWriter)
234 new ActivityTemplateFactoryBuilderWriter(activityBuilderWriter, schemaContext),
235 delegate(System.Xaml.XamlWriter writer)
237 // If ViewStateManager property is attached, remove it. It needs to be regenerated if we target 4.5.
238 // It should be removed if we're targeting 4.0.
239 AttachablePropertyServices.RemoveProperty(obj, WorkflowViewState.ViewStateManagerProperty);
241 this.CreateXamlObjectReaders(obj, schemaContext, out outerReader, out innerReader);
247 #if ERROR_TOLERANT_SUPPORT
248 if (ErrorActivity.GetHasErrorActivities(obj))
250 ErrorTolerantObjectWriter.TransformAndStripErrors(outerReader, writer);
255 XamlServices.Transform(outerReader, writer);
256 #if ERROR_TOLERANT_SUPPORT
262 modelItemObjectSequence = innerReader.SequenceNumberToObjectMap;
268 string retVal = textWriter.ToString();
270 if (targetFramework.IsLessThan45())
272 if (sourceFile != null)
274 XamlDebuggerXmlReader.SetFileName(rootWorkflowElement, sourceFile);
278 IList<XamlLoadErrorInfo> loadErrors;
279 Dictionary<object, SourceLocation> sourceLocations;
280 object deserializedObject = this.DeserializeString(retVal, out loadErrors, out sourceLocations);
282 if (!string.IsNullOrEmpty(fileName) && targetFramework.Is45OrHigher())
284 this.LastWorkflowSymbol = this.GetWorkflowSymbol(fileName, deserializedObject, sourceLocations);
285 if (this.LastWorkflowSymbol != null)
287 retVal = retVal.Replace(DesignTimeXamlWriter.EmptyWorkflowSymbol, this.LastWorkflowSymbol.Encode());
291 // The symbol is actually removed in GetAttachedWorkflowSymbol() after deserialization completes.
292 System.Xaml.AttachablePropertyServices.RemoveProperty(GetRootWorkflowElement(deserializedObject), DebugSymbol.SymbolName);
293 this.CreateXamlObjectReaders(deserializedObject, schemaContext, out outerReader, out innerReader);
294 Dictionary<object, object> sourceLocationObjectToModelItemObjectMapping = new Dictionary<object, object>(ObjectReferenceEqualityComparer<object>.Default);
299 while (outerReader.Read())
303 Dictionary<int, object> sourceLocationObjectSequence = innerReader.SequenceNumberToObjectMap;
304 foreach (KeyValuePair<int, object> sourceLocationObjectEntry in sourceLocationObjectSequence)
306 int key = sourceLocationObjectEntry.Key;
307 object sourceLocationObject = sourceLocationObjectEntry.Value;
308 object modelItemObject;
310 if (modelItemObjectSequence.TryGetValue(key, out modelItemObject))
312 sourceLocationObjectToModelItemObjectMapping.Add(sourceLocationObject, modelItemObject);
318 this.OnSerializationCompleted(sourceLocationObjectToModelItemObjectMapping);
322 internal void CreateXamlObjectReaders(object deserializedObject, XamlSchemaContext schemaContext, out XamlReader newWorkflowReader, out XamlObjectReaderWithSequence deserializedObjectSequenceBuilder)
324 deserializedObjectSequenceBuilder = new XamlObjectReaderWithSequence(deserializedObject, schemaContext);
325 if (this.FrameworkName.Is45OrHigher())
327 newWorkflowReader = ViewStateXamlHelper.ConvertAttachedPropertiesToViewState(deserializedObjectSequenceBuilder, this.IdManager);
331 newWorkflowReader = ViewStateXamlHelper.RemoveIdRefs(deserializedObjectSequenceBuilder);
335 internal object DeserializeString(string text)
337 IList<XamlLoadErrorInfo> loadErrors;
338 Dictionary<object, SourceLocation> sourceLocations;
339 return this.DeserializeString(text, out loadErrors, out sourceLocations);
342 internal object DeserializeString(string text, out IList<XamlLoadErrorInfo> loadErrors, out Dictionary<object, SourceLocation> sourceLocations)
346 return this.DeserializeString(text, DeserializationMode.Default, out loadErrors, out sourceLocations);
348 catch (XamlObjectWriterException)
350 // Fall back to error-tolerant path. We don't do this by default for perf reasons.
351 return this.DeserializeString(text, DeserializationMode.ErrorTolerant, out loadErrors, out sourceLocations);
355 //// XAML writer may throw exception during dispose, therefore the following code will cause exception masking
357 //// using (XamlWriter xamlWriter)
362 //// If there are any exception A thrown within the block, and if xamlWriter.Dispose() throws an exception B
363 //// The exception B will mask exception A and the exception is the ErrorTolerant scenario will be broken.
365 //// The fix to this problem is to ---- any XamlException thrown during Dispose(), we are in general not
366 //// interested in those exceptions.
367 private static void UsingXamlWriter<T>(T xamlWriter, Action<T> work) where T : XamlWriter
369 if (xamlWriter != null)
381 catch (XamlException e)
383 // ignore any XAML exception during closing a XamlWriter
384 Trace.WriteLine(e.Message);
390 // there are two kind of attribute:
391 // 1) in lined : argument="some value"
393 // <Expression ....../>
395 // here, for (1) return the source location of "some value".
396 // for (2) return null
397 private static SourceLocation GetInlineAttributeValueLocation(LineColumnPair startPoint, SourceTextScanner sourceTextScanner)
399 const char SingleQuote = '\'';
400 const char DoubleQuote = '"';
401 const char StartAngleBracket = '<';
402 Tuple<LineColumnPair, char> start = sourceTextScanner.SearchCharAfter(startPoint, SingleQuote, DoubleQuote, StartAngleBracket);
408 if (start.Item2 == StartAngleBracket)
413 Tuple<LineColumnPair, char> end = sourceTextScanner.SearchCharAfter(start.Item1, start.Item2);
416 SharedFx.Assert("end of SourceLocation is not found");
420 return new SourceLocation(null, start.Item1.LineNumber, start.Item1.ColumnNumber, end.Item1.LineNumber, end.Item1.ColumnNumber);
423 private WorkflowSymbol GetWorkflowSymbol(string fileName, object deserializedObject, Dictionary<object, SourceLocation> sourceLocations)
425 if (deserializedObject != null)
427 Activity deserializedRootElement = GetRootWorkflowElement(deserializedObject);
428 if (deserializedRootElement != null)
432 deserializedRootElement = GetRootElementForSymbol(deserializedObject, deserializedRootElement);
433 return new WorkflowSymbol
436 Symbols = SourceLocationProvider.GetSymbols(deserializedRootElement, sourceLocations)
441 if (SharedFx.IsFatal(ex))
446 // This happens when the workflow is invalid so GetSymbols fails.
447 // ---- exception here.
455 private object DeserializeString(string text, DeserializationMode mode, out IList<XamlLoadErrorInfo> loadErrors, out Dictionary<object, SourceLocation> sourceLocations)
457 object result = null;
459 Dictionary<object, SourceLocation> collectingSourceLocations = new Dictionary<object, SourceLocation>(ObjectReferenceEqualityComparer<object>.Default);
460 SourceLocationFoundCallback sourceLocationFoundCallback = new SourceLocationFoundCallback((obj, sourceLocation) =>
462 // If an object appear more than once in the XAML stream (e.g. System.Type, which is cached by reflection)
463 // we count the first occurrence.
464 if (!collectingSourceLocations.ContainsKey(obj))
466 collectingSourceLocations.Add(obj, sourceLocation);
469 this.OnSourceLocationFound(obj, sourceLocation);
472 this.XamlSchemaContext.ContainsConversionRequiredType = false;
473 Dictionary<string, SourceLocation> viewStateDataSourceLocationMapping = null;
474 using (XamlDebuggerXmlReader debuggerReader = new XamlDebuggerXmlReader(new StringReader(text), this.XamlSchemaContext))
476 using (System.Xaml.XamlReader activityBuilderReader = ActivityXamlServices.CreateBuilderReader(debuggerReader))
478 using (System.Xaml.XamlReader activityTemplateFactoryBuilderReader = new ActivityTemplateFactoryBuilderReader(activityBuilderReader, this.XamlSchemaContext))
480 debuggerReader.SourceLocationFound += delegate(object sender, SourceLocationFoundEventArgs args)
482 sourceLocationFoundCallback(args.Target, args.SourceLocation);
485 this.OnBeforeDeserialize();
486 debuggerReader.CollectNonActivitySourceLocation = this.FrameworkName.Is45OrHigher();
488 using (System.Xaml.XamlReader reader = ViewStateXamlHelper.ConvertViewStateToAttachedProperties(activityTemplateFactoryBuilderReader, this.IdManager, out viewStateDataSourceLocationMapping))
492 #if ERROR_TOLERANT_SUPPORT
493 case DeserializationMode.ErrorTolerant:
495 ErrorTolerantObjectWriter tolerantWriter = new ErrorTolerantObjectWriter(reader.SchemaContext);
496 tolerantWriter.LocalAssemblyName = this.LocalAssemblyName;
497 XamlServices.Transform(reader, tolerantWriter);
498 loadErrors = this.CheckFileFormatError(tolerantWriter.LoadErrors);
499 result = tolerantWriter.Result;
500 ErrorActivity.SetHasErrorActivities(result, true);
505 case DeserializationMode.Default:
507 result = this.TransformAndGetPropertySourceLocation(reader, new SourceTextScanner(text), sourceLocationFoundCallback);
509 loadErrors = this.CheckFileFormatError(loadErrors);
519 sourceLocations = collectingSourceLocations;
520 this.OnAfterDeserialize(viewStateDataSourceLocationMapping);
524 // For dynamic activity property, we needs to collect the source location of
525 // its default value when the value is inlined.
526 private KeyValuePair<string, SourceLocation> TransformDynamicActivityProperty(
528 XamlObjectWriter objectWriter,
529 SourceTextScanner sourceTextScanner)
531 // (Number of SM -Number of EM) since SM DAP.Name is read.
532 // SO DAP ---nameReadingLevel=0
533 // SM NAME ---nameReadingLevel=1
534 // SO String ---nameReadingLevel=1
535 // SM Initialize ---nameReadingLevel=2
536 // VA StringValue ---nameReadingLevel=2
537 // EM ---nameReadingLevel=1
538 // SO ---nameReadingLevel=1
539 // EM ---nameReadingLevel=0
540 // EO ---nameReadingLevel=0
541 int nameReadingLevel = 0;
543 IXamlLineInfo lineInfo = (IXamlLineInfo)reader;
544 SourceLocation defaultValueLocation = null;
545 string propertyName = null;
547 while (reader.Read())
549 switch (reader.NodeType)
551 case XamlNodeType.StartMember:
552 if (nameReadingLevel > 0
553 || reader.Member == this.dynamicActivityPropertyNameMember)
557 else if (reader.Member == this.dynamicActivityPropertyValueMember)
559 LineColumnPair startPoint = new LineColumnPair(lineInfo.LineNumber, lineInfo.LinePosition);
560 defaultValueLocation = GetInlineAttributeValueLocation(startPoint, sourceTextScanner);
565 case XamlNodeType.EndMember:
566 if (nameReadingLevel > 0)
573 case XamlNodeType.Value:
574 if (nameReadingLevel > 0)
576 propertyName = reader.Value as string;
582 objectWriter.WriteNode(reader);
585 if (propertyName != null && defaultValueLocation != null)
587 return new KeyValuePair<string, SourceLocation>(propertyName, defaultValueLocation);
590 return new KeyValuePair<string, SourceLocation>();
593 private object TransformAndGetPropertySourceLocation(XamlReader reader, SourceTextScanner sourceTextScanner, SourceLocationFoundCallback sourceLocationFoundCallback)
595 // <property name, value's start location>
596 Dictionary<string, SourceLocation> propertyValueLocationMapping = new Dictionary<string, SourceLocation>();
598 object deserializedObject = null;
599 object earlyResult = null;
602 new XamlObjectWriter(reader.SchemaContext),
603 delegate(XamlObjectWriter objectWriter)
605 if (this.XamlSchemaContext.HasLocalAssembly)
607 this.CopyNamespacesAndAddLocalAssembly(reader, objectWriter);
610 if (!(reader is IXamlLineInfo))
612 XamlServices.Transform(reader, objectWriter);
613 earlyResult = objectWriter.Result;
617 XamlType dynamicActivityPropertyType = this.XamlSchemaContext.GetXamlType(typeof(DynamicActivityProperty));
618 while (reader.Read())
620 // read SubTree will moves the reader pointed to
621 // element after its EO. So, we need to use a while
622 while (!reader.IsEof && reader.NodeType == XamlNodeType.StartObject
623 && dynamicActivityPropertyType == reader.Type)
625 KeyValuePair<string, SourceLocation> nameSourceLocation = this.TransformDynamicActivityProperty(reader.ReadSubtree(), objectWriter, sourceTextScanner);
626 if (nameSourceLocation.Key != null && nameSourceLocation.Value != null && !propertyValueLocationMapping.ContainsKey(nameSourceLocation.Key))
628 propertyValueLocationMapping.Add(nameSourceLocation.Key, nameSourceLocation.Value);
634 objectWriter.WriteNode(reader);
638 deserializedObject = objectWriter.Result;
641 if (earlyResult != null)
646 ActivityBuilder activityBuilder = deserializedObject as ActivityBuilder;
647 if (activityBuilder == null)
649 return deserializedObject;
652 foreach (KeyValuePair<string, SourceLocation> propertyValueLocation in propertyValueLocationMapping)
654 string propertyName = propertyValueLocation.Key;
655 SourceLocation propertyLocation = propertyValueLocation.Value;
656 if (!activityBuilder.Properties.Contains(propertyName))
658 SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "no such property:{0}", propertyName));
662 DynamicActivityProperty property = activityBuilder.Properties[propertyName];
664 if (property == null || property.Value == null)
666 SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "no such property value:{0}", propertyName));
670 object expression = (property.Value is Argument) ? ((Argument)property.Value).Expression : null;
671 if (expression != null)
673 sourceLocationFoundCallback(expression, propertyLocation);
677 sourceLocationFoundCallback(property.Value, propertyLocation);
681 return deserializedObject;
684 private IList<XamlLoadErrorInfo> CheckFileFormatError(IList<XamlLoadErrorInfo> loadErrors)
686 IList<XamlLoadErrorInfo> result = loadErrors;
688 if (this.XamlSchemaContext.ContainsConversionRequiredType)
692 result = new List<XamlLoadErrorInfo>();
695 result.Add(new XamlLoadErrorInfo(SharedSR.FileFormatError, 0, 0));