File: System\Windows\Markup\Primitives\ExtensionSimplifierMarkupObject.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.
 
 
using System.ComponentModel;
using System.Text;
 
namespace System.Windows.Markup.Primitives
{
    /// <summary>
    /// Utility class use as a base class for classes wrap another
    /// instance by delegating MarkupItem implementation to that 
    /// instance.
    /// </summary>
    internal class MarkupObjectWrapper : MarkupObject
    {
        MarkupObject  _baseObject;
 
        public MarkupObjectWrapper(MarkupObject baseObject)
        {
            _baseObject = baseObject;
        }
 
        public override void AssignRootContext(IValueSerializerContext context)
        {
            _baseObject.AssignRootContext(context);
        }
 
        public override AttributeCollection Attributes
        {
            get { return _baseObject.Attributes;  }
        }
 
        public override Type ObjectType
        {
            get { return _baseObject.ObjectType; }
        }
 
        public override object Instance
        {
            get { return _baseObject.Instance; }
        }
 
        internal override IEnumerable<MarkupProperty> GetProperties(bool mapToConstructorArgs)
        {
            return _baseObject.GetProperties(mapToConstructorArgs);
        }
    }
 
    /// <summary>
    /// Utility class use as a base class for classes wrap another
    /// instance by delegating MarkupProperty implementation to that 
    /// instance.
    /// </summary>
    internal class MarkupPropertyWrapper : MarkupProperty 
    {
        MarkupProperty _baseProperty;
 
        /*
        protected MarkupProperty BaseProperty
        {
            get { return _baseProperty; }
        }
        */
 
        public MarkupPropertyWrapper(MarkupProperty baseProperty)
        {
            _baseProperty = baseProperty;
        }
 
        public override AttributeCollection Attributes
        {
            get { return _baseProperty.Attributes; }
        }
 
        public override IEnumerable<MarkupObject> Items
        {
            get { return _baseProperty.Items; }
        }
 
        public override string Name
        {
            get { return _baseProperty.Name; }
        }
 
        public override Type PropertyType
        {
            get { return _baseProperty.PropertyType; }
        }
 
        public override string StringValue
        {
            get { return _baseProperty.StringValue; }
        }
 
        public override IEnumerable<Type> TypeReferences
        {
            get { return _baseProperty.TypeReferences; }
        }
 
        public override object Value
        {
            get { return _baseProperty.Value; }
        }
 
        public override DependencyProperty DependencyProperty
        {
            get { return _baseProperty.DependencyProperty; }
        }
 
        public override bool IsAttached
        {
            get { return _baseProperty.IsAttached; }
        }
 
        public override bool IsComposite
        {
            get { return _baseProperty.IsComposite; }
        }
 
        public override bool IsConstructorArgument
        {
            get { return _baseProperty.IsConstructorArgument; }
        }
 
        public override bool IsKey
        {
            get { return _baseProperty.IsKey; }
        }
 
        public override bool IsValueAsString
        {
            get { return _baseProperty.IsValueAsString; }
        }
 
        public override bool IsContent
        {
            get { return _baseProperty.IsContent; }
        }
 
        public override PropertyDescriptor PropertyDescriptor
        {
            get { return _baseProperty.PropertyDescriptor; }
        }
 
        /// <summary>
        /// Checks to see that each markup object is of a public type.  Used in serialization.
        /// 
        /// This implementation just checks the base property.
        /// </summary>
        internal override void VerifyOnlySerializableTypes()
        {
            _baseProperty.VerifyOnlySerializableTypes();
        }
    }
 
    /// <summary>
    /// A MarkupItem wrapper that creates an ExtensionSimplifierProperty wrapper
    /// for every property returned. All other implementation is delegated to
    /// the wrapped item.
    /// </summary>
    internal class ExtensionSimplifierMarkupObject : MarkupObjectWrapper
    {
        IValueSerializerContext _context;
 
        public ExtensionSimplifierMarkupObject(MarkupObject baseObject, IValueSerializerContext context)
            : base(baseObject) 
        {
            _context = context;
        }
 
        /// This is placed in its own method to avoid accessing base.Properties from the
        /// iterator class generated by the code below because C# produces unverifiable
        /// code for the expression.
        private IEnumerable<MarkupProperty> GetBaseProperties(bool mapToConstructorArgs) {
            return base.GetProperties(mapToConstructorArgs);
        }
 
        internal override IEnumerable<MarkupProperty> GetProperties(bool mapToConstructorArgs)
        {
            foreach (MarkupProperty property in GetBaseProperties(mapToConstructorArgs))
            {
                yield return new ExtensionSimplifierProperty(property, _context);
            }
        }
 
        public override void AssignRootContext(IValueSerializerContext context)
        {
            _context = context;
            base.AssignRootContext(context);
        }
    }
 
    /// <summary>
    /// A MarkupProperty wrapper that creates simplifies items for objects
    /// of type MarkupExtension to a string if all its properties can be
    /// simplified into a string. This is recursive in that a markup extension
    /// can contain references to other markup extensions which are themselves
    /// simplified.
    /// </summary>
    internal class ExtensionSimplifierProperty : MarkupPropertyWrapper
    {
        IValueSerializerContext _context;
 
        public ExtensionSimplifierProperty(MarkupProperty baseProperty, IValueSerializerContext context) : base(baseProperty) 
        {
            _context = context;
        }
 
