File: System\Windows\PropertyPath.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.
 
//
//
//
// Description:
//     Used to describe a property as a "path" below another property.
//
// Example: "Background.Opacity" is a path.  It implies:
//     * Find the Background property, get the value object of that property.
//     * What we want is the Opacity property on that object.
//
 
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Data;
using System.Windows.Threading;
 
using System.Windows.Markup;
using MS.Internal;
using MS.Internal.Data;
using MS.Utility;           // FrugalList
 
// A property path really consists of two parts: a static part (PropertyPath)
// that describes the path, and a dynamic part (PropertyPathWorker) that knows
// how to evaluate the path, relative to a "root item".
//
// PropertyPath supports two modes of behavior:
//
// "Source" mode is appropriate when the path describes a "source" - some place
// from which we'll fetch values.  The user of PropertyPath typically creates
// workers explicitly - one for each root item - and calls them directly.  The
// workers are fully dynamic;  they listen for property and currency change
// events, maintain dependency sources, etc.  The connection between the worker
// and its root item is long-lived.  This mode is used by the Binding class in
// support of data binding.
//
// "Target" mode is appropriate when the path describes a "target" - some place
// into which we'll store values.  The user of PropertyPath typically does not
// create workers, but rather calls the convenience routines in PropertyPath
// (relying on the implicit "single" worker).  The connection between the
// worker and its root item is short-lived;  the caller typically connects to
// a root item, calls a few methods, then disconnects.  This mode is used by
// the property engine and by animation in support of timeline setters.
 
namespace System.Windows
{
    internal enum PropertyPathStatus : byte { Inactive, Active, PathError, AsyncRequestPending }
 
    internal struct IndexerParameterInfo
    {
        public Type type;       // user-specified type
        public object value;    // string or strongly typed value
    }
 
 
    /// <summary>
    /// Data structure for describing a property as a path below another
    /// </summary>
    [TypeConverter(typeof(PropertyPathConverter))]
    public sealed class PropertyPath
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        /// <summary>
        /// Construct a PropertyPath from a string and a list of parameters
        /// </summary>
        public PropertyPath(string path, params object[] pathParameters)
        {
            if (System.Windows.Threading.Dispatcher.CurrentDispatcher == null)
                throw new InvalidOperationException();  // This is actually never called since CurrentDispatcher will throw if null.
 
            _path = path;
 
            if (pathParameters != null && pathParameters.Length > 0)
            {
                // initialize internal pathParameters list
                PathParameterCollection parameters = new PathParameterCollection(pathParameters);
                SetPathParameterCollection(parameters);
            }
            PrepareSourceValueInfo(null);
        }
 
        /// <summary>
        /// Public constructor that takes a single parameter.  This is
        /// the degenerate PropertyPath (a path of a single step).
        /// </summary>
        public PropertyPath(object parameter)
            : this(SingleStepPath, parameter)
        {
        }
 
        // This constructor is for use by the PropertyPathConverter
        internal PropertyPath(string path, ITypeDescriptorContext typeDescriptorContext)
        {
            _path = path;
            PrepareSourceValueInfo(typeDescriptorContext);
            NormalizePath();
        }
 
        //------------------------------------------------------
        //
        //  Public properties
        //
        //------------------------------------------------------
 
        /// <summary> The string describing the path. </summary>
        public string Path
        {
            get { return _path; }
            set
            {
                _path = value;
                PrepareSourceValueInfo(null);
            }
        }
 
        /// <summary>
        /// The list of parameters to use when the
        /// path refers to indexed parameters.
        /// Each parameter in the list should be a DependencyProperty,
        /// a PropertyInfo, or a PropertyDescriptor.
        /// </summary>
        public Collection<object> PathParameters
        {
            get
            {
                if (_parameters == null)
                {
                    SetPathParameterCollection(new PathParameterCollection());
                }
                return _parameters;
            }
        }
 
        //------------------------------------------------------
        //
        //  Internal properties
        //
        //------------------------------------------------------
 
        // the number of levels in the path
        internal int Length { get { return _arySVI.Length; } }
 
        // the status of the PropertyPath
        internal PropertyPathStatus Status { get { return SingleWorker.Status; } }
 
        // the most recent error message
        internal string LastError { get { return _lastError; } }
 
