File: System\Windows\Markup\BamlReader.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 interface for reading BamlRecords with an interface
*           that is similar to XmlReader
*
\***************************************************************************/
 
using System.Xml;
using System.IO;
using System.Text;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
 
using System.Globalization;
 
namespace System.Windows.Markup
{
    /// <summary>
    /// Type of BAML node at the current BamlReader location in
    /// the BAML stream.
    /// </summary>
    internal enum BamlNodeType
    {
        /// <summary>
        /// This is returned if Read() has not been called, or the end of the
        /// file has been reached.
        /// </summary>
        None,
 
        /// <summary>
        /// Start of a BAML document.  This contains version information about the BAML.
        /// </summary>
        StartDocument,
 
        /// <summary>
        /// End of a BAML document
        /// </summary>
        EndDocument,
 
        /// <summary>
        /// Connection Id.
        /// </summary>
        ConnectionId,
 
        /// <summary>
        /// Start of an Element.  An Element is any object that exists in
        /// an object tree.  This has a rough correspondance to Element nodes in XML
        /// </summary>
        StartElement,
 
        /// <summary>
        /// End of an Element.
        /// </summary>
        EndElement,
 
        /// <summary>
        /// A simple property of an Element.  This can be a native property, attached
        /// property, or XML specific properties such as namespaces
        /// </summary>
        Property,
 
        /// <summary>
        /// The content property of an Element.
        /// </summary>
        ContentProperty,
 
        /// <summary>
        /// A namespace property that defines prefix to namespace mappings in xml
        /// </summary>
        XmlnsProperty,
 
        /// <summary>
        /// The start of a compound property on an Element.  This is used when the property value is
        /// represented as a single string Value, but as an element tree.
        /// </summary>
        StartComplexProperty,
 
        /// <summary>
        /// The end of a compound property on an Element.
        /// </summary>
        EndComplexProperty,
 
        /// <summary>
        /// A section of literal content that is handled by an object
        /// </summary>
        LiteralContent,
 
        /// <summary>
        /// Text content of an element.
        /// </summary>
        Text,
 
        /// <summary>
        /// A RoutedEvent
        /// </summary>
        RoutedEvent,
 
        /// <summary>
        /// A non-routed, or normal CLR Event
        /// </summary>
        Event,
 
        /// <summary>
        /// A reference to a Resource that is included at this point in the BAML stream
        /// </summary>
        IncludeReference,
 
        /// <summary>
        /// A specific attribute or property in the reserved "Definition" namespace.
        /// These attributes do not map to CLR properties or events, but are used
        /// as processing directives.
        /// </summary>
        DefAttribute,
 
        /// <summary>
        /// A specific attribute or property in the reserved "PresentationOptions" namespace.
        /// These attributes do not map to CLR properties or events, but are used
        /// as processing directives.
        /// </summary>
        PresentationOptionsAttribute,
 
        /// <summary>
        /// A namespace mapping instruction used for including custom code and namespaces
        /// </summary>
        PIMapping,
 
        /// <summary>
        /// Start of a section that specifies a list of objects to be passed to the
        /// element's constructor
        /// </summary>
        StartConstructor,
 
        /// <summary>
        /// End of a section that specifies a list of objects to be passed to the
        /// element's constructor
        /// </summary>
        EndConstructor
    }
 
    /// <summary>
    /// Reads BAML from a Stream and exposes an XmlReader-liker interface for BAML
    /// </summary>
    internal class BamlReader
    {
        #region Constructor
 
        /// <summary>
        /// Create an instance of the BamlReader on the passed stream using
        /// the passed ParserContext.
        /// </summary>summary>
        public BamlReader(Stream bamlStream)
        {
            _parserContext = new ParserContext();
            _parserContext.XamlTypeMapper = XmlParserDefaults.DefaultMapper;
            _bamlRecordReader = new BamlRecordReader(bamlStream, _parserContext, false);
            _readState = ReadState.Initial;
            _bamlNodeType = BamlNodeType.None;
            _prefixDictionary = new XmlnsDictionary();
            _value = string.Empty;
            _assemblyName = string.Empty;
            _prefix = string.Empty;
            _xmlNamespace = string.Empty;
            _clrNamespace = string.Empty;
            _name = string.Empty;
            _localName = string.Empty;
            _ownerTypeName = string.Empty;
            _properties = new ArrayList();
            _haveUnprocessedRecord = false;
            _deferableContentBlockDepth = -1;
            _nodeStack = new Stack<BamlNodeInfo>();
            _reverseXmlnsTable = new Dictionary<String, List<String>>();
        }
 
        #endregion Constructor
 
        #region Public Methods
 
        /// <summary>
        /// Return the number of properties.  Note that this
        /// does not include complex properties or children elements
        /// </summary>
        public int PropertyCount
        {
            get { return _properties.Count; }
        }
 
        /// <summary>
        /// Return true if the current node has any simple properties
        /// </summary>
        public bool HasProperties
        {
            get { return PropertyCount > 0; }
        }
 
        /// <summary>
        /// Return the connection Id of current element for hooking up
        /// IDs and events.
        /// </summary>
        public Int32 ConnectionId
        {
            get { return _connectionId; }
        }
 
        /// <summary>
        /// Defines what this attribute is used for such as being an alias for
        /// xml:lang, xml:space or x:ID
        /// </summary>
        public BamlAttributeUsage AttributeUsage
        {
            get { return _attributeUsage; }
        }
 
        /// <summary>
        /// Gets the type of the current node (eg  Element, StartComplexProperty,
        /// Text, etc)
        /// </summary>
        public BamlNodeType NodeType
        {
            get { return _bamlNodeType; }
        }
 
        /// <summary>
        /// Gets the fully qualified name of the current node.
        /// </summary>
        public string Name
        {
            get { return _name; }
        }
 
        /// <summary>
        /// Gets the local name only, with prefix and owning class removed
        /// </summary>
        public string LocalName
        {
            get { return _localName; }
        }
 
        /// <summary>
        /// Gets the prefix associated with the current node, if there is one
        /// </summary>
        //
        // NOTE: Used by the localization tools via reflection.
        //
        public string Prefix
        {
            get { return _prefix; }
        }
 
        /// <summary>
        /// Gets the assembly name associated with the type of the current node, if there is one
        /// </summary>
        public string AssemblyName
        {
            get { return _assemblyName; }
        }
 
        /// <summary>
        /// Gets the XML namespace URI of the node on which the reader is positioned
        /// </summary>
        public string XmlNamespace
        {
            get { return _xmlNamespace; }
        }
 
        /// <summary>
        /// Gets the CLR namespace of the node on which the reader is positioned
        /// </summary>
        public string ClrNamespace
        {
            get { return _clrNamespace; }
        }
 
        /// <summary>
        /// Gets the text value of the current node (eg  property value or text content)
        /// </summary>
        public string Value
        {
            get { return _value; }
        }
 
        public bool IsInjected
        {
            get { return _isInjected; }
        }
 
        // Whether this object instance is expected to be created via TypeConverter
        public bool CreateUsingTypeConverter
        {
            get { return _useTypeConverter; }
        }
 
        public string TypeConverterName
        {
            get { return _typeConverterName; }
        }
 
        public string TypeConverterAssemblyName
        {
            get { return _typeConverterAssemblyName; }
        }
 
        /// <summary>
        /// Reads the next node from the stream.
        /// </summary>
        public bool Read()
        {
            if (_readState == ReadState.EndOfFile ||
                _readState == ReadState.Closed)
            {
                throw new InvalidOperationException(SR.BamlReaderClosed);
            }
 
            ReadNextRecord();
 
            return _readState != ReadState.EndOfFile;
        }
 
        private BamlNodeType NodeTypeInternal
        {
            set { _bamlNodeType = value; }
        }
 
        private void AddToPropertyInfoCollection(object info)
        {
            _properties.Add(info);
        }
 
        /// <summary>
        /// Close the underlying BAML stream.
        /// </summary>
        /// <remarks>
        /// Once the BamlReader is closed, it cannot be used
        /// for any further operations.  Calling any public interfaces will fail.
        /// </remarks>
        public void Close()
        {
            if (_readState != ReadState.Closed)
            {
                _bamlRecordReader.Close();
                _currentBamlRecord = null;
                _bamlRecordReader = null;
                _readState = ReadState.Closed;
            }
        }
 
        /// <summary>
        /// Moves to the first property for this element or object.
        /// Return true if property exists, false otherwise.
        /// </summary>
        public bool MoveToFirstProperty()
        {
            if (HasProperties)
            {
                _propertiesIndex = -1;
                return MoveToNextProperty();
            }
            else
            {
                return false;
            }
        }
 
 
        /// <summary>
        /// Move to the next property for this element or object.
        /// Return true if there is a next property; false if there are no more properties.
        /// </summary>
        public bool MoveToNextProperty()
        {
            if (_propertiesIndex < _properties.Count - 1)
            {
                _propertiesIndex++;
                object obj = _properties[_propertiesIndex];
 
                BamlPropertyInfo info = obj as BamlPropertyInfo;
                if (info != null)
                {
                    _name = info.Name;
                    _localName = info.LocalName;
                    int index = info.Name.LastIndexOf('.');
                    if (index > 0)
                    {
                        _ownerTypeName = info.Name.Substring(0, index);
                    }
                    else
                    {
                        // Eg. xmlns property
                        _ownerTypeName = string.Empty;
                    }
                    _value = info.Value;
                    _assemblyName = info.AssemblyName;
                    _prefix = info.Prefix;
                    _xmlNamespace = info.XmlNamespace;
                    _clrNamespace = info.ClrNamespace;
                    _connectionId = 0;
                    _contentPropertyName = string.Empty;
                    _attributeUsage = info.AttributeUsage;
 
                    // There are several node types for properties, but for now the only one that
                    // doesn't map to BamlNodeType.Property is xml namespace declarations.
                    if (info.RecordType == BamlRecordType.XmlnsProperty)
                    {
                        NodeTypeInternal = BamlNodeType.XmlnsProperty;
                    }
                    else if (info.RecordType == BamlRecordType.DefAttribute)
                    {
                        NodeTypeInternal = BamlNodeType.DefAttribute;
                    }
                    else if (info.RecordType == BamlRecordType.PresentationOptionsAttribute)
                    {
                        NodeTypeInternal = BamlNodeType.PresentationOptionsAttribute;
                    }
                    else
                    {
                        NodeTypeInternal = BamlNodeType.Property;
                    }
                    return true;
                }
 
                BamlContentPropertyInfo cpInfo = obj as BamlContentPropertyInfo;
                if(null != cpInfo)
                {
                    _contentPropertyName = cpInfo.LocalName;
                    _connectionId = 0;
                    _prefix = string.Empty;
                    _name = cpInfo.Name;
                    int index = cpInfo.Name.LastIndexOf('.');
                    if (index > 0)
                    {
                        _ownerTypeName = cpInfo.Name.Substring(0, index);
                    }
 
                    _localName = cpInfo.LocalName;
                    _ownerTypeName = string.Empty;
                    _assemblyName = cpInfo.AssemblyName;
                    _xmlNamespace = string.Empty;
                    _clrNamespace = string.Empty;
                    _attributeUsage = BamlAttributeUsage.Default;
                    _value = cpInfo.LocalName;
                    NodeTypeInternal = BamlNodeType.ContentProperty;
                    return true;
                }
                // Otherwise it must be a ConnectionId.
                // Is there something we can Assert on for that?
                _connectionId = (Int32)obj;
                _contentPropertyName = string.Empty;
                _prefix = string.Empty;
                _name = string.Empty;
                _localName = string.Empty;
                _ownerTypeName = string.Empty;
                _assemblyName = string.Empty;
                _xmlNamespace = string.Empty;
                _clrNamespace = string.Empty;
                _attributeUsage = BamlAttributeUsage.Default;
                _value = _connectionId.ToString(CultureInfo.CurrentCulture);
                NodeTypeInternal = BamlNodeType.ConnectionId;
                return true;
            }
            else
            {
                return false;
            }
        }
 
