Могу ли я вставить экземпляр xi:fallback по умолчанию перед обработкой XInclude?

Скажем, у меня есть исходный XML-документ, который использует XInclude, например:

<?xml version="1.0" encoding="UTF-8"?>
<parent xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="parent01">
   <xi:include href="child01.xml"/>
   <xi:include href="child02.xml"/>
   <xi:include href="child03.xml"/>
</parent>

Три других XML-документа, которые он вызывает в XInclude, выглядят так:

child01.xml:

<?xml version="1.0" encoding="UTF-8"?>
<children>
   <child xml:id="child01">
      <p>This is child 1.</p>
   </child>
</children>

child02.xml:

<?xml version="1.0" encoding="UTF-8"?>
<children>
   <child xml:id="child02">
      <p>This is child 2.</p>
   </child>
</children>

child03.xml:

<?xml version="1.0" encoding="UTF-8"?>
<children>
   <child xml:id="child03">
      <p>This is child 3.</p>
   </child>
</children>

У меня есть XSLT 2.0 преобразование, как это:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
   <xsl:output method="xml" encoding="UTF-8" indent="yes"/>

   <xsl:strip-space elements="*"/>

   <xsl:template match="/">
      <xsl:apply-templates select="parent"/>
   </xsl:template>

   <xsl:template match="parent">
      <volume>
         <xsl:apply-templates select="@*|.//child"/>
      </volume>
   </xsl:template>

   <xsl:template match="child">
      <chapter>
         <xsl:apply-templates select="@*|*|text()"/>
      </chapter>
   </xsl:template>

   <xsl:template match="@*|*|text()">
      <xsl:copy copy-namespaces="no">
         <xsl:apply-templates select="@*|*|text()"/>
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

Когда все файлы, на которые ссылается XInclude, находятся в той же папке, что и parent01.xml, мое преобразование работает нормально и выдает следующий вывод:

<?xml version="1.0" encoding="UTF-8"?>
<volume xml:id="parent01">
   <chapter xml:id="child01">
      <p>This is child 1.</p>
   </chapter>
   <chapter xml:id="child02">
      <p>This is child 2.</p>
   </chapter>
   <chapter xml:id="child03">
      <p>This is child 3.</p>
   </chapter>
</volume>

Однако если один файл, скажем, child02.xml, отсутствует, преобразование завершается неудачно.

Этот сбой можно было бы предотвратить, если бы parent01.xml включал элементы xi: fallback, например:

<?xml version="1.0" encoding="UTF-8"?>
<parent xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="parent01">
   <xi:include href="child01.xml">
      <xi:fallback>
         <child>
            <p>The file is missing.</p>
         </child>
      </xi:fallback>
   </xi:include>
   <xi:include href="child02.xml">
      <xi:fallback>
         <child>
            <p>The file is missing.</p>
         </child>
      </xi:fallback>
   </xi:include>
   <xi:include href="child03.xml">
      <xi:fallback>
         <child>
            <p>The file is missing.</p>
         </child>
      </xi:fallback>
   </xi:include>
</parent>

Тогда результат был бы следующим:

<?xml version="1.0" encoding="UTF-8"?>
<volume xml:id="parent01">
   <chapter xml:id="child01">
      <p>This is child 1.</p>
   </chapter>
   <chapter>
      <p>The file is missing.</p>
   </chapter>
   <chapter xml:id="child03">
      <p>This is child 3.</p>
   </chapter>
</volume>

Мой вопрос заключается в следующем: можно ли написать мое XSLT-преобразование для вставки экземпляра xi: fallback в каждый xi: include перед обработкой XInclude, то есть для добавления экземпляра xi: fallback по умолчанию, где его нет, а затем для обработки XInclude, как если бы этот экземпляр xi: fallback присутствовал?

Спасибо за любой совет, который может предложить любой.

1 ответ

Решение

Расширяю свой комментарий до полного ответа, так как это интересный вопрос!

Преобразования XSLT работают не непосредственно с текстовым содержимым документов XML, а с древовидным представлением содержимого (DOM, XDM). Это представление или модель ввода обеспечивается синтаксическим анализатором XML, который теоретически может быть полностью независимым от процессора XSLT.

Теперь важный момент: синтаксический анализатор XML отвечает за выполнение XInclusion, а не процессор XSLT. Как только процессор XSLT увидит модель документа, нет способа узнать, имелось ли XInclusion. И нет, насколько мне известно, нет способа получить доступ к дереву документов как до, так и после XInclude за один шаг преобразования XSLT. Вы можете обрабатывать одни и те же входные узлы дважды в другом режиме, но вам также необходимо иметь возможность управлять функцией XInclude синтаксического анализатора XML из преобразования XSLT, что невозможно.

