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); } } }
/// <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(); } }