File: System\Windows\Markup\BamlWriter.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
/***************************************************************************\
*
* Purpose:  Public api for writing baml records to a stream
*
\***************************************************************************/
using System.IO;
using System.Collections;
using System.Reflection;
 
namespace System.Windows.Markup
{
    /// <summary>
    /// Writes BAML records to Stream and exposes an XmlWriter-liker interface for BAML
    /// </summary>
    internal class BamlWriter : IParserHelper
    {
#region Constructor
 
        /// <summary>
        /// Create a BamlWriter on the passed stream.  The stream must be writable.
        /// </summary>
        public BamlWriter(
            Stream stream)
        {
            ArgumentNullException.ThrowIfNull(stream);
            if (!stream.CanWrite)
            {
                throw new ArgumentException(SR.BamlWriterBadStream);
            }
            
            _parserContext = new ParserContext();
            if (null == _parserContext.XamlTypeMapper) 
            {
                _parserContext.XamlTypeMapper = new BamlWriterXamlTypeMapper(XmlParserDefaults.GetDefaultAssemblyNames(),
                                                                             XmlParserDefaults.GetDefaultNamespaceMaps());
            }
            _xamlTypeMapper = _parserContext.XamlTypeMapper;
            _bamlRecordWriter = new BamlRecordWriter(stream, _parserContext, true);
            _startDocumentWritten = false;
            _depth = 0;
            _closed = false;
            _nodeTypeStack = new ParserStack();
            _assemblies = new Hashtable(7);
           _extensionParser = new MarkupExtensionParser((IParserHelper)this, _parserContext);
           _markupExtensionNodes = new ArrayList();
        }
 
  
#endregion Constructor
 
#region Close
        /// <summary>
        /// Close the underlying stream and terminate write operations.
        /// </summary>
        /// <remarks>
        /// Once Close() is called, the BamlWriter cannot be used again and all
        /// subsequent calls to BamlWriter will fail.
        /// </remarks>
        public void Close()
        {
            _bamlRecordWriter.BamlStream.Close();
            _closed = true;
        }
#endregion Close
 
#region IParserHelper
 
        string IParserHelper.LookupNamespace(string prefix)
        {
            return _parserContext.XmlnsDictionary[prefix];
        }
 
        bool IParserHelper.GetElementType(
                bool    extensionFirst,  
                string  localName,
                string  namespaceURI,
            ref string  assemblyName,
            ref string  typeFullName,
            ref Type    baseType,
            ref Type    serializerType)
        {
            bool result = false;
 
            assemblyName   = string.Empty;
            typeFullName   = string.Empty;
            serializerType = null;
            baseType       = null;
 
            // if no namespaceURI or local name don't bother
            if (null == namespaceURI || null == localName)
            {
                return false;
            }
 
            TypeAndSerializer typeAndSerializer = 
                _xamlTypeMapper.GetTypeAndSerializer(namespaceURI, localName, null);
            // If the normal type resolution fails, try the name with "Extension" added
            // so that we'll find MarkupExtension subclasses.
            if (typeAndSerializer == null)
            {
                typeAndSerializer = _xamlTypeMapper.GetTypeAndSerializer(namespaceURI, $"{localName}Extension", null);
            }
 
            if (typeAndSerializer != null &&
                typeAndSerializer.ObjectType != null)
            {
                serializerType = typeAndSerializer.SerializerType;
                baseType = typeAndSerializer.ObjectType;
                typeFullName = baseType.FullName;
                assemblyName = baseType.Assembly.FullName;
                result = true;
                
                Debug.Assert(null != assemblyName,"assembly name returned from GetBaseElement is null");
                Debug.Assert(null != typeFullName,"Type name returned from GetBaseElement is null");
            }
 
            return result;
        }
        
 
        bool IParserHelper.CanResolveLocalAssemblies()
        {
            return false;
        }
        
 
#endregion IParserHelper
 
#region Record Writing
 
        /// <summary>
        /// Write the start of document record, giving the baml version string
        /// </summary>
        /// <remarks>
        /// This must be the first call made when creating a new baml file.  This
        /// is needed to specify the start of the document and baml version.
        /// </remarks>
        public void WriteStartDocument()
        {
            if (_closed)
            {
                throw new InvalidOperationException(SR.BamlWriterClosed);
            }
            if (_startDocumentWritten)
            {
                throw new InvalidOperationException(SR.BamlWriterStartDoc);
            }
            
            XamlDocumentStartNode node = new XamlDocumentStartNode(0,0,_depth);
            _bamlRecordWriter.WriteDocumentStart(node);
            _startDocumentWritten = true;
            Push(BamlRecordType.DocumentStart);
        }
 
        /// <summary>
        /// Write the end of document record.
        /// </summary>
        /// <remarks>
        /// This must be the last call made when creating a new baml file.
        /// </remarks>
        public void WriteEndDocument()
        {
            VerifyEndTagState(BamlRecordType.DocumentStart, 
                              BamlRecordType.DocumentEnd);
 
            XamlDocumentEndNode node = new XamlDocumentEndNode(0,0,_depth);
            _bamlRecordWriter.WriteDocumentEnd(node);
        }
 