        #endregion Public Methods
 
        #region Internal Methods
 
        /// <summary>
        /// Gets the next BamlRecord to be processed
        /// </summary>
        private void GetNextRecord()
        {
            if (_currentStaticResourceRecords != null)
            {
                // Load the record from the front loaded static resource
                _currentBamlRecord = _currentStaticResourceRecords[_currentStaticResourceRecordIndex++];
 
                if (_currentStaticResourceRecordIndex == _currentStaticResourceRecords.Count)
                {
                    // We are done with the records for this front loaded static resource
                    _currentStaticResourceRecords = null;
                    _currentStaticResourceRecordIndex = -1;
                }
            }
            else
            {
                // Use the BamlRecord Reader to get the record
                _currentBamlRecord = _bamlRecordReader.GetNextRecord();
            }
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadNextRecord
        *
        * Read the next record, setting the ReadState, NodeType and other pertinent
        * information about the record just read.
        *
        \***************************************************************************/
 
        private void ReadNextRecord()
        {
            // If this is the first call to Read.  Then read the Version Header.
            if(_readState == ReadState.Initial)
            {
                _bamlRecordReader.ReadVersionHeader();
            }
 
            // We'll read in a loop until we get to a significant record.  Note that Assembly,
            // Type and Attribute records are read and processed, but the BamlReader never stops
            // on one of these records, since they are not externally exposed.
            bool keepOnReading = true;
            while (keepOnReading)
            {
                // We may already have a record that was previously read in but not
                // processed.  This occurs when we've looped through all the properties
                // on an element and have encountered a non-property record to stop
                // the loop.  In that case don't read another record and just process
                // the one we have.
                if (_haveUnprocessedRecord)
                {
                    _haveUnprocessedRecord = false;
                }
                else
                {
                    GetNextRecord();
                }
 
                // If the current baml record is null, then the stream is finished, closed
                // or something else that prevents us reading further, so treat this
                // as an end-of-file condition
                if (_currentBamlRecord == null)
                {
                    NodeTypeInternal = BamlNodeType.None;
                    _readState = ReadState.EndOfFile;
                    ClearProperties();
                    return;
                }
 
 
                // By default, the read state is interactive after a record has been read, and we
                // should stop reading after this record is processed.  This may be altered for
                // specific record types.
                _readState = ReadState.Interactive;
                keepOnReading = false;
 
                switch (_currentBamlRecord.RecordType)
                {
                    // The following three records are internal to the BAMLReader and
                    // are not exposed publicly.  They are used to update the map table
                    // that maps ids to assemblies, types and attributes.
                    case BamlRecordType.AssemblyInfo:
                        ReadAssemblyInfoRecord();
                        keepOnReading = true;
                        break;
 
                    case BamlRecordType.TypeInfo:
                    case BamlRecordType.TypeSerializerInfo:
                        MapTable.LoadTypeInfoRecord((BamlTypeInfoRecord)_currentBamlRecord);
                        keepOnReading = true;
                        break;
 
                    case BamlRecordType.AttributeInfo:
                        MapTable.LoadAttributeInfoRecord((BamlAttributeInfoRecord)_currentBamlRecord);
                        keepOnReading = true;
                        break;
 
                    case BamlRecordType.StringInfo:
                        MapTable.LoadStringInfoRecord((BamlStringInfoRecord)_currentBamlRecord);
                        keepOnReading = true;
                        break;
 
                    case BamlRecordType.ContentProperty:
                        // This is just a cache of meta-data, no visible effect.
                        ReadContentPropertyRecord();
                        keepOnReading = true;
                        break;
 
                    // The following records are publically exposed
                    case BamlRecordType.DocumentStart:
                        ReadDocumentStartRecord();
                        break;
 
                    case BamlRecordType.DocumentEnd:
                        ReadDocumentEndRecord();
                        break;
 
                    case BamlRecordType.PIMapping:
                        ReadPIMappingRecord();
                        break;
 
                    case BamlRecordType.LiteralContent:
                        ReadLiteralContentRecord();
                        break;
 
                    case BamlRecordType.ElementStart:
                    case BamlRecordType.StaticResourceStart:
                        ReadElementStartRecord();
                        break;
 
                    case BamlRecordType.ElementEnd:
                    case BamlRecordType.StaticResourceEnd:
                        ReadElementEndRecord();
                        break;
 
                    case BamlRecordType.PropertyComplexStart:
                    case BamlRecordType.PropertyArrayStart:
                    case BamlRecordType.PropertyIListStart:
                    case BamlRecordType.PropertyIDictionaryStart:
                        ReadPropertyComplexStartRecord();
                        break;
 
                    case BamlRecordType.PropertyComplexEnd:
                    case BamlRecordType.PropertyArrayEnd:
                    case BamlRecordType.PropertyIListEnd:
                    case BamlRecordType.PropertyIDictionaryEnd:
                        ReadPropertyComplexEndRecord();
                        break;
 
                    case BamlRecordType.Text:
                    case BamlRecordType.TextWithId:
                    case BamlRecordType.TextWithConverter:
                        ReadTextRecord();
                        break;
 
                    case BamlRecordType.DeferableContentStart:
                        ReadDeferableContentRecord();
                        keepOnReading = true;
                        break;
 
                    case BamlRecordType.ConstructorParametersStart:
                        ReadConstructorStart();
                        break;
 
                    case BamlRecordType.ConstructorParametersEnd:
                        ReadConstructorEnd();
                        break;
 
                    case BamlRecordType.ConnectionId:
                        ReadConnectionIdRecord();
                        break;
 
                    case BamlRecordType.StaticResourceId:
                        ReadStaticResourceId();
                        keepOnReading = true;
                        break;
 
                    default:
                        // Can't have any other type of record at this point.
                        throw new InvalidOperationException(SR.Format(SR.ParserUnknownBaml,
                                         ((int)_currentBamlRecord.RecordType).ToString(CultureInfo.CurrentCulture)));
                }
            }
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadProperties
        *
        * This is called when an element start has been encountered (or something
        * similar) and a number of properties may or may not follow.  Read all the
        * properties, storing them in the _properties arraylist.  When a non-property
        * record is encountered, stop, but store that record as the
        * _currentBamlRecord.
        *
        \***************************************************************************/
 
        private void ReadProperties()
        {
            // Keep reading records until we get one that is not processed
            while (!_haveUnprocessedRecord)
            {
                GetNextRecord();
 
                ProcessPropertyRecord();
            }
        }
 
        /***************************************************************************\
        *
        * BamlReader.ProcessPropertyRecord
        *
        * This is called we assume we have a record that is an attribute in the start
        * tag of an xml element.  It is processed here.  If we encounter something
        * that is not a 'property', then set the _haveUnprocessedRecord flag.
        *
        \***************************************************************************/
 
        private void ProcessPropertyRecord()
        {
            switch (_currentBamlRecord.RecordType)
            {
                // The following five records are internal to the BAMLReader and
                // are not exposed publicly.  They are used to update the map table
                // that maps ids to assemblies, types and attributes.
                case BamlRecordType.AssemblyInfo:
                    ReadAssemblyInfoRecord();
                    break;
 
                case BamlRecordType.TypeInfo:
                case BamlRecordType.TypeSerializerInfo:
                    MapTable.LoadTypeInfoRecord((BamlTypeInfoRecord)_currentBamlRecord);
                    break;
 
                case BamlRecordType.AttributeInfo:
                    MapTable.LoadAttributeInfoRecord((BamlAttributeInfoRecord)_currentBamlRecord);
                    break;
 
                case BamlRecordType.StringInfo:
                    MapTable.LoadStringInfoRecord((BamlStringInfoRecord)_currentBamlRecord);
                    break;
 
                // The following records are property-like
                case BamlRecordType.XmlnsProperty:
                    ReadXmlnsPropertyRecord();
                    break;
 
                case BamlRecordType.ConnectionId:
                    ReadConnectionIdRecord();
                    break;
 
                case BamlRecordType.Property:
                case BamlRecordType.PropertyWithConverter:
                    ReadPropertyRecord();
                    break;
 
                case BamlRecordType.ContentProperty:
                    // This is just a cache of meta-data, no visible effect.
                    ReadContentPropertyRecord();
                    break;
 
                case BamlRecordType.PropertyStringReference:
                    ReadPropertyStringRecord();
                    break;
 
                case BamlRecordType.PropertyTypeReference:
                    ReadPropertyTypeRecord();
                    break;
 
                case BamlRecordType.PropertyWithExtension:
                    ReadPropertyWithExtensionRecord();
                    break;
 
                case BamlRecordType.PropertyWithStaticResourceId:
                    ReadPropertyWithStaticResourceIdRecord();
                    break;
 
                case BamlRecordType.PropertyCustom:
                    ReadPropertyCustomRecord();
                    break;
 
                case BamlRecordType.DefAttribute:
                    ReadDefAttributeRecord();
                    break;
 
                case BamlRecordType.PresentationOptionsAttribute:
                    ReadPresentationOptionsAttributeRecord();
                    break;
 
                case BamlRecordType.DefAttributeKeyType:
                    ReadDefAttributeKeyTypeRecord();
                    break;
 
                case BamlRecordType.RoutedEvent:
                    ReadRoutedEventRecord();
                    break;
 
                case BamlRecordType.ClrEvent:
                    ReadClrEventRecord();
                    break;
 
                case BamlRecordType.KeyElementStart:
                    {
                        // Process the subtree that is stored as part of a key tree and
                        // translate this back into a compact MarkupExtension string that
                        // is represented as a x:Key="something"
                        BamlKeyInfo info = ProcessKeyTree();
                        AddToPropertyInfoCollection(info);
                    }
                    break;
 
                // Any other record types are not processed here
                default:
                    _haveUnprocessedRecord = true;
                    break;
            }
        }
 
 
        /***************************************************************************\
        *
        * BamlReader.ReadXmlnsPropertyRecord
        *
        * Read the namespace record and update the namespace dictionary with the
        * key being the namespace prefix and the value being the namespace string.
        * Note that Xmlns properties are not the same as regular properties, since
        * prefix, xmlnamespace, clrnamespace and name have quite different meanings.
        *
        \***************************************************************************/
 
        private void ReadXmlnsPropertyRecord()
        {
            BamlXmlnsPropertyRecord bamlRecord = (BamlXmlnsPropertyRecord)_currentBamlRecord;
            _parserContext.XmlnsDictionary[bamlRecord.Prefix] = bamlRecord.XmlNamespace;
            _prefixDictionary[bamlRecord.XmlNamespace] = bamlRecord.Prefix;
 
            BamlPropertyInfo info = new BamlPropertyInfo();
            info.Value = bamlRecord.XmlNamespace;
            info.XmlNamespace = string.Empty;
            info.ClrNamespace = string.Empty;
            info.AssemblyName = string.Empty;
            info.Prefix = "xmlns";
            info.LocalName = bamlRecord.Prefix == null ? string.Empty : bamlRecord.Prefix;
            info.Name = string.IsNullOrEmpty(bamlRecord.Prefix) ?
                                          "xmlns" :
                                          $"xmlns:{bamlRecord.Prefix}";
            info.RecordType = BamlRecordType.XmlnsProperty;
 
            AddToPropertyInfoCollection(info);
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadPropertyRecord
        *
        * Read the property record and store the pertinent contents in the
        * _properties array list.
        *
        \***************************************************************************/
 
        private void ReadPropertyRecord()
        {
            string value = ((BamlPropertyRecord)_currentBamlRecord).Value;
 
            // Escape the text as necessary to avoid being mistaken for a MarkupExtension.
            value = MarkupExtensionParser.AddEscapeToLiteralString(value);
 
            AddToPropertyInfoCollection(ReadPropertyRecordCore(value));
        }
 
        private void ReadContentPropertyRecord()
        {
            BamlContentPropertyInfo cpInfo = new BamlContentPropertyInfo();
 
            BamlContentPropertyRecord bamlRecord = (BamlContentPropertyRecord)_currentBamlRecord;
            SetCommonPropertyInfo(cpInfo, bamlRecord.AttributeId);
 
            cpInfo.RecordType = _currentBamlRecord.RecordType;
            AddToPropertyInfoCollection(cpInfo);
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadPropertyStringRecord
        *
        * Read the property record and store the pertinent contents in the
        * _properties array list.
        *
        \***************************************************************************/
 
        private void ReadPropertyStringRecord()
        {
            string value = MapTable.GetStringFromStringId(((BamlPropertyStringReferenceRecord)_currentBamlRecord).StringId);
            AddToPropertyInfoCollection(ReadPropertyRecordCore(value));
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadPropertyTypeRecord
        *
        * Read the property record and store the pertinent contents in the
        * _properties array list.  Convert the TypeId into a fully qualified type
        * name.
        *
        \***************************************************************************/
 
        private void ReadPropertyTypeRecord()
        {
            BamlPropertyInfo info = new BamlPropertyInfo();
 
            SetCommonPropertyInfo(info,
                   ((BamlPropertyTypeReferenceRecord)_currentBamlRecord).AttributeId);
 
            info.RecordType = _currentBamlRecord.RecordType;
            info.Value = GetTypeValueString(((BamlPropertyTypeReferenceRecord)_currentBamlRecord).TypeId);
            info.AttributeUsage = BamlAttributeUsage.Default;
 
            AddToPropertyInfoCollection(info);
        }
 
        private void ReadPropertyWithExtensionRecord()
        {
            BamlPropertyInfo info = new BamlPropertyInfo();
            SetCommonPropertyInfo(info, ((BamlPropertyWithExtensionRecord)_currentBamlRecord).AttributeId);
 
            info.RecordType = _currentBamlRecord.RecordType;
            info.Value = GetExtensionValueString((IOptimizedMarkupExtension)_currentBamlRecord);
            info.AttributeUsage = BamlAttributeUsage.Default;
 
            AddToPropertyInfoCollection(info);
        }
 
        private void ReadPropertyWithStaticResourceIdRecord()
        {
            BamlPropertyWithStaticResourceIdRecord bamlPropertyWithStaticResourceIdRecord =
                (BamlPropertyWithStaticResourceIdRecord)_currentBamlRecord;
 
            BamlPropertyInfo info = new BamlPropertyInfo();
            SetCommonPropertyInfo(info, bamlPropertyWithStaticResourceIdRecord.AttributeId);
 
            info.RecordType = _currentBamlRecord.RecordType;
 
            BamlOptimizedStaticResourceRecord optimizedStaticResourceRecord =
                (BamlOptimizedStaticResourceRecord)_currentKeyInfo.StaticResources[bamlPropertyWithStaticResourceIdRecord.StaticResourceId][0];
 
            info.Value = GetExtensionValueString((IOptimizedMarkupExtension)optimizedStaticResourceRecord);
            info.AttributeUsage = BamlAttributeUsage.Default;
 
            AddToPropertyInfoCollection(info);
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadPropertyRecordCore
        *
        * Read the property record and return the pertinent contents in a
        * BamlPropertyInfo record.
        *
        \***************************************************************************/
 
        private BamlPropertyInfo ReadPropertyRecordCore(string value)
        {
            BamlPropertyInfo info = new BamlPropertyInfo();
 
            SetCommonPropertyInfo(info,
                   ((BamlPropertyRecord)_currentBamlRecord).AttributeId);
 
            info.RecordType = _currentBamlRecord.RecordType;
            info.Value = value;
 
            return info;
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadPropertyCustomRecord
        *
        * Read the custom property record and store the pertinent contents in the
        * _properties array list.  This involves reversing the binary representation
        * of the data back into a string using the TypeConverter for the
        * property type.
        *
        \***************************************************************************/
 
        private void ReadPropertyCustomRecord()
        {
            BamlPropertyInfo info = GetPropertyCustomRecordInfo();
            AddToPropertyInfoCollection(info);
        }
 
        private BamlPropertyInfo GetPropertyCustomRecordInfo()
        {
            BamlPropertyInfo info = new BamlPropertyInfo();
 
            BamlAttributeInfoRecord attrInfo = SetCommonPropertyInfo(info,
                ((BamlPropertyCustomRecord)_currentBamlRecord).AttributeId);
            info.RecordType = _currentBamlRecord.RecordType;
            info.AttributeUsage = BamlAttributeUsage.Default;
 
            BamlPropertyCustomRecord bamlRecord = (BamlPropertyCustomRecord)_currentBamlRecord;
 
            // Reverse the binary data stored in the record into a string by first getting the
            // property.  If it has not already been cached in the attribute info record, then
            // attempt to resolve it as a DependencyProperty or a PropertyInfo.
            if (attrInfo.DP == null && attrInfo.PropInfo == null)
            {
                attrInfo.DP = MapTable.GetDependencyProperty(attrInfo);
 
                if (attrInfo.OwnerType == null)
                {
                    throw new InvalidOperationException(SR.Format(SR.BamlReaderNoOwnerType, attrInfo.Name, AssemblyName));
                }
                if (attrInfo.DP == null)
                {
                    try
                    {
                        attrInfo.PropInfo = attrInfo.OwnerType.GetProperty(attrInfo.Name,
                                BindingFlags.Instance | BindingFlags.Public);
                    }
                    catch (AmbiguousMatchException)
                    {
                        // Handle ambiguous match just like XamlTypeMapper.PropertyInfoFromName does.
                        // This is for consistency, although it's probably wrong.
                        // The doc for GetProperties says:
                        //      The GetProperties method does not return properties
                        //      in a particular order, such as alphabetical or
                        //      declaration order. Your code must not depend on the
                        //      order in which properties are returned, because that
                        //      order varies.
                        // It's probably more correct to walk up the base class
                        // tree calling GetProperty with the DeclaredOnly flag.
 
                        PropertyInfo[] infos = attrInfo.OwnerType.GetProperties(
                                      BindingFlags.Instance | BindingFlags.Public);
                        for (int i = 0; i < infos.Length; i++)
                        {
                            if (infos[i].Name == attrInfo.Name)
                            {
                                attrInfo.PropInfo = infos[i];
                                break;
                            }
                        }
                    }
 
                    if (attrInfo.PropInfo == null)
                    {
                        throw new InvalidOperationException(SR.Format(SR.ParserCantGetDPOrPi, info.Name));
                    }
                }
            }
 
            // If we have a property, then get its type and call GetCustomValue,
            // which uses the XamlSerializer to turn the binary data into a
            // real object
            Type propertyType = attrInfo.GetPropertyType();
            string propertyName = attrInfo.Name;
            short sid = bamlRecord.SerializerTypeId;
 
            // if a Setter of Trigger's Property property is being set, then its value is always
            // a DP. Get the attribInfo of this DP property from the ValueId read into the custom
            // property record and resolve it into an actual DP instance.
            if (sid == (short)KnownElements.DependencyPropertyConverter)
            {
                Type declaringType = null;
                _propertyDP = _bamlRecordReader.GetCustomDependencyPropertyValue(bamlRecord, out declaringType);
                declaringType = declaringType == null ? _propertyDP.OwnerType : declaringType;
                info.Value = $"{declaringType.Name}.{_propertyDP.Name}";
 
                string xmlns = _parserContext.XamlTypeMapper.GetXmlNamespace(declaringType.Namespace,
                                                                             declaringType.Assembly.FullName);
 
                string prefix = GetXmlnsPrefix(xmlns);
                if (prefix != string.Empty)
                {
                    info.Value = $"{prefix}:{info.Value}";
                }
 
                if (!_propertyDP.PropertyType.IsEnum)
                {
                    _propertyDP = null;
                }
            }
            else
            {
                if (_propertyDP != null)
                {
                    propertyType = _propertyDP.PropertyType;
                    propertyName = _propertyDP.Name;
                    _propertyDP = null;
                }
 
                object value = _bamlRecordReader.GetCustomValue(bamlRecord, propertyType, propertyName);
 
                // Once we have a real object, turn that back into a string, and store this
                // as the value for this property
                TypeConverter converter = TypeDescriptor.GetConverter(value.GetType());
                info.Value = converter.ConvertToString(null,
                                                       TypeConverterHelper.InvariantEnglishUS,
                                                       value);
            }
 
            return info;
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadDefAttributeRecord
        *
        * Read a x: record that contains the object to use as a key when inserting
        * the current element into a dictionary.
        *
        \***************************************************************************/
 
        private void ReadDefAttributeRecord()
        {
            BamlDefAttributeRecord bamlRecord = (BamlDefAttributeRecord)_currentBamlRecord;
            bamlRecord.Name = MapTable.GetStringFromStringId(bamlRecord.NameId);
 
            BamlPropertyInfo info = new BamlPropertyInfo();
            info.Value = bamlRecord.Value;
            info.AssemblyName = string.Empty;
            info.Prefix = (string)_prefixDictionary[XamlReaderHelper.DefinitionNamespaceURI];
            info.XmlNamespace = XamlReaderHelper.DefinitionNamespaceURI;
            info.ClrNamespace = string.Empty;
            info.Name = bamlRecord.Name;
            info.LocalName = info.Name;
            info.RecordType = BamlRecordType.DefAttribute;
 
            AddToPropertyInfoCollection(info);
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadPresentationOptionsAttributeRecord
        *
        * Read a PresentationsOptions: record used for WPF-specific
        * parsing options (e.g., PresentationOptions:Freeze).
        *
        \***************************************************************************/
 
        private void ReadPresentationOptionsAttributeRecord()
        {
            BamlPresentationOptionsAttributeRecord bamlRecord = (BamlPresentationOptionsAttributeRecord)_currentBamlRecord;
            bamlRecord.Name = MapTable.GetStringFromStringId(bamlRecord.NameId);
 
            BamlPropertyInfo info = new BamlPropertyInfo();
            info.Value = bamlRecord.Value;
            info.AssemblyName = string.Empty;
            info.Prefix = (string)_prefixDictionary[XamlReaderHelper.PresentationOptionsNamespaceURI];
            info.XmlNamespace = XamlReaderHelper.PresentationOptionsNamespaceURI;
            info.ClrNamespace = string.Empty;
            info.Name = bamlRecord.Name;
            info.LocalName = info.Name;
            info.RecordType = BamlRecordType.PresentationOptionsAttribute;
 
            AddToPropertyInfoCollection(info);
        }
 
 
        /***************************************************************************\
        *
        * BamlReader.ReadDefAttributeKeyTypeRecord
        *
        * Read a x: record that contains the object to use as a key when inserting
        * the current element into a dictionary.
        *
        \***************************************************************************/
 
        private void ReadDefAttributeKeyTypeRecord()
        {
            BamlDefAttributeKeyTypeRecord bamlRecord = (BamlDefAttributeKeyTypeRecord)_currentBamlRecord;
 
            BamlPropertyInfo info = new BamlPropertyInfo();
            info.Value = GetTypeValueString(bamlRecord.TypeId);
            info.AssemblyName = string.Empty;
            info.Prefix = (string)_prefixDictionary[XamlReaderHelper.DefinitionNamespaceURI];
            info.XmlNamespace = XamlReaderHelper.DefinitionNamespaceURI;
            info.ClrNamespace = string.Empty;
            info.Name = XamlReaderHelper.DefinitionName;
            info.LocalName = info.Name;
            info.RecordType = BamlRecordType.DefAttribute;
 
            AddToPropertyInfoCollection(info);
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadDeferableContentRecord
        *
        * Read defered content section of a baml file.  Note that this is written
        * to baml with the following format:
        *     BamlDeferableContentStartRecord
        *     BamlDefAttributeKeyString/Type records, one for each value
        *     BamlElementStartRecord, one for each value
        *       BamlRecords within the element
        *     BamlElementEndRecord
        *
        * This is presented to the user like a 'normal' dictionary, so here is
        * what the user should see:
        *     ElementStart for the dictionary
        *      ElementStart, one for each value
        *       DictionaryKey record, one for each value
        *       Records within the element
        *      ElementEnd
        *     ElementEnd for the dictionary
        *
        * To do this, queue up the entire contents of the Deferable block and
        * re-arrange the records.
        *
        \***************************************************************************/
 
        private void ReadDeferableContentRecord()
        {
            _deferableContentBlockDepth = _nodeStack.Count;
 
            // The start of a block of deferable content has been reached.  Build
            // a key table that will be inserted into the Values as they are loaded.
            _deferableContentPosition = ReadDeferKeys();
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadDeferKeys
        *
        * Read the keys in a defered content section, and build a table that holds
        * these records.  These keys are automagically inserted into the outer level
        * value start element records to make it look like a x:Key attribute.
        * Return the baml stream position for the end of the key section to which
        * all key offsets are relative
        *
        \***************************************************************************/
 
        private Int64 ReadDeferKeys()
        {
            // Keep reading records until we get one that is not processed
            Int64 endOfDefKeys = -1;
            _deferKeys = new List<BamlKeyInfo>();
            while (!_haveUnprocessedRecord)
            {
                GetNextRecord();
 
                ProcessDeferKey();
 
                if (!_haveUnprocessedRecord)
                {
                    endOfDefKeys = _bamlRecordReader.StreamPosition;
                }
            }
 
            return endOfDefKeys;
        }
 
        /***************************************************************************\
        *
        * BamlReader.ProcessDeferKey
        *
        * Read a single baml record.  If it is a defer key, add it to the table of
        * keys.  If we encounter something that is not a 'key', then set the
        *_haveUnprocessedRecord flag.
        *
        \***************************************************************************/
 
        private void ProcessDeferKey()
        {
            switch (_currentBamlRecord.RecordType)
            {
                // The following three records are internal to the BAMLReader and
                // are not exposed publicly.  They are used to update the map table
                // that maps ids to assemblies, types and attributes.
                case BamlRecordType.DefAttributeKeyString:
                    BamlDefAttributeKeyStringRecord stringKeyRecord = _currentBamlRecord as BamlDefAttributeKeyStringRecord;
                    if (stringKeyRecord != null)
                    {
                        BamlKeyInfo info;
 
                        // The "Shared"ness is stored in the BAML with the Key
                        // But at the XAML level it is a sibling attribute of the key.
                        info = CheckForSharedness();
                        if (null != info)
                            _deferKeys.Add(info);
 
                        // Get the value string from the string table, and cache it in the
                        // record.
                        stringKeyRecord.Value = MapTable.GetStringFromStringId(
                                                        stringKeyRecord.ValueId);
 
                        // Add information to the key list to indicate we have a x:Key
                        // attribute
                        info = new BamlKeyInfo();
                        info.Value = stringKeyRecord.Value;
                        info.AssemblyName = string.Empty;
                        info.Prefix = (string)_prefixDictionary[XamlReaderHelper.DefinitionNamespaceURI];
                        info.XmlNamespace = XamlReaderHelper.DefinitionNamespaceURI;
                        info.ClrNamespace = string.Empty;
                        info.Name = XamlReaderHelper.DefinitionName;
                        info.LocalName = info.Name;
                        info.RecordType = BamlRecordType.DefAttribute;
                        info.Offset = ((IBamlDictionaryKey)stringKeyRecord).ValuePosition;
                        _deferKeys.Add(info);
                    }
                    break;
 
                case BamlRecordType.DefAttributeKeyType:
                    BamlDefAttributeKeyTypeRecord typeKeyRecord = _currentBamlRecord as BamlDefAttributeKeyTypeRecord;
                    if (typeKeyRecord != null)
                    {
                        // Translate the type information held in the baml record into
                        // the {x:Type prefix:Classname} format that would be used on
                        // a x:Key attribute.
                        string typeExtensionPrefix = (string)_prefixDictionary[XamlReaderHelper.DefinitionNamespaceURI];
                        string typeExtensionName;
                        if (typeExtensionPrefix != string.Empty)
                        {
                            typeExtensionName = $"{{{typeExtensionPrefix}:Type ";
                        }
                        else
                        {
                            typeExtensionName = "{Type ";
                        }
                        BamlTypeInfoRecord typeInfo = MapTable.GetTypeInfoFromId(typeKeyRecord.TypeId);
                        string typeName = typeInfo.TypeFullName;
                        typeName = typeName.Substring(typeName.LastIndexOf('.') + 1);
                        string assemblyName;
                        string prefix;
                        string xmlNamespace;
                        GetAssemblyAndPrefixAndXmlns(typeInfo, out assemblyName, out prefix, out xmlNamespace);
                        if (prefix != string.Empty)
                        {
                            typeName = $"{typeExtensionName}{prefix}:{typeName}}}";
                        }
                        else
                        {
                            typeName = $"{typeExtensionName}{typeName}}}";
                        }
 
                        // Add information to the key list to indicate we have a x:Key
                        // attribute
                        BamlKeyInfo info = new BamlKeyInfo();
                        info.Value = typeName;
                        info.AssemblyName = string.Empty;
                        info.Prefix = typeExtensionPrefix;
                        info.XmlNamespace = XamlReaderHelper.DefinitionNamespaceURI;
                        info.ClrNamespace = string.Empty;
                        info.Name = XamlReaderHelper.DefinitionName;
                        info.LocalName = info.Name;
                        info.RecordType = BamlRecordType.DefAttribute;
                        info.Offset = ((IBamlDictionaryKey)typeKeyRecord).ValuePosition;
                        _deferKeys.Add(info);
                    }
                    break;
 
                case BamlRecordType.KeyElementStart:
                    {
                        BamlKeyInfo info;
 
                        // The "Shared"ness is stored in the BAML with the Key
                        // But at the XAML level it is a sibling attribute of the key.
                        info = CheckForSharedness();
                        if(null != info)
                            _deferKeys.Add(info);
 
                        // Process the subtree that is stored as part of a key tree and
                        // translate this back into a compact MarkupExtension string.
                        // Add information to the key list to indicate we have a x:Key
                        // with a MarkupExtension
                        info = ProcessKeyTree();
                        _deferKeys.Add(info);
                    }
                    break;
 
                case BamlRecordType.StaticResourceStart:
                case BamlRecordType.OptimizedStaticResource:
                    {
                        // Process the subtree stored as part of a StaticResource
                        List<BamlRecord> srRecords = new List<BamlRecord>();
 
                        // This is for the start record
                        _currentBamlRecord.Pin();
                        srRecords.Add(_currentBamlRecord);
 
                        // Note that BamlOptmizedStaticResourceRecord is a singleton record
                        if (_currentBamlRecord.RecordType == BamlRecordType.StaticResourceStart)
                        {
                            // Process the subtree that is stored as part of this static resource
                            ProcessStaticResourceTree(srRecords);
                        }
 
                        // Add the current StaticResource record to the list of StaticResources held per key
                        BamlKeyInfo keyInfo = _deferKeys[_deferKeys.Count-1];
                        keyInfo.StaticResources.Add(srRecords);
                    }
                    break;
 
                // Any other record types are not processed here
                default:
                    _haveUnprocessedRecord = true;
                    break;
            }
        }
 
        private BamlKeyInfo CheckForSharedness()
        {
            IBamlDictionaryKey dictKey = (IBamlDictionaryKey)_currentBamlRecord;
            Debug.Assert(dictKey != null, "Bad Key record");
            if (!dictKey.SharedSet)
                return null;
 
            BamlKeyInfo info = new BamlKeyInfo();
            info.Value = dictKey.Shared.ToString();
            info.AssemblyName = string.Empty;
            info.Prefix = (string)_prefixDictionary[XamlReaderHelper.DefinitionNamespaceURI];
            info.XmlNamespace = XamlReaderHelper.DefinitionNamespaceURI;
            info.ClrNamespace = string.Empty;
            info.Name = XamlReaderHelper.DefinitionShared;
            info.LocalName = info.Name;
            info.RecordType = BamlRecordType.DefAttribute;
            info.Offset = dictKey.ValuePosition;
 
            return info;
        }
 
        /***************************************************************************\
        *
        * BamlReader.ProcessKeyTree
        *
        * Read a tree of baml records that make up a dictionary key and translate them
        * back in the compact syntax representation of a MarkupExtension section.
        * When we encounter KeyElementEnd record, then stop.
        *
        \***************************************************************************/
 
        private BamlKeyInfo ProcessKeyTree()
        {
            BamlKeyElementStartRecord keyStartRecord = _currentBamlRecord as BamlKeyElementStartRecord;
            Debug.Assert(keyStartRecord != null, "Bad Key Element Start record");
 
            // Translate the type information held in the baml record into
            // the "{prefix:Classname " format that would be used on
            // a x:Key attribute.
            BamlTypeInfoRecord typeInfo = MapTable.GetTypeInfoFromId(keyStartRecord.TypeId);
            string markupString = typeInfo.TypeFullName;
            markupString = markupString.Substring(markupString.LastIndexOf('.') + 1);
            string assemblyName;
            string prefix;
            string xmlNamespace;
            GetAssemblyAndPrefixAndXmlns(typeInfo, out assemblyName, out prefix, out xmlNamespace);
            if (prefix != string.Empty)
            {
                markupString = $"{{{prefix}:{markupString} ";
            }
            else
            {
                markupString = $"{{{markupString} ";
            }
 
            bool notDone = true;
            BamlNodeInfo nodeInfo;
 
            // Keep track of whether we have written a property or not at a given nesting
            // level so that we know when to add commas between properties.  Also keep
            // track of when we have entered a constructor parameter section and when
            // we have written out the first parameter to handle adding commas between
            // constructor parameters.
            Stack<bool> readProperty = new();
            Stack<bool> readConstructor = new();
            Stack<bool> readFirstConstructor = new();
            readProperty.Push(false);         // Property has not yet been read
            readConstructor.Push(false);      // Constructor section has not been read
            readFirstConstructor.Push(false); // First constructor parameter has not been read
 
            while (notDone)
            {
                // Read the next record.  Some of the processing below reads ahead one
                // record and sets _haveUnprocessedRecord to true, in which case we
                // don't want to read another one.
                if (!_haveUnprocessedRecord)
                {
                    GetNextRecord();
                }
                else
                {
                    _haveUnprocessedRecord = false;
                }
 
                switch (_currentBamlRecord.RecordType)
                {
                    // The following five records are internal to the BAMLReader and
                    // are not exposed publicly.  They are used to update the map table
                    // that maps ids to assemblies, types and attributes.
                    case BamlRecordType.AssemblyInfo:
                        ReadAssemblyInfoRecord();
                        break;
 
                    case BamlRecordType.TypeInfo:
                    case BamlRecordType.TypeSerializerInfo:
                        MapTable.LoadTypeInfoRecord((BamlTypeInfoRecord)_currentBamlRecord);
                        break;
 
                    case BamlRecordType.AttributeInfo:
                        MapTable.LoadAttributeInfoRecord((BamlAttributeInfoRecord)_currentBamlRecord);
                        break;
 
                    case BamlRecordType.StringInfo:
                        MapTable.LoadStringInfoRecord((BamlStringInfoRecord)_currentBamlRecord);
                        break;
 
 
                    case BamlRecordType.PropertyComplexStart:
                        ReadPropertyComplexStartRecord();
                        nodeInfo = _nodeStack.Pop();
                        if (readProperty.Pop())
                        {
                            markupString += ", ";
                        }
                        markupString += $"{nodeInfo.LocalName}=";
                        readProperty.Push(true);
                        break;
 
                    case BamlRecordType.PropertyComplexEnd:
                        break;
 
                    case BamlRecordType.Text:
                    case BamlRecordType.TextWithId:
 
                        BamlTextWithIdRecord textWithIdRecord = _currentBamlRecord as BamlTextWithIdRecord;
                        if (textWithIdRecord != null)
                        {
                            // Get the value string from the string table, and cache it in the
                            // record.
                            textWithIdRecord.Value = MapTable.GetStringFromStringId(
                                                            textWithIdRecord.ValueId);
                        }
 
                        // If the text contains '{' or '}' then we have to escape these
                        // so that it won't be interpreted as a MarkupExtension
                        string escapedString = EscapeString(((BamlTextRecord)_currentBamlRecord).Value);
                        if (readFirstConstructor.Peek())
                        {
                            markupString += ", ";
                        }
                        markupString += escapedString;
                        if (readConstructor.Peek())
                        {
                            readFirstConstructor.Pop();
                            readFirstConstructor.Push(true);
                        }
                        break;
 
                    case BamlRecordType.ElementStart:
                        // Process commas between constructor parameters
                        if (readFirstConstructor.Peek())
                        {
                            markupString += ", ";
                        }
                        if (readConstructor.Peek())
                        {
                            readFirstConstructor.Pop();
                            readFirstConstructor.Push(true);
                        }
                        // Setup for the next level
                        readProperty.Push(false);
                        readConstructor.Push(false);
                        readFirstConstructor.Push(false);
                        // Write element type. Translate the type information held in the
                        // baml record into the "prefix:Classname" format
                        BamlElementStartRecord elementStartRecord = _currentBamlRecord as BamlElementStartRecord;
                        BamlTypeInfoRecord elementTypeInfo = MapTable.GetTypeInfoFromId(elementStartRecord.TypeId);
                        string typename = elementTypeInfo.TypeFullName;
                        typename = typename.Substring(typename.LastIndexOf('.') + 1);
                        GetAssemblyAndPrefixAndXmlns(elementTypeInfo, out assemblyName, out prefix, out xmlNamespace);
                        if (prefix != string.Empty)
                        {
                            markupString += $"{{{prefix}:{typename} ";
                        }
                        else
                        {
                            markupString = $"{{{typename} ";
                        }
                        break;
 
                    case BamlRecordType.ElementEnd:
                        readProperty.Pop();
                        readConstructor.Pop();
                        readFirstConstructor.Pop();
                        markupString += "}";
                        break;
 
                    case BamlRecordType.ConstructorParametersStart:
                        readConstructor.Pop();
                        readConstructor.Push(true);
                        break;
 
                    case BamlRecordType.ConstructorParametersEnd:
                        readConstructor.Pop();
                        readConstructor.Push(false);
                        readFirstConstructor.Pop();
                        readFirstConstructor.Push(false);
                        break;
 
                    case BamlRecordType.ConstructorParameterType:
                        // Process commas between constructor parameters
                        if (readFirstConstructor.Peek())
                        {
                            markupString += ", ";
                        }
                        if (readConstructor.Peek())
                        {
                            readFirstConstructor.Pop();
                            readFirstConstructor.Push(true);
                        }
                        BamlConstructorParameterTypeRecord constTypeRecord = _currentBamlRecord as BamlConstructorParameterTypeRecord;
                        markupString += GetTypeValueString(constTypeRecord.TypeId);
                        break;
 
                    case BamlRecordType.Property:
                    case BamlRecordType.PropertyWithConverter:
                        {
                            string value = ((BamlPropertyRecord)_currentBamlRecord).Value;
                            BamlPropertyInfo propertyInfo = ReadPropertyRecordCore(value);
                            if (readProperty.Pop())
                            {
                                markupString += ", ";
                            }
                            markupString += $"{propertyInfo.LocalName}={propertyInfo.Value}";
                            readProperty.Push(true);
                        }
                        break;
 
                    case BamlRecordType.PropertyCustom:
                        {
                            BamlPropertyInfo propertyInfo = GetPropertyCustomRecordInfo();
                            if (readProperty.Pop())
                            {
                                markupString += ", ";
                            }
                            markupString += $"{propertyInfo.LocalName}={propertyInfo.Value}";
                            readProperty.Push(true);
                        }
                        break;
 
                    case BamlRecordType.PropertyStringReference:
                        {
                            string value = MapTable.GetStringFromStringId(((BamlPropertyStringReferenceRecord)_currentBamlRecord).StringId);
                            BamlPropertyInfo propertyInfo = ReadPropertyRecordCore(value);
                            if (readProperty.Pop())
                            {
                                markupString += ", ";
                            }
                            markupString += $"{propertyInfo.LocalName}={propertyInfo.Value}";
                            readProperty.Push(true);
                        }
                        break;
 
                    case BamlRecordType.PropertyTypeReference:
                        {
                            string value = GetTypeValueString(((BamlPropertyTypeReferenceRecord)_currentBamlRecord).TypeId);
                            string attributeName = MapTable.GetAttributeNameFromId(
                                                          ((BamlPropertyTypeReferenceRecord)_currentBamlRecord).AttributeId);
                            if (readProperty.Pop())
                            {
                                markupString += ", ";
                            }
                            markupString += $"{attributeName}={value}";
                            readProperty.Push(true);
                        }
                        break;
 
                    case BamlRecordType.PropertyWithExtension:
                        {
                            string value = GetExtensionValueString((BamlPropertyWithExtensionRecord)_currentBamlRecord);
                            string attributeName = MapTable.GetAttributeNameFromId(
                                                          ((BamlPropertyWithExtensionRecord)_currentBamlRecord).AttributeId);
                            if (readProperty.Pop())
                            {
                                markupString += ", ";
                            }
                            markupString += $"{attributeName}={value}";
                            readProperty.Push(true);
                        }
                        break;
 
                    case BamlRecordType.KeyElementEnd:
                        markupString += "}";
                        notDone = false;
                        _haveUnprocessedRecord = false;
                        break;
 
                    default:
                        // Can't have any other type of record at this point.
                        throw new InvalidOperationException(SR.Format(SR.ParserUnknownBaml,
                                         ((int)_currentBamlRecord.RecordType).ToString(CultureInfo.CurrentCulture)));
                }
            }
 
            // At this point the markup string representing the MarkupExtension should
            // be complete, so set this as the value for this key.
            BamlKeyInfo info = new BamlKeyInfo();
            info.Value = markupString;
            info.AssemblyName = string.Empty;
            info.Prefix = (string)_prefixDictionary[XamlReaderHelper.DefinitionNamespaceURI];
            info.XmlNamespace = XamlReaderHelper.DefinitionNamespaceURI;
            info.ClrNamespace = string.Empty;
            info.Name = XamlReaderHelper.DefinitionName;
            info.LocalName = info.Name;
            info.RecordType = BamlRecordType.DefAttribute;
            info.Offset = ((IBamlDictionaryKey)keyStartRecord).ValuePosition;
 
            return info;
        }
 
        /// <summary>
        /// Picks up all the BamlRecords for a front loaded static resource into a
        /// list of BamlRecords.
        /// </summary>
        private void ProcessStaticResourceTree(List<BamlRecord> srRecords)
        {
            bool notDone = true;
            while (notDone)
            {
                // We may already have a record that was previously read in but not
                // processed.  This occurs when we've looped through all the properties
                // on an element and have encountered a non-property record to stop
                // the loop.  In that case don't read another record and just process
                // the one we have.
                if (_haveUnprocessedRecord)
                {
                    _haveUnprocessedRecord = false;
                }
                else
                {
                    GetNextRecord();
                }
 
                // Remember the BamlRecords beloning to this StaticResource
                _currentBamlRecord.Pin();
                srRecords.Add(_currentBamlRecord);
 
                if (_currentBamlRecord.RecordType == BamlRecordType.StaticResourceEnd)
                {
                    notDone = false;
                }
            }
        }
 
        /// <summary>
        /// Picks up the list of BamlRecords in the deferred
        /// section corresponding to this StaticResourceId
        /// </summary>
        private void ReadStaticResourceId()
        {
            BamlStaticResourceIdRecord bamlRecord = (BamlStaticResourceIdRecord)_currentBamlRecord;
            _currentStaticResourceRecords = _currentKeyInfo.StaticResources[bamlRecord.StaticResourceId];
            _currentStaticResourceRecordIndex = 0;
        }
 
        /***************************************************************************\
        *
        * BamlReader.EscapeString
        *
        * Check for '{' and '}' and escape any that are found in the passed value.
        * Don't create a new string unless you have to.
        *
        \***************************************************************************/
 
        private string EscapeString(string value)
        {
            StringBuilder builder = null;
            for (int i=0; i<value.Length; i++)
            {
                if (value[i] == '{' || value[i] == '}')
                {
                    if (builder == null)
                    {
                        builder = new StringBuilder(value.Length+2);
                        builder.Append(value,0,i);
                    }
                    builder.Append('\\');
                }
                if (builder != null)
                {
                    builder.Append(value[i]);
                }
            }
 
            if (builder == null)
            {
                return value;
            }
            else
            {
                return builder.ToString();
            }
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadRoutedEventRecord
        *
        * Read a routed event record.  These are currently not stored in the
        * BAML stream, but are handled by code generated by the compiler.
        *
        \***************************************************************************/
 
        private void ReadRoutedEventRecord()
        {
            throw new InvalidOperationException(SR.Format(SR.ParserBamlEvent, string.Empty));
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadClrEventRecord
        *
        * Read a clr event record.  These are currently not stored in the
        * BAML stream, but are handled by code generated by the compiler.
        *
        \***************************************************************************/
 
        private void ReadClrEventRecord()
        {
            throw new InvalidOperationException(SR.Format(SR.ParserBamlEvent, string.Empty));
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadDocumentStartRecord
        *
        * Read the start of the document record.  This should contain some
        * version information.
        *
        \***************************************************************************/
 
        private void ReadDocumentStartRecord()
        {
            ClearProperties();
            NodeTypeInternal = BamlNodeType.StartDocument;
 
            BamlDocumentStartRecord documentStartRecord = (BamlDocumentStartRecord)_currentBamlRecord;
            _parserContext.IsDebugBamlStream = documentStartRecord.DebugBaml;
 
            // Push information on the node stack to indicate we have a start document
            BamlNodeInfo nodeInfo = new BamlNodeInfo();
            nodeInfo.RecordType = BamlRecordType.DocumentStart;
            _nodeStack.Push(nodeInfo);
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadDocumentEndRecord
        *
        * Read the end of the document record.  This is used to flag that the end
        * of the file has been reached.
        *
        \***************************************************************************/
 
        private void ReadDocumentEndRecord()
        {
            // Pop information off the node stack to ensure we have matched all the
            // start and end nodes and have nothing left but the start document node.
            BamlNodeInfo nodeInfo = _nodeStack.Pop();
            if (nodeInfo.RecordType != BamlRecordType.DocumentStart)
            {
                throw new InvalidOperationException(SR.Format(SR.BamlScopeError,
                                                    nodeInfo.RecordType.ToString(),
                                                    "DocumentEnd"));
            }
 
            ClearProperties();
            NodeTypeInternal = BamlNodeType.EndDocument;
        }
 
        private void ReadAssemblyInfoRecord()
        {
            BamlAssemblyInfoRecord asmRecord = (BamlAssemblyInfoRecord)_currentBamlRecord;
            MapTable.LoadAssemblyInfoRecord(asmRecord);
 
            Assembly asm = Assembly.Load(asmRecord.AssemblyFullName);
            foreach (XmlnsDefinitionAttribute xmlnsDef in asm.GetCustomAttributes(typeof(XmlnsDefinitionAttribute), true))
            {
                SetXmlNamespace(xmlnsDef.ClrNamespace, asm.FullName, xmlnsDef.XmlNamespace);
            }
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadPIMappingRecord
        *
        * Read the clr to xml namespace to assembly mapping record.  The contents
        * of this record are represented as three properties, one each for
        * XmlNamespace, ClrNamespace and Assembly Name.
        *
        \***************************************************************************/
 
        private void ReadPIMappingRecord()
        {
            BamlPIMappingRecord piMappingRecord = (BamlPIMappingRecord)_currentBamlRecord;
            BamlAssemblyInfoRecord assemblyInfo = MapTable.GetAssemblyInfoFromId(
                                                                  piMappingRecord.AssemblyId);
            if (assemblyInfo == null)
            {
                throw new InvalidOperationException(SR.ParserMapPIMissingAssembly);
            }
 
            // If this mapping has not already been set up, then set it now
            if (!_parserContext.XamlTypeMapper.PITable.Contains(piMappingRecord.XmlNamespace))
            {
                // Add information to the MappingPI hashtable and the reverse lookup
                // hashtable.
                _parserContext.XamlTypeMapper.AddMappingProcessingInstruction(piMappingRecord.XmlNamespace,
                                                   piMappingRecord.ClrNamespace,
                                                   assemblyInfo.AssemblyFullName);
            }
 
            ClearProperties();
            NodeTypeInternal = BamlNodeType.PIMapping;
            _name = "Mapping";
            _localName = _name;
            _ownerTypeName = string.Empty;
 
            // Set the xml namespace, clr namespace and assembly properties as defined
            // in the mapping PI.
            _xmlNamespace = piMappingRecord.XmlNamespace;
            _clrNamespace = piMappingRecord.ClrNamespace;
            _assemblyName = assemblyInfo.AssemblyFullName;
 
            _value = string.Create(null, stackalloc char[100], $"XmlNamespace=\"{_xmlNamespace}\" ClrNamespace=\"{_clrNamespace}\" Assembly=\"{_assemblyName}\"");
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadLiteralContentRecord
        *
        * Read literal content record, which is the responsibility of the current
        * element to parse.
        *
        \***************************************************************************/
 
        private void ReadLiteralContentRecord()
        {
            ClearProperties();
 
            BamlLiteralContentRecord bamlRecord = (BamlLiteralContentRecord)_currentBamlRecord;
            NodeTypeInternal = BamlNodeType.LiteralContent;
            _value = bamlRecord.Value;
        }
 
        private void ReadConnectionIdRecord()
        {
            BamlConnectionIdRecord bamlRecord = (BamlConnectionIdRecord)_currentBamlRecord;
            AddToPropertyInfoCollection(bamlRecord.ConnectionId);
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadElementStartRecord
        *
        * Read the start of an element.  This is either a CLR or DependencyObject
        * that is part of an object tree.
        *
        \***************************************************************************/
 
        private void ReadElementStartRecord()
        {
            ClearProperties();
            _propertyDP = null;
            _parserContext.PushScope();
            _prefixDictionary.PushScope();
 
            BamlElementStartRecord bamlRecord = (BamlElementStartRecord)_currentBamlRecord;
            BamlTypeInfoRecord typeInfo = MapTable.GetTypeInfoFromId(bamlRecord.TypeId);
 
            NodeTypeInternal = BamlNodeType.StartElement;
            _name = typeInfo.TypeFullName;
            _localName = _name.Substring(_name.LastIndexOf('.') + 1);
            _ownerTypeName = string.Empty;
            _clrNamespace = typeInfo.ClrNamespace;
            GetAssemblyAndPrefixAndXmlns(typeInfo, out _assemblyName, out _prefix, out _xmlNamespace);
 
            // Push information on the node stack to indicate we have a start element
            BamlNodeInfo nodeInfo = new BamlNodeInfo();
            nodeInfo.Name = _name;
            nodeInfo.LocalName = _localName;
            nodeInfo.AssemblyName = _assemblyName;
            nodeInfo.Prefix = _prefix;
            nodeInfo.ClrNamespace = _clrNamespace;
            nodeInfo.XmlNamespace = _xmlNamespace;
            nodeInfo.RecordType = BamlRecordType.ElementStart;
 
            _useTypeConverter = bamlRecord.CreateUsingTypeConverter;
            _isInjected = bamlRecord.IsInjected;
 
            // If we are in a deferable block, then see if this is a top level element for
            // that block that matches an offset in the list of defered dictionary keys.  If
            // so, then insert a x:Key="keystring" to make this appear like a normal
            // dictionary.
            if (_deferableContentBlockDepth == _nodeStack.Count)
            {
                // Calculate the offset for the start of the current element record in
                // the baml stream.
                Int32 offset = (Int32)(_bamlRecordReader.StreamPosition - _deferableContentPosition);
 
                // Subtract off the size of the current Record.
                offset -= bamlRecord.RecordSize + BamlRecord.RecordTypeFieldLength;
 
                // If there is a debug extension record then subtract that off also.
                if (BamlRecordHelper.HasDebugExtensionRecord(_parserContext.IsDebugBamlStream, bamlRecord))
                {
                    BamlRecord bamlDebugRecord = bamlRecord.Next;
                    offset -= bamlDebugRecord.RecordSize + BamlRecord.RecordTypeFieldLength;
                }
                InsertDeferedKey(offset);
            }
 
            _nodeStack.Push(nodeInfo);
 
            // Read the properties that may be part of the start tag of this element
            ReadProperties();
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadElementEndRecord
        *
        * Read the end of an element.  This is either a CLR or DependencyObject
        * that is part of an object tree.
        *
        \***************************************************************************/
 
        private void ReadElementEndRecord()
        {
            // If we are processing a deferable content block and we've reached the
            // end record for the deferable element, then pop off the deferable content
            // start record that is on the stack.
            if (_deferableContentBlockDepth == _nodeStack.Count)
            {
                _deferableContentBlockDepth = -1;
                _deferableContentPosition = -1;
            }
 
            // Pop information off the node stack that tells us what element this
            // is the end of.  Check to make sure the record on the stack is for a
            // start element.
            BamlNodeInfo nodeInfo = _nodeStack.Pop();
            if (nodeInfo.RecordType != BamlRecordType.ElementStart)
            {
                throw new InvalidOperationException(SR.Format(SR.BamlScopeError,
                                                 _currentBamlRecord.RecordType.ToString(),
                                                 BamlRecordType.ElementEnd.ToString()));
            }
 
            ClearProperties();
            NodeTypeInternal = BamlNodeType.EndElement;
            _name = nodeInfo.Name;
            _localName = nodeInfo.LocalName;
            _ownerTypeName = string.Empty;
            _assemblyName = nodeInfo.AssemblyName;
            _prefix = nodeInfo.Prefix;
            _xmlNamespace = nodeInfo.XmlNamespace;
            _clrNamespace = nodeInfo.ClrNamespace;
            _parserContext.PopScope();
            _prefixDictionary.PopScope();
 
            // read properties, if any, after this end tag.
            ReadProperties();
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadPropertyComplexStartRecord
        *
        * Read the start of a complex property.  This can be any type of complex
        * property, including arrays, ILists, IDictionaries, Clr properties or
        * dependency properties.
        *
        \***************************************************************************/
 
        private void ReadPropertyComplexStartRecord()
        {
            ClearProperties();
            _parserContext.PushScope();
            _prefixDictionary.PushScope();
 
            BamlNodeInfo nodeInfo = new BamlNodeInfo();
 
            SetCommonPropertyInfo(nodeInfo,
                      ((BamlPropertyComplexStartRecord)_currentBamlRecord).AttributeId);
 
            // Set instance variables to node info extracted from record.
            NodeTypeInternal = BamlNodeType.StartComplexProperty;
            _localName = nodeInfo.LocalName;
            int index = nodeInfo.Name.LastIndexOf('.');
            if (index > 0)
            {
                _ownerTypeName = nodeInfo.Name.Substring(0, index);
            }
            else
            {
                // Eg. xmlns property
                _ownerTypeName = string.Empty;
            }
            _name = nodeInfo.Name;
            _clrNamespace = nodeInfo.ClrNamespace;
            _assemblyName = nodeInfo.AssemblyName;
            _prefix = nodeInfo.Prefix;
            _xmlNamespace = nodeInfo.XmlNamespace;
            nodeInfo.RecordType = _currentBamlRecord.RecordType;
 
 
            _nodeStack.Push(nodeInfo);
 
            // Read the properties that may be part of the start tag
            ReadProperties();
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadPropertyComplexEndRecord
        *
        * Read the end of a complex property.  This can be any type of complex
        * property, including arrays, ILists, IDictionaries, Clr properties or
        * dependency properties.
        *
        \***************************************************************************/
 
        private void ReadPropertyComplexEndRecord()
        {
            // Pop information off the node info stack that tells us what the starting
            // record was for this ending record.  Check to make sure it is the
            // correct type.  If not, throw an exception.
            BamlNodeInfo nodeInfo = _nodeStack.Pop();
            BamlRecordType expectedType;
            switch (nodeInfo.RecordType)
            {
                case BamlRecordType.PropertyComplexStart:
                    expectedType = BamlRecordType.PropertyComplexEnd;
                    break;
                case BamlRecordType.PropertyArrayStart:
                    expectedType = BamlRecordType.PropertyArrayEnd;
                    break;
                case BamlRecordType.PropertyIListStart:
                    expectedType = BamlRecordType.PropertyIListEnd;
                    break;
                case BamlRecordType.PropertyIDictionaryStart:
                    expectedType = BamlRecordType.PropertyIDictionaryEnd;
                    break;
                default:
                    expectedType = BamlRecordType.Unknown;
                    break;
            }
 
            if (_currentBamlRecord.RecordType != expectedType)
            {
                throw new InvalidOperationException(SR.Format(SR.BamlScopeError,
                                          _currentBamlRecord.RecordType.ToString(),
                                          expectedType.ToString()));
            }
 
            ClearProperties();
            NodeTypeInternal = BamlNodeType.EndComplexProperty;
            _name = nodeInfo.Name;
            _localName = nodeInfo.LocalName;
            int index = nodeInfo.Name.LastIndexOf('.');
            if (index > 0)
            {
                _ownerTypeName = nodeInfo.Name.Substring(0, index);
            }
            else
            {
                // Eg. xmlns property
                _ownerTypeName = string.Empty;
            }
            _assemblyName = nodeInfo.AssemblyName;
            _prefix = nodeInfo.Prefix;
            _xmlNamespace = nodeInfo.XmlNamespace;
            _clrNamespace = nodeInfo.ClrNamespace;
            _parserContext.PopScope();
            _prefixDictionary.PopScope();
 
            ReadProperties();
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadTextRecord
        *
        * Read record containing text content that goes between the start and end
        * tags of an object.
        *
        \***************************************************************************/
 
        private void ReadTextRecord()
        {
            ClearProperties();
 
            BamlTextWithIdRecord textWithIdRecord = _currentBamlRecord as BamlTextWithIdRecord;
            if (textWithIdRecord != null)
            {
                // Get the value string from the string table, and cache it in the
                // record.
                textWithIdRecord.Value = MapTable.GetStringFromStringId(
                                                textWithIdRecord.ValueId);
            }
 
            BamlTextWithConverterRecord textWithConverter = _currentBamlRecord as BamlTextWithConverterRecord;
            if (textWithConverter != null)
            {
                short converterTypeId = textWithConverter.ConverterTypeId;
                Type converter = MapTable.GetTypeFromId(converterTypeId);
                _typeConverterAssemblyName = converter.Assembly.FullName;
                _typeConverterName = converter.FullName;
            }
 
            NodeTypeInternal = BamlNodeType.Text;
            _prefix = string.Empty;
            _value = ((BamlTextRecord)_currentBamlRecord).Value;
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadConstructorStart
        *
        * Read a <x:ConstructorParameters   ...   > start tag, which indicates that
        * the following objects are to be used as constructor parameters.
        *
        \***************************************************************************/
 
        private void ReadConstructorStart()
        {
            ClearProperties();
 
            NodeTypeInternal = BamlNodeType.StartConstructor;
 
            // Push information on the node stack to indicate we have a start array
            BamlNodeInfo nodeInfo = new BamlNodeInfo();
            nodeInfo.RecordType = BamlRecordType.ConstructorParametersStart;
 
            _nodeStack.Push(nodeInfo);
        }
 
        /***************************************************************************\
        *
        * BamlReader.ReadConstructorEnd
        *
        * Read a <\x:ConstructorParameters   ...   > end tag, which indicates that
        * the previous objects are to be used as constructor parameters.
        *
        \***************************************************************************/
 
        private void ReadConstructorEnd()
        {
            ClearProperties();
 
            NodeTypeInternal = BamlNodeType.EndConstructor;
 
            // Pop information off the node stack that tells us what element this
            // is the end of.  Check to make sure the record on the stack is for a
            // start element.
            BamlNodeInfo nodeInfo = _nodeStack.Pop();
            if (nodeInfo.RecordType != BamlRecordType.ConstructorParametersStart)
            {
                throw new InvalidOperationException(SR.Format(SR.BamlScopeError,
                                                 _currentBamlRecord.RecordType.ToString(),
                                                 BamlRecordType.ConstructorParametersEnd.ToString()));
            }
 
            // read properties, if any, after this end tag.
            ReadProperties();
        }
 
        /***************************************************************************\
        *
        * BamlReader.InsertDeferedKey
        *
        * Search the _deferedKeys list for a dictionary key that has the same offset
        * as the current baml stream position.  If one is found, generate a
        * def attribute record to simulate a dictionary key.
        *
        \***************************************************************************/
 
        private void InsertDeferedKey(Int32 valueOffset)
        {
            if (_deferKeys == null)
            {
                return;
            }
 
            BamlKeyInfo keyInfo = _deferKeys[0];
            while (keyInfo.Offset == valueOffset)
            {
                // Remember the _currentKeyInfo so that we can use it to resolve StaticResourceId
                // records that may occur within the corresponding value.
                _currentKeyInfo = keyInfo;
 
                BamlPropertyInfo info = new BamlPropertyInfo();
                info.Value = keyInfo.Value;
                info.AssemblyName = string.Empty;
                info.Prefix = (string)_prefixDictionary[XamlReaderHelper.DefinitionNamespaceURI];
                info.XmlNamespace = XamlReaderHelper.DefinitionNamespaceURI;
                info.ClrNamespace = string.Empty;
                info.Name = keyInfo.Name;
                info.LocalName = info.Name;
                info.RecordType = BamlRecordType.DefAttribute;
 
                AddToPropertyInfoCollection(info);
 
                // We no longer need this key record, so remove it to make subsequent
                // searches faster.
                _deferKeys.RemoveAt(0);
 
                if (_deferKeys.Count > 0)
                {
                    keyInfo = _deferKeys[0];
                }
                else
                {
                    return;
                }
            }
        }
 
        /***************************************************************************\
        *
        * BamlReader.ClearProperties
        *
        * Clear properties that are likely to change as different baml records
        * are read in.
        *
        \***************************************************************************/
 
        private void ClearProperties()
        {
            _value = string.Empty;
            _prefix = string.Empty;
            _name = string.Empty;
            _localName = string.Empty;
            _ownerTypeName = string.Empty;
            _assemblyName = string.Empty;
            _xmlNamespace = string.Empty;
            _clrNamespace = string.Empty;
            _connectionId = 0;
            _contentPropertyName = string.Empty;
            _attributeUsage = BamlAttributeUsage.Default;
            _typeConverterAssemblyName = string.Empty;
            _typeConverterName = string.Empty;
            _properties.Clear();
        }
 
        /***************************************************************************\
        *
        * BamlReader.SetCommonPropertyInfo
        *
        * Get information that is common to all types of property records and
        * fill in the passed node info record with this information.
        * Return the attribute info found.
        *
        \***************************************************************************/
 
        private BamlAttributeInfoRecord SetCommonPropertyInfo(
            BamlNodeInfo       nodeInfo,
            short              attrId)
        {
            BamlAttributeInfoRecord attrInfo = MapTable.GetAttributeInfoFromId(attrId);
            BamlTypeInfoRecord typeInfo = MapTable.GetTypeInfoFromId(attrInfo.OwnerTypeId);
 
            // Fill node info record with this data.
            nodeInfo.LocalName = attrInfo.Name;
            nodeInfo.Name = $"{typeInfo.TypeFullName}.{nodeInfo.LocalName}";
            string assembly, prefix, namespaceUri;
            GetAssemblyAndPrefixAndXmlns(typeInfo, out assembly, out prefix, out namespaceUri);
            nodeInfo.AssemblyName = assembly;
            nodeInfo.Prefix = prefix;
            nodeInfo.XmlNamespace = namespaceUri;
            nodeInfo.ClrNamespace = typeInfo.ClrNamespace;
            nodeInfo.AttributeUsage = attrInfo.AttributeUsage;
 
            return attrInfo;
        }
 
        private string GetTemplateBindingExtensionValueString(short memberId)
        {
            string valueString = string.Empty;
            string valuePrefix = null;
            string typeName = null;
            string propName = null;
 
            if (memberId < 0)
            {
                memberId = (short)-memberId;
                DependencyProperty dp = null;
                if (memberId < (short)KnownProperties.MaxDependencyProperty)
                {
                    KnownProperties knownId = (KnownProperties)(memberId);
                    {
                        dp = KnownTypes.GetKnownDependencyPropertyFromId(knownId);
                    }
                }
 
                if (dp == null)
                {
                    throw new InvalidOperationException(SR.BamlBadExtensionValue);
                }
                else
                {
                    typeName = dp.OwnerType.Name;
                    propName = dp.Name;
                }
 
                object prefixObject = _prefixDictionary[XamlReaderHelper.DefaultNamespaceURI];
                valuePrefix = (prefixObject == null) ? string.Empty : (string)prefixObject;
            }
            else
            {
                BamlAttributeInfoRecord attrInfo = MapTable.GetAttributeInfoFromId(memberId);
                BamlTypeInfoRecord valueTypeInfo = MapTable.GetTypeInfoFromId(attrInfo.OwnerTypeId);
                string valueXmlNamespace;
                string valueAssemblyName;
                GetAssemblyAndPrefixAndXmlns(valueTypeInfo, out valueAssemblyName, out valuePrefix, out valueXmlNamespace);
                typeName = valueTypeInfo.TypeFullName;
                typeName = typeName.Substring(typeName.LastIndexOf('.') + 1);
                propName = attrInfo.Name;
            }
 
            if (valuePrefix == string.Empty)
            {
                valueString += typeName;
            }
            else
            {
                valueString += $"{valuePrefix}:{typeName}";
            }
 
            valueString += $".{propName}}}";
            return valueString;
        }
 
        private string GetStaticExtensionValueString(short memberId)
        {
            string valueString = string.Empty;
            string valuePrefix = null;
            string typeName = null;
            string propName = null;
            string extensionPrefix = (string)_prefixDictionary[XamlReaderHelper.DefinitionNamespaceURI];
 
            if (extensionPrefix != string.Empty)
            {
                valueString = $"{{{extensionPrefix}:Static ";
            }
            else
            {
                valueString = "{Static ";
            }
 
            if (memberId < 0)
            {
                memberId = (short)-memberId;
                bool isKey = true;
 
                // this is a known StaticExtension param.
                // if keyId is more than the range it is the actual resource,
                // else it is the key.
 
                memberId = SystemResourceKey.GetSystemResourceKeyIdFromBamlId(memberId, out isKey);
 
                SystemResourceKeyID keyId = (SystemResourceKeyID)memberId;
                if (Enum.IsDefined(keyId))
                {
                    typeName = SystemKeyConverter.GetSystemClassName(keyId);
 
                    if (isKey)
                    {
                        propName = SystemKeyConverter.GetSystemKeyName(keyId);
                    }
                    else
                    {
                        propName = SystemKeyConverter.GetSystemPropertyName(keyId);
                    }
                }
                else
                {
                    throw new InvalidOperationException(SR.BamlBadExtensionValue);
                }
 
                object prefixObject = _prefixDictionary[XamlReaderHelper.DefaultNamespaceURI];
                valuePrefix = (prefixObject == null) ? string.Empty : (string)prefixObject;
            }
            else
            {
                BamlAttributeInfoRecord attrInfo = MapTable.GetAttributeInfoFromId(memberId);
                BamlTypeInfoRecord valueTypeInfo = MapTable.GetTypeInfoFromId(attrInfo.OwnerTypeId);
                string valueXmlNamespace;
                string valueAssemblyName;
                GetAssemblyAndPrefixAndXmlns(valueTypeInfo, out valueAssemblyName, out valuePrefix, out valueXmlNamespace);
                typeName = valueTypeInfo.TypeFullName;
                typeName = typeName.Substring(typeName.LastIndexOf('.') + 1);
                propName = attrInfo.Name;
            }
 
            if (valuePrefix == string.Empty)
            {
                valueString += typeName;
            }
            else
            {
                valueString += $"{valuePrefix}:{typeName}";
            }
 
            valueString += $".{propName}}}";
            return valueString;
        }
 
        private string GetExtensionPrefixString(string extensionName)
        {
            string valueString = string.Empty;
            string extensionPrefix = (string)_prefixDictionary[XamlReaderHelper.DefaultNamespaceURI];
 
            if (!string.IsNullOrEmpty(extensionPrefix))
            {
                valueString = $"{{{extensionPrefix}:{extensionName} ";
            }
            else
            {
                valueString = $"{{{extensionName} ";
            }
 
            return valueString;
        }
 
        private string GetInnerExtensionValueString(IOptimizedMarkupExtension optimizedMarkupExtensionRecord)
        {
            string valueString = string.Empty;
            short memberId = optimizedMarkupExtensionRecord.ValueId;
 
            if (optimizedMarkupExtensionRecord.IsValueTypeExtension)
            {
                valueString = GetTypeValueString(memberId);
            }
            else if (optimizedMarkupExtensionRecord.IsValueStaticExtension)
            {
                valueString = GetStaticExtensionValueString(memberId);
            }
            else
            {
                valueString = MapTable.GetStringFromStringId(memberId);
            }
 
            return $"{valueString}}}";
        }
 
        private string GetExtensionValueString(IOptimizedMarkupExtension optimizedMarkupExtensionRecord)
        {
            string valueString = string.Empty;
            short memberId = optimizedMarkupExtensionRecord.ValueId;
            short extensionId = optimizedMarkupExtensionRecord.ExtensionTypeId;
 
            switch (extensionId)
            {
                case (short)KnownElements.StaticExtension:
                    valueString = GetStaticExtensionValueString(memberId);
                    break;
 
                case (short)KnownElements.TemplateBindingExtension:
                    valueString = GetExtensionPrefixString("TemplateBinding");
                    valueString += GetTemplateBindingExtensionValueString(memberId);
                    break;
 
                case (short)KnownElements.DynamicResourceExtension:
                    valueString = GetExtensionPrefixString("DynamicResource");
                    valueString += GetInnerExtensionValueString(optimizedMarkupExtensionRecord);
                    break;
 
                case (short)KnownElements.StaticResourceExtension:
                    valueString = GetExtensionPrefixString("StaticResource");
                    valueString += GetInnerExtensionValueString(optimizedMarkupExtensionRecord);
                    break;
            }
 
            return valueString;
        }
 
        /***************************************************************************\
        *
        * BamlReader.GetTypeValueString
        *
        * Construct a MarkupExtension that represents the type given its ID in the
        * BamlMapTable.
        *
        \***************************************************************************/
 
        private string GetTypeValueString(short typeId)
        {
            string typeExtensionPrefix = (string)_prefixDictionary[XamlReaderHelper.DefinitionNamespaceURI];
            string valueString;
            if (typeExtensionPrefix != string.Empty)
            {
                valueString = $"{{{typeExtensionPrefix}:Type ";
            }
            else
            {
                valueString = "{Type ";
            }
 
            BamlTypeInfoRecord valueTypeInfo = MapTable.GetTypeInfoFromId(typeId);
            string valueXmlNamespace;
            string valuePrefix;
            string valueAssemblyName;
            GetAssemblyAndPrefixAndXmlns(valueTypeInfo, out valueAssemblyName, out valuePrefix, out valueXmlNamespace);
            string typeName = valueTypeInfo.TypeFullName;
            typeName = typeName.Substring(typeName.LastIndexOf('.') + 1);
            if (valuePrefix == string.Empty)
            {
                valueString += typeName;
            }
            else
            {
                valueString += $"{valuePrefix}:{typeName}";
            }
            valueString +="}";
 
            return valueString;
        }
 
        /***************************************************************************\
        *
        * BamlReader.GetAssemblyAndPrefixAndXmlns
        *
        * Get a namespace prefix and the associated Xml namespace for a given type.
        * If none, return empty strings.
        *
        \***************************************************************************/
 
        private void GetAssemblyAndPrefixAndXmlns(
                BamlTypeInfoRecord typeInfo,
            out string assemblyFullName,
            out string prefix,
            out string xmlns)
        {
            // If the typeInfo indicates the type is NOT a core Avalon type, then the
            // assembly should be in the Assembly table of the BamlMapTable.  Otherwise
            // we have to get the Assembly information from the actual type.
            if (typeInfo.AssemblyId >= 0 || typeInfo.Type == null)
            {
                BamlAssemblyInfoRecord assyInfo = MapTable.GetAssemblyInfoFromId(
                                                                     typeInfo.AssemblyId);
                assemblyFullName = assyInfo.AssemblyFullName;
            }
            else
            {
                Assembly typeAssembly = typeInfo.Type.Assembly;
                assemblyFullName = typeAssembly.FullName;
            }
 
            // Look through the mapping table for an xml namespace that matches
            // the assembly and type namespace.
            // Note that it may be one of the known namespaces, such as the definition
            // namespace so check for that first.
            if (typeInfo.ClrNamespace == "System.Windows.Markup" &&
                (assemblyFullName.StartsWith("PresentationFramework", StringComparison.Ordinal)
                || assemblyFullName.StartsWith("System.Xaml", StringComparison.Ordinal)))
            {
                xmlns = XamlReaderHelper.DefinitionNamespaceURI;
            }
            else
            {
                // XamlTypeMapper only stored MappingPI Xml Namesaces
                xmlns = _parserContext.XamlTypeMapper.GetXmlNamespace(
                          typeInfo.ClrNamespace, assemblyFullName);
 
                // Now check our own private list for URI based Xml Namespaces
                if(String.IsNullOrEmpty(xmlns))
                {
                    List<String> xmlnsList = GetXmlNamespaceList(typeInfo.ClrNamespace, assemblyFullName);
                    prefix = GetXmlnsPrefix(xmlnsList);
                    return;
                }
            }
 
            prefix = GetXmlnsPrefix(xmlns);
        }
 
 
        // store the all XmlNs UIRs that map to each CLRNamespace + AssemblyName
        private void SetXmlNamespace(string clrNamespace, string assemblyFullName, string xmlNs)
        {
            String fullName = $"{clrNamespace}#{assemblyFullName}";
            List<String> list;
            if(_reverseXmlnsTable.ContainsKey(fullName))
            {
                list = _reverseXmlnsTable[fullName];
            }
            else
            {
                list = new List<String>();
                _reverseXmlnsTable[fullName] = list;
            }
            list.Add(xmlNs);
        }
 
 
        // Retrieve the XmlNs UIRs that map to a CLRNamespace + AssemblyName
        private List<String> GetXmlNamespaceList(string clrNamespace, string assemblyFullName)
        {
            String fullName = $"{clrNamespace}#{assemblyFullName}";
            List<String> xmlnsList=null;
 
            if (_reverseXmlnsTable.ContainsKey(fullName))
            {
                xmlnsList = _reverseXmlnsTable[fullName];
            }
            return xmlnsList;
        }
 
 
        internal string GetXmlnsPrefix(string xmlns)
        {
            string prefix = string.Empty;
 
            // If we don't find an xmlns, then the clr namespace must be in the
            // default definition file group for this file.  Otherwise, lookup the
            // prefix in the xmlns-to-prefix dictionary built up in ReadXmlnsProperty
            if (xmlns == string.Empty)
            {
                xmlns = _parserContext.XmlnsDictionary[string.Empty];
            }
            else
            {
                object prefixObject = _prefixDictionary[xmlns];
                // If there is nothing in the prefix dictionary for this namespace,
                // then assume this is the default namespace and set prefix to
                // an empty string.
                if (prefixObject != null)
                {
                    prefix = (string)prefixObject;
                }
            }
 
            return prefix;
        }
 
        private string GetXmlnsPrefix(List<String> xmlnsList)
        {
            string prefix;
            string xmlns;
 
            if (xmlnsList != null)
            {
                // return the first non-null prefix defined.
                // the default prefix is "" and is non-null.
                for (int i=0; i<xmlnsList.Count; i++)
                {
                    xmlns = xmlnsList[i];
                    prefix = _prefixDictionary[xmlns];
                    if(prefix != null)
                        return prefix;
                }
            }
            return String.Empty;   // and error actually but old code defaulted this way.
        }
 
        /***************************************************************************\
        *
        * BamlReader.MapTable
        *
        * Get value of map table from the parser context.
        *
        \***************************************************************************/
 
        private BamlMapTable MapTable
        {
            get { return _parserContext.MapTable; }
        }
 
        #endregion Internal Methods
 
        #region Data
 
        // The BamlRecordReader that is handling getting records from the baml stream
        private BamlRecordReader _bamlRecordReader;
 
        // Dictionary with XML namespaces as keys and prefixes as values.  This is
        // the same information kept in _parserContext.XmlnsDictionary, but we lookup
        // by Xmlns more often, so it is more efficient to keep another dictionary.
        private XmlnsDictionary _prefixDictionary;
 
        // The last record read in from the _bamlRecordReader
        private BamlRecord _currentBamlRecord;
 
        // The _currentBamlRecord is valid, but has not been processed yet.
        private bool _haveUnprocessedRecord;
 
        // Stack depth where a deferable content block starts.  -1 if not in deferable content.
        private int _deferableContentBlockDepth;
 
        // The position in the stream where deferable content values begin
        private Int64 _deferableContentPosition;
 
        // List of keys for a deferable content dictionary.  These are arranged at the front
        // of the deferable content block and are read into a list before processing the
        // values for the dictionary.
        private List<BamlKeyInfo> _deferKeys;
 
        // Info for the key being currently read.
        private BamlKeyInfo _currentKeyInfo;
 
        // The currently active StaticResourceInfo
        private List<BamlRecord> _currentStaticResourceRecords;
        private int              _currentStaticResourceRecordIndex;
 
        // The type of current BAML node (or a condensed version of the real baml record type)
        private BamlNodeType _bamlNodeType;
 
        // The current read state of this BamlReader.
        private ReadState _readState;
 
        // The value of the assembly name for the current node
        private string _assemblyName;
 
        // The value of the namespace prefix for the current node
        private string _prefix;
 
        // Xml namespace for the current node;
        private string _xmlNamespace;
 
        // Clr namespace for the current node;
        private string _clrNamespace;
 
        // The attribute value for the current node, if a property or namespace
        private string _value;
 
        // The fully qualified name of the current node
        private string _name;
 
        // The local part of the name of the current node, without class or prefix
        private string _localName;
 
        // The fully qualified class name of the current node's owner type, without prefix
        // Applies only to properties and events
        private string _ownerTypeName;
 
        // Arraylist of various PropertyInfo objects in the order retrieved from the baml stream
        private ArrayList _properties;
 
        // DP value of a property. If this is set the type of this property is used to resolve the
        // value of any subsequent property. Setter.Property & Setter.Value is an example of this sceanrio.
        private DependencyProperty _propertyDP;
 
        // Index of current property in _properties collection that is being viewed.
        private int _propertiesIndex;
 
        // connection Id of current element for hooking up IDs and events.
        private Int32 _connectionId;
 
        // contentProperty Name of current element.
        private string _contentPropertyName;
 
        // Defines what this property is used for such as being an alias for
        // xml:lang, xml:space or x:ID
        private BamlAttributeUsage _attributeUsage;
 
        // Stack of node information about the element tree being built.
        private readonly Stack<BamlNodeInfo> _nodeStack;
 
        // Context information used when reading baml file.  This contains the XamlTypeMapper used
        // for resolving binary property information into strings.
        private ParserContext _parserContext;
 
        private bool _isInjected;
        private bool _useTypeConverter;
 
        private string _typeConverterAssemblyName;
        private string _typeConverterName;
 
        // Maps CLRNameSpace#AssemblyFullName  <-->  List of XmlNamespacesURIs.
        private Dictionary<String, List<String>> _reverseXmlnsTable;
 
#endregion Data
 
 
        /***************************************************************************\
        *
        * BamlNodeInfo
        *
        * This class holds information about a single element or other node record
        * that is encountered when reading the baml file.
        *
        \***************************************************************************/
 
        internal class BamlNodeInfo
        {
            // Create an empty property info record
            internal BamlNodeInfo()
            {
            }
 
            // The type of record, be it element, complex property, array, etc.
            internal BamlRecordType RecordType
            {
                get { return _recordType; }
                set { _recordType = value; }
            }
 
            // The value of the assembly name for the declaring type of the current node or property
            internal string AssemblyName
            {
                get { return _assemblyName; }
                set { _assemblyName = value; }
            }
 
            // The value of the namespace prefix for the current node or property
            internal string Prefix
            {
                get { return _prefix; }
                set { _prefix = value; }
            }
 
            // Xml namespace for the current node or property
            internal string XmlNamespace
            {
                get { return _xmlNamespace; }
                set { _xmlNamespace = value; }
            }
 
            // Clr namespace for the current node or property
            internal string ClrNamespace
            {
                get { return _clrNamespace; }
                set { _clrNamespace = value; }
            }
 
            // The fully qualified name of the current node or property
            internal string Name
            {
                get { return _name; }
                set { _name = value; }
            }
 
            // The local part of the name of the current node or property
            internal string LocalName
            {
                get { return _localName; }
                set { _localName = value; }
            }
 
            // Defines what this property is used for such as being an alias for
            // xml:lang, xml:space or x:ID
            internal BamlAttributeUsage AttributeUsage
            {
                get { return _attributeUsage; }
                set { _attributeUsage = value; }
            }
 
            // The type of record, be it element, complex property, array, etc.
            private BamlRecordType _recordType;
 
            // The value of the assembly name for the declaring type of the current node or property
            private string _assemblyName;
 
            // The value of the namespace prefix for the current node or property
            private string _prefix;
 
            // Xml namespace for the current node or property
            private string _xmlNamespace;
 
            // Clr namespace for the current node or property
            private string _clrNamespace;
 
            // The fully qualified name of the current node or property
            private string _name;
 
            // The local part of the name of the current node or property
            private string _localName;
 
            // Defines what this property is used for such as being an alias for
            // xml:lang, xml:space or x:ID
            private BamlAttributeUsage _attributeUsage;
        }
 
        /***************************************************************************\
        *
        * BamlPropertyInfo
        *
        * This class holds information about a single Baml property record that is
        * encountered when reading all the property-like records on an element.
        *
        \***************************************************************************/
 
        internal class BamlPropertyInfo : BamlNodeInfo
        {
            // Create an empty property info record
            internal BamlPropertyInfo()
            {
            }
 
            // The string value for the current property
            internal string Value
            {
                get { return _value; }
                set { _value = value; }
            }
 
            // The string value for the current property
            private string _value;
        }
 
        /***************************************************************************\
        *
        * BamlContentPropertyInfo
        *
        * This class holds information about a single Baml property record that is
        * encountered when reading all the property-like records on an element.
        *
        \***************************************************************************/
 
        internal class BamlContentPropertyInfo : BamlNodeInfo
        {
            // this doesn't need any different fields it just needs to be
            // a different type.
        }
 
        /***************************************************************************\
        *
        * BamlKeyInfo
        *
        * This class holds information about a single Baml property record that is
        * encountered when reading all the property-like records on an element.
        *
        \***************************************************************************/
 
        [DebuggerDisplay("{_offset}")]
        internal class BamlKeyInfo : BamlPropertyInfo
        {
            // Create an empty info record
            internal BamlKeyInfo()
            {
            }
 
            // The offset of the value from the start of the values section.
            internal Int32 Offset
            {
                get { return _offset; }
                set { _offset = value; }
            }
 
            internal List<List<BamlRecord>> StaticResources
            {
                get
                {
                    if (_staticResources == null)
                    {
                        _staticResources = new List<List<BamlRecord>>();
                    }
 
                    return _staticResources;
                }
            }
 
            private Int32 _offset;
            private List<List<BamlRecord>> _staticResources;
        }
    }
}