|
// <copyright>
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
namespace Microsoft.Activities.Presentation.Xaml
{
using System;
using System.Activities;
using System.Activities.Debugger.Symbol;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.ServiceModel.Activities;
using System.Xaml;
using System.Xml;
class DesignTimeXamlWriter : XamlXmlWriter
{
//namespaces to ignore (don't load assembilies for) at root node
HashSet<string> namespacesToIgnore;
//namespaces we've seen at root level, we use this to figure out appropriate alias for MC namespace
HashSet<string> rootLevelNamespaces;
// for duplicate namespace filtering (happens if we're using the local assembly to compile itself)
HashSet<string> emittedNamespacesInLocalAssembly;
//For namespace defined in local assembly with assembly info in namespace declaration, we'll strip out the assembly info
//and hold the namespace temporarily. Before writing the start object, we'll check whether the short version gets written
//as a separate declaration, if not, we write it out.
List<NamespaceDeclaration> localNamespacesWithAssemblyInfo;
WorkflowDesignerXamlSchemaContext schemaContext;
int currentDepth;
int debugSymbolDepth;
bool writeDebugSymbol;
bool debugSymbolNamespaceAdded;
bool isWritingElementStyleString;
internal static readonly string EmptyWorkflowSymbol = (new WorkflowSymbol() { FileName = @"C:\Empty.xaml" }).Encode();
private bool shouldWriteDebugSymbol;
public DesignTimeXamlWriter(TextWriter textWriter, WorkflowDesignerXamlSchemaContext context, bool shouldWriteDebugSymbol)
: this(new NamespaceIndentingXmlWriter(textWriter), context, shouldWriteDebugSymbol)
{
}
DesignTimeXamlWriter(NamespaceIndentingXmlWriter underlyingWriter, WorkflowDesignerXamlSchemaContext context, bool shouldWriteDebugSymbol)
: base(underlyingWriter, context,
// Setting AssumeValidInput to true allows to save a document even if it has duplicate members
new XamlXmlWriterSettings { AssumeValidInput = true })
{
underlyingWriter.Parent = this;
this.namespacesToIgnore = new HashSet<string>();
this.rootLevelNamespaces = new HashSet<string>();
this.schemaContext = context;
this.currentDepth = 0;
this.shouldWriteDebugSymbol = shouldWriteDebugSymbol;
}
public override void WriteNamespace(NamespaceDeclaration namespaceDeclaration)
{
if (this.currentDepth == 0)
{
//we need to track every namespace alias appeared in root element to figure out right alias for MC namespace
this.rootLevelNamespaces.Add(namespaceDeclaration.Prefix);
//Remember namespaces needed to be ignored at top level so we will add ignore attribute for them when we write start object
if (NameSpaces.ShouldIgnore(namespaceDeclaration.Namespace))
{
this.namespacesToIgnore.Add(namespaceDeclaration.Prefix);
}
if (namespaceDeclaration.Namespace == NameSpaces.DebugSymbol)
{
debugSymbolNamespaceAdded = true;
}
}
EmitNamespace(namespaceDeclaration);
}
void EmitNamespace(NamespaceDeclaration namespaceDeclaration)
{
// Write the namespace, filtering for duplicates in the local assembly because VS might be using it to compile itself.
if (schemaContext.IsClrNamespaceWithNoAssembly(namespaceDeclaration.Namespace))
{
// Might still need to trim a semicolon, even though it shouldn't strictly be there.
string nonassemblyQualifedNamespace = namespaceDeclaration.Namespace;
if (nonassemblyQualifedNamespace[nonassemblyQualifedNamespace.Length - 1] == ';')
{
nonassemblyQualifedNamespace = nonassemblyQualifedNamespace.Substring(0, nonassemblyQualifedNamespace.Length - 1);
namespaceDeclaration = new NamespaceDeclaration(nonassemblyQualifedNamespace, namespaceDeclaration.Prefix);
}
EmitLocalNamespace(namespaceDeclaration);
}
else if (schemaContext.IsClrNamespaceInLocalAssembly(namespaceDeclaration.Namespace))
{
string nonassemblyQualifedNamespace = schemaContext.TrimLocalAssembly(namespaceDeclaration.Namespace);
namespaceDeclaration = new NamespaceDeclaration(nonassemblyQualifedNamespace, namespaceDeclaration.Prefix);
if (this.localNamespacesWithAssemblyInfo == null)
{
this.localNamespacesWithAssemblyInfo = new List<NamespaceDeclaration>();
}
this.localNamespacesWithAssemblyInfo.Add(namespaceDeclaration);
}
else
{
base.WriteNamespace(namespaceDeclaration);
}
}
void EmitLocalNamespace(NamespaceDeclaration namespaceDeclaration)
{
if (this.emittedNamespacesInLocalAssembly == null) // lazy initialization
{
this.emittedNamespacesInLocalAssembly = new HashSet<string>();
}
// Write the namespace only once. Add() returns false if it was already there.
if (this.emittedNamespacesInLocalAssembly.Add(namespaceDeclaration.Namespace))
{
base.WriteNamespace(namespaceDeclaration);
}
}
public override void WriteStartObject(XamlType type)
{
if (type.UnderlyingType == typeof(string))
{
isWritingElementStyleString = true;
}
// this is the top-level object
if (this.currentDepth == 0)
{
if (!this.debugSymbolNamespaceAdded)
{
string sadsNamespaceAlias = GenerateNamespaceAlias(NameSpaces.DebugSymbolPrefix);
this.WriteNamespace(new NamespaceDeclaration(NameSpaces.DebugSymbol, sadsNamespaceAlias));
this.debugSymbolNamespaceAdded = true;
}
// we need to write MC namespace if any namespaces need to be ignored
if (this.namespacesToIgnore.Count > 0)
{
string mcNamespaceAlias = GenerateNamespaceAlias(NameSpaces.McPrefix);
this.WriteNamespace(new NamespaceDeclaration(NameSpaces.Mc, mcNamespaceAlias));
}
if (this.localNamespacesWithAssemblyInfo != null)
{
foreach (NamespaceDeclaration xamlNamespace in this.localNamespacesWithAssemblyInfo)
{
if ((this.emittedNamespacesInLocalAssembly == null) || (!this.emittedNamespacesInLocalAssembly.Contains(xamlNamespace.Namespace)))
{
base.WriteNamespace(xamlNamespace);
}
}
}
if ((type.UnderlyingType == typeof(Activity)) ||
(type.IsGeneric && type.UnderlyingType != null && type.UnderlyingType.GetGenericTypeDefinition() == typeof(Activity<>)) ||
(type.UnderlyingType == typeof(WorkflowService)))
{ // Exist ActivityBuilder, DebugSymbolObject will be inserted at the depth == 1.
debugSymbolDepth = 1;
}
else
{
debugSymbolDepth = 0;
}
}
if (this.currentDepth == debugSymbolDepth)
{
if (type.UnderlyingType != null && type.UnderlyingType.IsSubclassOf(typeof(Activity)) && this.shouldWriteDebugSymbol)
{
this.writeDebugSymbol = true;
}
}
base.WriteStartObject(type);
if (this.currentDepth == 0)
{
// we need to add Ignore attribute for all namespaces which we don't want to load assemblies for
// this has to be done after WriteStartObject
if (this.namespacesToIgnore.Count > 0)
{
string nsString = null;
foreach (string ns in this.namespacesToIgnore)
{
if (nsString == null)
{
nsString = ns;
}
else
{
nsString += " " + ns;
}
}
XamlDirective ignorable = new XamlDirective(NameSpaces.Mc, "Ignorable");
base.WriteStartMember(ignorable);
base.WriteValue(nsString);
base.WriteEndMember();
this.namespacesToIgnore.Clear();
}
}
++this.currentDepth;
}
public override void WriteGetObject()
{
++this.currentDepth;
base.WriteGetObject();
}
public override void WriteEndObject()
{
--this.currentDepth;
SharedFx.Assert(this.currentDepth >= 0, "Unmatched WriteEndObject");
if (this.currentDepth == this.debugSymbolDepth && this.writeDebugSymbol)
{
base.WriteStartMember(new XamlMember(DebugSymbol.SymbolName.MemberName,
this.SchemaContext.GetXamlType(typeof(DebugSymbol)), true));
base.WriteValue(EmptyWorkflowSymbol);
base.WriteEndMember();
this.writeDebugSymbol = false;
}
base.WriteEndObject();
isWritingElementStyleString = false;
}
string GenerateNamespaceAlias(string prefix)
{
string aliasPostfix = string.Empty;
//try "mc"~"mc1000" first
for (int i = 1; i <= 1000; i++)
{
string mcAlias = prefix + aliasPostfix;
if (!this.rootLevelNamespaces.Contains(mcAlias))
{
return mcAlias;
}
aliasPostfix = i.ToString(CultureInfo.InvariantCulture);
}
//roll the dice
return prefix + Guid.NewGuid().ToString();
}
class NamespaceIndentingXmlWriter : XmlTextWriter
{
int currentDepth;
TextWriter textWriter;
public NamespaceIndentingXmlWriter(TextWriter textWriter)
: base(textWriter)
{
this.textWriter = textWriter;
this.Formatting = Formatting.Indented;
}
public DesignTimeXamlWriter Parent { get; set; }
public override void WriteStartElement(string prefix, string localName, string ns)
{
base.WriteStartElement(prefix, localName, ns);
this.currentDepth++;
}
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
if (prefix == "xmlns" && (this.currentDepth == 1))
{
this.textWriter.Write(new char[] { '\r', '\n' });
}
base.WriteStartAttribute(prefix, localName, ns);
}
public override void WriteEndElement()
{
if (this.Parent.isWritingElementStyleString)
{
base.WriteRaw(string.Empty);
}
base.WriteEndElement();
this.currentDepth--;
}
public override void WriteStartDocument()
{
// No-op to avoid XmlDeclaration from being written.
// Overriding this is equivalent of XmlWriterSettings.OmitXmlDeclaration = true.
}
public override void WriteStartDocument(bool standalone)
{
// No-op to avoid XmlDeclaration from being written.
// Overriding this is equivalent of XmlWriterSettings.OmitXmlDeclaration = true.
}
public override void WriteEndDocument()
{
// No-op to avoid end of XmlDeclaration from being written.
// Overriding this is equivalent of XmlWriterSettings.OmitXmlDeclaration = true.
}
}
}
}
|