Fix XMM scanning on Mac x86.
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / Microsoft.Tools.Common / Microsoft / Activities / Presentation / Xaml / DesignTimeXamlWriter.cs
1 // <copyright>
2 //   Copyright (c) Microsoft Corporation.  All rights reserved.
3 // </copyright>
4
5 namespace Microsoft.Activities.Presentation.Xaml
6 {
7     using System;
8     using System.Activities;
9     using System.Activities.Debugger.Symbol;
10     using System.Collections.Generic;
11     using System.Globalization;
12     using System.IO;
13     using System.ServiceModel.Activities;
14     using System.Xaml;
15     using System.Xml;
16
17     class DesignTimeXamlWriter : XamlXmlWriter
18     {
19         //namespaces to ignore (don't load assembilies for) at root node
20         HashSet<string> namespacesToIgnore;
21
22         //namespaces we've seen at root level, we use this to figure out appropriate alias for MC namespace
23         HashSet<string> rootLevelNamespaces;
24
25         // for duplicate namespace filtering (happens if we're using the local assembly to compile itself)
26         HashSet<string> emittedNamespacesInLocalAssembly;
27
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;
32
33         WorkflowDesignerXamlSchemaContext schemaContext;
34
35         int currentDepth;
36         int debugSymbolDepth;
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;
42
43         public DesignTimeXamlWriter(TextWriter textWriter, WorkflowDesignerXamlSchemaContext context, bool shouldWriteDebugSymbol)
44             : this(new NamespaceIndentingXmlWriter(textWriter), context, shouldWriteDebugSymbol)
45         {
46         }
47
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 })
52         {
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;
59         }
60
61         public override void WriteNamespace(NamespaceDeclaration namespaceDeclaration)
62         {
63             if (this.currentDepth == 0)
64             {
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);
67
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))
70                 {
71                     this.namespacesToIgnore.Add(namespaceDeclaration.Prefix);
72                 }
73
74                 if (namespaceDeclaration.Namespace == NameSpaces.DebugSymbol)
75                 {
76                     debugSymbolNamespaceAdded = true;
77                 }
78             }
79
80             EmitNamespace(namespaceDeclaration);
81         }
82
83         void EmitNamespace(NamespaceDeclaration namespaceDeclaration)
84         {
85             // Write the namespace, filtering for duplicates in the local assembly because VS might be using it to compile itself.
86
87             if (schemaContext.IsClrNamespaceWithNoAssembly(namespaceDeclaration.Namespace))
88             {
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] == ';')
92                 {
93                     nonassemblyQualifedNamespace = nonassemblyQualifedNamespace.Substring(0, nonassemblyQualifedNamespace.Length - 1);
94                     namespaceDeclaration = new NamespaceDeclaration(nonassemblyQualifedNamespace, namespaceDeclaration.Prefix);
95                 }
96                 EmitLocalNamespace(namespaceDeclaration);
97             }
98             else if (schemaContext.IsClrNamespaceInLocalAssembly(namespaceDeclaration.Namespace))
99             {
100                 string nonassemblyQualifedNamespace = schemaContext.TrimLocalAssembly(namespaceDeclaration.Namespace);
101                 namespaceDeclaration = new NamespaceDeclaration(nonassemblyQualifedNamespace, namespaceDeclaration.Prefix);
102                 if (this.localNamespacesWithAssemblyInfo == null)
103                 {
104                     this.localNamespacesWithAssemblyInfo = new List<NamespaceDeclaration>();
105                 }
106                 this.localNamespacesWithAssemblyInfo.Add(namespaceDeclaration);
107             }
108             else
109             {
110                 base.WriteNamespace(namespaceDeclaration);
111             }
112         }
113
114         void EmitLocalNamespace(NamespaceDeclaration namespaceDeclaration)
115         {
116             if (this.emittedNamespacesInLocalAssembly == null) // lazy initialization
117             {
118                 this.emittedNamespacesInLocalAssembly = new HashSet<string>();
119             }
120
121             // Write the namespace only once. Add() returns false if it was already there.
122             if (this.emittedNamespacesInLocalAssembly.Add(namespaceDeclaration.Namespace))
123             {
124                 base.WriteNamespace(namespaceDeclaration);
125             }
126         }
127
128         public override void WriteStartObject(XamlType type)
129         {
130             if (type.UnderlyingType == typeof(string))
131             {
132                 isWritingElementStyleString = true;
133             }
134             // this is the top-level object
135             if (this.currentDepth == 0)
136             {
137                 if (!this.debugSymbolNamespaceAdded)
138                 {
139                     string sadsNamespaceAlias = GenerateNamespaceAlias(NameSpaces.DebugSymbolPrefix);
140                     this.WriteNamespace(new NamespaceDeclaration(NameSpaces.DebugSymbol, sadsNamespaceAlias));
141                     this.debugSymbolNamespaceAdded = true;
142                 }
143
144                 // we need to write MC namespace if any namespaces need to be ignored
145                 if (this.namespacesToIgnore.Count > 0)
146                 {
147                     string mcNamespaceAlias = GenerateNamespaceAlias(NameSpaces.McPrefix);
148                     this.WriteNamespace(new NamespaceDeclaration(NameSpaces.Mc, mcNamespaceAlias));
149                 }
150
151
152                 if (this.localNamespacesWithAssemblyInfo != null)
153                 {
154                     foreach (NamespaceDeclaration xamlNamespace in this.localNamespacesWithAssemblyInfo)
155                     {
156                         if ((this.emittedNamespacesInLocalAssembly == null) || (!this.emittedNamespacesInLocalAssembly.Contains(xamlNamespace.Namespace)))
157                         {
158                             base.WriteNamespace(xamlNamespace);
159                         }
160                     }
161                 }
162
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;
168                 }
169                 else
170                 {
171                     debugSymbolDepth = 0;
172                 }
173             }
174
175             if (this.currentDepth == debugSymbolDepth)
176             {
177                 if (type.UnderlyingType != null && type.UnderlyingType.IsSubclassOf(typeof(Activity)) && this.shouldWriteDebugSymbol)
178                 {
179                     this.writeDebugSymbol = true;
180                 }
181             }
182
183             base.WriteStartObject(type);
184
185             if (this.currentDepth == 0)
186             {
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)
190                 {
191                     string nsString = null;
192                     foreach (string ns in this.namespacesToIgnore)
193                     {
194                         if (nsString == null)
195                         {
196                             nsString = ns;
197                         }
198                         else
199                         {
200                             nsString += " " + ns;
201                         }
202                     }
203
204                     XamlDirective ignorable = new XamlDirective(NameSpaces.Mc, "Ignorable");
205                     base.WriteStartMember(ignorable);
206                     base.WriteValue(nsString);
207                     base.WriteEndMember();
208                     this.namespacesToIgnore.Clear();
209                 }
210             }
211
212             ++this.currentDepth;
213
214         }
215
216         public override void WriteGetObject()
217         {
218             ++this.currentDepth;
219             base.WriteGetObject();
220         }
221
222         public override void WriteEndObject()
223         {
224             --this.currentDepth;
225             SharedFx.Assert(this.currentDepth >= 0, "Unmatched WriteEndObject");
226             if (this.currentDepth == this.debugSymbolDepth && this.writeDebugSymbol)
227             {
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;
233             }
234             base.WriteEndObject();
235             isWritingElementStyleString = false;
236         }
237
238         string GenerateNamespaceAlias(string prefix)
239         {
240             string aliasPostfix = string.Empty;
241             //try "mc"~"mc1000" first
242             for (int i = 1; i <= 1000; i++)
243             {
244                 string mcAlias = prefix + aliasPostfix;
245                 if (!this.rootLevelNamespaces.Contains(mcAlias))
246                 {
247                     return mcAlias;
248                 }
249                 aliasPostfix = i.ToString(CultureInfo.InvariantCulture);
250             }
251
252             //roll the dice
253             return prefix + Guid.NewGuid().ToString();
254         }
255
256         class NamespaceIndentingXmlWriter : XmlTextWriter
257         {
258             int currentDepth;
259             TextWriter textWriter;
260
261             public NamespaceIndentingXmlWriter(TextWriter textWriter)
262                 : base(textWriter)
263             {
264                 this.textWriter = textWriter;
265                 this.Formatting = Formatting.Indented;
266             }
267
268             public DesignTimeXamlWriter Parent { get; set; }
269
270             public override void WriteStartElement(string prefix, string localName, string ns)
271             {
272                 base.WriteStartElement(prefix, localName, ns);
273                 this.currentDepth++;
274             }
275
276             public override void WriteStartAttribute(string prefix, string localName, string ns)
277             {
278                 if (prefix == "xmlns" && (this.currentDepth == 1))
279                 {
280                     this.textWriter.Write(new char[] { '\r', '\n' });
281                 }
282                 base.WriteStartAttribute(prefix, localName, ns);
283             }
284
285             public override void WriteEndElement()
286             {
287                 if (this.Parent.isWritingElementStyleString)
288                 {
289                     base.WriteRaw(string.Empty);
290                 }
291                 base.WriteEndElement();
292                 this.currentDepth--;
293             }
294
295             public override void WriteStartDocument()
296             {
297                 // No-op to avoid XmlDeclaration from being written.
298                 // Overriding this is equivalent of XmlWriterSettings.OmitXmlDeclaration = true.
299             }
300
301             public override void WriteStartDocument(bool standalone)
302             {
303                 // No-op to avoid XmlDeclaration from being written.
304                 // Overriding this is equivalent of XmlWriterSettings.OmitXmlDeclaration = true.
305             }
306
307             public override void WriteEndDocument()
308             {
309                 // No-op to avoid end of XmlDeclaration from being written.
310                 // Overriding this is equivalent of XmlWriterSettings.OmitXmlDeclaration = true.
311             }
312         }
313     }
314 }