Таблица стилей XSLT заменяет самозакрывающиеся теги пустыми парными тегами

Я использую XSLT для обработки файла ASP.Net web.config для вставки дополнительной конфигурации log4net. Применяется стандартной задачей NANT, которая называется <style>, Успешно вставляя новый контент, он превращает множество самозакрывающихся тегов в пустые парные теги. Например, частичный файл web.config выглядит следующим образом:

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<configSections>
    <section name="log4net"
             type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<appSettings>
    <add key="SomeKey" value="SomeValue"/>
</appSettings>

После применения таблицы стилей <section> а также <add> теги (и все остальные теги) больше не являются самозакрывающимися:

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
    <configSections>
        <section name="log4net"
         type="log4net.Config.Log4NetConfigurationSectionHandler, log4net">
        </section>
    </configSections>
    <appSettings>
        <add key="SomeKey" value="SomeValue">
        </add>
    </appSettings>

Моя таблица стилей выглядит так:

<?xml version="1.0" encoding="utf-8"?>
<!-- This stylesheet is applied to web.config files to insert log4net appender
filters that will prevent logging messages resulting from pages requested by
AIS monitoring systems. -->
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
    exclude-result-prefixes="msxsl">
    <xsl:output method="xml" indent="yes" />
    <xsl:preserve-space elements="configuration"/>
    <!-- Copy input to output, most of the time -->
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" />
        </xsl:copy>
    </xsl:template>

    <!-- Within log4net <appender> elements, insert standard filters to
    exclude logging traffic resulting from AIS monitoring.  Any existing
    filters are preserved. -->
    <xsl:template match="/configuration/log4net/appender">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" />
            <xsl:comment
            > Filters inserted by build server during deployment </xsl:comment>
            <filter name="AIS monitor"
             type="log4net.Filter.PropertyFilter">
                <regexToMatch value="^35\.8\.113\.[0-9]+$"/>
                <key value="ClientIP"/>
                <acceptOnMatch value="false"/>
            </filter>
            <filter name="AIS load balancer"
             type="log4net.Filter.PropertyFilter">
                <regexToMatch value="^10\.160\.0\.[0-9]+$" />
                <key value="ClientIP"/>
                <acceptOnMatch value="false"/>
            </filter>
            <filter name="localhost" type="log4net.Filter.PropertyFilter">
                <stringToMatch value="127.0.0.1"/>
                <key value="ClientIP"/>
                <acceptOnMatch value="false"/>
            </filter>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Прежде чем использовать NANT для обработки таблицы стилей, я попробовал MSBuild, используя задачу пакета расширений MSBuild. XmlTask, Он сохранил самозакрывающиеся теги, но потерял бы большую часть разрывов строк, что делало файл нечитаемым (хотя в остальном корректным). Использование NANT прекрасно вписывается в мой процесс сборки, поэтому я бы предпочел использовать его, если смогу.

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

4 ответа

Решение

Самозакрывающиеся теги <empty/> и пустой элемент с начальным и конечным тегами <empty></empty> семантически идентичны. Поэтому процессор XSLT может выводить то, что видит лучше.

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

<!-- Define a dummy variable with empty content -->
<xsl:variable name="empty" select="''"/>

<!-- Copy input to output, most of the time -->
<xsl:template match="@* | node()">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()" />
<!-- Insert empty content into copied element -->
        <xsl:value-of select="$empty"/>
    </xsl:copy>
</xsl:template>

Или вы можете ограничить это пустыми элементами, сохранив исходный шаблон идентификации и добавив следующее:

<!-- Identity template for empty elements -->
<xsl:template match="*[not(node())]">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()" />
        <xsl:value-of select="$empty"/>
    </xsl:copy>
</xsl:template>

Функциональность зависит от процессора XSLT.

Примечание. Правильный инструмент XML не должен иметь разницы между самозакрывающимися тегами или пустыми элементами с начальным и конечным тегами. Поэтому, если эта разница в синтаксисе действительно вызывает у вас проблемы, вам следует пересмотреть, какие методы или инструменты вы используете или как вы их используете.


Обновить

Дерьмо. Каким-то образом я продолжал читать ваш вопрос в противоположном вам смысле (вероятно, это значит, что мне пора вздремнуть). Итак... приведенный выше код пытается преобразовать самозакрывающиеся теги в пустые пары, тогда как вы хотели противоположного <tag></tag> -> <tag/>, Извините за несоответствие, позвольте мне попробовать еще раз.

В комментарии вы сказали:

До этого он был на новой строке и с отступом в ту же позицию, что и открывающий тег.

С точки зрения модели данных XML это означает, что узел имеет только пустой пробел в качестве дочернего содержимого. Один из способов избежать копирования этих текстовых узлов - предоставить для них пустой шаблон. Это может вызвать проблемы со смешанным контентом.

<xsl:template match="text()[normalize-space() = '']"/>

Другое возможное решение состоит в том, что, когда мы сталкиваемся с пустыми элементами, мы создаем новый элемент с тем же именем, а не копируем его.

<xsl:template match="*[not(comment() | processing-instruction() | *)][normalize-space(text()) = '']">
    <xsl:element name="{name()}" namespace="{namespace-uri()}">
        <xsl:for-each select="@* | namespace::*">
            <xsl:copy/>
        </xsl:for-each>
    </xsl:element>
</xsl:template>

Этот шаблон также копирует (неиспользуемые) определения пространства имен в пустые элементы, что на самом деле кажется совершенно ненужным. <xsl:for-each> используется вместо <xsl:apply-templates> только потому что template match не позволяет использовать namespace ось. <xsl:apply-templates select="@*"/> тоже работает, если вы не хотите сохранять дополнительные определения пространства имен.

Но, в конце концов, AFAIK - это все обходные пути для конкретных процессоров. Когда процессор XSLT 1.0 создает пустой элемент, он может свободно выбирать, использовать ли самозакрывающийся тег или пустую пару. Кто-нибудь, пожалуйста, поправьте меня, если я ошибаюсь.

Просто добавьте комментарий в элемент, он не закроется сам.

Почему бы не использовать

<xsl:output method="html" />

Большинство процессоров XSLT сериализуют пустой элемент в выводе как <x/> скорее, чем <x></x>, Мне не ясно, какой процессор XSLT вы используете или почему он этого не делает, но он полностью в пределах своих прав - эти две конструкции на 100% эквивалентны, и любой, кто правильно использует XML, не будет заботиться о том, какая форма используется.

Другие вопросы по тегам