//---------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------- namespace System.Activities.XamlIntegration { using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Runtime; using System.Windows.Markup; using System.Xaml; using System.Xaml.Schema; using System.Xml; // This class rewrites an case (or null if not an ActivityBuilder), // so we need to compute this value dynamically XamlType activityBuilderXamlType; XamlType activityXamlType; XamlType activityPropertyXamlType; XamlType xamlTypeXamlType; XamlType typeXamlType; XamlType activityPropertyReferenceXamlType; XamlMember activityPropertyType; XamlMember activityPropertyName; XamlMember activityPropertyValue; XamlMember activityBuilderName; XamlMember activityBuilderAttributes; XamlMember activityBuilderProperties; XamlMember activityBuilderPropertyReference; XamlMember activityBuilderPropertyReferences; bool notRewriting; int currentDepth; // we need to accrue namespace so that we can resolve DynamicActivityProperty.Type // and correctly strip superfluous wrapper nodes around default values NamespaceTable namespaceTable; BuilderXamlNode currentState; Stack pendingStates; public ActivityBuilderXamlWriter(XamlWriter innerWriter) : base() { this.innerWriter = innerWriter; this.currentState = new RootNode(this); this.namespaceTable = new NamespaceTable(); } public override XamlSchemaContext SchemaContext { get { return this.innerWriter.SchemaContext; } } void SetActivityType(XamlType activityXamlType, XamlType activityBuilderXamlType) { if (activityXamlType == null) { this.notRewriting = true; } else { this.activityXamlType = activityXamlType; this.activityBuilderXamlType = activityBuilderXamlType; this.xamlTypeXamlType = this.SchemaContext.GetXamlType(typeof(XamlType)); this.typeXamlType = this.SchemaContext.GetXamlType(typeof(Type)); this.activityPropertyXamlType = this.SchemaContext.GetXamlType(typeof(DynamicActivityProperty)); this.activityPropertyType = this.activityPropertyXamlType.GetMember("Type"); this.activityPropertyName = this.activityPropertyXamlType.GetMember("Name"); this.activityPropertyValue = this.activityPropertyXamlType.GetMember("Value"); this.activityBuilderName = this.activityBuilderXamlType.GetMember("Name"); this.activityBuilderAttributes = this.activityBuilderXamlType.GetMember("Attributes"); this.activityBuilderProperties = this.activityBuilderXamlType.GetMember("Properties"); this.activityBuilderPropertyReference = this.SchemaContext.GetXamlType(typeof(ActivityBuilder)).GetAttachableMember("PropertyReference"); this.activityBuilderPropertyReferences = this.SchemaContext.GetXamlType(typeof(ActivityBuilder)).GetAttachableMember("PropertyReferences"); this.activityPropertyReferenceXamlType = this.SchemaContext.GetXamlType(typeof(ActivityPropertyReference)); } } public override void WriteNamespace(NamespaceDeclaration namespaceDeclaration) { if (this.notRewriting) { this.innerWriter.WriteNamespace(namespaceDeclaration); return; } if (this.namespaceTable != null) { this.namespaceTable.AddNamespace(namespaceDeclaration); } this.currentState.WriteNamespace(namespaceDeclaration); } public override void WriteValue(object value) { if (this.notRewriting) { this.innerWriter.WriteValue(value); return; } this.currentState.WriteValue(value); } public override void WriteStartObject(XamlType xamlType) { if (this.notRewriting) { this.innerWriter.WriteStartObject(xamlType); return; } EnterDepth(); this.currentState.WriteStartObject(xamlType); } public override void WriteGetObject() { if (this.notRewriting) { this.innerWriter.WriteGetObject(); return; } EnterDepth(); this.currentState.WriteGetObject(); } public override void WriteEndObject() { if (this.notRewriting) { this.innerWriter.WriteEndObject(); return; } this.currentState.WriteEndObject(); ExitDepth(); } public override void WriteStartMember(XamlMember xamlMember) { if (this.notRewriting) { this.innerWriter.WriteStartMember(xamlMember); return; } EnterDepth(); this.currentState.WriteStartMember(xamlMember); } public override void WriteEndMember() { if (this.notRewriting) { this.innerWriter.WriteEndMember(); return; } this.currentState.WriteEndMember(); ExitDepth(); } void PushState(BuilderXamlNode state) { if (this.pendingStates == null) { this.pendingStates = new Stack(); } this.pendingStates.Push(this.currentState); this.currentState = state; } void EnterDepth() { Fx.Assert(!this.notRewriting, "we only use depth calculation if we're rewriting"); this.currentDepth++; if (this.namespaceTable != null) { this.namespaceTable.EnterScope(); } } void ExitDepth() { Fx.Assert(!this.notRewriting, "we only use depth calculation if we're rewriting"); if (this.currentState.Depth == this.currentDepth) { // complete the current state this.currentState.Complete(); // and pop off the next state to look for if (this.pendingStates.Count > 0) { this.currentState = this.pendingStates.Pop(); } } this.currentDepth--; if (this.namespaceTable != null) { this.namespaceTable.ExitScope(); } } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { ((IDisposable)this.innerWriter).Dispose(); } } abstract class BuilderXamlNode { protected BuilderXamlNode(ActivityBuilderXamlWriter writer) { this.Depth = writer.currentDepth; this.Writer = writer; this.CurrentWriter = writer.innerWriter; } public int Depth { get; private set; } // a lot of nodes just redirect output, this // allows them to avoid overriding everything just for that public XamlWriter CurrentWriter { get; protected set; } protected ActivityBuilderXamlWriter Writer { get; private set; } protected internal virtual void Complete() { } protected internal virtual void WriteNamespace(NamespaceDeclaration namespaceDeclaration) { CurrentWriter.WriteNamespace(namespaceDeclaration); } protected internal virtual void WriteStartObject(XamlType xamlType) { CurrentWriter.WriteStartObject(xamlType); } protected internal virtual void WriteGetObject() { CurrentWriter.WriteGetObject(); } protected internal virtual void WriteEndObject() { CurrentWriter.WriteEndObject(); } protected internal virtual void WriteStartMember(XamlMember xamlMember) { CurrentWriter.WriteStartMember(xamlMember); } protected internal virtual void WriteEndMember() { CurrentWriter.WriteEndMember(); } protected internal virtual void WriteValue(object value) { CurrentWriter.WriteValue(value); } } // RootNode needs to buffer nodes until we finish processing Name + Properties // because we need to insert our namespace _before_ the first StartObject. // this is the starting value for ActivityBuilderXamlWriter.currentNode class RootNode : BuilderXamlNode { const string PreferredXamlNamespaceAlias = "x"; const string PreferredClassAlias = "this"; bool wroteXamlNamespace; HashSet rootLevelPrefixes; XamlNodeQueue pendingNodes; public RootNode(ActivityBuilderXamlWriter writer) : base(writer) { this.pendingNodes = new XamlNodeQueue(writer.SchemaContext); base.CurrentWriter = this.pendingNodes.Writer; } protected internal override void WriteNamespace(NamespaceDeclaration namespaceDeclaration) { if (Writer.currentDepth == 0 && !this.wroteXamlNamespace) { if (namespaceDeclaration.Namespace == XamlLanguage.Xaml2006Namespace) { this.wroteXamlNamespace = true; } else { if (this.rootLevelPrefixes == null) { this.rootLevelPrefixes = new HashSet(); } this.rootLevelPrefixes.Add(namespaceDeclaration.Prefix); } } base.WriteNamespace(namespaceDeclaration); } protected internal override void WriteStartObject(XamlType xamlType) { if (Writer.currentDepth == 1) { XamlType activityXamlType = null; // root object: see if we're serializing an ActivityBuilder if (xamlType.UnderlyingType == typeof(ActivityBuilder)) { activityXamlType = Writer.SchemaContext.GetXamlType(typeof(Activity)); } // or an ActivityBuilder else if (xamlType.IsGeneric && xamlType.UnderlyingType != null && xamlType.UnderlyingType.GetGenericTypeDefinition() == typeof(ActivityBuilder<>)) { Type activityType = xamlType.TypeArguments[0].UnderlyingType; activityXamlType = Writer.SchemaContext.GetXamlType(typeof(Activity<>).MakeGenericType(activityType)); } Writer.SetActivityType(activityXamlType, xamlType); if (activityXamlType != null) { Writer.PushState(new BuilderClassNode(this, Writer)); return; } else { // we should be a pass through. Flush any buffered nodes and get out of the way FlushPendingNodes(null); } } base.WriteStartObject(xamlType); } public void FlushPendingNodes(string classNamespace) { base.CurrentWriter = this.Writer.innerWriter; if (!Writer.notRewriting) { // make sure we have any required namespaces if (!this.wroteXamlNamespace) { string xamlNamespaceAlias = GenerateNamespacePrefix(PreferredXamlNamespaceAlias); this.WriteNamespace(new NamespaceDeclaration(XamlLanguage.Xaml2006Namespace, xamlNamespaceAlias)); } // If there's an x:Class="Foo.Bar", add a namespace declaration for Foo in the local assembly so we can // say stuff like this:Bar.MyProperty later on. DON'T add the namespace declaration if somebody has already // declared the namespace in the nodestream though (duplicates are an error). if (classNamespace != null) { bool sawClassNamespace = false; XamlReader reader = this.pendingNodes.Reader; XamlWriter writer = this.Writer.innerWriter; while (reader.Read() && reader.NodeType == XamlNodeType.NamespaceDeclaration) { if (classNamespace.Equals(reader.Namespace.Namespace)) { sawClassNamespace = true; } writer.WriteNode(reader); } if (!sawClassNamespace) { string classNamespaceAlias = GenerateNamespacePrefix(PreferredClassAlias); writer.WriteNamespace(new NamespaceDeclaration(classNamespace, classNamespaceAlias)); } // We may have consumed the first non-namespace node off the reader in order // to check it for being a NamespaceDeclaration. Make sure it still gets written. if (!reader.IsEof) { writer.WriteNode(reader); } } this.rootLevelPrefixes = null; // not needed anymore } XamlServices.Transform(this.pendingNodes.Reader, this.Writer.innerWriter, false); this.pendingNodes = null; } string GenerateNamespacePrefix(string desiredPrefix) { string aliasPostfix = string.Empty; // try postfixing 1-1000 first for (int i = 1; i <= 1000; i++) { string alias = desiredPrefix + aliasPostfix; if (!this.rootLevelPrefixes.Contains(alias)) { return alias; } aliasPostfix = i.ToString(CultureInfo.InvariantCulture); } // fall back to GUID return desiredPrefix + Guid.NewGuid().ToString(); } } // ... class BuilderClassNode : BuilderXamlNode { RootNode rootNode; string xClassNamespace; XamlType xClassXamlType; XamlNodeQueue xClassNodes; XamlNodeQueue xClassAttributeNodes; XamlNodeQueue xPropertiesNodes; XamlNodeQueue otherNodes; List> defaultValueNodes; public BuilderClassNode(RootNode rootNode, ActivityBuilderXamlWriter writer) : base(writer) { this.rootNode = rootNode; // by default, if we're not in a special sub-tree, ferret the nodes away on the side this.otherNodes = new XamlNodeQueue(writer.SchemaContext); base.CurrentWriter = this.otherNodes.Writer; } public void SetXClass(string builderName, XamlNodeQueue nameNodes) { this.xClassNodes = new XamlNodeQueue(Writer.SchemaContext); this.xClassNodes.Writer.WriteStartMember(XamlLanguage.Class); this.xClassNamespace = null; string xClassName = builderName; if (string.IsNullOrEmpty(xClassName)) { xClassName = string.Format(CultureInfo.CurrentCulture, "_{0}", Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 4)); } if (nameNodes != null) { XamlServices.Transform(nameNodes.Reader, this.xClassNodes.Writer, false); } else { this.xClassNodes.Writer.WriteValue(xClassName); this.xClassNodes.Writer.WriteEndMember(); } int nameStartIndex = xClassName.LastIndexOf('.'); if (nameStartIndex > 0) { this.xClassNamespace = builderName.Substring(0, nameStartIndex); xClassName = builderName.Substring(nameStartIndex + 1); } this.xClassNamespace = string.Format(CultureInfo.CurrentUICulture, "clr-namespace:{0}", this.xClassNamespace ?? string.Empty); this.xClassXamlType = new XamlType(this.xClassNamespace, xClassName, null, Writer.SchemaContext); } // Attributes [DependsOn("Name")] public void SetAttributes(XamlNodeQueue attributeNodes) { this.xClassAttributeNodes = attributeNodes; } // Properties [DependsOn("Attributes")] public void SetProperties(XamlNodeQueue propertyNodes, List> defaultValueNodes) { this.xPropertiesNodes = propertyNodes; this.defaultValueNodes = defaultValueNodes; // exiting the properties tag. So we've now accrued any instances of Name and Attributes // that could possibly be hit flush our preamble FlushPreamble(); } void FlushPreamble() { if (this.otherNodes == null) // already flushed { return; } CurrentWriter = this.Writer.innerWriter; string classNamespace = null; // first, see if we need to emit a namespace corresponding to our class if (this.defaultValueNodes != null) { classNamespace = this.xClassNamespace; } this.rootNode.FlushPendingNodes(classNamespace); this.rootNode = null; // not needed anymore CurrentWriter.WriteStartObject(this.Writer.activityXamlType); // first dump x:Class if (this.xClassNodes == null) { SetXClass(null, null); // this will setup a default } XamlServices.Transform(this.xClassNodes.Reader, CurrentWriter, false); // String default values get written in attribute form immediately. // Other values get deferred until after x:Members, etc. XamlNodeQueue deferredPropertyNodes = null; if (this.defaultValueNodes != null) { foreach (KeyValuePair defaultValueNode in this.defaultValueNodes) { XamlReader reader = defaultValueNode.Value.Reader; if (reader.Read()) { bool isStringValue = false; if (reader.NodeType == XamlNodeType.Value) { string stringValue = reader.Value as string; if (stringValue != null) { isStringValue = true; } } if (isStringValue) { CurrentWriter.WriteStartMember(new XamlMember(defaultValueNode.Key, this.xClassXamlType, true)); CurrentWriter.WriteNode(reader); XamlServices.Transform(defaultValueNode.Value.Reader, CurrentWriter, false); // don't need an EndMember since it will be sitting in the node list (we only needed to strip the StartMember) } else { // Else: We'll write this out in a minute, after the x:ClassAttributes and x:Properties if (deferredPropertyNodes == null) { deferredPropertyNodes = new XamlNodeQueue(Writer.SchemaContext); } deferredPropertyNodes.Writer.WriteStartMember(new XamlMember(defaultValueNode.Key, this.xClassXamlType, true)); deferredPropertyNodes.Writer.WriteNode(reader); XamlServices.Transform(defaultValueNode.Value.Reader, deferredPropertyNodes.Writer, false); } } } } // then dump x:ClassAttributes if we have any if (this.xClassAttributeNodes != null) { XamlServices.Transform(this.xClassAttributeNodes.Reader, CurrentWriter, false); } // and x:Properties if (this.xPropertiesNodes != null) { XamlServices.Transform(this.xPropertiesNodes.Reader, CurrentWriter, false); } if (deferredPropertyNodes != null) { XamlServices.Transform(deferredPropertyNodes.Reader, CurrentWriter, false); } if (this.otherNodes.Count > 0) { XamlServices.Transform(this.otherNodes.Reader, CurrentWriter, false); } this.otherNodes = null; // done with this } protected internal override void Complete() { if (this.otherNodes != null) { // need to flush FlushPreamble(); } } protected internal override void WriteStartMember(XamlMember xamlMember) { if (Writer.currentDepth == this.Depth + 1 && !xamlMember.IsAttachable) { if (xamlMember == Writer.activityBuilderName) { // record that we're in ActivityBuilder.Name, since we'll need the class name for // default value output Writer.PushState(new BuilderNameNode(this, Writer)); return; } else if (xamlMember == Writer.activityBuilderAttributes) { // rewrite ActivityBuilder.Attributes to x:ClassAttributes Writer.PushState(new AttributesNode(this, Writer)); return; } else if (xamlMember == Writer.activityBuilderProperties) { // rewrite ActivityBuilder.Properties to x:Members Writer.PushState(new PropertiesNode(this, Writer)); return; } else { // any other member means we've passed properties due to [DependsOn] relationships FlushPreamble(); if (xamlMember.DeclaringType == Writer.activityBuilderXamlType) { // Rewrite "" to "" xamlMember = Writer.activityXamlType.GetMember(xamlMember.Name); if (xamlMember == null) { throw FxTrace.Exception.AsError(new InvalidOperationException( SR.MemberNotSupportedByActivityXamlServices(xamlMember.Name))); } if (xamlMember.Name == "Implementation") { Writer.PushState(new ImplementationNode(Writer)); } } } } base.WriteStartMember(xamlMember); } } // node that we'll map to x:Class class BuilderNameNode : BuilderXamlNode { BuilderClassNode classNode; string builderName; XamlNodeQueue nameNodes; public BuilderNameNode(BuilderClassNode classNode, ActivityBuilderXamlWriter writer) : base(writer) { this.classNode = classNode; this.nameNodes = new XamlNodeQueue(writer.SchemaContext); base.CurrentWriter = this.nameNodes.Writer; } protected internal override void Complete() { this.classNode.SetXClass(this.builderName, this.nameNodes); } protected internal override void WriteValue(object value) { if (Writer.currentDepth == this.Depth) { this.builderName = (string)value; } base.WriteValue(value); } } // node that we'll map to x:ClassAttributes class AttributesNode : BuilderXamlNode { XamlNodeQueue attributeNodes; BuilderClassNode classNode; public AttributesNode(BuilderClassNode classNode, ActivityBuilderXamlWriter writer) : base(writer) { this.classNode = classNode; this.attributeNodes = new XamlNodeQueue(writer.SchemaContext); base.CurrentWriter = this.attributeNodes.Writer; CurrentWriter.WriteStartMember(XamlLanguage.ClassAttributes); } protected internal override void Complete() { this.classNode.SetAttributes(this.attributeNodes); } } // node that we'll map to x:Members // since x:Members doesn't have GetObject/StartMember wrappers around the value, we need to eat those class PropertiesNode : BuilderXamlNode { List> defaultValueNodes; XamlNodeQueue propertiesNodes; BuilderClassNode classNode; bool skipGetObject; public PropertiesNode(BuilderClassNode classNode, ActivityBuilderXamlWriter writer) : base(writer) { this.classNode = classNode; this.propertiesNodes = new XamlNodeQueue(writer.SchemaContext); base.CurrentWriter = this.propertiesNodes.Writer; CurrentWriter.WriteStartMember(XamlLanguage.Members); } protected internal override void WriteStartObject(XamlType xamlType) { if (xamlType == Writer.activityPropertyXamlType && Writer.currentDepth == this.Depth + 3) { xamlType = XamlLanguage.Property; Writer.PushState(new PropertyNode(this, Writer)); } base.WriteStartObject(xamlType); } protected internal override void WriteGetObject() { if (Writer.currentDepth == this.Depth + 1) { this.skipGetObject = true; } else { base.WriteGetObject(); } } protected internal override void WriteEndObject() { if (this.skipGetObject && Writer.currentDepth == this.Depth + 1) { this.skipGetObject = false; } else { base.WriteEndObject(); } } protected internal override void WriteStartMember(XamlMember xamlMember) { if (this.skipGetObject && Writer.currentDepth == this.Depth + 2) { return; } base.WriteStartMember(xamlMember); } protected internal override void WriteEndMember() { if (this.skipGetObject && Writer.currentDepth == this.Depth + 2) { return; } base.WriteEndMember(); } protected internal override void Complete() { this.classNode.SetProperties(this.propertiesNodes, this.defaultValueNodes); } public void AddDefaultValue(string propertyName, XamlNodeQueue value) { if (this.defaultValueNodes == null) { this.defaultValueNodes = new List>(); } if (string.IsNullOrEmpty(propertyName)) { // default a name if one doesn't exist propertyName = string.Format(CultureInfo.CurrentCulture, "_{0}", Guid.NewGuid().ToString().Replace("-", string.Empty)); } this.defaultValueNodes.Add(new KeyValuePair(propertyName, value)); } } // ... class PropertyNode : BuilderXamlNode { PropertiesNode properties; string propertyName; XamlType propertyType; XamlNodeQueue defaultValue; public PropertyNode(PropertiesNode properties, ActivityBuilderXamlWriter writer) : base(writer) { this.properties = properties; base.CurrentWriter = properties.CurrentWriter; } public void SetName(string name) { this.propertyName = name; } public void SetType(XamlType type) { this.propertyType = type; } public void SetDefaultValue(XamlNodeQueue defaultValue) { this.defaultValue = defaultValue; } protected internal override void WriteStartMember(XamlMember xamlMember) { if (xamlMember.DeclaringType == Writer.activityPropertyXamlType && Writer.currentDepth == this.Depth + 1) { if (xamlMember == Writer.activityPropertyName) { // record that we're in a property name, since we'll need this for default value output Writer.PushState(new PropertyNameNode(this, Writer)); xamlMember = DynamicActivityXamlReader.xPropertyName; } else if (xamlMember == Writer.activityPropertyType) { // record that we're in a property type, since we'll need this for default value output Writer.PushState(new PropertyTypeNode(this, Writer)); xamlMember = DynamicActivityXamlReader.xPropertyType; } else if (xamlMember == Writer.activityPropertyValue) { // record that we're in a property value, since we'll need this for default value output. // don't write anything since we'll dump the default values after we exit ActivityBuilder.Properties Writer.PushState(new PropertyValueNode(this, Writer)); xamlMember = null; } } if (xamlMember != null) { base.WriteStartMember(xamlMember); } } protected internal override void Complete() { if (this.defaultValue != null) { if (string.IsNullOrEmpty(this.propertyName)) { // default a name if one doesn't exist this.propertyName = string.Format(CultureInfo.CurrentCulture, "_{0}", Guid.NewGuid().ToString().Replace("-", string.Empty)); } if (this.defaultValue != null && this.propertyType != null) { // post-process the default value nodes to strip out // StartObject+StartMember _Initialization+EndMember+EndObject // wrapper nodes if the type of the object matches the // property Type (since we are moving from "object Value" to "T Value" this.defaultValue = StripTypeWrapping(this.defaultValue, this.propertyType); } this.properties.AddDefaultValue(this.propertyName, this.defaultValue); } } static XamlNodeQueue StripTypeWrapping(XamlNodeQueue valueNodes, XamlType propertyType) { XamlNodeQueue targetNodes = new XamlNodeQueue(valueNodes.Reader.SchemaContext); XamlReader source = valueNodes.Reader; XamlWriter target = targetNodes.Writer; int depth = 0; bool consumeWrapperEndTags = false; bool hasBufferedStartObject = false; while (source.Read()) { switch (source.NodeType) { case XamlNodeType.StartObject: depth++; // only strip the wrapping type nodes if we have exactly this sequence: // StartObject StartMember(Intialization) Value EndMember EndObject. if (targetNodes.Count == 0 && depth == 1 && source.Type == propertyType && valueNodes.Count == 5) { hasBufferedStartObject = true; continue; } break; case XamlNodeType.GetObject: depth++; break; case XamlNodeType.StartMember: depth++; if (hasBufferedStartObject) { if (depth == 2 && source.Member == XamlLanguage.Initialization) { consumeWrapperEndTags = true; continue; } else { hasBufferedStartObject = false; targetNodes.Writer.WriteStartObject(propertyType); } } break; case XamlNodeType.EndMember: depth--; if (consumeWrapperEndTags && depth == 1) { continue; } break; case XamlNodeType.EndObject: depth--; if (consumeWrapperEndTags && depth == 0) { consumeWrapperEndTags = false; continue; } break; } target.WriteNode(source); } return targetNodes; } } // ... class PropertyNameNode : BuilderXamlNode { PropertyNode property; public PropertyNameNode(PropertyNode property, ActivityBuilderXamlWriter writer) : base(writer) { this.property = property; base.CurrentWriter = property.CurrentWriter; } protected internal override void WriteValue(object value) { if (Writer.currentDepth == this.Depth) { property.SetName((string)value); } base.WriteValue(value); } } // ... class PropertyTypeNode : BuilderXamlNode { PropertyNode property; public PropertyTypeNode(PropertyNode property, ActivityBuilderXamlWriter writer) : base(writer) { this.property = property; base.CurrentWriter = property.CurrentWriter; } protected internal override void WriteValue(object value) { if (Writer.currentDepth == this.Depth) { // We only support property type as an attribute XamlTypeName xamlTypeName = XamlTypeName.Parse(value as string, Writer.namespaceTable); XamlType xamlType = Writer.SchemaContext.GetXamlType(xamlTypeName); property.SetType(xamlType); // supports null } base.WriteValue(value); } } // ... class PropertyValueNode : BuilderXamlNode { PropertyNode property; XamlNodeQueue valueNodes; public PropertyValueNode(PropertyNode property, ActivityBuilderXamlWriter writer) : base(writer) { this.property = property; this.valueNodes = new XamlNodeQueue(writer.SchemaContext); base.CurrentWriter = this.valueNodes.Writer; } protected internal override void Complete() { this.property.SetDefaultValue(this.valueNodes); base.Complete(); } } // ... // We need to convert any inside here into . class ImplementationNode : BuilderXamlNode { Stack objectStack; public ImplementationNode(ActivityBuilderXamlWriter writer) : base(writer) { this.objectStack = new Stack(); } internal void AddPropertyReference(ActivityPropertyReference propertyReference) { ObjectFrame currentFrame = this.objectStack.Peek(); Fx.Assert(currentFrame.Type != null, "Should only create PropertyReferencesNode inside a StartObject"); if (currentFrame.PropertyReferences == null) { currentFrame.PropertyReferences = new List(); } currentFrame.PropertyReferences.Add(propertyReference); } internal void SetUntransformedPropertyReferences(XamlMember propertyReferencesMember, XamlNodeQueue untransformedNodes) { ObjectFrame currentFrame = this.objectStack.Peek(); Fx.Assert(currentFrame.Type != null, "Should only create PropertyReferencesNode inside a StartObject"); currentFrame.AddMember(propertyReferencesMember, untransformedNodes); } protected internal override void WriteStartMember(XamlMember xamlMember) { ObjectFrame currentFrame = this.objectStack.Peek(); if (currentFrame.Type == null) { base.WriteStartMember(xamlMember); } else if (xamlMember == Writer.activityBuilderPropertyReference || xamlMember == Writer.activityBuilderPropertyReferences) { // Parse out the contents of using a PropertyReferencesNode Writer.PushState(new PropertyReferencesNode(Writer, xamlMember, this)); } else { this.CurrentWriter = currentFrame.StartMember(xamlMember, CurrentWriter); } } protected internal override void WriteStartObject(XamlType xamlType) { this.objectStack.Push(new ObjectFrame { Type = xamlType }); base.WriteStartObject(xamlType); } protected internal override void WriteGetObject() { this.objectStack.Push(new ObjectFrame()); base.WriteGetObject(); } protected internal override void WriteEndObject() { ObjectFrame frame = this.objectStack.Pop(); frame.FlushMembers(CurrentWriter); base.WriteEndObject(); } protected internal override void WriteEndMember() { // Stack can be empty here if this is the EndMember that closes out the Node ObjectFrame currentFrame = this.objectStack.Count > 0 ? this.objectStack.Peek() : null; if (currentFrame == null || currentFrame.Type == null) { base.WriteEndMember(); } else { CurrentWriter = currentFrame.EndMember(); } } class ObjectFrame { XamlWriter parentWriter; XamlNodeQueue currentMemberNodes; public XamlType Type { get; set; } public XamlMember CurrentMember { get; set; } public List> Members { get; set; } public List PropertyReferences { get; set; } public XamlWriter StartMember(XamlMember member, XamlWriter parentWriter) { this.CurrentMember = member; this.parentWriter = parentWriter; this.currentMemberNodes = new XamlNodeQueue(parentWriter.SchemaContext); return this.currentMemberNodes.Writer; } public XamlWriter EndMember() { AddMember(this.CurrentMember, this.currentMemberNodes); this.CurrentMember = null; this.currentMemberNodes = null; XamlWriter parentWriter = this.parentWriter; this.parentWriter = null; return parentWriter; } public void AddMember(XamlMember member, XamlNodeQueue content) { if (this.Members == null) { this.Members = new List>(); } this.Members.Add(new KeyValuePair(member, content)); } public void FlushMembers(XamlWriter parentWriter) { if (this.Type == null) { Fx.Assert(Members == null, "We shouldn't buffer members on GetObject"); return; } if (Members != null) { foreach (KeyValuePair member in Members) { parentWriter.WriteStartMember(member.Key); XamlServices.Transform(member.Value.Reader, parentWriter, false); parentWriter.WriteEndMember(); } } if (PropertyReferences != null) { foreach (ActivityPropertyReference propertyReference in PropertyReferences) { XamlMember targetProperty = this.Type.GetMember(propertyReference.TargetProperty) ?? new XamlMember(propertyReference.TargetProperty, this.Type, false); parentWriter.WriteStartMember(targetProperty); WritePropertyReference(parentWriter, targetProperty, propertyReference.SourceProperty); parentWriter.WriteEndMember(); } } } void WritePropertyReference(XamlWriter parentWriter, XamlMember targetProperty, string sourceProperty) { Type propertyReferenceType = typeof(PropertyReferenceExtension<>).MakeGenericType(targetProperty.Type.UnderlyingType ?? typeof(object)); XamlType propertyReferenceXamlType = parentWriter.SchemaContext.GetXamlType(propertyReferenceType); parentWriter.WriteStartObject(propertyReferenceXamlType); if (sourceProperty != null) { parentWriter.WriteStartMember(propertyReferenceXamlType.GetMember("PropertyName")); parentWriter.WriteValue(sourceProperty); parentWriter.WriteEndMember(); } parentWriter.WriteEndObject(); } } } // is stripped out and the inner // s map to PropertyReferenceNodes class PropertyReferencesNode : BuilderXamlNode { XamlNodeQueue untransformedNodes; // nodes that couldn't be transformed to PropertyReference form XamlMember originalStartMember; public PropertyReferencesNode(ActivityBuilderXamlWriter writer, XamlMember originalStartMember, ImplementationNode parent) : base(writer) { this.untransformedNodes = new XamlNodeQueue(Writer.SchemaContext); this.originalStartMember = originalStartMember; this.Parent = parent; base.CurrentWriter = this.untransformedNodes.Writer; } public bool HasUntransformedChildren { get; set; } public ImplementationNode Parent { get; private set; } public XamlWriter UntransformedNodesWriter { get { return this.untransformedNodes.Writer; } } protected internal override void WriteStartObject(XamlType xamlType) { if (xamlType == Writer.activityPropertyReferenceXamlType) { Writer.PushState(new PropertyReferenceNode(this.Writer, this)); return; } base.WriteStartObject(xamlType); } protected internal override void WriteEndMember() { // We only want the untransformedNodes writer to contain our member contents, not the // Start/End members, so don't write our closing EM if (Writer.currentDepth != this.Depth) { base.WriteEndMember(); } } protected internal override void Complete() { if (this.HasUntransformedChildren) { // Some ActivityPropertyReferences couldn't be transformed to properties. Leave them unchanged. this.Parent.SetUntransformedPropertyReferences(this.originalStartMember, this.untransformedNodes); } } } // maps to // class PropertyReferenceNode : BuilderXamlNode { XamlNodeQueue propertyReferenceNodes; PropertyReferencesNode parent; string sourceProperty; string targetProperty; bool inSourceProperty; bool inTargetProperty; public PropertyReferenceNode(ActivityBuilderXamlWriter writer, PropertyReferencesNode parent) : base(writer) { this.propertyReferenceNodes = new XamlNodeQueue(writer.SchemaContext); this.parent = parent; // save the untransformed output in case we're not able to perform the transformation base.CurrentWriter = this.propertyReferenceNodes.Writer; } protected internal override void WriteStartMember(XamlMember xamlMember) { if (Writer.currentDepth == this.Depth + 1 // SM && xamlMember.DeclaringType == Writer.activityPropertyReferenceXamlType) { if (xamlMember.Name == "SourceProperty") { this.inSourceProperty = true; } else if (xamlMember.Name == "TargetProperty") { this.inTargetProperty = true; } } base.WriteStartMember(xamlMember); // save output just in case } protected internal override void WriteValue(object value) { if (this.inSourceProperty) { this.sourceProperty = (string)value; } else if (this.inTargetProperty) { this.targetProperty = (string)value; } base.WriteValue(value); // save output just in case } protected internal override void WriteEndMember() { if (Writer.currentDepth == this.Depth + 1) { this.inSourceProperty = false; this.inTargetProperty = false; } base.WriteEndMember(); // save output just in case } protected internal override void Complete() { if (this.targetProperty == null) { // can't transform to , dump original nodes this.parent.HasUntransformedChildren = true; this.parent.UntransformedNodesWriter.WriteStartObject(Writer.activityPropertyReferenceXamlType); XamlServices.Transform(this.propertyReferenceNodes.Reader, this.parent.UntransformedNodesWriter, false); } else { ActivityPropertyReference propertyReference = new ActivityPropertyReference { SourceProperty = this.sourceProperty, TargetProperty = this.targetProperty }; parent.Parent.AddPropertyReference(propertyReference); } } } } }