File: MS\Internal\Globalization\BamlResourceDeserializer.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.
 
// Internal class that build the baml tree for localization
 
 
using System.IO;
using System.Collections;
 
using System.Windows;
using System.Windows.Markup;
 
namespace MS.Internal.Globalization
{
    /// <summary>
    /// BamlResourceDeserializer. This class strictly operates on Baml format, and doesn't
    /// know any localization specific details
    /// </summary>
    internal sealed class BamlResourceDeserializer
    {
        //----------------------------
        // Internal static
        //----------------------------
        internal static BamlTree LoadBaml(Stream bamlStream)
        {
            // thread safe implementation
            return (new BamlResourceDeserializer()).LoadBamlImp(bamlStream);
        }
 
        //------------------------------
        // Private constructor
        //------------------------------
        private BamlResourceDeserializer()
        {
        }
 
        //-----------------------------
        // private  methods
        //-----------------------------
        /// <summary>
        /// build the baml element tree as well as the localizability inheritance tree
        /// </summary>
        /// <param name="bamlSteam">input baml stream.</param>
        /// <returns>the tree constructed.</returns>
        private BamlTree LoadBamlImp(Stream bamlSteam)
        {
            _reader = new BamlReader(bamlSteam);
            _reader.Read();
 
            if (_reader.NodeType != BamlNodeType.StartDocument)
            {
                throw new XamlParseException(SR.InvalidStartOfBaml);
            }
 
            // create root element.
            _root = new BamlStartDocumentNode();
            PushNodeToStack(_root);
 
            // A hash table used to handle duplicate properties within an element
            Hashtable propertyOccurrences = new Hashtable(8);
 
            // create the tree by depth first traversal.
            while (_bamlTreeStack.Count > 0 && _reader.Read())
            {
                switch (_reader.NodeType)
                {
                    case BamlNodeType.StartElement:
                        {
                            BamlTreeNode bamlNode = new BamlStartElementNode(
                                _reader.AssemblyName,
                                _reader.Name,
                                _reader.IsInjected,
                                _reader.CreateUsingTypeConverter
                                );
                            PushNodeToStack(bamlNode);
                            break;
                        }
                    case BamlNodeType.EndElement:
                        {
                            BamlTreeNode bamlNode = new BamlEndElementNode();
                            AddChildToCurrentParent(bamlNode);
                            PopStack();
                            break;
                        }
                    case BamlNodeType.StartComplexProperty:
                        {
                            BamlStartComplexPropertyNode bamlNode = new BamlStartComplexPropertyNode(
                                _reader.AssemblyName,
                                _reader.Name.Substring(0, _reader.Name.LastIndexOf('.')),
                                _reader.LocalName
                                );
 
                            bamlNode.LocalizabilityAncestor = PeekPropertyStack(bamlNode.PropertyName);
                            PushPropertyToStack(bamlNode.PropertyName, (ILocalizabilityInheritable)bamlNode);
                            PushNodeToStack(bamlNode);
                            break;
                        }
                    case BamlNodeType.EndComplexProperty:
                        {
                            BamlTreeNode bamlNode = new BamlEndComplexPropertyNode();
                            AddChildToCurrentParent(bamlNode);
                            PopStack();
                            break;
                        }
                    case BamlNodeType.Event:
                        {
                            BamlTreeNode bamlNode = new BamlEventNode(_reader.Name, _reader.Value);
                            AddChildToCurrentParent(bamlNode);
                            break;
                        }
                    case BamlNodeType.RoutedEvent:
                        {
                            BamlTreeNode bamlNode = new BamlRoutedEventNode(
                                _reader.AssemblyName,
                                _reader.Name.Substring(0, _reader.Name.LastIndexOf('.')),
                                _reader.LocalName,
                                _reader.Value
                                );
                            AddChildToCurrentParent(bamlNode);
                            break;
                        }
                    case BamlNodeType.PIMapping:
                        {
                            BamlTreeNode bamlNode = new BamlPIMappingNode(
                                _reader.XmlNamespace,
                                _reader.ClrNamespace,
                                _reader.AssemblyName
                                );
                            AddChildToCurrentParent(bamlNode);
                            break;
                        }
                    case BamlNodeType.LiteralContent:
                        {
                            BamlTreeNode bamlNode = new BamlLiteralContentNode(_reader.Value);
                            AddChildToCurrentParent(bamlNode);
                            break;
                        }
                    case BamlNodeType.Text:
                        {
                            BamlTreeNode bamlNode = new BamlTextNode(
                                _reader.Value,
                                _reader.TypeConverterAssemblyName,
                                _reader.TypeConverterName
                                );
 
                            AddChildToCurrentParent(bamlNode);
                            break;
                        }
                    case BamlNodeType.StartConstructor:
                        {
                            BamlTreeNode bamlNode = new BamlStartConstructorNode();
                            AddChildToCurrentParent(bamlNode);
                            break;
                        }
                    case BamlNodeType.EndConstructor:
                        {
                            BamlTreeNode bamlNode = new BamlEndConstructorNode();
                            AddChildToCurrentParent(bamlNode);
                            break;
                        }
                    case BamlNodeType.EndDocument:
                        {
                            BamlTreeNode bamlNode = new BamlEndDocumentNode();
                            AddChildToCurrentParent(bamlNode);
                            PopStack();
                            break;
                        }
                    default:
                        {
                            throw new XamlParseException(SR.Format(SR.UnRecognizedBamlNodeType, _reader.NodeType));
                        }
                }
 
                // read properties if it has any.
                if (_reader.HasProperties)
                {
                    // The Hashtable is used to auto-number repeated properties. The usage is as the following:
                    // When encountering the 1st occurrence of the property, it stores a reference to the property's node (BamlTreeNode).
                    // When encountering the property the 2nd time, we start auto-numbering the property including the 1st occurrence
                    // and store the index count (int) in the slot from that point onwards.                                         
                    propertyOccurrences.Clear();
 
                    _reader.MoveToFirstProperty();
                    do
                    {
                        switch (_reader.NodeType)
                        {
                            case BamlNodeType.ConnectionId:
                                {
                                    BamlTreeNode bamlNode = new BamlConnectionIdNode(_reader.ConnectionId);
                                    AddChildToCurrentParent(bamlNode);
                                    break;
                                }
                            case BamlNodeType.Property:
                                {
                                    BamlPropertyNode bamlNode = new BamlPropertyNode(
                                        _reader.AssemblyName,
                                        _reader.Name.Substring(0, _reader.Name.LastIndexOf('.')),
                                        _reader.LocalName,
                                        _reader.Value,
                                        _reader.AttributeUsage
                                        );
 
                                    bamlNode.LocalizabilityAncestor = PeekPropertyStack(bamlNode.PropertyName);
                                    PushPropertyToStack(bamlNode.PropertyName, (ILocalizabilityInheritable)bamlNode);
 
                                    AddChildToCurrentParent(bamlNode);
 
                                    if (propertyOccurrences.Contains(_reader.Name))
                                    {
                                        // we autonumber properties that have occurrences larger than 1
                                        object occurrence = propertyOccurrences[_reader.Name];
                                        int index = 2;
                                        if (occurrence is BamlPropertyNode)
                                        {
                                            // start numbering this property as the 2nd occurrence is encountered
                                            // the value stores the 1st occurrence of the property at this point
                                            ((BamlPropertyNode)occurrence).Index = 1;
                                        }
                                        else
                                        {
                                            // For the 3rd or more occurrences, the value stores the next index 
                                            // to assign to the property
                                            index = (int)occurrence;
                                        }
 
                                        // auto-number the current property node
                                        ((BamlPropertyNode)bamlNode).Index = index;
                                        propertyOccurrences[_reader.Name] = ++index;
                                    }
                                    else
                                    {
                                        // store the first occurrence of the property
                                        propertyOccurrences[_reader.Name] = bamlNode;
                                    }
 
                                    break;
                                }
                            case BamlNodeType.DefAttribute:
                                {
                                    if (_reader.Name == XamlReaderHelper.DefinitionUid)
                                    {
                                        // set the Uid proeprty when we see it.
                                        ((BamlStartElementNode)_currentParent).Uid = _reader.Value;
                                    }
 
                                    BamlTreeNode bamlNode = new BamlDefAttributeNode(
                                        _reader.Name,
                                        _reader.Value
                                        );
                                    AddChildToCurrentParent(bamlNode);
                                    break;
                                }
                            case BamlNodeType.XmlnsProperty:
                                {
                                    BamlTreeNode bamlNode = new BamlXmlnsPropertyNode(
                                        _reader.LocalName,
                                        _reader.Value
                                        );
                                    AddChildToCurrentParent(bamlNode);
                                    break;
                                }
                            case BamlNodeType.ContentProperty:
                                {
                                    BamlTreeNode bamlNode = new BamlContentPropertyNode(
                                        _reader.AssemblyName,
                                        _reader.Name.Substring(0, _reader.Name.LastIndexOf('.')),
                                        _reader.LocalName
                                        );
 
                                    AddChildToCurrentParent(bamlNode);
                                    break;
                                }
                            case BamlNodeType.PresentationOptionsAttribute:
                                {
                                    BamlTreeNode bamlNode = new BamlPresentationOptionsAttributeNode(
                                        _reader.Name,
                                        _reader.Value
                                        );
                                    AddChildToCurrentParent(bamlNode);
                                    break;
                                }
                            default:
                                {
                                    throw new XamlParseException(SR.Format(SR.UnRecognizedBamlNodeType, _reader.NodeType));
                                }
                        }
                    } while (_reader.MoveToNextProperty());
                }
            }
 
            // At this point, the baml tree stack should be completely unwinded and also nothing more to read.
            if (_reader.Read() || _bamlTreeStack.Count > 0)
            {
                throw new XamlParseException(SR.InvalidEndOfBaml);
            }
 
            return new BamlTree(_root, _nodeCount);
            //notice that we don't close the input stream because we don't own it.
        }
 
