File: system\cfgparser.cs
Project: ndp\clr\src\bcl\mscorlib.csproj (mscorlib)
// ==++==
//
//   Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--==
/*============================================================
 **
 ** Class: CfgParser
 **
 **
 ** Purpose: XMLParser and Tree builder internal to BCL
 **
 **
 ===========================================================*/
 
namespace System
{
    using System.Runtime.InteropServices;
    using System.Collections;
    using System.Collections.Generic;
    using System.Runtime.CompilerServices;
    using System.Security.Permissions;
    using System.Security;
    using System.Globalization;
    using System.IO;
    using System.Runtime.Versioning;
    using System.Diagnostics.Contracts;
 
    [Serializable]
    internal enum ConfigEvents
    {
        StartDocument     = 0,
        StartDTD          = StartDocument + 1,
        EndDTD            = StartDTD + 1,
        StartDTDSubset    = EndDTD + 1,
        EndDTDSubset      = StartDTDSubset + 1,
        EndProlog         = EndDTDSubset + 1,
        StartEntity       = EndProlog + 1,
        EndEntity         = StartEntity + 1,
        EndDocument       = EndEntity + 1,
        DataAvailable     = EndDocument + 1,
        LastEvent         = DataAvailable
    }
 
    [Serializable]
    internal enum ConfigNodeType
    {
        Element = 1,
        Attribute   = Element + 1,
        Pi  = Attribute + 1,
        XmlDecl = Pi + 1,
        DocType = XmlDecl + 1,
        DTDAttribute    = DocType + 1,
        EntityDecl  = DTDAttribute + 1,
        ElementDecl = EntityDecl + 1,
        AttlistDecl = ElementDecl + 1,
        Notation    = AttlistDecl + 1,
        Group   = Notation + 1,
        IncludeSect = Group + 1,
        PCData  = IncludeSect + 1,
        CData   = PCData + 1,
        IgnoreSect  = CData + 1,
        Comment = IgnoreSect + 1,
        EntityRef   = Comment + 1,
        Whitespace  = EntityRef + 1,
        Name    = Whitespace + 1,
        NMToken = Name + 1,
        String  = NMToken + 1,
        Peref   = String + 1,
        Model   = Peref + 1,
        ATTDef  = Model + 1,
        ATTType = ATTDef + 1,
        ATTPresence = ATTType + 1,
        DTDSubset   = ATTPresence + 1,
        LastNodeType    = DTDSubset + 1
    } 
 
    [Serializable]
    internal enum ConfigNodeSubType
    {
        Version = (int)ConfigNodeType.LastNodeType,
        Encoding    = Version + 1,
        Standalone  = Encoding + 1,
        NS  = Standalone + 1,
        XMLSpace    = NS + 1,
        XMLLang = XMLSpace + 1,
        System  = XMLLang + 1,
        Public  = System + 1,
        NData   = Public + 1,
        AtCData = NData + 1,
        AtId    = AtCData + 1,
        AtIdref = AtId + 1,
        AtIdrefs    = AtIdref + 1,
        AtEntity    = AtIdrefs + 1,
        AtEntities  = AtEntity + 1,
        AtNmToken   = AtEntities + 1,
        AtNmTokens  = AtNmToken + 1,
        AtNotation  = AtNmTokens + 1,
        AtRequired  = AtNotation + 1,
        AtImplied   = AtRequired + 1,
        AtFixed = AtImplied + 1,
        PentityDecl = AtFixed + 1,
        Empty   = PentityDecl + 1,
        Any = Empty + 1,
        Mixed   = Any + 1,
        Sequence    = Mixed + 1,
        Choice  = Sequence + 1,
        Star    = Choice + 1,
        Plus    = Star + 1,
        Questionmark    = Plus + 1,
        LastSubNodeType = Questionmark + 1
    }
 
    internal abstract class BaseConfigHandler
    {
        // These delegates must be at the very start of the object
        // This is necessary because unmanaged code takes a dependency on this layout
        // Any changes made to this must be reflected in ConfigHelper.h in ConfigFactory class
        protected Delegate[] eventCallbacks;
        public BaseConfigHandler()
        {
            InitializeCallbacks();
        }
        private void InitializeCallbacks()
        {
            if (eventCallbacks == null)
            {
                eventCallbacks = new Delegate[6];
                eventCallbacks[0] = new NotifyEventCallback(this.NotifyEvent);
                eventCallbacks[1] = new BeginChildrenCallback(this.BeginChildren);
                eventCallbacks[2] = new EndChildrenCallback(this.EndChildren);
                eventCallbacks[3] = new ErrorCallback(this.Error);
                eventCallbacks[4] = new CreateNodeCallback(this.CreateNode);
                eventCallbacks[5] = new CreateAttributeCallback(this.CreateAttribute);
            }
        }
 
