Применение XSLT преобразований для конвертации XAML между различными платформами

в 9:09, , рубрики: .net, silverlight, wpf, XAML, xslt, Блог компании DevExpress, разработка

Платформы WPF и Silverlight используют язык разметки XAML для описания элементов пользовательского интерфейса, шаблонов и стилей. Если вы разрабатываете одновременно под разные XAML платформы, то естественно, возникает желание иметь общие файлы разметки для этих платформ.

Разметка в WPF и Silverlight очень схожа, но имеются досадные отличия, которые сильно осложняют ее шаринг. В нашей компании эта проблема была решена несколько лет назад в виде внутреннего инструмента, который называется WPF2SL.

WPF2SL слишком специфичен, чтобы быть полезным широкой публике, поэтому мы не планируем его публиковать. В этой статье я расскажу об особенностях XSLT преобразований применительно к разметке XAML и о некоторых сложностях и особенностях, с которыми мы столкнулись.

Проект WPF2SL стартовал 4 года назад, когда мы решили создать линейки компонентов для платформ WPF и Silverlight. WPF контролы у нас были готовы раньше, поэтому возникла идея сделать шаринг разметки между платформами. В то время разрыв между разметкой WPF и Silverlight был больше, чем сейчас, потому что в Silverlight 3 не было implicit styles, markup extensions и были сильно ограничены байндинги.

Кстати, некоторые наши конкуренты пошли по другому пути. У них были сначала готовы Silverlight контролы и их линейка WPF контролов получена из априори урезанной платформы, поэтому они до сих пор в полной мере не используют всех возможностей WPF платформы.

Начнем с создания System.Xml.Xsl.XslCompiledTransform. Тут всё, как написано в MSDN. Однако следует помнить, что загрузка XSLT файла методом XslCompiledTransform.Load занимает много времени, потому что в этот момент в памяти будет создана временная сборка для конечного автомата, который описан в XSLT файле.

В одной из ранних версий WPF2SL на каждый входной XAML файл производилась полная инициализация с вызовом XslCompiledTransform.Load. Это сильно замедляло работу утилиты. В XslCompiledTransform загружается XSLT файл, содержащий описания преобразований для узлов и атрибутов исходного дерева. Преобразования в XSLT файле упорядочены по возрастанию приоритета. Правило с самым низким приоритетом — первое. Это копирующее правило.

<xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
</xsl:template>

Если для узла или атрибута не найдется более приоритетного правила, от будет скопирован.

Отсутствие DynamicResource в Silverlight

Если просто заменить DynamicResource на StaticResource, полученная разметка будет содержать много ошибок, связанных с неверным следованием ресурсов, потому что StaticResource требует, чтобы ресурс был объявлен до его использования. Решением стало ручное упорядочивание ресурсов внутри файла. XSLT шаблон для замены DynamicResource на StaticResource выглядит так.

<xsl:template match="@*">
…
<xsl:attribute name="{local-name(.)}" namespace="{namespace-uri(.)}">
         <xsl:variable name="tempValue1">
            <xsl:call-template name="globalReplace">
              <xsl:with-param name="outputString" select="."/>
              <xsl:with-param name="target" select="'DynamicResource'"/>
              <xsl:with-param name="replacement" select="'StaticResource'"/>
            </xsl:call-template>
         </xsl:variable>
         <xsl:value-of select="normalize-space($tempValue1)"/>
</xsl:attribute>
</xsl:template>
 

Проблема усложняется, когда имеются ссылки на ресурсы, объявленные в другом файле. Эту часть проблемы не удалось решить XSLT преобразованиями. Для этого у нас есть отдельный этап пост-обработки, про который надо писать отдельную статью.

Вырезание узлов и атрибутов которые отсутствуют в Silverlight

Так как WPF разметка значительно богаче Silverlight разметки, нам придется вырезать узлы и атрибуты из XAML дерева. Это очень просто делается в XSLT.
Пример вырезания атрибута:

<xsl:template match="@FocusVisualStyle"/>

Пример вырезания поддерева:

<xsl:template match="wpf:Style.Triggers"/>
Особенности преобразования ключей ресурсов