        /// <summary>
        /// Write the connection Id record.
        /// </summary>
        /// <remarks>
        /// A record that contains the connection Id of the current element
        /// for hooking up IDs and events.
        /// </remarks>
        public void WriteConnectionId(Int32 connectionId)
        {
            VerifyWriteState();
 
            _bamlRecordWriter.WriteConnectionId(connectionId);
        }
 
 
        /// <summary>
        /// Write the start of element record.
        /// </summary>
        /// <remarks>
        /// An element start marks the beginning of an object that exists
        /// in a tree structure.  This may the contents of a complex property,
        /// or an element in the logical tree.
        /// </remarks>
        public void WriteStartElement(
            string assemblyName,
            string typeFullName,
            bool isInjected,
            bool useTypeConverter)
        {
            VerifyWriteState();
            _dpProperty = null;
            _parserContext.PushScope();
            ProcessMarkupExtensionNodes();
 
            Type elementType = GetType(assemblyName, typeFullName);
            Type serializerType = _xamlTypeMapper.GetXamlSerializerForType(elementType);
            Push(BamlRecordType.ElementStart, elementType);
            XamlElementStartNode node = new XamlElementStartNode(
                                               0,
                                               0,
                                               _depth++,
                                               assemblyName,
                                               typeFullName,
                                               elementType,
                                               serializerType);  
            node.IsInjected = isInjected;
            node.CreateUsingTypeConverter = useTypeConverter;
            
            _bamlRecordWriter.WriteElementStart(node);
        }
 
        /// <summary>
        /// Write the end of element record.
        /// </summary>
        /// <remarks>
        /// An element end marks the ending of an object that exists
        /// in a tree structure.  This may the contents of a complex property,
        /// or an element in the logical tree.
        /// </remarks>
        public void WriteEndElement()
        {
            VerifyEndTagState(BamlRecordType.ElementStart, 
                              BamlRecordType.ElementEnd);
            ProcessMarkupExtensionNodes();
            
            XamlElementEndNode node = new XamlElementEndNode(
                                               0,
                                               0,
                                               --_depth);
            _bamlRecordWriter.WriteElementEnd(node);
            _parserContext.PopScope();
        }
        
        /// <summary>
        /// Write the start of constructor section that follows the start of an element.
        /// </summary>
        public void WriteStartConstructor()
        {
            VerifyWriteState();
            Push(BamlRecordType.ConstructorParametersStart);
            
            XamlConstructorParametersStartNode node = new XamlConstructorParametersStartNode(
                                               0,
                                               0,
                                               --_depth);
            _bamlRecordWriter.WriteConstructorParametersStart(node);
        }
 
        /// <summary>
        /// Write the end of constructor section that follows the start of an element.
        /// </summary>
        public void WriteEndConstructor()
        {
            VerifyEndTagState(BamlRecordType.ConstructorParametersStart, 
                              BamlRecordType.ConstructorParametersEnd);
            
            XamlConstructorParametersEndNode node = new XamlConstructorParametersEndNode(
                                               0,
                                               0,
                                               --_depth);
            _bamlRecordWriter.WriteConstructorParametersEnd(node);
        }
        
        /// <summary>
        /// Write simple property information to baml.
        /// </summary>
        /// <remarks>
        /// If the type of this property supports
        /// custom serialization using a XamlSerializer and custom serialization
        /// is chosen, then write out a 
        /// Custom serialization record.  Note that for custom serialization
        /// to work, the assembly that contains the property's type must be 
        /// loaded. If custom serialization is not chosen, then
        /// write out a 'normal' record, which will cause type conversion to happen
        /// at load time from the stored string.
        /// </remarks>
        public void WriteProperty(
            string             assemblyName,
            string             ownerTypeFullName,
            string             propName,
            string             value,
            BamlAttributeUsage propUsage)
        {
            VerifyWriteState();
            
            BamlRecordType parentType = PeekRecordType();
            if (parentType != BamlRecordType.ElementStart)
            {
                throw new InvalidOperationException(SR.Format(SR.BamlWriterNoInElement,
                                                           "WriteProperty",
                                                           parentType.ToString()));
            }
 
            object dpOrPi;
            Type   declaringType;
            GetDpOrPi(assemblyName, ownerTypeFullName, propName, out dpOrPi, out declaringType);
 
            // Check if the value is a MarkupExtension.  If so it must be expanded into
            // a series of baml records.  Otherwise just write out the property.
            AttributeData data = _extensionParser.IsMarkupExtensionAttribute(
                                                        declaringType,    
                                                        propName,       
                                                    ref value,
                                                        0,       // No line numbers for baml
                                                        0,
                                                        0,
                                                        dpOrPi);
 
            if (data == null)
            {
                XamlPropertyNode propNode = new XamlPropertyNode(
                                            0,
                                            0,
                                            _depth,
                                            dpOrPi,   
                                            assemblyName,
                                            ownerTypeFullName,
                                            propName,
                                            value,
                                            propUsage,
                                            false);
 
                Type propType = XamlTypeMapper.GetPropertyType(dpOrPi);
                if (propType == typeof(DependencyProperty))
                {
                    Type ownerType = null;
                    _dpProperty = XamlTypeMapper.ParsePropertyName(_parserContext, value, ref ownerType);
 
                    if (_bamlRecordWriter != null && _dpProperty != null)
                    {
                        short typeId;
                        short propertyId = _parserContext.MapTable.GetAttributeOrTypeId(_bamlRecordWriter.BinaryWriter,
                                                                                        ownerType,
                                                                                        _dpProperty.Name,
                                                                                        out typeId);
 
                        if (propertyId < 0)
                        {
                            propNode.ValueId = propertyId;
                            propNode.MemberName = null;
                        }
                        else
                        {
                            propNode.ValueId = typeId;
                            propNode.MemberName = _dpProperty.Name;
                        }
                    }
                }
                else if (_dpProperty != null)
                {
                    propNode.ValuePropertyType = _dpProperty.PropertyType;
                    propNode.ValuePropertyMember = _dpProperty;
                    propNode.ValuePropertyName = _dpProperty.Name;
                    propNode.ValueDeclaringType = _dpProperty.OwnerType;
                    string propAssemblyName = _dpProperty.OwnerType.Assembly.FullName;
                    _dpProperty = null;
                }
 
                _bamlRecordWriter.WriteProperty(propNode);            
            }
            else
            {
                if (data.IsSimple)
                {
                    if (data.IsTypeExtension)
                    {
                        Type typeValue = _xamlTypeMapper.GetTypeFromBaseString(data.Args,
                                                                               _parserContext,
                                                                               true);
                        Debug.Assert(typeValue != null);
 
                        XamlPropertyWithTypeNode xamlPropertyWithTypeNode =
                            new XamlPropertyWithTypeNode(0,
                                                         0,
                                                         _depth,
                                                         dpOrPi,   
                                                         assemblyName,
                                                         ownerTypeFullName,
                                                         propName,
                                                         typeValue.FullName,
                                                         typeValue.Assembly.FullName,
                                                         typeValue,   
                                                         string.Empty,
                                                         string.Empty);
 
                        _bamlRecordWriter.WritePropertyWithType(xamlPropertyWithTypeNode);
                    }
                    else
                    {
                        XamlPropertyWithExtensionNode xamlPropertyWithExtensionNode =
                            new XamlPropertyWithExtensionNode(0,
                                                              0,
                                                              _depth,
                                                              dpOrPi,
                                                              assemblyName,
                                                              ownerTypeFullName,
                                                              propName,
                                                              data.Args,
                                                              data.ExtensionTypeId,
                                                              data.IsValueNestedExtension,
                                                              data.IsValueTypeExtension);
 
                        _bamlRecordWriter.WritePropertyWithExtension(xamlPropertyWithExtensionNode);
                    }
                }
                else
                {
                    _extensionParser.CompileAttribute(
                                            _markupExtensionNodes, data);
                }
            }
        }
 
