File: cdf\src\NetFx40\Tools\Microsoft.Tools.Common\Microsoft\Activities\Presentation\Xaml\DesignTimeXamlWriter.cs
Project: ndp\System.Data.csproj (System.Data)
// <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.
            }
        }
    }
}