        private delegate void NotifyEventCallback(ConfigEvents nEvent);
        public abstract void NotifyEvent(ConfigEvents nEvent);
 
        private delegate void BeginChildrenCallback(int size,
                           ConfigNodeSubType subType,
                           ConfigNodeType nType,
                           int terminal,
                           [MarshalAs(UnmanagedType.LPWStr)] String text,
                           int textLength,
                           int prefixLength);
        public abstract void BeginChildren(int size,
                           ConfigNodeSubType subType,
                           ConfigNodeType nType,
                           int terminal,
                           [MarshalAs(UnmanagedType.LPWStr)] String text,
                           int textLength,
                           int prefixLength);
 
        private delegate void EndChildrenCallback(int fEmpty,
                         int size,
                         ConfigNodeSubType subType,
                         ConfigNodeType nType,
                         int terminal,
                         [MarshalAs(UnmanagedType.LPWStr)] String text,
                         int textLength,
                         int prefixLength);
        public abstract void EndChildren(int fEmpty,
                         int size,
                         ConfigNodeSubType subType,
                         ConfigNodeType nType,
                         int terminal,
                         [MarshalAs(UnmanagedType.LPWStr)] String text,
                         int textLength,
                         int prefixLength);
 
        private delegate void ErrorCallback(int size,
                   ConfigNodeSubType subType,
                   ConfigNodeType nType,
                   int terminal,
                   [MarshalAs(UnmanagedType.LPWStr)]String text,
                   int textLength,
                   int prefixLength);
        public abstract void Error(int size,
                   ConfigNodeSubType subType,
                   ConfigNodeType nType,
                   int terminal,
                   [MarshalAs(UnmanagedType.LPWStr)]String text,
                   int textLength,
                   int prefixLength);
 
        private delegate void CreateNodeCallback(int size,
                        ConfigNodeSubType subType,
                        ConfigNodeType nType,
                        int terminal,
                        [MarshalAs(UnmanagedType.LPWStr)]String text,
                        int textLength,
                        int prefixLength);
        public abstract void CreateNode(int size,
                        ConfigNodeSubType subType,
                        ConfigNodeType nType,
                        int terminal,
                        [MarshalAs(UnmanagedType.LPWStr)]String text,
                        int textLength,
                        int prefixLength);
 
        private delegate void CreateAttributeCallback(int size,
                             ConfigNodeSubType subType,
                             ConfigNodeType nType,
                             int terminal,
                             [MarshalAs(UnmanagedType.LPWStr)]String text,
                             int textLength,
                             int prefixLength);
        public abstract void CreateAttribute(int size,
                             ConfigNodeSubType subType,
                             ConfigNodeType nType,
                             int terminal,
                             [MarshalAs(UnmanagedType.LPWStr)]String text,
                             int textLength,
                             int prefixLength);
 
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [MethodImplAttribute(MethodImplOptions.InternalCall)]
        internal extern void RunParser(String fileName);
    }
 
    // Class used to build a DOM like tree of parsed XML
    internal class ConfigTreeParser : BaseConfigHandler
    {
        ConfigNode rootNode = null;
        ConfigNode currentNode = null;
        String fileName = null;
        int attributeEntry;
        String key = null;
        String [] treeRootPath = null; // element to start tree
        bool parsing = false;
        int depth = 0;
        int pathDepth = 0;
        int searchDepth = 0;
        bool bNoSearchPath = false;
 
        // Track state for error message formatting
        String lastProcessed = null;
        bool lastProcessedEndElement;
 
 
        // NOTE: This parser takes a path eg. /configuration/system.runtime.remoting
        // and will return a node which matches this.
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        internal ConfigNode Parse(String fileName, String configPath)      
        {
            return Parse(fileName, configPath, false);
        }
 