        private void PushNodeToStack(BamlTreeNode node)
        {
            if (_currentParent != null)
                _currentParent.AddChild(node);
 
            _bamlTreeStack.Push(node);
            _currentParent = node;
            _nodeCount++;
        }
 
        private void AddChildToCurrentParent(BamlTreeNode node)
        {
            if (_currentParent == null)
            {
                throw new InvalidOperationException(SR.NullParentNode);
            }
 
            _currentParent.AddChild(node);
            _nodeCount++;
        }
 
        private void PopStack()
        {
            BamlTreeNode node = _bamlTreeStack.Pop();
            if (node.Children != null)
            {
                // pop properties from property inheritance stack as well
                foreach (BamlTreeNode child in node.Children)
                {
                    BamlStartComplexPropertyNode propertyNode = child as BamlStartComplexPropertyNode;
                    if (propertyNode != null)
                    {
                        PopPropertyFromStack(propertyNode.PropertyName);
                    }
                }
            }
 
            if (_bamlTreeStack.Count > 0)
            {
                _currentParent = _bamlTreeStack.Peek();
            }
            else
            {
                // stack is empty. Set CurrentParent to null
                _currentParent = null;
            }
        }
 
        private void PushPropertyToStack(string propertyName, ILocalizabilityInheritable node)
        {
            Stack<ILocalizabilityInheritable> stackForProperty;
            if (_propertyInheritanceTreeStack.ContainsKey(propertyName))
            {
                // get the stack
                stackForProperty = _propertyInheritanceTreeStack[propertyName];
            }
            else
            {
                stackForProperty = new Stack<ILocalizabilityInheritable>();
                _propertyInheritanceTreeStack.Add(propertyName, stackForProperty);
            }
 
            // push the node
            stackForProperty.Push(node);
        }
 
