/*
         * static IEnumerable<XElement> GetResourcesUnderApplicationDotResourcesNode(XElement applicationDotResources, out XElement resourceDictionaryNode)
         * {
         *  // First, check if the "<ResourceDictionary>" is explicitly specified, or if it is implied:
         *  resourceDictionaryNode = applicationDotResources.Elements(DefaultXamlNamespace + "ResourceDictionary").FirstOrDefault();
         *  bool isResourceDictionarySpecified = (resourceDictionaryNode != null);
         *
         *  // If no "<ResourceDictionary>" is explicitly specified, it means that it is implicit, so we set it to be the parent node:
         *  if (!isResourceDictionarySpecified)
         *      resourceDictionaryNode = applicationDotResources;
         *
         *  // Get the children of the ResourceDictionary:
         *  IEnumerable<XElement> childrenOfResourceDictionary = resourceDictionaryNode.Elements();
         *
         *  // Get the resources by taking the children of the ResourceDictionary and removing the ones that correspond to ResourceDictionary properties (such as <ResourceDictionary.MergedDictionaries>):
         *  IEnumerable<XElement> resources = from elt in childrenOfResourceDictionary
         *                                    where !elt.Name.LocalName.Contains(".")
         *                                    select elt;
         *
         *  return resources;
         * }
         */

        public static void ResolveAndMergeTheMergedDictionaries(XElement element, string currentXamlFileNameAndPath, Dictionary <string, Project> assemblyNameToProjectDictionary, HashSet <string> allXamlFilesVisitedDuringRecursion, ResourcesCache resourcesCache) // "allXamlFilesVisitedDuringRecursion" is used to prevent infinite execution during the recursion due to circular references.
        {
            // First, let us put the <ResourceDictionary> nodes into a List<> so that we can modify them during the foreach without causing issues with the iterator on a collection being modified:
            List <XElement> resourceDictionaryNodes = element.Descendants(DefaultXamlNamespace + "ResourceDictionary").ToList();

            // Then, iterate through all the <ResourceDictionary> nodes and "in-place expand" the ones that reference another file:
            foreach (XElement resourceDictionaryNode in resourceDictionaryNodes)
            {
                var sourceAttribute = resourceDictionaryNode.Attributes("Source").FirstOrDefault();
                if (sourceAttribute != null)
                {
                    string uri = sourceAttribute.Value.ToString();

                    // Get the full referenced ResourceDictionary (or read from cache if any):
                    string   referencedResourceDictionaryFullPath;
                    XElement resourceDictionary = GetResourceDictionary(uri, currentXamlFileNameAndPath, assemblyNameToProjectDictionary, resourcesCache, out referencedResourceDictionaryFullPath);

                    // Change all the the children "x:Name" into "x:Key" so that we do not get an error if there are two entries with the same "x:Name" in the whole document:
                    ChangeAllXNameIntoXKey(resourceDictionary);

                    // Apply the recursion to resolve the nested resource dictionary references:
                    if (!allXamlFilesVisitedDuringRecursion.Contains(referencedResourceDictionaryFullPath))
                    {
                        allXamlFilesVisitedDuringRecursion.Add(referencedResourceDictionaryFullPath);
                        ResolveAndMergeTheMergedDictionaries(resourceDictionary, referencedResourceDictionaryFullPath, assemblyNameToProjectDictionary, allXamlFilesVisitedDuringRecursion, resourcesCache);
                    }
                    else
                    {
                        throw new Exception("Circular references in ResourceDictionary files has been detected (file name: " + referencedResourceDictionaryFullPath + ").");
                    }

                    // Replace the current node with the full ResourceDictionary:
                    resourceDictionaryNode.ReplaceWith(resourceDictionary);
                }
            }
        }
        static XElement GetResourceDictionary(string uri, string currentXamlFileNameAndPath, Dictionary <string, Project> assemblyNameToProjectDictionary, ResourcesCache resourcesCache, out string resourceDictionaryFullPath)
        {
            // Determine the file absolute path of the file (it includes the file name and path):
            resourceDictionaryFullPath = GetAbsolutePath(uri, currentXamlFileNameAndPath, assemblyNameToProjectDictionary);

            // Read from cache if any (this avoids reloading the files at every refresh, to improve performance:
            XElement resourceDictionaryContent;

            if (resourcesCache.ResourceDictionaryFileNameToContent.ContainsKey(resourceDictionaryFullPath))
            {
                resourceDictionaryContent = resourcesCache.ResourceDictionaryFileNameToContent[resourceDictionaryFullPath];
            }
            else
            {
                //------------------------
                // Not found in the cache
                //------------------------

                // Get the file content:
                string resourceDictionaryXaml = File.ReadAllText(resourceDictionaryFullPath);

                // Remove the content of all the "HtmlPresenter" nodes, because the content may be not well formatted and may lead to a syntax error when parsing the XDocument:
                resourceDictionaryXaml = HtmlPresenterRemover.RemoveHtmlPresenterNodes(resourceDictionaryXaml);

                // Load the file as XML:
                XDocument xdoc = XDocument.Parse(resourceDictionaryXaml);

                // Get the content:
                resourceDictionaryContent = xdoc.Root;

                // Add to cache:
                resourcesCache.ResourceDictionaryFileNameToContent.Add(resourceDictionaryFullPath, resourceDictionaryContent);
            }

            return(resourceDictionaryContent);
        }