        /// <summary>
        /// Write Content Property Record.
        /// </summary>
        public void WriteContentProperty(
            string             assemblyName,
            string             ownerTypeFullName,
            string             propName )
        {
            object dpOrPi;
            Type   declaringType;
            GetDpOrPi(assemblyName, ownerTypeFullName, propName, out dpOrPi, out declaringType);
 
            XamlContentPropertyNode CpaNode = new XamlContentPropertyNode(
                                                    0, 0, _depth,
                                                    dpOrPi,
                                                    assemblyName,
                                                    ownerTypeFullName,
                                                    propName);
            _bamlRecordWriter.WriteContentProperty( CpaNode );
        }
 
        /// <summary>
        /// Write xml namespace declaration information to baml.
        /// </summary>
        /// <remarks>
        /// This is used for setting the default namespace, or for mapping
        /// a namespace prefix (or localName) to a namespace.  If prefix is 
        /// an empty string, then this sets the default namespace.
        /// </remarks>
        public void WriteXmlnsProperty(
            string  localName,
            string  xmlNamespace)
        {
            VerifyWriteState();
            
            BamlRecordType parentType = PeekRecordType();
            if (parentType != BamlRecordType.ElementStart &&
                parentType != BamlRecordType.PropertyComplexStart &&
                parentType != BamlRecordType.PropertyArrayStart &&
                parentType != BamlRecordType.PropertyIListStart &&
                parentType != BamlRecordType.PropertyIDictionaryStart)
            {
                throw new InvalidOperationException(SR.Format(SR.BamlWriterBadXmlns,
                                                           "WriteXmlnsProperty",
                                                           parentType.ToString()));
            }
            
            XamlXmlnsPropertyNode xmlnsNode = new XamlXmlnsPropertyNode(
                                                    0,
                                                    0,
                                                    _depth,
                                                    localName,
                                                    xmlNamespace);
        
            _bamlRecordWriter.WriteNamespacePrefix(xmlnsNode);
            _parserContext.XmlnsDictionary[localName] = xmlNamespace;
        }
 