        // convenience properties for a frequent special case
        internal object LastItem { get { return GetItem(Length - 1); } }
        internal object LastAccessor { get { return GetAccessor(Length - 1); } }
        internal object[] LastIndexerArguments { get { return GetIndexerArguments(Length - 1); } }
 
        // test for static properties
        internal bool StartsWithStaticProperty { get { return Length > 0 && IsStaticProperty(_earlyBoundPathParts[0]); } }
 
        internal static bool IsStaticProperty(object accessor)
        {
            MethodInfo mi;
            DependencyProperty dp;
            PropertyInfo pi;
            PropertyDescriptor pd;
            DynamicObjectAccessor doa;
            DowncastAccessor(accessor, out dp, out pi, out pd, out doa);
 
            if (pi != null)
            {
                mi =  pi.GetGetMethod();
                return mi != null && mi.IsStatic;
            }
 
            return false;
        }
 
        //------------------------------------------------------
        //
        //  Internal methods
        //
        //------------------------------------------------------
 
        // Convert an "accessor" into one of the legal types
        internal static void DowncastAccessor(object accessor,
                            out DependencyProperty dp, out PropertyInfo pi, out PropertyDescriptor pd, out DynamicObjectAccessor doa)
        {
            if ((dp = accessor as DependencyProperty) != null)
            {
                pd = null;
                pi = null;
                doa = null;
            }
            else if ((pi = accessor as PropertyInfo) != null)
            {
                pd = null;
                doa = null;
            }
            else if ((pd = accessor as PropertyDescriptor) != null)
            {
                doa = null;
            }
            else
            {
                doa = accessor as DynamicObjectAccessor;
            }
        }
 
        // Set the context for the path.  Use this method in "target" mode
        // to connect the path to a rootItem for a short time:
        //      using (path.SetContext(myItem))
        //      {
        //          ... call target-mode convenience methods ...
        //      }
        internal IDisposable SetContext(object rootItem)
        {
            return SingleWorker.SetContext(rootItem);
        }
 
        // return the item for level k.  This is the result of evaluating the
        // path up to level k-1, starting at the root item.
        internal object GetItem(int k)
        {
            return SingleWorker.GetItem(k);
        }
 
        // return the "accessor" for level k.  This is the object used to get
        // the value of level k (together with the level-k item).  It can be
        // a DP, a PropertyInfo, a PropertyDescriptor, etc.
        internal object GetAccessor(int k)
        {
            object accessor = _earlyBoundPathParts[k];
 
            if (accessor == null)
            {
                accessor = SingleWorker.GetAccessor(k);
            }
 
            return accessor;
        }
 
        // return the arguments to use when the accessor at level k is an
        // indexer.  (If it's not an indexer, this returns null.)
        internal object[] GetIndexerArguments(int k)
        {
            return SingleWorker.GetIndexerArguments(k);
        }
 
        // return the value of the path.  Must be called within the scope
        // of SetContext.
        internal object GetValue()
        {
            return SingleWorker.RawValue();
        }
 
        // return the number of unresolved attached properties (called by Binding)
        internal int ComputeUnresolvedAttachedPropertiesInPath()
        {
            // the path uses attached properties by the syntax (ClassName.PropName).
            // If there are any such properties in the path, the binding needs the
            // tree context to resolve the class name.
            int result = 0;
 
            for (int k=Length-1; k>=0; --k)
            {
                if (_earlyBoundPathParts[k] == null)
                {
                    string name = _arySVI[k].name;
                    if (IsPropertyReference(name))
                    {
                        // a dot inside parens, when there's no early-bound accessor,
                        // is an unresolved PD name
                        if (name.Contains('.'))
                            ++ result;
                    }
                }
            }
 
            return result;
        }
 
        //------------------------------------------------------
        //
        //  Internal properties and methods for use by PropertyPathWorker only
        //
        //------------------------------------------------------
 
        internal SourceValueInfo[] SVI
        {
            get
            {
                //Debug.Assert(Helper.IsCallerOfType(typeof(PropertyPathWorker)));
                return _arySVI;
            }
        }
 
        internal object ResolvePropertyName(int level, object item, Type ownerType, object context)
        {
            //Debug.Assert(Helper.IsCallerOfType(typeof(PropertyPathWorker)));
 
            // if user told us explicitly what to use, use it
            object accessor = _earlyBoundPathParts[level];
 
