Ejemplo n.º 1
0
        public static void FixSelectorItemsSourceOrder(XElement currentElement, ReflectionOnSeparateAppDomainHandler reflectionOnSeparateAppDomain)
        {
            // Check if the current element is an object (rather than a property):
            bool isElementAnObjectRatherThanAProperty = !currentElement.Name.LocalName.Contains(".");

            if (isElementAnObjectRatherThanAProperty)
            {
                //check if the element has a attribute called ItemsSource:
                XAttribute itemsSourceAttribute   = currentElement.Attribute(XName.Get("ItemsSource"));
                bool       hasItemsSourceProperty = itemsSourceAttribute != null;

                //if the element has an attribute called ItemsSource, we check if it represents an object of a type that inherits from Selector:
                if (hasItemsSourceProperty)
                {
                    // Get the namespace, local name, and optional assembly that correspond to the element:
                    string namespaceName, localTypeName, assemblyNameIfAny;
                    GettingInformationAboutXamlTypes.GetClrNamespaceAndLocalName(currentElement.Name, out namespaceName, out localTypeName, out assemblyNameIfAny);
                    string selectorNamespaceName, selectorLocalTypeName, selectorAssemblyNameIfAny;
                    GettingInformationAboutXamlTypes.GetClrNamespaceAndLocalName("Selector", out selectorNamespaceName, out selectorLocalTypeName, out selectorAssemblyNameIfAny);
                    bool typeInheritsFromSelector = reflectionOnSeparateAppDomain.IsTypeAssignableFrom(namespaceName, localTypeName, assemblyNameIfAny, selectorNamespaceName, selectorLocalTypeName, selectorAssemblyNameIfAny);
                    //Type elementType = reflectionOnSeparateAppDomain.GetCSharpEquivalentOfXamlType(namespaceName, localTypeName, assemblyNameIfAny);
                    //if the type inherits from the element, we want to put the itemsSource attribute to the end:
                    if (typeInheritsFromSelector)
                    {
                        itemsSourceAttribute.Remove();
                        currentElement.Add(itemsSourceAttribute);
                    } //else we do nothing
                }     //else we do nothing
            }         //else we do nothing

            //we move on to the children of the element.
            if (currentElement.HasElements)
            {
                foreach (XElement child in currentElement.Elements())
                {
                    FixSelectorItemsSourceOrder(child, reflectionOnSeparateAppDomain);
                }
            }
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Generates a XElement with the name as given as a parameter and which contains the elements defined in the attributeValue parameter
        /// </summary>
        /// <param name="nodeName">the name Of the XElement to create (for example: Border.Background)</param>
        /// <param name="attributeValue">a string containing the whole attribute's definition (for example: "{Binding Toto, Mode = TwoWay, Converter = {StaticResource Myconverter}}")</param>
        /// <param name="reflectionOnSeparateAppDomain"></param>
        /// <returns></returns>
        private static XElement GenerateNodeForAttribute(XName nodeName, string attributeValue, XNamespace lastDefaultNamespace, ReflectionOnSeparateAppDomainHandler reflectionOnSeparateAppDomain, Func <string, XNamespace> getNamespaceOfPrefix)
        {
            //we get rid of the part that defines the type of the attribute (in the example: we get rid of "{Binding ")
            //if (attributeValue.StartsWith("{"))
            //{
            //    int indexOfFirstSpace = attributeValue.IndexOf(' ');
            //    attributeValue = attributeValue.Remove(0, indexOfFirstSpace + 1); //+1 because we want to remove the space

            //    //we also need to remove the '}' at the end:
            //    attributeValue = attributeValue.Remove(attributeValue.Length - 1);

            //}

            Dictionary <string, string> listOfSubAttributes = GenerateListOfAttributesFromString(attributeValue);
            List <XElement>             elementsToAdd       = new List <XElement>();
            List <XAttribute>           attributesToAdd     = new List <XAttribute>();

            foreach (string keyString in listOfSubAttributes.Keys)
            {
                string currentAttribute  = listOfSubAttributes[keyString];
                bool   isMarkupExtension = currentAttribute.StartsWith("{");
                if (isMarkupExtension)
                {
                    int    indexOfNextClosingBracket = currentAttribute.IndexOf("}");
                    string contentBetweenBrackets    = currentAttribute.Substring(1, indexOfNextClosingBracket - 1);
                    isMarkupExtension = (!string.IsNullOrWhiteSpace(contentBetweenBrackets));
                    if (isMarkupExtension)
                    {
                        //We check whether the first character is a Number because of StringFormat (example: "{Binding ... StringFormat={0:N4}}" the "{0:N4}" part is not a MarkupExtension).
                        string tempCurrentAttribute = contentBetweenBrackets.Trim();
                        char   c = tempCurrentAttribute[0];
                        isMarkupExtension = !(c >= '0' && c <= '9');
                    }
                }
                if (isMarkupExtension) //example: {Binding Toto, Mode = TwoWay, Converter = {StaticResource Myconverter}}
                {
                    //-------------------------
                    // Markup Extension
                    //-------------------------

                    try
                    {
                        int indexOfCharacterAfterClassName = currentAttribute.IndexOf(' ');
                        if (indexOfCharacterAfterClassName < 0)
                        {
                            indexOfCharacterAfterClassName = currentAttribute.IndexOf('}');
                        }

                        string currentSubAttributeWithoutUselessPart = currentAttribute;
                        string nextClassName = currentAttribute.Substring(0, indexOfCharacterAfterClassName); //nextClassName = "{Binding"
                        nextClassName = nextClassName.Remove(0, 1);                                           //we remove the '{'

                        currentSubAttributeWithoutUselessPart = currentSubAttributeWithoutUselessPart.Remove(0, indexOfCharacterAfterClassName).Trim();
                        currentSubAttributeWithoutUselessPart = currentSubAttributeWithoutUselessPart.Remove(currentSubAttributeWithoutUselessPart.Length - 1, 1); //to remove the '}' at the end

                        //we replace {TemplateBinding ...} with {Binding ..., RelativeSource={RelativeSource TemplatedParent}}
                        if (nextClassName == "TemplateBinding")
                        {
                            nextClassName = "Binding";
                            currentSubAttributeWithoutUselessPart += ", RelativeSource={RelativeSource TemplatedParent}"; //we simply add it at the end because the order doesn't change anything.
                        }

                        // We add the suffix "Extension" to the markup extension name (unless it is a Binding or RelativeSource). For example, "StaticResource" becomes "StaticResourceExtension":
                        if (!nextClassName.EndsWith("Binding") && !nextClassName.EndsWith("RelativeSource") && !nextClassName.EndsWith("Extension"))
                        {
                            // this is a trick, we need to check if :
                            // - type named 'MyCurrentMarkupExtensionName' exist.
                            // - if the previous does not exist, look for 'MyCurrentMarkupExtensionNameExtension'.
                            // - if none exist throw an Exception
                            // note: currently, this implementation can lead to a crash if a MarkupExtension is named "xxxxxExtensionExtension" and is used with "xxxxxExtension" in some xaml code.
                            nextClassName += "Extension";
                        }

                        // Determine the namespace and local name:
                        XNamespace ns;
                        string     localName;
                        if (!TryGetNamespaceFromNameThatMayHaveAPrefix(nextClassName, getNamespaceOfPrefix, out ns, out localName))
                        {
                            ns        = lastDefaultNamespace;
                            localName = nextClassName;
                        }

                        // Generate the elements:
                        XElement subXElement  = GenerateNodeForAttribute(ns + localName, currentSubAttributeWithoutUselessPart, lastDefaultNamespace, reflectionOnSeparateAppDomain, getNamespaceOfPrefix);
                        XElement subXElement1 = subXElement;
                        if (!nodeName.LocalName.Contains('.'))
                        {
                            subXElement1 = new XElement(nodeName + "." + keyString, subXElement);
                        }
                        elementsToAdd.Add(subXElement1);
                    }
                    catch (Exception ex)
                    {
                        throw new wpf::System.Windows.Markup.XamlParseException("Error in the following markup extension: \"" + currentAttribute + "\". " + ex.Message);
                    }
                }
                else //it can be directly set as an attribute because it is not a markupExtension:
                {
                    //-------------------------
                    // Not a markup extension
                    //-------------------------

                    string keyStringAfterPlaceHolderReplacement = keyString;
                    if (keyString == "_placeHolderForDefaultValue") //this test is to replace the name of the attribute (which is in keyString) if it was a placeholder
                    {
                        string namespaceName, localName, assemblyNameIfAny;
                        GettingInformationAboutXamlTypes.GetClrNamespaceAndLocalName(nodeName, out namespaceName, out localName, out assemblyNameIfAny);
                        keyStringAfterPlaceHolderReplacement = reflectionOnSeparateAppDomain.GetContentPropertyName(namespaceName, localName, assemblyNameIfAny);
                    }
                    else if (keyStringAfterPlaceHolderReplacement.StartsWith("{")) //if we enter this if, it means that keyString is of the form "{Binding ElementName" so we want to remove "{Binding "
                    {
                        int indexOfFirstSpace = keyStringAfterPlaceHolderReplacement.IndexOf(' ');
                        keyStringAfterPlaceHolderReplacement = keyStringAfterPlaceHolderReplacement.Remove(0, indexOfFirstSpace + 1); //+1 because we want to remove the space
                    }
                    if (currentAttribute.EndsWith("}"))
                    {
                        int i = 0;
                        foreach (char c in currentAttribute)
                        {
                            if (c == '{')
                            {
                                ++i;
                            }
                            if (c == '}')
                            {
                                --i;
                            }
                        }
                        if (i < 0) // We do not want to remove the last '}' if it comes from something like "StringFormat=This is {0}" (in Bindings here).
                        {
                            currentAttribute = currentAttribute.Remove(currentAttribute.Length - 1);
                        }
                    }
                    currentAttribute = currentAttribute.Replace("<COMMA>", ","); // Unescape (cf. code where <COMMA> is added)
                    XAttribute attribute = new XAttribute(keyStringAfterPlaceHolderReplacement, currentAttribute);
                    attributesToAdd.Add(attribute);
                }
            }

            XNamespace xNamespace     = @"http://schemas.microsoft.com/winfx/2006/xaml/presentation"; //todo: support markup extensions that use custom namespaces.
            string     actualNodeName = nodeName.LocalName;
            string     nodeNamespace  = "{" + nodeName.NamespaceName + "}";

            string[] splittedNodeName = actualNodeName.Split('.');
            if (splittedNodeName.Length == 3) //case where we have an attached property (ex: <Border Canvas.Left={Binding...})
            {
                actualNodeName = splittedNodeName[1] + "." + splittedNodeName[2];
            }
            XElement xElement = new XElement(nodeNamespace + actualNodeName, attributesToAdd, elementsToAdd);

            return(xElement);
        }
        static void TraverseNextElement(XElement currentElement, int currentElementIndex, /*Stack<Dictionary<int, int>> indexesMapper*/ Stack <List <int> > indexesMapper, ReflectionOnSeparateAppDomainHandler reflectionOnSeparateAppDomain)
        {
            bool skipTraversalOfChildren = false;

            // Copy the children into a new array so that if we remove items from the collection, it does not affect the traversal:
            XElement[] children = currentElement.Elements().ToArray();

            // Check if the current element is an object (rather than a property):
            bool       isElementAnObjectRatherThanAProperty = !currentElement.Name.LocalName.Contains(".");
            List <int> indexesMap = new List <int>(children.Length);

            if (isElementAnObjectRatherThanAProperty)
            {
                //----------------------------------
                // CASE: OBJECT (e.g. <Button> or <TextBlock>)
                //----------------------------------

                // Make a list of all the child nodes that are not part of a property of the current element (e.g. if the current element is a Border that contains a Button, we detect "<Button>" but we ignore "<Border.Child>" and "<ToolTipService.ToolTip>" because they are properties):
                List <XElement> nodesThatAreNotPropertiesOfTheObject = new List <XElement>();
                XElement        child;
                for (int i = 0; i < children.Length; i++)
                {
                    child = children[i];
                    if (!child.Name.LocalName.Contains("."))
                    {
                        nodesThatAreNotPropertiesOfTheObject.Add(child);
                        indexesMap.Add(i);
                    }
                }

                //-------------------------------------------------------------
                // Explicitly add the "ContentProperty" to the XAML. For example, <Border><TextBlock/></Border> becomes <Border><Border.Child><TextBlock/></Border.Child></Border>
                //-------------------------------------------------------------
                // If that list is not empty, put those child elements into a group, which name is the default children property (aka "ContentProperty") of the parent:
                if (nodesThatAreNotPropertiesOfTheObject.Count > 0)
                {
                    // Find out the name of the default children property (aka "ContentProperty") of the current element:
                    string namespaceName, localName, assemblyNameIfAny;
                    GettingInformationAboutXamlTypes.GetClrNamespaceAndLocalName(currentElement.Name, out namespaceName, out localName, out assemblyNameIfAny);
                    var      contentPropertyName = reflectionOnSeparateAppDomain.GetContentPropertyName(namespaceName, localName, assemblyNameIfAny);
                    XElement contentWrapper;
                    if (contentPropertyName != null)
                    {
                        // Wrap the child elements:
                        contentWrapper = new XElement(currentElement.Name + "." + contentPropertyName);
                    }
                    else
                    {
                        contentWrapper = currentElement;
                    }
                    foreach (var childElement in nodesThatAreNotPropertiesOfTheObject)
                    {
                        childElement.Remove();
                    }
                    contentWrapper.Add(nodesThatAreNotPropertiesOfTheObject.ToArray <object>());
                    if (contentWrapper != currentElement)
                    {
                        currentElement.Add(contentWrapper);
                    }
                }

                //-------------------------------------------------------------
                // If there is some direct text content (such as <Button>content</Button), convert the text into an attribute (such as <Button Content="content"></Button>) if the element has the "[ContentProperty]" attribute.
                // Note: if the type is a system type (such as <sys:Double>50</sys:Double>), we ignore it because later its value will be directly assigned.
                // Similarly, if the type has the "[SupportsDirectContentViaTypeFromStringConvertersAttribute]" attribute or is an Enum, we also ignore it because later it will be transformed into a call to the "TypeFromStringConverters" class.
                //-------------------------------------------------------------
                XText directTextContent;
                if (DoesElementContainDirectTextContent(currentElement, out directTextContent))
                {
                    // Read the content:
                    string contentValue = directTextContent.Value;

                    // Get information about the element namespace and assembly:
                    string namespaceName, localName, assemblyNameIfAny;
                    GettingInformationAboutXamlTypes.GetClrNamespaceAndLocalName(currentElement.Name, out namespaceName, out localName, out assemblyNameIfAny);

                    // Distinguish system types (string, double, etc.) to other types:
                    if (SystemTypesHelper.IsSupportedSystemType(namespaceName, localName, assemblyNameIfAny))
                    {
                        // In this case we do nothing because system types are handled later in the process. Example: "<sys:Double>50</sys:Double>" becomes: "Double x = 50;"
                    }
                    else
                    {
                        // Ensure the content is not empty:
                        if (!string.IsNullOrWhiteSpace(contentValue)) //todo: do we really need this?
                        {
                            List <int> siblings = indexesMapper.Peek();

                            //If it is the first child, we want to trim the start of the string. (Silverlight behavior)
                            if (currentElementIndex == siblings[0]) //at least of size 1 (it contains currentElement)
                            {
                                contentValue = contentValue.TrimStart();
                            }
                            //If it is the last child, we want to trim the end of the string. (Silverlight behavior)
                            if (currentElementIndex == siblings[siblings.Count - 1])
                            {
                                contentValue = contentValue.TrimEnd();
                            }
                            // Replace multiple spaces (and line returns) with just one space (same behavior as in WPF): //cf. http://stackoverflow.com/questions/1279859/how-to-replace-multiple-white-spaces-with-one-white-space
                            contentValue = Regex.Replace(contentValue, @"\s{2,}", " ");

                            // Check if the type has the "[SupportsDirectContentViaTypeFromStringConvertersAttribute]" attribute (such as "<Color>Red</Color>")
                            // or it is an Enum (such as "<Visibility>Collapsed</Visibility>"):
                            if (reflectionOnSeparateAppDomain.DoesTypeContainAttributeToConvertDirectContent(namespaceName, localName, assemblyNameIfAny) ||
                                reflectionOnSeparateAppDomain.IsTypeAnEnum(namespaceName, localName, assemblyNameIfAny))
                            {
                                // Add the attribute that will tell the compiler to later intialize the type by converting from the string using the "TypeFromStringConverters" class.
                                currentElement.SetAttributeValue(AttributeNameForTypesToBeInitializedFromString, contentValue);

                                // Remove the direct text content:
                                directTextContent.Remove();
                            }
                            else
                            {
                                // Find out the name of the default children property (aka "ContentProperty") of the current element:
                                var contentPropertyName = reflectionOnSeparateAppDomain.GetContentPropertyName(namespaceName, localName, assemblyNameIfAny);

                                // Verify that the default children property (aka "ContentProperty") was found:
                                if (string.IsNullOrEmpty(contentPropertyName))
                                {
                                    throw new wpf::System.Windows.Markup.XamlParseException(string.Format("The element '{0}' does not support direct content.", currentElement.Name));
                                }

                                // Verify that the attribute is not already set:
                                if (currentElement.Attribute(contentPropertyName) != null)
                                {
                                    throw new wpf::System.Windows.Markup.XamlParseException(string.Format("The property '{0}' is set more than once.", contentPropertyName));
                                }

                                // SPECIAL CASE: If we are in a TextBlock, we want to set the property "TextBlock.Text" instead of "TextBlock.Inlines":
                                if (currentElement.Name == GeneratingCSharpCode.DefaultXamlNamespace + "TextBlock" ||
                                    currentElement.Name == GeneratingCSharpCode.DefaultXamlNamespace + "Run")
                                {
                                    contentPropertyName = "Text";
                                }

                                // Add the Content attribute:
                                XAttribute attribute = new XAttribute(contentPropertyName, contentValue);
                                currentElement.Add(attribute);

                                // Remove the direct text content:
                                directTextContent.Remove();
                            }
                        }
                    }
                }
            }
            else
            {
                //----------------------------------
                // CASE: PROPERTY (e.g. <Button.Visibility> or <TextBlock.Text> or <ToolTipService.ToolTip>)
                //----------------------------------

                // If there is some direct text content (such as <Button.Visibility>Collapsed</Button.Visibility> or <ToolTipService.ToolTip>Test</ToolTipService.ToolTip>), we convert the text into an attribute (such as <Button Visibility="Collapsed"></Button> or <Button ToolTipService.ToolTip="Test></Button>):
                XText directTextContent;
                if (DoesElementContainDirectTextContent(currentElement, out directTextContent))
                {
                    // Check if we are on a direct object property (such as <Button.Visibility>) or an attached property (such as <ToolTipService.ToolTip>). For example, if the current element is <TextBlock.Text> and the parent is <TextBlock>, the result is true. Conversely, if the current element is <ToolTipService.ToolTip> and the parent is <Border>, the result is false.
                    bool isAttachedProperty = currentElement.Parent.Name != (currentElement.Name.Namespace + currentElement.Name.LocalName.Substring(0, currentElement.Name.LocalName.IndexOf(".")));

                    // Read the content:
                    string contentValue = directTextContent.Value;

                    // Get the property name:
                    string contentPropertyName = isAttachedProperty ? currentElement.Name.LocalName : currentElement.Name.LocalName.Substring(currentElement.Name.LocalName.IndexOf(".") + 1);

                    // Replace multiple spaces (and line returns) with just one space (same behavior as in WPF): //cf. http://stackoverflow.com/questions/1279859/how-to-replace-multiple-white-spaces-with-one-white-space
                    contentValue = Regex.Replace(contentValue, @"\s{2,}", " ").Trim();
                    if (!string.IsNullOrEmpty(contentValue))
                    {
                        contentValue = contentValue[0] == '{' ? "{}" + contentValue : contentValue;
                    }
                    // Verify that the attribute is not already set:
                    if (currentElement.Attribute(contentPropertyName) != null)
                    {
                        throw new wpf::System.Windows.Markup.XamlParseException(string.Format("The property '{0}' is set more than once.", contentPropertyName));
                    }

                    // Add the attribute:
                    XAttribute attribute = new XAttribute(contentPropertyName, contentValue);
                    currentElement.Parent.Add(attribute);

                    // Remove the element:
                    currentElement.Remove();

                    // It's useless to traverse the children because we have removed the element:
                    skipTraversalOfChildren = true;
                }
                else
                {
                    XElement child;
                    for (int i = 0; i < children.Length; i++)
                    {
                        child = children[i];
                        if (!child.Name.LocalName.Contains("."))
                        {
                            indexesMap.Add(i);
                        }
                    }
                }
            }

            // Recursion:
            if (!skipTraversalOfChildren)
            {
                indexesMapper.Push(indexesMap);
                int i = 0;
                foreach (var childElements in children)
                {
                    TraverseNextElement(childElements, i, indexesMapper, reflectionOnSeparateAppDomain);
                    ++i;
                }
                indexesMapper.Pop();
            }
        }