        /// <summary>
        /// Write an attribute in the Definition namespace.  
        /// </summary>
        /// <remarks>
        /// This is really a processing directive, rather than an actual property.
        /// It is used to define keys for dictionaries, resource references,
        /// language declarations, base class names for tags, etc.
        /// </remarks>
        public void WriteDefAttribute(
            string name,
            string value)
        {
            VerifyWriteState();
            
            BamlRecordType parentType = PeekRecordType();
            if (parentType != BamlRecordType.ElementStart &&
                name != "Uid" ) // Parser's supposed to ignore x:Uid everywhere
            {
                throw new InvalidOperationException(SR.Format(SR.BamlWriterNoInElement,
                                                           "WriteDefAttribute",
                                                           parentType.ToString()));
            }
            else if (name == XamlReaderHelper.DefinitionName)
            {
                // Check if this is a MarkupExtension, and if so expand it in xaml so that
                // it represents a key tree.
                DefAttributeData data = _extensionParser.IsMarkupExtensionDefAttribute(
                                                            PeekElementType(),    
                                                            ref value, 0, 0, 0);
 
                if (data != null)
                {
                    if (name != XamlReaderHelper.DefinitionName)
                    {
                        data.IsSimple = false;
                    }
                    if (data.IsSimple)
                    {
                        // If the MarkupExtension does not expand out into a complex property
                        // subtree, but can be handled inline with other properties, then
                        // process it immediately in place of the normal property
                        int colonIndex = data.Args.IndexOf(':');
                        string prefix = string.Empty;
                        string typeName = data.Args;
                        if (colonIndex > 0)
                        {
                            prefix = data.Args.Substring(0, colonIndex);
                            typeName = data.Args.Substring(colonIndex+1);
                        }
 
                        string valueNamespaceURI = _parserContext.XmlnsDictionary[prefix];
                        string valueAssemblyName = string.Empty;
                        string valueTypeFullName = string.Empty;
                        Type   valueElementType = null;
                        Type   valueSerializerType = null;
 
                        bool resolvedTag = ((IParserHelper)this).GetElementType(false, typeName, 
                                                        valueNamespaceURI,ref valueAssemblyName,
                                                        ref valueTypeFullName, ref valueElementType, 
                                                        ref valueSerializerType);
 
                        // If we can't resolve a simple TypeExtension value at compile time,
                        // then write it out as a normal type extension and wait until runtime
                        // since the type may not be visible now.
                        if (resolvedTag)
                        {
                            XamlDefAttributeKeyTypeNode defKeyNode = new XamlDefAttributeKeyTypeNode(
                                                                    0,
                                                                    0,
                                                                    _depth,
                                                                    valueTypeFullName,  
                                                                    valueElementType.Assembly.FullName,  
                                                                    valueElementType);
                            _bamlRecordWriter.WriteDefAttributeKeyType(defKeyNode);
                        }
                        else
                        {
                            data.IsSimple = false;
                            data.Args += "}";
                        }
                    }
                    if (!data.IsSimple)
                    {
                        _extensionParser.CompileDictionaryKey(
                                            _markupExtensionNodes, data);
                    }
                    return;
                }
            }
            
            XamlDefAttributeNode defNode = new XamlDefAttributeNode(
                                                    0,
                                                    0,
                                                    _depth,
                                                    name,
                                                    value);
        
            _bamlRecordWriter.WriteDefAttribute(defNode);
        }
 
        /// <summary>
        /// Write an attribute in the PresentationOptions namespace.  
        /// </summary>
        /// <remarks>
        /// This is really a processing directive, rather than an actual property.
        /// It is used to define WPF-specific parsing options (e.g., PresentationOptions:Freeze)
        /// </remarks>
        public void WritePresentationOptionsAttribute(
            string name,
            string value)
        {
            VerifyWriteState();
            
            XamlPresentationOptionsAttributeNode defNode = new XamlPresentationOptionsAttributeNode(
                                                    0,
                                                    0,
                                                    _depth,
                                                    name,
                                                    value);
        
            _bamlRecordWriter.WritePresentationOptionsAttribute(defNode);
        }        
 
        /// <summary>
        /// Write the start of a complex property
        /// </summary>
        /// <remarks>
        /// A complex property start marks the beginning of an object that exists
        /// in a property on an element in the tree.
        /// </remarks>
        public void WriteStartComplexProperty(
            string assemblyName,
            string ownerTypeFullName,
            string propName)
        {
            VerifyWriteState();
            _parserContext.PushScope();
            ProcessMarkupExtensionNodes();
 
            object dpOrPi;
            Type   ownerType;
            Type   propertyType = null;
            bool   propertyCanWrite = true;
            
            GetDpOrPi(assemblyName, ownerTypeFullName, propName, out dpOrPi, out ownerType);
            if (dpOrPi == null)
            {
                MethodInfo mi = GetMi(assemblyName, ownerTypeFullName, propName, out ownerType);
                if (mi != null)
                {
                    XamlTypeMapper.GetPropertyType(mi, out propertyType, out propertyCanWrite);
                }
            }
            else
            {
                propertyType = XamlTypeMapper.GetPropertyType(dpOrPi);
                PropertyInfo pi = dpOrPi as PropertyInfo;
                if (pi != null)
                {
                    propertyCanWrite = pi.CanWrite;
                }
                else
                {
                    DependencyProperty dp = dpOrPi as DependencyProperty;
                    if (dp != null)
                    {
                        propertyCanWrite = !dp.ReadOnly;
                    }
                }
            }
 
            // Based on the type of the property, we could write this as 
            // a complex IList, Array, IDictionary or a regular complex property.
            // NOTE:  The order this is checked in must the same order as the
            //        XamlReaderHelper.CompileComplexProperty so that we have
            //        the same property behavior (eg - if something implements
            //        IDictionary and IList, it is treated as an IList)
            if (propertyType == null)
            {
                // Unknown complex properties are written out using the string
                // information passed.  This applies to things like Set.Value
                // and other style related DPs
                Push(BamlRecordType.PropertyComplexStart);
                XamlPropertyComplexStartNode propertyStart = new XamlPropertyComplexStartNode(
                                                                 0,
                                                                 0,
                                                                 _depth++,
                                                                 null,
                                                                 assemblyName,
                                                                 ownerTypeFullName,
                                                                 propName);
                _bamlRecordWriter.WritePropertyComplexStart(propertyStart);
            }
            else
            {
                BamlRecordType recordType = BamlRecordManager.GetPropertyStartRecordType(propertyType, propertyCanWrite);
                Push(recordType);
                
                switch (recordType)
                {
                    case BamlRecordType.PropertyArrayStart:
                    {
                        XamlPropertyArrayStartNode arrayStart = new XamlPropertyArrayStartNode(
                                                                    0,
                                                                    0,
                                                                    _depth++,
                                                                    dpOrPi,
                                                                    assemblyName,
                                                                    ownerTypeFullName,
                                                                    propName);
                        _bamlRecordWriter.WritePropertyArrayStart(arrayStart);
                        break;
                    }
                    case BamlRecordType.PropertyIDictionaryStart:
                    {
                         XamlPropertyIDictionaryStartNode dictionaryStart = 
                                                  new XamlPropertyIDictionaryStartNode(
                                                              0,
                                                              0,
                                                              _depth++,
                                                              dpOrPi,
                                                              assemblyName,
                                                              ownerTypeFullName,
                                                              propName);
                        _bamlRecordWriter.WritePropertyIDictionaryStart(dictionaryStart);
                        break;
                    }
                    case BamlRecordType.PropertyIListStart:
                    {
                        XamlPropertyIListStartNode listStart = new XamlPropertyIListStartNode(
                                                                        0,
                                                                        0,
                                                                        _depth++,
                                                                        dpOrPi,
                                                                        assemblyName,
                                                                        ownerTypeFullName,
                                                                        propName);
                        _bamlRecordWriter.WritePropertyIListStart(listStart);
                        break;
                    }
                    default: // PropertyComplexStart
                    {
                         XamlPropertyComplexStartNode node = new XamlPropertyComplexStartNode(
                                                       0,
                                                       0,
                                                       _depth++,
                                                       dpOrPi,
                                                       assemblyName,
                                                       ownerTypeFullName,
                                                       propName);
                    
                        _bamlRecordWriter.WritePropertyComplexStart(node);
                        break;
                    }
                }
            }
        }
 