            if (accessor == null)
            {
                accessor = ResolvePropertyName(_arySVI[level].name, item, ownerType, context, false);
            }
 
            return accessor;
        }
 
        internal IndexerParameterInfo[] ResolveIndexerParams(int level, object context)
        {
            IndexerParameterInfo[] parameters = _earlyBoundPathParts[level] as IndexerParameterInfo[];
 
            if (parameters == null)
            {
                parameters = ResolveIndexerParams(_arySVI[level].paramList, context, false);
            }
 
            return parameters;
        }
 
        // PropertyPathWorker may choose to replace an indexer by a property
        internal void ReplaceIndexerByProperty(int level, string name)
        {
            _arySVI[level].name = name;
            _arySVI[level].propertyName = name;
            _arySVI[level].type = SourceValueType.Property;
 
            _earlyBoundPathParts[level] = null;
        }
 
        //------------------------------------------------------
        //
        //  Private properties
        //
        //------------------------------------------------------
 
        PropertyPathWorker SingleWorker
        {
            get
            {
                if (_singleWorker == null)
                    _singleWorker = new PropertyPathWorker(this);
                return _singleWorker;
            }
        }
 
        //------------------------------------------------------
        //
        //  Private methods
        //
        //------------------------------------------------------
 
        // parse the path to figure out what kind of
        // SourceValueInfo we're going to need
        private void PrepareSourceValueInfo(ITypeDescriptorContext typeDescriptorContext)
        {
            PathParser parser = DataBindEngine.CurrentDataBindEngine.PathParser;
            _arySVI = parser.Parse(Path);
 
            if (_arySVI.Length == 0)
            {
                string detail = parser.Error;
                if (detail == null)
                    detail = Path;
                throw new InvalidOperationException(SR.Format(SR.PropertyPathSyntaxError, detail));
            }
 
            ResolvePathParts(typeDescriptorContext);
        }
 