И в WPF, и в Silvelight в разметке XAML можно задать ResourceDictionary, в котором будут храниться ресурсы. Ресурсы доступны по ключу. В WPF ключом может быть любой объект, а в SL ключ должен быть обязательно строковый.

Для унификации, конечно можно ввести в WPF ограничение, чтобы ключом была только строка, но нам нравится строгая типизация, которая достижима именно на объектных ключах. В WPF возможно написать вот так

<SolidColorBrush x:Key="{dxt:FloatingContainerThemeKey ResourceKey=FloatingContainerBackground}" Color="#FFA3C3EC" />

Где FloatingContainerThemeKey — это специальный дженерик объект, унаследованный от System.Windows.ResourceKey. Дженерик принимает параметром тип Enum, который описывает возможные названия ключей.

public class FloatingContainerThemeKeyExtension : ThemeKeyExtensionBase<FloatingContainerThemeKey> { }
public enum FloatingContainerThemeKey {
        FloatingContainerAdornerTemplate,
        FloatingContainerPopupTemplate,
        FloatingContainerWindowTemplate,
   }

Применение XSLT преобразований для конвертации XAML между различными платформами

За счет этого в WPF сложнее ошибиться в названии ключа в объявлении ресурса или в ссылке на ресурс.

Вернемся к преобразованию XAML. В Silverlight объектных ключей нет, поэтому

<SolidColorBrush x:Key="{dxt:FloatingContainerThemeKey ResourceKey=FloatingContainerBackground}" Color="#FFA3C3EC" />

преобразуется в строчку

<SolidColorBrush x:Key="FloatingContainerThemeKey_FloatingContainerBackground" Color="#FFA3C3EC" />
XML namespaces

Многие схожие элементы в WPF и Silverlight находятся в разных xml неймспейсах. Это различие породило вот такие шаблоны.

<xsl:template match="wpf:Label">
    <sdk:Label xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
      <xsl:apply-templates select="@* | node()"/>
    </sdk:Label>
</xsl:template>

<xsl:template match="wpf:TreeView">
    <sdk:TreeView xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
      <xsl:apply-templates select="@* | node()"/>
    </sdk:TreeView>
</xsl:template>

Когда мы поняли, что таких шаблонов придется сделать очень много, мы создали своего наследника стандартного класса XmlTextWriter, у которого перегружен метод WriteString.

public override void WriteString(string text) {
 if(NamespacesReplacementTable.ContainsKey(text)) base.WriteString(NamespacesReplacementTable[text]);
            else base.WriteString(text);

…
}

Этого наследника можно отдать в метод XslCompiledTransform.Transfrom(reader, writer) в качестве второго параметра. Перегруженый WriteString в соответствии с таблицей замен подменяет неймспейс при записи.

Интеграция в процесс компиляции

WPF2SL — это консольное приложение. В наших SL проектах на Pre-build event прописан вызов WPF2SL с соответствующими параметрами.

Но тут не все просто как кажется. Практически у всех сейчас машины с многоядерными процессорами, на которых msbuild делает одновременную сборку сразу для нескольких проектов. WPF2SL в процессе работы создавала временные файлы в Temp. Поскольку их названия совпадали, возникал конфликт доступа. Проблема была решена добавлением ID процесса к названию файла.

Диагностика проблем в XSLT преобразовании

К сожалению, удобного средства диагностики XSLT преобразований нет (по крайней мере, автору о них не известно). Когда какие-то из XSLT преобразований работают не так, как ожидается, самый действенный способ — итеративная модификация XSLT с анализом результатов. Если результат преобразования сильно отличается от ожидаемого, смело помещайте в коментарий половину XSLT файла; если всё еще не понятно, еще половину и так далее. Этот способ получил у нас название: «метод половинного комента».

Описанный способ является универсальным для всех декларативных языков, в том числе и для XAML. Если не понятно, какой из шаблонов сформировал неверную строчку в выходном файле, можно временно в шаблон вписать строчку, которая позволит однозначно его идентифицировать.

Выводы

XSLT преобразования хорошо работают в задаче конвертации XAML разметки между различными платформами, а .NET реализация XSLT преобразований XslCompiledTransform достаточно гибкая, производительная и расширяемая.

Литература

Сэл Мангано. XSLT. Сборник рецептов

Автор: xtraroman

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js