        /// <summary>
        /// Write the end of the complex property
        /// </summary>
        public void WriteEndComplexProperty()
        {
            VerifyWriteState();
 
            // The type of end record written depends on what the start record
            // was.  This is held on the _nodeTypeStack
            BamlRecordType startTagType = Pop();
 
            switch (startTagType)
            {
                case BamlRecordType.PropertyArrayStart:
                    XamlPropertyArrayEndNode arrayEnd = 
                                      new XamlPropertyArrayEndNode(
                                                  0, 
                                                  0, 
                                                  --_depth);
                    _bamlRecordWriter.WritePropertyArrayEnd(arrayEnd);
                    break;
                    
                case BamlRecordType.PropertyIListStart:
                    XamlPropertyIListEndNode listEnd = 
                                      new XamlPropertyIListEndNode(
                                                  0, 
                                                  0, 
                                                  --_depth);
                    _bamlRecordWriter.WritePropertyIListEnd(listEnd);
                    break;
        
                case BamlRecordType.PropertyIDictionaryStart:
                    XamlPropertyIDictionaryEndNode dictionaryEnd = 
                                      new XamlPropertyIDictionaryEndNode(
                                                  0, 
                                                  0, 
                                                  --_depth);
                    _bamlRecordWriter.WritePropertyIDictionaryEnd(dictionaryEnd);
                    break;
                    
                case BamlRecordType.PropertyComplexStart:
                    XamlPropertyComplexEndNode complexEnd = 
                                       new XamlPropertyComplexEndNode(
                                                  0, 
                                                  0, 
                                                  --_depth);
                    _bamlRecordWriter.WritePropertyComplexEnd(complexEnd);
                    break;
 
                default:
                    throw new InvalidOperationException(
                                    SR.Format(SR.BamlWriterBadScope,
                                           startTagType.ToString(),
                                           BamlRecordType.PropertyComplexEnd.ToString()));
            }                        
            _parserContext.PopScope();
        }
 
        /// <summary>
        /// Write a literal content record to baml stream
        /// </summary>
        public void WriteLiteralContent(
            string contents)
        {
            VerifyWriteState();
            ProcessMarkupExtensionNodes();
 
            XamlLiteralContentNode literalContent = new XamlLiteralContentNode(
                                                               0,
                                                               0,
                                                               _depth,
                                                               contents);
            _bamlRecordWriter.WriteLiteralContent(literalContent);
        }
 
        /// <summary>
        /// Write a mapping processing instruction to baml stream
        /// </summary>
        public void WritePIMapping(
            string    xmlNamespace,
            string    clrNamespace,
            string    assemblyName)
        {
            VerifyWriteState();
            ProcessMarkupExtensionNodes();
 
            XamlPIMappingNode piMapping = new XamlPIMappingNode(
                                                     0,
                                                     0,
                                                     _depth,
                                                     xmlNamespace,
                                                     clrNamespace,
                                                     assemblyName);
            if (!_xamlTypeMapper.PITable.Contains(xmlNamespace))
            {
                ClrNamespaceAssemblyPair mapping = new ClrNamespaceAssemblyPair(clrNamespace, assemblyName);
                _xamlTypeMapper.PITable.Add(xmlNamespace, mapping);
            }
            // Write it out anyway.  It is redundant but we are being asked to write it so write it.
            // In the round trip case this will preserve the exact file data.
            _bamlRecordWriter.WritePIMapping(piMapping);
        }
 
        /// <summary>
        /// Write text content into baml stream
        /// </summary>
        public void WriteText(
            string textContent,
            string typeConverterAssemblyName,
            string typeConverterName)
        {
            VerifyWriteState();
            ProcessMarkupExtensionNodes();
 
            Type typeConverter=null;
            if (!String.IsNullOrEmpty(typeConverterName))
            {
                typeConverter = GetType(typeConverterAssemblyName, typeConverterName);
            }
 
            XamlTextNode textNode = new XamlTextNode(
                                             0,
                                             0,
                                             _depth,
                                             textContent,
                                             typeConverter);
            _bamlRecordWriter.WriteText(textNode);
        }
 