        public override bool IsComposite
        {
            get
            {
                // See if we can convert an extension into a string.
                if (!base.IsComposite) 
                {
                    // If it is already a string then we can.
                    return false;
                }
 
                // If the property is a collection, this property is a composite.
                if (IsCollectionProperty)
                {
                    return true;
                }
 
                bool first = true;
                foreach (MarkupObject item in Items)
                {
                    // If there is more than one MarkupExtension, we can't.
                    // If it is not a markup extension we can't.
                    if (!first ||
                        !typeof(MarkupExtension).IsAssignableFrom(item.ObjectType))
                    {
                        return true;
                    }
                    first = false;
                    
                    // If any of the properties are composite we can't. This is recursive to this
                    // routine because of the wrapping below.
                    item.AssignRootContext(_context);
                    foreach (MarkupProperty property in item.Properties)
                    {
                        if (property.IsComposite)
                        {
                            return true;
                        }
                    }
                }
 
                // We can turn this into a string if we have seen at least one item
                return first;
            }
        }
 
        /// This is placed in its own method to avoid accessing base.Items from the
        /// iterator class generated by the code below because C# produces unverifiable
        /// code for the expression.
        private IEnumerable<MarkupObject> GetBaseItems() 
        {
            return base.Items;
        }
 
        public override IEnumerable<MarkupObject> Items
        {
            get
            {
                // Wrap all of the items from the property we are wrapping.
                foreach (MarkupObject baseItem in GetBaseItems())
                {
                    ExtensionSimplifierMarkupObject item = new ExtensionSimplifierMarkupObject(baseItem, _context);
                    item.AssignRootContext(_context);
                    yield return item;
                }
            }
        }
 
        private const int EXTENSIONLENGTH = 9; // the number of characters in the string "Extension"
 
        public override string StringValue
        {
            get
            {
                string result = null;
                
                if (!base.IsComposite)
                {
                    // Escape the text as necessary to avoid being mistaken for a MarkupExtension.
                    result = MarkupExtensionParser.AddEscapeToLiteralString(base.StringValue);
                }
                else
                {
                    // Convert the markup extension into a string
                    foreach (MarkupObject item in Items)
                    {
                        result = ConvertMarkupItemToString(item);
                        break;
                    }
 
                    if (result == null)
                    {
                        Debug.Fail("No items where found and IsComposite return true");
                        result = "";
                    }
                }
                return result;
            }
        }
 
        private string ConvertMarkupItemToString(MarkupObject item)
        {
            ValueSerializer typeSerializer = _context.GetValueSerializerFor(typeof(Type));
            Debug.Assert(typeSerializer != null, "Could not retrieve typeSerializer for Type");
 
            // Serialize the markup extension into a string
            StringBuilder resultBuilder = new StringBuilder();
            resultBuilder.Append('{');
            
            string typeName = typeSerializer.ConvertToString(item.ObjectType, _context);
            
            if (typeName.EndsWith("Extension", StringComparison.Ordinal))
            {
                // The "Extension" suffix is optional, much like the Attribute suffix of an Attribute.
                // The normalized version is without the suffix.
                resultBuilder.Append(typeName, 0, typeName.Length - EXTENSIONLENGTH);
            }
            else
            {
                resultBuilder.Append(typeName);
            }
            
            bool first = true;
            bool propertyWritten = false;
            
            foreach (MarkupProperty property in item.Properties)
            {
                resultBuilder.Append(first ? " " : ", ");
 
                first = false;
                if (!property.IsConstructorArgument)
                {
                    resultBuilder.Append(property.Name);
                    resultBuilder.Append('=');
                    propertyWritten = true;
                }
                else
                {
                    Debug.Assert(!propertyWritten, "An argument was returned after a property was set. All arguments must be returned first and in order");
                }
                
                ReadOnlySpan<char> value = property.StringValue;
                
                if (!value.IsEmpty)
                {
                    if (value[0] == '{')
                    {
                        if (value.Length > 1 && value[1] == '}')
                        {
                            // It is a literal quote, remove the literals and write the text with escapes.
                            value = value.Slice(2);
                        }
                        else
                        {
                            // It is a nested markup-extension, just insert the text literally.
                            resultBuilder.Append(value);
                            continue;
                        }
                    }
 
                    // Escape the string
                    for (int i = 0; i < value.Length; i++)
                    {
                        char ch = value[i];
                        switch (ch)
                        {
                            case '{':
                                resultBuilder.Append(@"\{");
                                break;
                            case '}':
                                resultBuilder.Append(@"\}");
                                break;
                            case ',':
                                resultBuilder.Append(@"\,");
                                break;
                            default:
                                resultBuilder.Append(ch);
                                break;
                        }
                    }
                }
            }
            resultBuilder.Append('}');
 
            return resultBuilder.ToString();
        }
 
        /// <summary>
        /// Checks to see that each markup object is of a public type.  Used in serialization.
        /// 
        /// This implementation checks the base property, checks the item's type, and recursively checks each of
        /// the item's properties.
        /// </summary>
        internal override void VerifyOnlySerializableTypes()
        {
            base.VerifyOnlySerializableTypes();
 
            if(base.IsComposite)
            {
                foreach (MarkupObject item in Items)
                {
                    MarkupWriter.VerifyTypeIsSerializable(item.ObjectType);
 
                    foreach (MarkupProperty property in item.Properties)
                    {
                        property.VerifyOnlySerializableTypes();
                    }
                }
            }
        }
    }
}