        [System.Security.SecuritySafeCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        internal ConfigNode Parse(String fileName, String configPath, bool skipSecurityStuff)
        {
            if (fileName == null)
                throw new ArgumentNullException("fileName");
            Contract.EndContractBlock();
            this.fileName = fileName;
            if (configPath[0] == '/'){
                treeRootPath = configPath.Substring(1).Split('/');
                pathDepth = treeRootPath.Length - 1;
                bNoSearchPath = false;
            }
            else{
                treeRootPath = new String[1];
                treeRootPath[0] = configPath;
                bNoSearchPath = true;
            }
 
            if (!skipSecurityStuff) {
                (new FileIOPermission( FileIOPermissionAccess.Read, System.IO.Path.GetFullPathInternal( fileName ) )).Demand();
            }
#pragma warning disable 618
            (new SecurityPermission(SecurityPermissionFlag.UnmanagedCode)).Assert();
#pragma warning restore 618
 
            try
            {
                RunParser(fileName);
            }
            catch(FileNotFoundException) {
                throw; // Pass these through unadulterated.
            }
            catch(DirectoryNotFoundException) {
                throw; // Pass these through unadulterated.
            }
            catch(UnauthorizedAccessException) {
                throw;
            }
            catch(FileLoadException) {
                throw;
            }
            catch(Exception inner) {
                String message = GetInvalidSyntaxMessage();
                // Neither Exception nor ApplicationException are the "right" exceptions here.
                // Desktop throws ApplicationException for backwards compatibility.
                // On Silverlight we don't have ApplicationException, so fall back to Exception.
#if FEATURE_CORECLR
                throw new Exception(message, inner);
#else
                throw new ApplicationException(message, inner);
#endif
            }
            return rootNode;
        }
 
        public override void NotifyEvent(ConfigEvents nEvent)
        {
            BCLDebug.Trace("REMOTE", "NotifyEvent "+((Enum)nEvent).ToString()+"\n");
        }
 
        public override void BeginChildren(int size,
                                  ConfigNodeSubType subType, 
                                  ConfigNodeType nType,                                   
                                  int terminal, 
                                  [MarshalAs(UnmanagedType.LPWStr)] String text, 
                                  int textLength, 
                                  int prefixLength)
        {
            //Trace("BeginChildren",size,subType,nType,terminal,text,textLength,prefixLength,0);
            if (!parsing &&
                (!bNoSearchPath 
                 && depth == (searchDepth + 1)
                 && String.Compare(text, treeRootPath[searchDepth], StringComparison.Ordinal) == 0))
            {
                searchDepth++;
            }
        }
 
        public override void EndChildren(int fEmpty, 
                                int size,
                                ConfigNodeSubType subType, 
                                ConfigNodeType nType,                               
                                int terminal, 
                                [MarshalAs(UnmanagedType.LPWStr)] String text, 
                                int textLength, 
                                int prefixLength)
        {
            lastProcessed = text;
            lastProcessedEndElement = true;
            if (parsing)
            {
                //Trace("EndChildren",size,subType,nType,terminal,text,textLength,prefixLength,fEmpty);
 
                if (currentNode == rootNode)
                {
                    // End of section of tree which is parsed
                    parsing = false;
                }
 
                currentNode = currentNode.Parent;
            }
            else if (nType == ConfigNodeType.Element){
                if(depth == searchDepth && String.Compare(text, treeRootPath[searchDepth - 1], StringComparison.Ordinal) == 0)
                {
                    searchDepth--;
                    depth--;
                }
                else 
                    depth--;
            }            
        }
 
        public override void Error(int size,
                          ConfigNodeSubType subType, 
                          ConfigNodeType nType, 
                          int terminal, 
                          [MarshalAs(UnmanagedType.LPWStr)]String text, 
                          int textLength, 
                          int prefixLength)
        {
            //Trace("Error",size,subType,nType,terminal,text,textLength,prefixLength,0);                        
        }
 
        public override void CreateNode(int size,
                               ConfigNodeSubType subType, 
                               ConfigNodeType nType, 
                               int terminal, 
                               [MarshalAs(UnmanagedType.LPWStr)]String text, 
                               int textLength, 
                               int prefixLength)
        {
            //Trace("CreateNode",size,subType,nType,terminal,text,textLength,prefixLength,0);
 
            if (nType == ConfigNodeType.Element)
            {
                // New Node
                lastProcessed = text;
                lastProcessedEndElement = false;
 
                if (parsing  
                    || (bNoSearchPath &&
                        String.Compare(text, treeRootPath[0], StringComparison.OrdinalIgnoreCase) == 0)
                    || (depth == searchDepth && searchDepth == pathDepth && 
                        String.Compare(text, treeRootPath[pathDepth], StringComparison.OrdinalIgnoreCase) == 0 ))
                    {
                        parsing = true;
                        
                        ConfigNode parentNode = currentNode;
                        currentNode = new ConfigNode(text, parentNode);
                        if (rootNode == null)
                            rootNode = currentNode;
                        else
                            parentNode.AddChild(currentNode);
                    }
                else 
                    depth++;
            }
            else if (nType == ConfigNodeType.PCData)
            {
                // Data node
                if (currentNode != null)
                {
                    currentNode.Value = text;
                }
            }
        }
 
        public override void CreateAttribute(int size,
                                    ConfigNodeSubType subType, 
                                    ConfigNodeType nType,                                   
                                    int terminal, 
                                    [MarshalAs(UnmanagedType.LPWStr)]String text, 
                                    int textLength, 
                                    int prefixLength)
        {
            //Trace("CreateAttribute",size,subType,nType,terminal,text,textLength,prefixLength,0);
            if (parsing)
            {
                // if the value of the attribute is null, the parser doesn't come back, so need to store the attribute when the
                // attribute name is encountered
                if (nType == ConfigNodeType.Attribute)
                {
                    attributeEntry = currentNode.AddAttribute(text, "");
                    key = text;
                }
                else if (nType == ConfigNodeType.PCData)
                {
                    currentNode.ReplaceAttribute(attributeEntry, key, text);
                }
                else
                {
                    String message = GetInvalidSyntaxMessage();
                    // Neither Exception nor ApplicationException are the "right" exceptions here.
                    // Desktop throws ApplicationException for backwards compatibility.
                    // On Silverlight we don't have ApplicationException, so fall back to Exception.
#if FEATURE_CORECLR
                    throw new Exception(message);
#else
                    throw new ApplicationException(message);
#endif
                }
            }
        }
 
#if _DEBUG
        [System.Diagnostics.Conditional("_LOGGING")]        
        private void Trace(String name,
                           int size,
                           ConfigNodeSubType subType, 
                           ConfigNodeType nType,                           
                           int terminal, 
                           [MarshalAs(UnmanagedType.LPWStr)]String text, 
                           int textLength, 
                           int prefixLength, int fEmpty)
        {
 
            BCLDebug.Trace("REMOTE","Node "+name);
            BCLDebug.Trace("REMOTE","text "+text);
            BCLDebug.Trace("REMOTE","textLength "+textLength);          
            BCLDebug.Trace("REMOTE","size "+size);
            BCLDebug.Trace("REMOTE","subType "+((Enum)subType).ToString());
            BCLDebug.Trace("REMOTE","nType "+((Enum)nType).ToString());
            BCLDebug.Trace("REMOTE","terminal "+terminal);
            BCLDebug.Trace("REMOTE","prefixLength "+prefixLength);          
            BCLDebug.Trace("REMOTE","fEmpty "+fEmpty+"\n");
        }
#endif
 
        private String GetInvalidSyntaxMessage()
        {
            String lastProcessedTag = null;
 
            if (lastProcessed != null)
                lastProcessedTag = (lastProcessedEndElement ? "</" : "<") + lastProcessed + ">";
 
            return Environment.GetResourceString("XML_Syntax_InvalidSyntaxInFile", fileName, lastProcessedTag);
        }
    }
 