Я предлагаю вам сделать небольшой обход и решить вашу проблему в два этапа: написать XSLT-преобразование, которое вы применяете без XInclude (намеренно отключая это в настройках XML-анализатора вашей XML IDE, такой как Oxygen или в командной строке), чтобы исправить отсутствующие откаты:

XSLT исправит ошибки

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xi="http://www.w3.org/2001/XInclude"
    version="2.0">

    <xsl:output method="xml" indent="yes"/>

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

    <xsl:template match="xi:include[not(xi:fallback)]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xi:fallback>
                <child>
                    <p>The file is missing.</p>
                </child>
            </xi:fallback>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

После этого временный выходной файл будет выглядеть так:

<?xml version="1.0" encoding="UTF-8"?>
<parent xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="parent01">
    <xi:include href="child01.xml">
        <xi:fallback>
            <child>
                <p>The file is missing.</p>
            </child>
        </xi:fallback>
    </xi:include>
    <xi:include href="child02.xml">
        <xi:fallback>
            <child>
                <p>The file is missing.</p>
            </child>
        </xi:fallback>
    </xi:include>
    <xi:include href="child03.xml">
        <xi:fallback>
            <child>
                <p>The file is missing.</p>
            </child>
        </xi:fallback>
    </xi:include>
</parent>

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


Если это неприемлемо для вас, вы можете взглянуть на XIPr, процессор XInclude, написанный Эриком Уайльдом исключительно для XSLT 2.0. Импортируя таблицы стилей XIPr в исходные таблицы стилей XSLT, вы можете сначала предоставить недостающие запасные варианты, как я показал вам выше, а затем обработать результат с помощью mode="xipr", В этом случае вам следует отключить любую другую обработку XInclude с помощью вашей IDE или инструмента командной строки.

Вот как вы могли бы это сделать (да, это немного усложняется):

Прежде всего, href Атрибуты, указывающие на ваши файлы, должны быть абсолютными из-за особенностей процессора XIPr:

Ввод XML

<?xml version="1.0" encoding="UTF-8"?>
<parent xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="parent01">
    <xi:include href="file:/Users/User/Desktop/child01.xml"/>
    <xi:include href="file:/Users/User/Desktop/child02.xml"/>
    <xi:include href="file:/Users/User/Desktop/child03.xml"/>
</parent>

Таблица стилей XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
    xmlns:xi="http://www.w3.org/2001/XInclude"
    exclude-result-prefixes="xi">

    <xsl:import href="xipr.xsl"/>

    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/">
        <xsl:variable name="fixedfallbacks">
            <xsl:apply-templates select="." mode="fixfallbacks"/>
        </xsl:variable>
        <xsl:variable name="xincluded">
            <xsl:apply-templates select="$fixedfallbacks" mode="xipr"/>
        </xsl:variable>
        <xsl:apply-templates select="$xincluded/*" mode="#default"/>
    </xsl:template>

    <xsl:template match="xi:include[not(xi:fallback)]" mode="fixfallbacks">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" mode="fixfallbacks"/>
            <xi:fallback>
                <child>
                    <p>The file is missing.</p>
                </child>
            </xi:fallback>
        </xsl:copy>
    </xsl:template>

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

    <xsl:template match="parent">
        <volume>
            <xsl:apply-templates select="@*|.//child"/>
        </volume>
    </xsl:template>

    <xsl:template match="child">
        <chapter>
            <xsl:apply-templates select="@*|*|text()"/>
        </chapter>
    </xsl:template>

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

</xsl:stylesheet>

Перед выполнением этого вам нужно отключить опцию XInclude вашего XML-парсера, скачать здесь таблицу стилей XIPr, открыть ее и заменить строку 52

<xsl:variable name="include-uri" select="resolve-uri(@href, document-uri(/))"/>

с

<xsl:variable name="include-uri" select="resolve-uri(@href)"/>

Вы должны сделать это, потому что вы просите XIPr XInclude промежуточный результат, который является временным деревом. Если вы используете document-uri(/) в таком дереве он вернет пустую последовательность, которая не допускается в качестве второго аргумента resolve-uri(),

Теперь, наконец, если один из файлов не существует, результат будет

Окончательный вывод XML

<?xml version="1.0" encoding="UTF-8"?>
<volume xml:id="parent01">
   <chapter>
      <p>The file is missing.</p>
   </chapter>
   <chapter xml:id="child02">
      <p>This is child 2.</p>
   </chapter>
   <chapter xml:id="child03">
      <p>This is child 3.</p>
   </chapter>
</volume>
Другие вопросы по тегам