diff --git a/WinUI3XamlPreview/WinUI3XamlPreview/MainPage.xaml.cpp b/WinUI3XamlPreview/WinUI3XamlPreview/MainPage.xaml.cpp index 247058a..303d7c4 100644 --- a/WinUI3XamlPreview/WinUI3XamlPreview/MainPage.xaml.cpp +++ b/WinUI3XamlPreview/WinUI3XamlPreview/MainPage.xaml.cpp @@ -4,15 +4,13 @@ #include "MainPage.g.cpp" #endif #include -#include -#include #include "Preview.h" #include +#include "XamlProcessor.h" using namespace winrt; using namespace std::string_view_literals; namespace mut = winrt::Microsoft::UI::Text; -namespace wdxd = Windows::Data::Xml::Dom; double GetScaleComboBoxSelectedScalePercentage(muxc::ComboBox const& comboBox) { @@ -23,101 +21,6 @@ double GetScaleComboBoxSelectedScalePercentage(muxc::ComboBox const& comboBox) namespace winrt::WinUI3XamlPreview::implementation { - bool IsAttrValid(wdxd::XmlDocument const& doc, wdxd::XmlElement const& element, wdxd::XmlAttribute const& attr) - { - auto copied = element.CloneNode(false); - auto attributes = copied.Attributes(); - while (true) - { - auto iter = attributes.First(); - if (!iter.HasCurrent()) - { - break; - } - auto attrNode = iter.Current(); - if (attrNode.NodeType() != wdxd::NodeType::AttributeNode) - { - return false; - } - auto victim = attrNode.try_as(); - if (victim == nullptr) - { - return false; - } - attributes.RemoveNamedItem(victim.as().Name()); - } - auto defaultNsAttr = doc.CreateAttribute(L"xmlns"); - defaultNsAttr.Value(L"http://schemas.microsoft.com/winfx/2006/xaml/presentation"); - attributes.SetNamedItem(defaultNsAttr); - auto copiedAttr = attr.CloneNode(true); - auto namespaceUri = attr.NamespaceUri().try_as(); - if (namespaceUri == L"") - { - attributes.SetNamedItem(copiedAttr); - } - else - { - attributes.SetNamedItemNS(copiedAttr); - } - auto xml = copied.GetXml(); - try - { - muxm::XamlReader::Load(xml); - return true; - } - catch (...) - { - return false; - } - } - void VisitAndTrim(wdxd::XmlDocument const& doc, wdxd::IXmlNode const& candidate) - { - for (auto&& node : candidate.ChildNodes()) - { - auto element = node.try_as(); - if (element == nullptr) - { - continue; - } - if (element.NodeName() == L"Window") - { - auto border = doc.CreateElement(L"Border"); - auto children = element.ChildNodes(); - for (auto&& child : children) - { - border.AppendChild(child.CloneNode(true)); - } - auto attributes = element.Attributes(); - for (auto&& attrNode : attributes) - { - border.SetAttributeNode(attrNode.CloneNode(true).as()); - } - candidate.RemoveChild(element); - candidate.AppendChild(border); - element = border; - } - auto attributes = element.Attributes(); - for (int i = 0; i < attributes.Size(); ++i) - { - auto attrNode = attributes.GetAt(i); - if (attrNode.NodeType() != wdxd::NodeType::AttributeNode) - { - continue; - } - auto attr = attrNode.try_as(); - if (attr == nullptr) - { - continue; - } - auto isAttrValid = IsAttrValid(doc, element, attr); - if (!isAttrValid) - { - element.RemoveAttributeNode(attr); - } - } - VisitAndTrim(doc, element); - } - } MainPage::MainPage() { _filePathChangedToken = Preview::InstanceInternal()->FilePathChanged({get_weak(), &MainPage::OnFilePathChanged}); @@ -138,17 +41,22 @@ namespace winrt::WinUI3XamlPreview::implementation { try { - wdxd::XmlDocument doc; - doc.LoadXml(xaml); - VisitAndTrim(doc, doc); - auto trimmedXaml = doc.GetXml(); - auto tree = muxm::XamlReader::Load(trimmedXaml); - auto element = tree.try_as(); - if (element == nullptr) + auto processResult{ XamlProcessor::ProcessXaml(xaml) }; + if (auto singleElement = std::get_if(&processResult); singleElement != nullptr) + { + elementWrapper().Child(singleElement->element); + } + else if (auto multipleElement = std::get_if(&processResult); multipleElement != nullptr) { - return; + auto& elements = multipleElement->elements; + if (elements.empty()) + { + toast().ShowError(L"Nothing to show", + L"ResourceDictionary doesn't contain any control template"); + return; + } + elementWrapper().Child(elements.front()); } - elementWrapper().Child(element); } catch (...) { diff --git a/WinUI3XamlPreview/WinUI3XamlPreview/WinUI3XamlPreview.vcxproj b/WinUI3XamlPreview/WinUI3XamlPreview/WinUI3XamlPreview.vcxproj index 7bac0d7..c1ef69a 100644 --- a/WinUI3XamlPreview/WinUI3XamlPreview/WinUI3XamlPreview.vcxproj +++ b/WinUI3XamlPreview/WinUI3XamlPreview/WinUI3XamlPreview.vcxproj @@ -121,6 +121,7 @@ Toast.xaml Code + @@ -151,6 +152,7 @@ Toast.xaml Code + diff --git a/WinUI3XamlPreview/WinUI3XamlPreview/WinUI3XamlPreview.vcxproj.filters b/WinUI3XamlPreview/WinUI3XamlPreview/WinUI3XamlPreview.vcxproj.filters index f71da46..b31de52 100644 --- a/WinUI3XamlPreview/WinUI3XamlPreview/WinUI3XamlPreview.vcxproj.filters +++ b/WinUI3XamlPreview/WinUI3XamlPreview/WinUI3XamlPreview.vcxproj.filters @@ -12,9 +12,11 @@ + + diff --git a/WinUI3XamlPreview/WinUI3XamlPreview/XamlProcessor.cpp b/WinUI3XamlPreview/WinUI3XamlPreview/XamlProcessor.cpp new file mode 100644 index 0000000..aa0737e --- /dev/null +++ b/WinUI3XamlPreview/WinUI3XamlPreview/XamlProcessor.cpp @@ -0,0 +1,211 @@ +#include "pch.h" +#include "XamlProcessor.h" + +using namespace winrt; +namespace mut = winrt::Microsoft::UI::Text; +namespace wdxd = Windows::Data::Xml::Dom; + +constexpr auto defaultNamespace = L"http://schemas.microsoft.com/winfx/2006/xaml/presentation"; + +std::vector GetNamespaces(wdxd::XmlDocument const& doc) +{ + std::vector namespaces; + auto docAttributes{ doc.DocumentElement().Attributes() }; + for (auto&& docAttr : docAttributes) + { + if (std::wstring_view(docAttr.NodeName())._Starts_with(L"xmlns")) + { + namespaces.emplace_back(docAttr.CloneNode(true).as()); + } + } + return namespaces; +} +bool IsAttrValid(wdxd::XmlDocument const& doc, wdxd::XmlElement const& element, wdxd::XmlAttribute const& attr) +{ + auto copied = element.CloneNode(false); + auto attributes = copied.Attributes(); + while (true) + { + auto iter = attributes.First(); + if (!iter.HasCurrent()) + { + break; + } + auto attrNode = iter.Current(); + if (attrNode.NodeType() != wdxd::NodeType::AttributeNode) + { + return false; + } + auto victim = attrNode.try_as(); + if (victim == nullptr) + { + return false; + } + attributes.RemoveNamedItem(victim.as().Name()); + } + auto copiedAttr = attr.CloneNode(true); + auto namespaceUri = attr.NamespaceUri().try_as(); + if (namespaceUri == L"") + { + attributes.SetNamedItem(copiedAttr); + } + else + { + attributes.SetNamedItemNS(copiedAttr); + } + auto namespaces{ GetNamespaces(doc) }; + for (auto&& aNamespace : namespaces) + { + copied.Attributes().SetNamedItemNS(aNamespace); + } + auto xml = copied.GetXml(); + try + { + muxm::XamlReader::Load(xml); + return true; + } + catch (...) + { + return false; + } +} +void VisitAndTrim(wdxd::XmlDocument const& doc, wdxd::IXmlNode const& candidate, std::vector& templateTargetTypes) +{ + for (auto&& node : candidate.ChildNodes()) + { + auto element = node.try_as(); + if (element == nullptr) + { + continue; + } + if (element.NodeName() == L"Window") + { + auto border = doc.CreateElementNS(box_value(winrt::hstring(defaultNamespace)), L"Border"); + auto children = element.ChildNodes(); + for (auto&& child : children) + { + border.AppendChild(child.CloneNode(true)); + } + auto attributes = element.Attributes(); + for (auto&& attrNode : attributes) + { + border.SetAttributeNode(attrNode.CloneNode(true).as()); + } + candidate.RemoveChild(element); + candidate.AppendChild(border); + element = border; + } + auto attributes = element.Attributes(); + // ControlTempalte is a sealed class so it's OK to just check default NS + tag name to see if it's a ControlTemplate + auto elemetName = element.LocalName().try_as(); + auto elementNamespace = element.NamespaceUri().try_as(); + auto isControlTemplate = elemetName == L"ControlTemplate" && elementNamespace == defaultNamespace; + auto isSetter = elemetName == L"Setter" && elementNamespace == defaultNamespace; + if (isSetter) + { + VisitAndTrim(doc, element, templateTargetTypes); + continue; + } + for (uint32_t i = 0; i < attributes.Size(); ++i) + { + auto attrNode = attributes.GetAt(i); + if (attrNode.NodeType() != wdxd::NodeType::AttributeNode) + { + continue; + } + auto attr = attrNode.try_as(); + if (attr == nullptr) + { + continue; + } + auto isAttrValid = IsAttrValid(doc, element, attr); + if (!isAttrValid) + { + element.RemoveAttributeNode(attr); + } + else + { + auto name = attr.NodeName(); + if (isControlTemplate && attr.Name() == L"TargetType") + { + templateTargetTypes.emplace_back(attr.Value()); + } + } + } + VisitAndTrim(doc, element, templateTargetTypes); + } +} +std::pair SplitTargetTypeToNamespaceAndLocalName(std::wstring_view targetType) +{ + auto colonIndex = targetType.find(L":"); + if (colonIndex == -1) + { + return { targetType.substr(0, 0), targetType}; + } + return { targetType.substr(0, colonIndex), targetType.substr(colonIndex + 1) }; +} +winrt::WinUI3XamlPreview::ProcessResult winrt::WinUI3XamlPreview::XamlProcessor::ProcessXaml(winrt::hstring const& xaml) +{ + wdxd::XmlDocument doc; + doc.LoadXml(xaml); + std::vector templateTargetTypes; + VisitAndTrim(doc, doc, templateTargetTypes); + auto processedXaml = doc.GetXml(); + auto tree = muxm::XamlReader::Load(processedXaml); + if (auto element = tree.try_as(); element != nullptr) + { + return SingleElement{ std::move(element) }; + } + if (auto dict = tree.try_as(); dict != nullptr) + { + if (templateTargetTypes.empty()) + { + return MultipleElement{}; + } + std::vector elements; + for (auto&& targetType : templateTargetTypes) + { + auto elementRoot = doc.CreateElementNS(box_value(winrt::hstring(defaultNamespace)), L"Border"); + auto namespaces{ GetNamespaces(doc) }; + for (auto&& aNamespace : namespaces) + { + elementRoot.Attributes().SetNamedItem(aNamespace); + } + auto elementResource = doc.CreateElementNS(box_value(winrt::hstring(defaultNamespace)), L"Border.Resources"); + elementResource.AppendChild(doc.DocumentElement().CloneNode(true)); + elementRoot.AppendChild(elementResource); + auto targetTypeParts{ SplitTargetTypeToNamespaceAndLocalName(targetType) }; + auto& targetTypePrefix{ targetTypeParts.first }; + winrt::hstring targetTypeNamespaceUri; + for (auto&& aNamespace : namespaces) + { + if (aNamespace.LocalName().try_as() == targetTypePrefix) + { + targetTypeNamespaceUri = aNamespace.Value(); + break; + } + } + if (targetTypeNamespaceUri == L"") + { + // Can't find the namespace for the target type... + continue; + } + auto targetTypeElement = doc.CreateElementNS(box_value(targetTypeNamespaceUri), targetTypeParts.second); + elementRoot.AppendChild(targetTypeElement); + auto elementRootXaml = elementRoot.GetXml(); + // TODO: Assign control template's style name + auto targetTypeTree = muxm::XamlReader::Load(elementRootXaml); + if (auto element = targetTypeTree.try_as(); element != nullptr) + { + elements.emplace_back(std::move(element)); + } + else + { + // TODO: Log? + continue; + } + } + return MultipleElement{ std::move(elements) }; + } + throw hresult_not_implemented(); +} diff --git a/WinUI3XamlPreview/WinUI3XamlPreview/XamlProcessor.h b/WinUI3XamlPreview/WinUI3XamlPreview/XamlProcessor.h new file mode 100644 index 0000000..de6d605 --- /dev/null +++ b/WinUI3XamlPreview/WinUI3XamlPreview/XamlProcessor.h @@ -0,0 +1,26 @@ +#pragma once + +#include "pch.h" +#include +#include + +namespace winrt::WinUI3XamlPreview +{ + struct SingleElement + { + mux::UIElement element; + }; + + struct MultipleElement + { + std::vector elements; + }; + + using ProcessResult = std::variant; + + class XamlProcessor + { + public: + static ProcessResult ProcessXaml(winrt::hstring const& xaml); + }; +} diff --git a/WinUI3XamlPreview/WinUI3XamlPreview/pch.h b/WinUI3XamlPreview/WinUI3XamlPreview/pch.h index c76a3e3..e069cab 100644 --- a/WinUI3XamlPreview/WinUI3XamlPreview/pch.h +++ b/WinUI3XamlPreview/WinUI3XamlPreview/pch.h @@ -9,6 +9,7 @@ // conflict with Storyboard::GetCurrentTime #undef GetCurrentTime +#include #include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include #include #include