    // Node in Tree produced by ConfigTreeParser
    internal class ConfigNode
    {
        String m_name = null;
        String m_value = null;
        ConfigNode m_parent = null;
        List<ConfigNode> m_children = new List<ConfigNode>(5);
        List<DictionaryEntry> m_attributes = new List<DictionaryEntry>(5);
 
        internal ConfigNode(String name, ConfigNode parent)
        {
            m_name = name;
            m_parent = parent;
        }
 
        internal String Name
        {
            get {return m_name;}
        }
 
        internal String Value
        {
            get {return m_value;}
            set {m_value = value;}
        }
 
        internal ConfigNode Parent
        {
            get {return m_parent;}
        }
 
        internal List<ConfigNode> Children
        {
            get {return m_children;}
        }
 
        internal List<DictionaryEntry> Attributes
        {
            get {return m_attributes;}
        }
 
        internal void AddChild(ConfigNode child)
        {
            child.m_parent = this;
            m_children.Add(child);
        }
 
        internal int AddAttribute(String key, String value)
        {
            m_attributes.Add(new DictionaryEntry(key, value));
            return m_attributes.Count-1;
        }
 
        internal void ReplaceAttribute(int index, String key, String value)
        {
            m_attributes[index] = new DictionaryEntry(key, value);
        }
 
#if _DEBUG
        [System.Diagnostics.Conditional("_LOGGING")]
        internal void Trace()
        {
            BCLDebug.Trace("REMOTE","************ConfigNode************");
            BCLDebug.Trace("REMOTE","Name = "+m_name);
            if (m_value != null)
                BCLDebug.Trace("REMOTE","Value = "+m_value);
            if (m_parent != null)
                BCLDebug.Trace("REMOTE","Parent = "+m_parent.Name);
            for (int i=0; i<m_attributes.Count; i++)
            {
                DictionaryEntry de = (DictionaryEntry)m_attributes[i];
                BCLDebug.Trace("REMOTE","Key = "+de.Key+"   Value = "+de.Value);
            }
 
            for (int i=0; i<m_children.Count; i++)
            {
                ((ConfigNode)m_children[i]).Trace();
            }
        }
#endif
    }
}