        /// <summary>
        /// Write a routed event record to BAML.  
        /// </summary>
        /// <remarks>
        /// The Avalon parser does not process routed event records itself and will
        /// throw an exception if it encounters a routed event record in BAML.  It
        /// is included here for completeness and future expandability.
        /// </remarks>
        public void WriteRoutedEvent(
            string     assemblyName,
            string     ownerTypeFullName,
            string     eventIdName,
            string     handlerName)
        {
#if EVENTSUPPORT            
            VerifyWriteState();
 
            XamlRoutedEventNode eventNode = new XamlRoutedEventNode(
                                                      0,
                                                      0,
                                                      _depth,
                                                      null,
                                                      assemblyName,
                                                      ownerTypeFullName,
                                                      eventIdName,
                                                      handlerName);
            _bamlRecordWriter.WriteRoutedEvent(eventNode);
#else
            throw new NotSupportedException(SR.Format(SR.ParserBamlEvent, eventIdName));
#endif
 
        }
 
        /// <summary>
        /// Write an event record to BAML.  
        /// </summary>
        /// <remarks>
        /// The Avalon parser does not process event records itself and will
        /// throw an exception if it encounters  an event record in BAML.  It
        /// is included here for completeness and future expandability.
        /// </remarks>
        public void WriteEvent(
            string    eventName,
            string    handlerName)
        {
#if EVENTSUPPORT            
            VerifyWriteState();
 
            XamlClrEventNode eventNode = new XamlClrEventNode(
                                                  0,
                                                  0,
                                                  _depth,
                                                  eventName,
                                                  null,
                                                  handlerName);
            _bamlRecordWriter.WriteClrEvent(eventNode);
#else
            throw new NotSupportedException(SR.Format(SR.ParserBamlEvent, eventName));
#endif
        }
 
#endregion Record Writing
 
#region Internal Methods
 
    /***************************************************************************\
    *
    * BamlWriter.ProcessMarkupExtensionNodes
    *
    * Write out baml records for all the xamlnodes contained in the buffered
    * markup extension list.
    * NOTE:  This list must contain only xamlnodes that are known to be part of
    *        MarkupExtensions.  This is NOT a general method to handle all types
    *        of xaml nodes.
    *
    \***************************************************************************/
 
    private void ProcessMarkupExtensionNodes()
    {
        for (int i=0; i < _markupExtensionNodes.Count; i++)
        {
            XamlNode node = _markupExtensionNodes[i] as XamlNode;
            switch (node.TokenType)
            {
                case XamlNodeType.ElementStart:
                    _bamlRecordWriter.WriteElementStart((XamlElementStartNode)node);
                    break;
                case XamlNodeType.ElementEnd:
                    _bamlRecordWriter.WriteElementEnd((XamlElementEndNode)node);
                    break;
                case XamlNodeType.KeyElementStart:
                    _bamlRecordWriter.WriteKeyElementStart((XamlKeyElementStartNode)node);
                    break;
                case XamlNodeType.KeyElementEnd:
                    _bamlRecordWriter.WriteKeyElementEnd((XamlKeyElementEndNode)node);
                    break;
                case XamlNodeType.Property:
                    _bamlRecordWriter.WriteProperty((XamlPropertyNode)node);
                    break;
                case XamlNodeType.PropertyWithExtension:
                    _bamlRecordWriter.WritePropertyWithExtension((XamlPropertyWithExtensionNode)node);
                    break;
                case XamlNodeType.PropertyWithType:
                    _bamlRecordWriter.WritePropertyWithType((XamlPropertyWithTypeNode)node);
                    break;
                case XamlNodeType.PropertyComplexStart:
                    _bamlRecordWriter.WritePropertyComplexStart((XamlPropertyComplexStartNode)node);
                    break;
                case XamlNodeType.PropertyComplexEnd:
                    _bamlRecordWriter.WritePropertyComplexEnd((XamlPropertyComplexEndNode)node);
                    break;
                case XamlNodeType.Text:
                    _bamlRecordWriter.WriteText((XamlTextNode)node);
                    break;
                case XamlNodeType.EndAttributes:
                    _bamlRecordWriter.WriteEndAttributes((XamlEndAttributesNode)node);
                    break;
                case XamlNodeType.ConstructorParametersStart:
                    _bamlRecordWriter.WriteConstructorParametersStart((XamlConstructorParametersStartNode)node);
                    break;
                case XamlNodeType.ConstructorParametersEnd:
                    _bamlRecordWriter.WriteConstructorParametersEnd((XamlConstructorParametersEndNode)node);
                    break;
                default:
                    throw new InvalidOperationException(SR.BamlWriterUnknownMarkupExtension);
            }
        }
        _markupExtensionNodes.Clear();
    }
 
    /***************************************************************************\
    *
    * BamlWriter.VerifyWriteState
    *
    * Verify that we are in a good state to perform a record write.  Throw
    * appropriate exceptions if not.
    *
    \***************************************************************************/
    
    private void VerifyWriteState()
    {
        if (_closed)
        {
            throw new InvalidOperationException(SR.BamlWriterClosed);
        }
        if (!_startDocumentWritten)
        {
            throw new InvalidOperationException(SR.BamlWriterStartDoc);
        }
    }
 
    /***************************************************************************\
    *
    * BamlWriter.VerifyEndTagState
    *
    * Verify that we are in a good state to perform a record write and that
    * the xamlnodetype on the node type stack is of the expected type.  This
    * is called when an end tag record is written
    *
    \***************************************************************************/
    