        // "normalize" the path - i.e. load the PathParameters with the early-bound
        // accessors, and replace the corresponding parts of the path with
        // parameter references
        private void NormalizePath()
        {
            StringBuilder builder = new StringBuilder();
            PathParameterCollection parameters = new PathParameterCollection();
 
            for (int i=0; i<_arySVI.Length; ++i)
            {
                switch (_arySVI[i].drillIn)
                {
                    case DrillIn.Always:
                        builder.Append('/');
                        break;
 
                    case DrillIn.Never:
                        if (_arySVI[i].type == SourceValueType.Property)
                        {
                            builder.Append('.');
                        }
                        break;
 
                    case DrillIn.IfNeeded:
                        break;
                }
 
                switch (_arySVI[i].type)
                {
                    case SourceValueType.Property:
                        if (_earlyBoundPathParts[i] != null)
                        {
                            builder.Append('(');
                            builder.Append(parameters.Count.ToString(TypeConverterHelper.InvariantEnglishUS.NumberFormat));
                            builder.Append(')');
 
                            parameters.Add(_earlyBoundPathParts[i]);
                        }
                        else
                        {
                            builder.Append(_arySVI[i].name);
                        }
                        break;
 
                    case SourceValueType.Indexer:
                        builder.Append('[');
                        if (_earlyBoundPathParts[i] != null)
                        {
                            IndexerParameterInfo[] aryIPI = (IndexerParameterInfo[])_earlyBoundPathParts[i];
                            // the params should be at the very least a single empty string
                            Debug.Assert(aryIPI.Length > 0);
                            int j = 0;
                            while (true)
                            {
                                IndexerParameterInfo info = aryIPI[j];
                                if (info.type != null)
                                {
                                    builder.Append('(');
                                    builder.Append(parameters.Count.ToString(TypeConverterHelper.InvariantEnglishUS.NumberFormat));
                                    builder.Append(')');
 
                                    parameters.Add(info.value);
                                }
                                else
                                {
                                    builder.Append(info.value);
                                }
                                ++j;
 
                                if (j < aryIPI.Length)
                                {
                                    builder.Append(',');
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                        else
                        {
                            builder.Append(_arySVI[i].name);
                        }
                        builder.Append(']');
                        break;
 
                    case SourceValueType.Direct:
                        break;
                }
 
            }
 
            if (parameters.Count > 0)
            {
                _path = builder.ToString();
                SetPathParameterCollection(parameters);
            }
        }
 
        // set new parameter collection; update collection change notification handler
        private void SetPathParameterCollection(PathParameterCollection parameters)
        {
            if (_parameters != null)
            {
                _parameters.CollectionChanged -= new NotifyCollectionChangedEventHandler(ParameterCollectionChanged);
            }
            _parameters = parameters;
            if (_parameters != null)
            {
                _parameters.CollectionChanged += new NotifyCollectionChangedEventHandler(ParameterCollectionChanged);
            }
        }
 
        // path parameters were added/removed, update SourceValueInfo
        private void ParameterCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            PrepareSourceValueInfo(null);
        }
 
 
        // resolve the property names and path parameters early, if possible
        void ResolvePathParts(ITypeDescriptorContext typeDescriptorContext)
        {
            bool throwOnError = (typeDescriptorContext != null);
 
            object context = null;
 
            TypeConvertContext typeConvertContext = typeDescriptorContext as TypeConvertContext;
            if( typeConvertContext != null )
                context = typeConvertContext.ParserContext;
 
            if (context == null)
                context = typeDescriptorContext;
            _earlyBoundPathParts = new object[Length];
 
            for (int level=Length-1; level>=0; --level)
            {
                if (_arySVI[level].type == SourceValueType.Property)
                {
                    string name = _arySVI[level].name;
                    if (IsPropertyReference(name))
                    {
                        object accessor = ResolvePropertyName(name, null, null, context, throwOnError);
                        _earlyBoundPathParts[level] = accessor;
 
                        if (accessor != null)
                        {
                            _arySVI[level].propertyName = GetPropertyName(accessor);
                        }
                    }
                    else
                    {
                        _arySVI[level].propertyName = name;
                    }
                }
                else if (_arySVI[level].type == SourceValueType.Indexer)
                {
                    IndexerParameterInfo[] indexerParams = ResolveIndexerParams(_arySVI[level].paramList, context, throwOnError);
                    _earlyBoundPathParts[level] = indexerParams;
                    _arySVI[level].propertyName = Binding.IndexerName;
                }
            }
        }
 
        // resolve a single DP name
        object ResolvePropertyName(string name, object item, Type ownerType, object context, bool throwOnError)
        {
            string propertyName = name;
            int index;
 
            // first see if the name is an index into the parameter list
            if (IsParameterIndex(name, out index))
            {
                if (0 <= index && index < PathParameters.Count)
                {
                    object accessor = PathParameters[index];
                    // always throw if the accessor isn't valid - this error cannot
                    // be corrected later on.
                    if (!IsValidAccessor(accessor))
                        throw new InvalidOperationException(SR.Format(SR.PropertyPathInvalidAccessor,
                                    (accessor != null) ? accessor.GetType().FullName : "null"));
 
                    return accessor;
                }
                else if (throwOnError)
                    throw new InvalidOperationException(SR.Format(SR.PathParametersIndexOutOfRange, index, PathParameters.Count));
                else return null;
            }
 
            // handle attached-property syntax:  (TypeName.PropertyName)
            if (IsPropertyReference(name))
            {
                name = name.Substring(1, name.Length-2);
 
                int lastIndex = name.LastIndexOf('.');
                if (lastIndex >= 0)
                {
                    // attached property - get the owner type
                    propertyName = name.Substring(lastIndex + 1).Trim();
                    string ownerName = name.Substring(0, lastIndex).Trim();
                    ownerType = GetTypeFromName(ownerName, context);
                    if (ownerType == null && throwOnError)
                        throw new InvalidOperationException(SR.Format(SR.PropertyPathNoOwnerType, ownerName));
                }
                else
                {
                    // simple name in parens - just strip the parens
                    propertyName = name;
                }
            }
 
            if (ownerType != null)
            {
                // get an appropriate accessor from the ownerType and propertyName.
                // We prefer accessors in a certain order, defined below.
                object accessor;
 
                // 1. DependencyProperty on the given type.
                accessor = DependencyProperty.FromName(propertyName, ownerType);
 
                // 2. PropertyDescriptor from item's custom lookup.
                // When the item implements custom properties, we must use them.
                if (accessor == null && item is ICustomTypeDescriptor)
                {
                    accessor = TypeDescriptor.GetProperties(item)[propertyName];
                }
 
                // 3a. PropertyInfo, when item exposes INotifyPropertyChanged.
                // 3b. PropertyInfo, when item is a DependencyObject (bug 1373351).
                // This uses less working set than PropertyDescriptor, and we don't need
                // the ValueChanged pattern.  (If item is a DO and wants to raise
                // change notifications, it should make the property a DP.)
                if (accessor == null &&
                    (item is INotifyPropertyChanged || item is DependencyObject))
                {
                    accessor = GetPropertyHelper(ownerType, propertyName);
                }
 
                // 4. PropertyDescriptor (obtain from item - this is reputedly
                // slower than obtaining from type, but the latter doesn't
                // discover properties obtained from TypeDescriptorProvider -
                // see bug 1713000).
                // This supports the WinForms ValueChanged pattern.
                if (accessor == null && item != null)
                {
                    accessor = TypeDescriptor.GetProperties(item)[propertyName];
                }
 
                // 5. PropertyInfo.
                if (accessor == null)
                {
                    accessor = GetPropertyHelper(ownerType, propertyName);
                }
 
                // 6. IDynamicMetaObjectProvider
                // This supports the DLR's dynamic objects
                if (accessor == null && SystemCoreHelper.IsIDynamicMetaObjectProvider(item))
                {
                    accessor = SystemCoreHelper.NewDynamicPropertyAccessor(item.GetType(), propertyName);
                }
 
                if (accessor == null && throwOnError)
                    throw new InvalidOperationException(SR.Format(SR.PropertyPathNoProperty, ownerType.Name, propertyName));
 
                return accessor;
            }
 
            return null;
        }
 
        private PropertyInfo GetPropertyHelper(Type ownerType, string propertyName)
        {
            PropertyInfo result = null;
            bool enumerateBaseClasses = false;
            bool returnIndexerProperty = false;
 
            try
            {
                result = ownerType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
            }
            catch (AmbiguousMatchException)
            {
                // this happens when ownerType hides a base class property with 'new'
                // (it also happens by mistake when a non-generic class overrides
                // a generic base class property - see DDB 105201).
                // We'll resolve this by returning the most specific property.
                enumerateBaseClasses = true;
            }
 
            if (enumerateBaseClasses)
            {
                try
                {
                    for (result = null;  result == null && ownerType != null;  ownerType = ownerType.BaseType)
                    {
                        result = ownerType.GetProperty(propertyName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
                    }
                }
                catch (AmbiguousMatchException)
                {
                    // if a single class declares the property twice, it must be
                    // an indexed property (with different index parameters)
                    returnIndexerProperty = true;
                }
            }
 
            if (PropertyPathWorker.IsIndexedProperty(result))
            {
                // the property is indexed (this can't happen in C#, but can in VB)
                returnIndexerProperty = true;
            }
 
            if (returnIndexerProperty)
            {
                result = IndexerPropertyInfo.Instance;
            }
 
            return result;
        }
 
        // resolve indexer parameters
        IndexerParameterInfo[] ResolveIndexerParams(FrugalObjectList<IndexerParamInfo> paramList, object context, bool throwOnError)
        {
            IndexerParameterInfo[] args = new IndexerParameterInfo[paramList.Count];
            for (int i = 0; i < args.Length; ++i)
            {
                if (String.IsNullOrEmpty(paramList[i].parenString))
                {
                    // no paren string "foo" - value is (uninterpreted) value string
                    args[i].value = paramList[i].valueString;
                }
                else if (String.IsNullOrEmpty(paramList[i].valueString))
                {
                    // no value string "(2)" - value comes from PathParameter list
                    int index;
                    if (Int32.TryParse( paramList[i].parenString.Trim(),
                                        NumberStyles.Integer,
                                        TypeConverterHelper.InvariantEnglishUS.NumberFormat,
                                        out index))
                    {
                        if (0 <= index && index < PathParameters.Count)
                        {
                            object value = PathParameters[index];
                            if (value != null)
                            {
                                args[i].value = value;
                                args[i].type = value.GetType();
                            }
                            else if (throwOnError)
                            {
                                // info.value will still be "(n)"
                                throw new InvalidOperationException(SR.Format(SR.PathParameterIsNull, index));
                            }
                        }
                        else if (throwOnError)
                            throw new InvalidOperationException(SR.Format(SR.PathParametersIndexOutOfRange, index, PathParameters.Count));
                    }
                    else
                    {
                        // parens didn't hold an integer "(abc)" - value is (uninterpreted) paren string
                        // [this could be considered an error, but the original code
                        // treated it like this, so to preserve compatibility...]
                        args[i].value = $"({paramList[i].parenString})";
                    }
                }
                else
                {
                    // both strings appear "(Double)3.14159" - value is type-converted from value string
                    args[i].type = GetTypeFromName(paramList[i].parenString, context);
                    if (args[i].type != null)
                    {
                        object value = GetTypedParamValue(paramList[i].valueString.Trim(), args[i].type, throwOnError);
                        if (value != null)
                        {
                            args[i].value = value;
                        }
                        else
                        {
                            if (throwOnError)
                                throw new InvalidOperationException(SR.Format(SR.PropertyPathIndexWrongType, paramList[i].parenString, paramList[i].valueString));
                            args[i].type = null;
                        }
                    }
                    else
                    {
                        // parens didn't hold a type name "(abc)xyz" - value is (uninterpreted) string
                        // [this could be considered an error, but the original code
                        // treated it like this, so to preserve compatibility...]
                        args[i].value = $"({paramList[i].parenString}){paramList[i].valueString}";
                    }
                }
            }
            return args;
        }
 
        object GetTypedParamValue(string param, Type type, bool throwOnError)
        {
            object value = null;
            if (type == typeof(string))
                return param;
 
            TypeConverter tc = TypeDescriptor.GetConverter(type);
 
            if (tc != null && tc.CanConvertFrom(typeof(string)))
            {
                // PreSharp uses message numbers that the C# compiler doesn't know about.
                // Disable the C# complaints, per the PreSharp documentation.
                #pragma warning disable 1634, 1691
 
                // PreSharp complains about catching NullReference (and other) exceptions.
                // It doesn't recognize that IsCritical[Application]Exception() handles these correctly.
                #pragma warning disable 56500
 
                try
                {
                    value = tc.ConvertFromString(null, CultureInfo.InvariantCulture,
                                                    param);
                    // technically the converter can return null as a legitimate
                    // value.  In practice, this seems always to be a sign that
                    // the conversion didn't work (often because the converter
                    // reverts to the default behavior - returning null).  So
                    // we treat null as an "error", and keep trying for something
                    // better.  (See bug 861966)
                }
                // catch all exceptions.  We simply want to move on to the next
                // candidate indexer.
                catch (Exception ex)
                {
                    if (CriticalExceptions.IsCriticalApplicationException(ex) || throwOnError)
                        throw;
                }
                catch
                {
                    if (throwOnError)
                        throw;
                }
 
                #pragma warning restore 56500
                #pragma warning restore 1634, 1691
            }
 
            if (value == null && type.IsAssignableFrom(typeof(string)))
            {
                value = param;
            }
            return value;
        }
 
 
        // Return the type named by the given name
        Type GetTypeFromName(string name, object context)
        {
            // use the parser context, if available.  This allows early resolution.
            // bchapman 5/8/2009 - I believe with System.Xaml there is never an old parserContext here.
            // But cannot be sure.
            ParserContext parserContext = context as ParserContext;
            if (parserContext != null)
            {
                // Find the namespace prefix
                string nsPrefix;
                int nsIndex = name.IndexOf(':');
                if (nsIndex == -1)
                    nsPrefix = string.Empty;
                else
                {
                    // Found a namespace prefix separator, so create replacement _pathString.
                    // String processing - split "foons" from "BarClass.BazProp"
                    nsPrefix = name.Substring(0, nsIndex).TrimEnd();
                    name = name.Substring(nsIndex + 1).TrimStart();
                }
 
                // Find the namespace URI, even if its the default one
                string namespaceURI = parserContext.XmlnsDictionary[nsPrefix];
                if (namespaceURI == null)
                {
                    throw new ArgumentException(SR.Format(SR.ParserPrefixNSProperty, nsPrefix, name));
                }
 
                TypeAndSerializer typeAndSerializer = parserContext.XamlTypeMapper.GetTypeOnly(namespaceURI, name);
 
                return (typeAndSerializer != null) ? typeAndSerializer.ObjectType : null;
            }
 
            else
            {
                if (context is IServiceProvider)
                {
 
                    IXamlTypeResolver xtr = (context as IServiceProvider).GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
 
                    if (xtr != null)
                    {
                        return xtr.Resolve(name);
                    }
                }
 
                IValueSerializerContext serializerContext = context as IValueSerializerContext;
                if (serializerContext != null)
                {
                    ValueSerializer typeSerializer = ValueSerializer.GetSerializerFor(typeof(Type), serializerContext);
                    if (typeSerializer != null)
                        return typeSerializer.ConvertFromString(name, serializerContext) as Type;
                }
            }
 
            // if there's no parser or serializer context, use the tree context
            DependencyObject hostElement = context as DependencyObject;
            if (hostElement == null)
            {
                if (FrameworkCompatibilityPreferences.TargetsDesktop_V4_0)
                {
                    // WPF "OneTime" Data binding can work inconsistently when running
                    // a .NET 4 application on .NET 4.5 compared to running it on .NET 4
                    // app targets 4.0, so return null, for compat 
                    return null;
                }
                else
                {
                    hostElement = new DependencyObject();   // at least pick up the default namespaces
                }
            }
 
            var wpfSharedSchemaContext = XamlReader.BamlSharedSchemaContext;
            Type type = wpfSharedSchemaContext.ResolvePrefixedNameWithAdditionalWpfSemantics(name, hostElement);
            return type;
        }
 
        // return true if the name has the form:  (property)
        internal static bool IsPropertyReference(string name)
        {
            return (name != null && name.Length > 1 && name[0] == '(' && (name[name.Length - 1] == ')'));
        }
 
        // return true if the name has the form:  (nnn)
        internal static bool IsParameterIndex(string name, out int index)
        {
            ReadOnlySpan<char> toParse;
            if (IsPropertyReference(name))
            {
                toParse = name.AsSpan(1, name.Length - 2);
            }
            else
            {
                index = -1;
                return false;
            }
 
            return Int32.TryParse(toParse,
                                NumberStyles.Integer,
                                TypeConverterHelper.InvariantEnglishUS.NumberFormat,
                                out index);
        }
 
        // determine if an object is one of the accessors we support
        static bool IsValidAccessor(object accessor)
        {
            return  accessor is DependencyProperty ||
                    accessor is PropertyInfo  ||
                    accessor is PropertyDescriptor ||
                    accessor is DynamicObjectAccessor;
        }
 
        // determine the name of an accessor
        static string GetPropertyName(object accessor)
        {
            DependencyProperty dp;
            PropertyInfo pi;
            PropertyDescriptor pd;
            DynamicObjectAccessor doa;
 
            if ((dp = accessor as DependencyProperty) != null)
                return dp.Name;
            else if ((pi = accessor as PropertyInfo) != null)
                return pi.Name;
            else if ((pd = accessor as PropertyDescriptor) != null)
                return pd.Name;
            else if ((doa = accessor as DynamicObjectAccessor) != null)
                return doa.PropertyName;
            else
            {
                Invariant.Assert(false, "Unknown accessor type");
                return null;
            }
        }
 
        //------------------------------------------------------
        //
        //  Private Enums, Structs, Constants
        //
        //------------------------------------------------------
 
        const string SingleStepPath = "(0)";
        static readonly Char[] s_comma = new Char[]{','};
 
        //------------------------------------------------------
        //
        //  Private data
        //
        //------------------------------------------------------
 
        string _path = String.Empty;        // the path
        PathParameterCollection _parameters; // list of DPs to inject into the path
 
        SourceValueInfo[] _arySVI;          // static description of each level in the path
        string _lastError = String.Empty;   // most recent error message
        object[] _earlyBoundPathParts;      // accessors and indexer parameters that got resolved early
        PropertyPathWorker _singleWorker;   // shared worker - used in "target" mode
 
        //------------------------------------------------------
        //
        //  Private types
        //
        //------------------------------------------------------
        private class PathParameterCollection : ObservableCollection<object>
        {
            public PathParameterCollection()
            {
            }
 
            public PathParameterCollection(object[] parameters)
            {
                IList<object> items = Items;
                foreach (object o in parameters)
                {
                    items.Add(o);
                }
            }
        }
    }
}