Exemplo n.º 3
0
        static bool ProcessXamlForDisplay(string sourceXaml, string sourceXamlFileNameAndPath, EnvDTE.Project currentProject, EnvDTE.Solution currentSolution, Dictionary <string, EnvDTE.Project> assemblyNameToProjectDictionary, ResourcesCache resourcesCache, out string outputXaml, out string errorsIfAny, out HashSet <string> warningsAndTips)
        {
            warningsAndTips = new HashSet <string>();

            // Default output values:
            outputXaml  = sourceXaml;
            errorsIfAny = "";

            //---------------------------------------
            // Remove the content of all the "HtmlPresenter" nodes, because the content may be not well formatted and may lead to a syntax error when parsing the XDocument:
            //---------------------------------------
            sourceXaml = HtmlPresenterRemover.RemoveHtmlPresenterNodes(sourceXaml);

            //---------------------------------------
            // Read the XDocument:
            //---------------------------------------
            System.Xml.Linq.XDocument xdoc;
            try
            {
                xdoc = System.Xml.Linq.XDocument.Parse(sourceXaml);
            }
            catch (Exception ex)
            {
                errorsIfAny = ex.Message;
                return(false);
            }

            //---------------------------------------
            // Remove the "x:Class" attribute if any:
            //---------------------------------------

            if (xdoc.Root != null)
            {
                // Remove the "x:Class" attribute if any:
                XNamespace xNamespace          = "http://schemas.microsoft.com/winfx/2006/xaml";
                XAttribute classAttributeIfAny = xdoc.Root.Attribute(xNamespace + "Class");
                if (classAttributeIfAny != null)
                {
                    classAttributeIfAny.Remove();
                }
            }

            //---------------------------------------
            // Replace the root control with a UserControl (if it is not already one) and keep only the properties and attributes supported by the UserControl class:
            //---------------------------------------

            ProcessNodeToMakeItCompatibleWithWpf.ReplaceRootWithUserControl(xdoc);


            //---------------------------------------
            // Get the styles and other resources defined in App.xaml
            //---------------------------------------

            IEnumerable <XElement> appDotXamlResources;
            string appDotXamlFullPath;

            if (resourcesCache.AppDotXamlResources != null) // This avoids reloading App.xaml at every refresh, to improve performance.
            {
                // Read from cache:
                appDotXamlResources = resourcesCache.AppDotXamlResources;
                appDotXamlFullPath  = resourcesCache.AppDotXamlFullPath;
            }
            else
            {
                // Attempt to find App.xaml and read it:
                appDotXamlResources = ResolvingReferencedXamlResources.GetAppDotXamlResources(currentProject, currentSolution, out appDotXamlFullPath);
                resourcesCache.AppDotXamlResources = appDotXamlResources;
                resourcesCache.AppDotXamlFullPath  = appDotXamlFullPath;
            }

            //---------------------------------------
            // Resolve all the "Merged Dictionaries", and merge them so that there are no more references to other XAML files:
            //---------------------------------------

            // Process the resources defined in App.xaml:
            if (appDotXamlResources != null)
            {
                try
                {
                    foreach (XElement element in appDotXamlResources)
                    {
                        ResolvingReferencedXamlResources.ResolveAndMergeTheMergedDictionaries(element, appDotXamlFullPath, assemblyNameToProjectDictionary, new HashSet <string>(), resourcesCache);
                    }
                }
                catch (Exception ex)
                {
                    errorsIfAny = ex.Message;
                    return(false);
                }
            }

            // Process the resources defined in the current file:
            try
            {
                ResolvingReferencedXamlResources.ResolveAndMergeTheMergedDictionaries(xdoc.Root, sourceXamlFileNameAndPath, assemblyNameToProjectDictionary, new HashSet <string>(), resourcesCache);
            }
            catch (Exception ex)
            {
                errorsIfAny = ex.Message;
                return(false);
            }

            //---------------------------------------
            // Surround the document with a control in which we inject all the resources that we found in the end-user's "App.xaml" file:
            //---------------------------------------

            if (appDotXamlResources != null && appDotXamlResources.Any())
            {
                // Put those resouces in a control that will surround the xaml of the current page:
                var surroundingControlForResources = new XElement(DefaultXamlNamespace + "Border", xdoc.Root);
                surroundingControlForResources.Add(new XAttribute(XNamespace.Xmlns + "x", @"http://schemas.microsoft.com/winfx/2006/xaml")); // Note: This is required in case the XAML code contains the "x" prefix inside Markup Extensions (such as "{x:Null}"). It is not needed for the "x" prefix in elements and attributes (such as "x:Name"), because those are automatically imported when copying nodes, where as markup extensions are considered as simple strings by "Linq to XML".
                surroundingControlForResources.Add(new XAttribute("Tag", "AddedByDesignerToInjectAppDotXamlResources"));
                var resourcesContainer = new XElement(DefaultXamlNamespace + "Border.Resources");
                surroundingControlForResources.AddFirst(resourcesContainer);
                resourcesContainer.Add(appDotXamlResources);

                // Replace the document with the one surrounded by the new control:
                xdoc = new XDocument(surroundingControlForResources);
            }

#if Display_The_Processed_XAML
            // Uncomment to display the processed XAML:
            errorsIfAny = xdoc.Root.ToString();
            outputXaml  = xdoc.Root.ToString();
            return(false);
#endif

            //---------------------------------------
            // Iterate through the document using a Stack<XElement> (pre-order traversal, no recursion) and process the nodes:
            //---------------------------------------

            XElement root = xdoc.Root;
            Stack <System.Xml.Linq.XElement> stack = new Stack <System.Xml.Linq.XElement>();
            stack.Push(root);
            while (stack.Count > 0)
            {
                System.Xml.Linq.XElement current       = stack.Pop();
                bool elementWasReplacedWithPlaceholder = false; // default value

                //---------------------------------------
                // Process node to make it compatible with WPF (remove events such as PointerPressed, change "Page" to "UserControl", etc.):
                //---------------------------------------
                ProcessNodeToMakeItCompatibleWithWpf.Process(current, ref warningsAndTips);

                //---------------------------------------
                // Remove unknown nodes and attributes, and display "Cannot preview this element" instead:
                //---------------------------------------

                // Verify that the node is not a property:
                if (!current.Name.LocalName.Contains("."))
                {
                    bool isKnownType = false;

                    // Check to see if the element corresponds to a known type (only for elements that are supposed to be in the default namespace):
                    Type type;
                    if (current.Name.NamespaceName == DefaultXamlNamespace &&
                        TryGetType(current.Name, out type))
                    {
                        isKnownType = true;

                        // List all the event handlers of the type:
                        List <string> eventHandlers = new List <string>();
                        foreach (EventInfo eventInfo in type.GetEvents())
                        {
                            eventHandlers.Add(eventInfo.Name);
                        }

                        // Duplicate the list of attributes so that when we remove one, it doesn't affect the iteration:
                        List <System.Xml.Linq.XAttribute> attributesListCopy = new List <System.Xml.Linq.XAttribute>();
                        foreach (System.Xml.Linq.XAttribute attr in current.Attributes())
                        {
                            attributesListCopy.Add(attr);
                        }

                        // Remove event handlers:
                        foreach (System.Xml.Linq.XAttribute attr in attributesListCopy)
                        {
                            // Check if the attribute is an event handler:
                            //test+= "  ||||  " + attr.Name.LocalName + "  " + attr.Name.NamespaceName + "  " + (!attr.Name.LocalName.Contains(".")).ToString() + "  " + eventHandlers.Contains(attr.Name.LocalName).ToString();
                            if (!attr.Name.LocalName.Contains(".") && eventHandlers.Contains(attr.Name.LocalName))
                            {
                                // Remove the attrbute:
                                attr.Remove();
                            }
                        }
                    }
                    else if (current.Name.NamespaceName.EndsWith("assembly=mscorlib"))
                    {
                        isKnownType = true;
                    }

                    // If not known type, replace the element with a placeholder that says "Unable to display":
                    if (!isKnownType)
                    {
                        // Create a border with inside a TextBlock that says "Unable to display":
                        System.Xml.Linq.XElement msg    = new System.Xml.Linq.XElement(DefaultXamlNamespace + "Border");
                        System.Xml.Linq.XElement msgTxt = new System.Xml.Linq.XElement(DefaultXamlNamespace + "TextBlock");

                        // Add the newly created stuff to the XAML:
                        current.ReplaceWith(msg);
                        msg.Add(msgTxt);

                        // Set some attributes:
                        msg.Add(new XAttribute("Background", "DarkRed"));
                        msg.Add(new XAttribute("Opacity", "0.5"));
                        msg.Add(new XAttribute("Tag", "AddedByDesignerAsPlaceholder"));
                        msgTxt.Add(new XAttribute("Text", "Unable to preview this element"));
                        msgTxt.Add(new XAttribute("TextWrapping", "Wrap"));
                        msgTxt.Add(new XAttribute("TextAlignment", "Center"));
                        msgTxt.Add(new XAttribute("HorizontalAlignment", "Stretch"));
                        msgTxt.Add(new XAttribute("VerticalAlignment", "Center"));
                        msgTxt.Add(new XAttribute("FontSize", "12"));
                        msgTxt.Add(new XAttribute("Foreground", "#AAFFFFFF"));
                        msgTxt.Add(new XAttribute("Tag", "AddedByDesignerForRendering"));

                        // Give to that Border the same positioning information as the element that it replaces:
                        MoveAttributeIfAny("Width", current, msg);
                        MoveAttributeIfAny("Height", current, msg);
                        MoveAttributeIfAny("HorizontalAlignment", current, msg);
                        MoveAttributeIfAny("VerticalAlignment", current, msg);
                        MoveAttributeIfAny("Margin", current, msg);
                        MoveAttributeIfAny("Opacity", current, msg);
                        MoveAttributeIfAny("MaxWidth", current, msg);
                        MoveAttributeIfAny("MaxHeight", current, msg);
                        MoveAttributeIfAny("MinWidth", current, msg);
                        MoveAttributeIfAny("MinHeight", current, msg);
                        MoveAttributeIfAny("Visibility", current, msg);
                        MoveAttributeIfAny("Canvas.Left", current, msg);
                        MoveAttributeIfAny("Canvas.Top", current, msg);
                        MoveAttributeIfAny((XNamespace)"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit" + "DockPanel.Dock", current, msg);
                        MoveAttributeIfAny((XNamespace)"http://schemas.microsoft.com/winfx/2006/xaml" + "Name", current, msg);
                        MoveAttributeIfAny((XNamespace)"http://schemas.microsoft.com/winfx/2006/xaml" + "Key", current, msg);

                        // Remember that the element was replaced:
                        elementWasReplacedWithPlaceholder = true;
                    }
                }

                // Continue with the children of this element:
                if (!elementWasReplacedWithPlaceholder)
                {
                    foreach (XElement element in current.Elements())
                    {
                        stack.Push(element);
                    }
                }
            }



            errorsIfAny = ""; //xdoc.ToString();
            outputXaml  = root.ToString();
            return(true);
        }