    private void VerifyEndTagState(
        BamlRecordType   expectedStartTag,
        BamlRecordType   endTagBeingWritten)
    {
        VerifyWriteState();
 
        BamlRecordType startTagState = Pop();
        if (startTagState != expectedStartTag)
        {
            throw new InvalidOperationException(SR.Format(SR.BamlWriterBadScope,
                                                       startTagState.ToString(),
                                                       endTagBeingWritten.ToString()));
        }
    }
 
    /***************************************************************************\
    *
    * BamlWriter.GetAssembly
    *
    * Get the Assembly given a name.  This uses the LoadWrapper to load the
    * assembly from the current directory.  
    * NOTE:  Assembly paths are not currently supported, but may be in the
    *        future if the need arises.
    *
    \***************************************************************************/
 
    private Assembly GetAssembly(string assemblyName)
    {
        Assembly assy = _assemblies[assemblyName] as Assembly;
        if (assy == null)
        {
            assy = ReflectionHelper.LoadAssembly(assemblyName, null);
            if (assy == null)
            {
                throw new ArgumentException(SR.Format(SR.BamlWriterBadAssembly, 
                                                   assemblyName));
            }
            else
            {
                _assemblies[assemblyName] = assy;
            }
        }
 
        return assy;
    }
 
    /***************************************************************************\
    *
    * BamlWriter.GetType
    *
    * Get the Type given an assembly name where the type is declared and the
    * type's fully qualified name
    *
    \***************************************************************************/
    
    private Type GetType(
        string assemblyName,
        string typeFullName)
    {
        // Get the assembly that contains the type of the element
        Assembly assembly = GetAssembly(assemblyName);
        
        // Now see if the type is declared in this assembly
        Type objectType = assembly.GetType(typeFullName);
 
        // Note that null objectType is allowed, since this may be something like
        // a <Set> element in a style, which is mapped to a element tag
        return objectType;
    }
 
    /***************************************************************************\
    *
    * BamlWriter.GetDpOrPi
    *
    * Get the DependencyProperty or the PropertyInfo that corresponds to the 
    * passed property name on the passed owner type (or type where the
    * property is declared).
    *
    \***************************************************************************/
 
    private object GetDpOrPi(
        Type    ownerType,
        string  propName)
    {
        // Now that we have the type, see if this is a DependencyProperty or
        // a PropertyInfo.  Note that ownerType may be null for fake properties
        // like those associated with Styles.  Note also that the property may
        // not resolve to a known DP.  This is allowed for fake properties like
        // those contained in Styles, such as Set.Value.
        object dpOrPi = null;
 
#if !PBTCOMPILER
        if (ownerType != null)
        {
            dpOrPi = DependencyProperty.FromName(propName, ownerType);
            if (dpOrPi == null)
            {
                PropertyInfo mostDerived = null;
                MemberInfo[] infos = ownerType.GetMember(propName, MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public);
                foreach(PropertyInfo pi in infos)
                {
                    if(pi.GetIndexParameters().Length == 0)
                    {
                        if(mostDerived == null || mostDerived.DeclaringType.IsAssignableFrom(pi.DeclaringType))
                        {
                            mostDerived = pi;
                        }
                    }
                }
                dpOrPi = mostDerived;
            }
        }
#else
        if (ownerType != null)
        {
 
            dpOrPi = KnownTypes.Types[(int)KnownElements.DependencyProperty].InvokeMember("FromName", 
                                             BindingFlags.InvokeMethod, 
                                             null, 
                                             null, 
                                             new object[] {propName, ownerType} );
 
            if (dpOrPi == null)
            {
                dpOrPi = ownerType.GetProperty(propName, 
                                   BindingFlags.Instance | BindingFlags.Public);
            }
        }
#endif
 
        return dpOrPi;
    }
    
    /***************************************************************************\
    *
    * BamlWriter.GetDpOrPi
    *
    * Get the DependencyProperty or the PropertyInfo that corresponds to the 
    * passed property name on the passed owner type name.  This typename is
    * first resolved to a type.
    *
    \***************************************************************************/
 
    private void GetDpOrPi(
            string  assemblyName,
            string  ownerTypeFullName,
            string  propName,
        out object  dpOrPi,
        out Type    ownerType)
    {
        // If there is no valid owner or assembly, then we can't resolve the type,
        // so just return null.  This can occur if we are writing 'fake' properties
        // that are used for things such as Style property triggers.
        if (assemblyName == string.Empty || ownerTypeFullName == string.Empty)
        {
            dpOrPi = null;
            ownerType = null;
        }
        else
        {
            ownerType = GetType(assemblyName, ownerTypeFullName);
            dpOrPi = GetDpOrPi(ownerType, propName);
        }
    }
 
