2 // Copyright (c) Microsoft Corporation. All rights reserved.
5 namespace Microsoft.Activities.Presentation.Xaml
8 using System.Activities;
9 using System.Activities.Debugger.Symbol;
10 using System.Collections.Generic;
11 using System.Globalization;
13 using System.ServiceModel.Activities;
17 class DesignTimeXamlWriter : XamlXmlWriter
19 //namespaces to ignore (don't load assembilies for) at root node
20 HashSet<string> namespacesToIgnore;
22 //namespaces we've seen at root level, we use this to figure out appropriate alias for MC namespace
23 HashSet<string> rootLevelNamespaces;
25 // for duplicate namespace filtering (happens if we're using the local assembly to compile itself)
26 HashSet<string> emittedNamespacesInLocalAssembly;
28 //For namespace defined in local assembly with assembly info in namespace declaration, we'll strip out the assembly info
29 //and hold the namespace temporarily. Before writing the start object, we'll check whether the short version gets written
30 //as a separate declaration, if not, we write it out.
31 List<NamespaceDeclaration> localNamespacesWithAssemblyInfo;
33 WorkflowDesignerXamlSchemaContext schemaContext;
37 bool writeDebugSymbol;
38 bool debugSymbolNamespaceAdded;
39 bool isWritingElementStyleString;
40 internal static readonly string EmptyWorkflowSymbol = (new WorkflowSymbol() { FileName = @"C:\Empty.xaml" }).Encode();
41 private bool shouldWriteDebugSymbol;
43 public DesignTimeXamlWriter(TextWriter textWriter, WorkflowDesignerXamlSchemaContext context, bool shouldWriteDebugSymbol)
44 : this(new NamespaceIndentingXmlWriter(textWriter), context, shouldWriteDebugSymbol)
48 DesignTimeXamlWriter(NamespaceIndentingXmlWriter underlyingWriter, WorkflowDesignerXamlSchemaContext context, bool shouldWriteDebugSymbol)
49 : base(underlyingWriter, context,
50 // Setting AssumeValidInput to true allows to save a document even if it has duplicate members
51 new XamlXmlWriterSettings { AssumeValidInput = true })
53 underlyingWriter.Parent = this;
54 this.namespacesToIgnore = new HashSet<string>();
55 this.rootLevelNamespaces = new HashSet<string>();
56 this.schemaContext = context;
57 this.currentDepth = 0;
58 this.shouldWriteDebugSymbol = shouldWriteDebugSymbol;
61 public override void WriteNamespace(NamespaceDeclaration namespaceDeclaration)
63 if (this.currentDepth == 0)
65 //we need to track every namespace alias appeared in root element to figure out right alias for MC namespace
66 this.rootLevelNamespaces.Add(namespaceDeclaration.Prefix);
68 //Remember namespaces needed to be ignored at top level so we will add ignore attribute for them when we write start object
69 if (NameSpaces.ShouldIgnore(namespaceDeclaration.Namespace))
71 this.namespacesToIgnore.Add(namespaceDeclaration.Prefix);
74 if (namespaceDeclaration.Namespace == NameSpaces.DebugSymbol)
76 debugSymbolNamespaceAdded = true;
80 EmitNamespace(namespaceDeclaration);
83 void EmitNamespace(NamespaceDeclaration namespaceDeclaration)
85 // Write the namespace, filtering for duplicates in the local assembly because VS might be using it to compile itself.
87 if (schemaContext.IsClrNamespaceWithNoAssembly(namespaceDeclaration.Namespace))
89 // Might still need to trim a semicolon, even though it shouldn't strictly be there.
90 string nonassemblyQualifedNamespace = namespaceDeclaration.Namespace;
91 if (nonassemblyQualifedNamespace[nonassemblyQualifedNamespace.Length - 1] == ';')
93 nonassemblyQualifedNamespace = nonassemblyQualifedNamespace.Substring(0, nonassemblyQualifedNamespace.Length - 1);
94 namespaceDeclaration = new NamespaceDeclaration(nonassemblyQualifedNamespace, namespaceDeclaration.Prefix);
96 EmitLocalNamespace(namespaceDeclaration);
98 else if (schemaContext.IsClrNamespaceInLocalAssembly(namespaceDeclaration.Namespace))
100 string nonassemblyQualifedNamespace = schemaContext.TrimLocalAssembly(namespaceDeclaration.Namespace);
101 namespaceDeclaration = new NamespaceDeclaration(nonassemblyQualifedNamespace, namespaceDeclaration.Prefix);
102 if (this.localNamespacesWithAssemblyInfo == null)
104 this.localNamespacesWithAssemblyInfo = new List<NamespaceDeclaration>();
106 this.localNamespacesWithAssemblyInfo.Add(namespaceDeclaration);
110 base.WriteNamespace(namespaceDeclaration);
114 void EmitLocalNamespace(NamespaceDeclaration namespaceDeclaration)
116 if (this.emittedNamespacesInLocalAssembly == null) // lazy initialization
118 this.emittedNamespacesInLocalAssembly = new HashSet<string>();
121 // Write the namespace only once. Add() returns false if it was already there.
122 if (this.emittedNamespacesInLocalAssembly.Add(namespaceDeclaration.Namespace))
124 base.WriteNamespace(namespaceDeclaration);
128 public override void WriteStartObject(XamlType type)
130 if (type.UnderlyingType == typeof(string))
132 isWritingElementStyleString = true;
134 // this is the top-level object
135 if (this.currentDepth == 0)
137 if (!this.debugSymbolNamespaceAdded)
139 string sadsNamespaceAlias = GenerateNamespaceAlias(NameSpaces.DebugSymbolPrefix);
140 this.WriteNamespace(new NamespaceDeclaration(NameSpaces.DebugSymbol, sadsNamespaceAlias));
141 this.debugSymbolNamespaceAdded = true;
144 // we need to write MC namespace if any namespaces need to be ignored
145 if (this.namespacesToIgnore.Count > 0)
147 string mcNamespaceAlias = GenerateNamespaceAlias(NameSpaces.McPrefix);
148 this.WriteNamespace(new NamespaceDeclaration(NameSpaces.Mc, mcNamespaceAlias));
152 if (this.localNamespacesWithAssemblyInfo != null)
154 foreach (NamespaceDeclaration xamlNamespace in this.localNamespacesWithAssemblyInfo)
156 if ((this.emittedNamespacesInLocalAssembly == null) || (!this.emittedNamespacesInLocalAssembly.Contains(xamlNamespace.Namespace)))
158 base.WriteNamespace(xamlNamespace);
163 if ((type.UnderlyingType == typeof(Activity)) ||
164 (type.IsGeneric && type.UnderlyingType != null && type.UnderlyingType.GetGenericTypeDefinition() == typeof(Activity<>)) ||
165 (type.UnderlyingType == typeof(WorkflowService)))
166 { // Exist ActivityBuilder, DebugSymbolObject will be inserted at the depth == 1.
167 debugSymbolDepth = 1;
171 debugSymbolDepth = 0;
175 if (this.currentDepth == debugSymbolDepth)
177 if (type.UnderlyingType != null && type.UnderlyingType.IsSubclassOf(typeof(Activity)) && this.shouldWriteDebugSymbol)
179 this.writeDebugSymbol = true;
183 base.WriteStartObject(type);
185 if (this.currentDepth == 0)
187 // we need to add Ignore attribute for all namespaces which we don't want to load assemblies for
188 // this has to be done after WriteStartObject
189 if (this.namespacesToIgnore.Count > 0)
191 string nsString = null;
192 foreach (string ns in this.namespacesToIgnore)
194 if (nsString == null)
200 nsString += " " + ns;
204 XamlDirective ignorable = new XamlDirective(NameSpaces.Mc, "Ignorable");
205 base.WriteStartMember(ignorable);
206 base.WriteValue(nsString);
207 base.WriteEndMember();
208 this.namespacesToIgnore.Clear();
216 public override void WriteGetObject()
219 base.WriteGetObject();
222 public override void WriteEndObject()
225 SharedFx.Assert(this.currentDepth >= 0, "Unmatched WriteEndObject");
226 if (this.currentDepth == this.debugSymbolDepth && this.writeDebugSymbol)
228 base.WriteStartMember(new XamlMember(DebugSymbol.SymbolName.MemberName,
229 this.SchemaContext.GetXamlType(typeof(DebugSymbol)), true));
230 base.WriteValue(EmptyWorkflowSymbol);
231 base.WriteEndMember();
232 this.writeDebugSymbol = false;
234 base.WriteEndObject();
235 isWritingElementStyleString = false;
238 string GenerateNamespaceAlias(string prefix)
240 string aliasPostfix = string.Empty;
241 //try "mc"~"mc1000" first
242 for (int i = 1; i <= 1000; i++)
244 string mcAlias = prefix + aliasPostfix;
245 if (!this.rootLevelNamespaces.Contains(mcAlias))
249 aliasPostfix = i.ToString(CultureInfo.InvariantCulture);
253 return prefix + Guid.NewGuid().ToString();
256 class NamespaceIndentingXmlWriter : XmlTextWriter
259 TextWriter textWriter;
261 public NamespaceIndentingXmlWriter(TextWriter textWriter)
264 this.textWriter = textWriter;
265 this.Formatting = Formatting.Indented;
268 public DesignTimeXamlWriter Parent { get; set; }
270 public override void WriteStartElement(string prefix, string localName, string ns)
272 base.WriteStartElement(prefix, localName, ns);
276 public override void WriteStartAttribute(string prefix, string localName, string ns)
278 if (prefix == "xmlns" && (this.currentDepth == 1))
280 this.textWriter.Write(new char[] { '\r', '\n' });
282 base.WriteStartAttribute(prefix, localName, ns);
285 public override void WriteEndElement()
287 if (this.Parent.isWritingElementStyleString)
289 base.WriteRaw(string.Empty);
291 base.WriteEndElement();
295 public override void WriteStartDocument()
297 // No-op to avoid XmlDeclaration from being written.
298 // Overriding this is equivalent of XmlWriterSettings.OmitXmlDeclaration = true.
301 public override void WriteStartDocument(bool standalone)
303 // No-op to avoid XmlDeclaration from being written.
304 // Overriding this is equivalent of XmlWriterSettings.OmitXmlDeclaration = true.
307 public override void WriteEndDocument()
309 // No-op to avoid end of XmlDeclaration from being written.
310 // Overriding this is equivalent of XmlWriterSettings.OmitXmlDeclaration = true.