        private void PopPropertyFromStack(string propertyName)
        {
            Stack<ILocalizabilityInheritable> stackForProperty = _propertyInheritanceTreeStack[propertyName];
            stackForProperty.Pop();
        }
 
        // Get the Top of the stack for a certain property
        private ILocalizabilityInheritable PeekPropertyStack(string propertyName)
        {
            if (_propertyInheritanceTreeStack.ContainsKey(propertyName))
            {
                Stack<ILocalizabilityInheritable> stackForProperty = _propertyInheritanceTreeStack[propertyName];
                if (stackForProperty.Count > 0)
                {
                    return stackForProperty.Peek();
                }
            }
 
            // return root of the document if there is inheritable property on stack
            return _root;
        }
 
        //-----------------------------
        // private members.
        //-----------------------------
 
        // stack for building baml trees
        private Stack<BamlTreeNode> _bamlTreeStack = new Stack<BamlTreeNode>();
 
        // stacks for building property's localizability inheritance tree.
        private Dictionary<string, Stack<ILocalizabilityInheritable>> _propertyInheritanceTreeStack
            = new Dictionary<string, Stack<ILocalizabilityInheritable>>(8);
 
        private BamlTreeNode _currentParent;
        private BamlStartDocumentNode _root;
        private BamlReader _reader;
        private int _nodeCount;         // count the total number of nodes in the tree.(for future use by status bar)
    }
}