    private MethodInfo GetMi(Type ownerType, string propName)
    {
        MethodInfo memberInfo = null;
 
        memberInfo = ownerType.GetMethod($"Set{propName}",
                                            BindingFlags.Public |
                                            BindingFlags.Static |
                                            BindingFlags.FlattenHierarchy);
        if (memberInfo != null && ((MethodInfo)memberInfo).GetParameters().Length != 2)
        {
            memberInfo = null;
        }
 
        // Try read-only case (Getter only)
        if (memberInfo == null)
        {
            memberInfo = ownerType.GetMethod($"Get{propName}",
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
            if (memberInfo != null && ((MethodInfo)memberInfo).GetParameters().Length != 1)
            {
                memberInfo = null;
            }
        }
        return memberInfo;
    }
 
    private MethodInfo GetMi(
            string assemblyName,
            string ownerTypeFullName,
            string propName,
            out Type ownerType)
    {
        MethodInfo mi = null;
 
        // If there is no valid owner or assembly, then we can't resolve the type,
        // so just return null.  This can occur if we are writing 'fake' properties
        // that are used for things such as Style property triggers.
        if (assemblyName == string.Empty || ownerTypeFullName == string.Empty)
        {
            mi = null;
            ownerType = null;
        }
        else
        {
            ownerType = GetType(assemblyName, ownerTypeFullName);
            mi = GetMi(ownerType, propName);
        }
        return mi;
    }
 
 
    // Helper methods for dealing with the node stack
 
    // Push a new record type on the stack.  If we have not generated an 
    // end attributes write operation for the current item on the stack, and
    // that item is a start element, then now is the time to do it.
    private void Push(BamlRecordType recordType)
    {
        CheckEndAttributes();
        _nodeTypeStack.Push(new WriteStackNode(recordType));
    }
    
    private void Push(BamlRecordType recordType, Type elementType)
    {
        CheckEndAttributes();
        _nodeTypeStack.Push(new WriteStackNode(recordType, elementType));
    }
 
    // Pop an item off the node stack and return its type.
    private BamlRecordType Pop()
    {
        WriteStackNode stackNode = _nodeTypeStack.Pop() as WriteStackNode;
        Debug.Assert(stackNode != null);
        return stackNode.RecordType;
    }
 
    // Return the record type on the top of the stack
    private BamlRecordType PeekRecordType()
    {
        WriteStackNode stackNode = _nodeTypeStack.Peek() as WriteStackNode;
        Debug.Assert(stackNode != null);
        return stackNode.RecordType;
    }
    
    // Return the element type on the top of the stack
    private Type PeekElementType()
    {
        WriteStackNode stackNode = _nodeTypeStack.Peek() as WriteStackNode;
        Debug.Assert(stackNode != null);
        return stackNode.ElementType;
    }
 
    // Check if we have to insert an EndAttributes xaml node at the end
    // of an element start tag.
    private void CheckEndAttributes()
    {
        if (_nodeTypeStack.Count > 0)
        {
            WriteStackNode parentNode = _nodeTypeStack.Peek() as WriteStackNode;
            if (!parentNode.EndAttributesReached &&
                parentNode.RecordType == BamlRecordType.ElementStart)
            {
                XamlEndAttributesNode node = new XamlEndAttributesNode(
                                                   0,
                                                   0,
                                                   _depth,
                                                   false);
                _bamlRecordWriter.WriteEndAttributes(node);
            }
            parentNode.EndAttributesReached = true;
        }
    }
 
 
#endregion Internal Methods
 
#region Data
 
        // Item pushed on a node stack to keep track for matching
        // end tags and for determining when the end of the 
        // start tag has been reached for generating an end attribute write.
        private class WriteStackNode
        {
            public WriteStackNode(
                BamlRecordType   recordType)
            {
                _recordType = recordType;
                _endAttributesReached = false;
            }
            
            public WriteStackNode(
                BamlRecordType   recordType,
                Type             elementType) : this(recordType)
            {
                _elementType = elementType;
            }
 
            public BamlRecordType RecordType
            {
                get { return _recordType; }
            }
 
            public bool EndAttributesReached 
            {
                get { return _endAttributesReached; }
                set { _endAttributesReached = value; }
            }
 
            public Type ElementType
            {
                get { return _elementType; }
            }
 
            bool           _endAttributesReached;
            BamlRecordType _recordType;
            Type           _elementType;
        }
 
        // Writer that actually writes BamlRecords to a stream.
        BamlRecordWriter      _bamlRecordWriter;
    
        // True if the DocumentStart record has been written to the stream.
        bool                  _startDocumentWritten;
 
        // The depth of the element tree, including complex properties
        int                   _depth;
 
        // True if Close() has been called.
        bool                  _closed;
 
        // If a custom property is of Type DependencyProperty, this is used to provide
        // info about the Type of value for such a property in order to write it out in
        // an optimized form by its custom serializer.
        DependencyProperty    _dpProperty;
 
        // Stack of the type of nodes written to the baml stream.  This is 
        // used for end-tag matching and basic structure checking.  This
        // contains WriteStackNode objects.
        ParserStack           _nodeTypeStack;
 
        // Cache of assemblies that are needed for type resolutions when
        // doingIBamlSerialize
        Hashtable             _assemblies;
    
        // XamlTypeMapper used by this writer
        XamlTypeMapper        _xamlTypeMapper;
 
        // ParserContext for this writer
        ParserContext         _parserContext;
 
        // The helper class that handles parsing of MarkupExtension values.
        MarkupExtensionParser _extensionParser;
 
        // Buffered XamlNodes that occur when a property with a MarkupExtension value
        // is written and expanded into a complex property subtree.
        ArrayList             _markupExtensionNodes;
 
#endregion Data
    }
 
    internal class BamlWriterXamlTypeMapper : XamlTypeMapper
    {
        internal BamlWriterXamlTypeMapper(
            string[] assemblyNames,
            NamespaceMapEntry[] namespaceMaps) : base(assemblyNames, namespaceMaps)
        {
        }
 
        /// <summary>
        /// Allows BamlWriter to allow access to legitimate internal types
        /// </summary>
        /// <param name="type">The internal type</param>
        /// <returns>
        /// Always Returns true by default, since this is used by localization
        /// </returns>
        sealed protected override bool AllowInternalType(Type type)
        {
            return true;
        }
    }
}