Используйте один шаблон для атрибута (по значению) и другой для (родительского) узла

Я получаю это неоднозначное предупреждение о совпадении с большим (420 строк) XSL-преобразованием большого (со вкусом TEI) XML-файла (~6000 строк) (с использованием Saxon-HE 9.5.1.6J в OS X). Я хотел бы понять (и исправить) предупреждение.

 Recoverable error 
  XTRE0540: Ambiguous rule match for /TEI/text[1]/group[1]/text[1]/body[1]/lg[33]/head[2]
 Matches both "tei:lg[@type='poem']/tei:head" on line 103 of
  file: hs2latex.xsl
 and "*[@rend='italics']" on line 110 of
  file: hs2latex.xsl

XML выглядит примерно так:

<lg type='poem'>
<head rend='italics'>Sonnet 3<head>
...
</lg>

С конфликтующими правилами XSL выглядит примерно так:

<xsl:template match="tei:lg[@type='poem']/tei:head">
...
<xsl:apply-templates />
</xsl:template>

а также

<xsl:template match="*[@rend='italics']"><!-- blah blah --></xsl:template>

Поскольку атрибут - это просто еще один узел, я подумал, что могу сопоставить его отдельно. Но если у меня есть только один атрибут в моем совпадении, я получаю ошибку, поэтому я ставлю звездочку используется для сопоставления всех узлов с атрибутами rend='italics', что затем приводит к неоднозначной ошибке, указанной выше.

Можно ли сделать то, что я пытаюсь здесь, а именно использовать один шаблон для сопоставления атрибутов, основанных на значении (независимо от типа элемента)? Мне интересно, чтобы один шаблон обрабатывал любой элемент, например, с атрибутом "@rend='italics'".

Я попытался воспроизвести эту проблему с минимальным рабочим примером, но придумал немного другой пример (который, возможно, приводит именно к тому, что я неправильно понимаю).

Минимальный рабочий XML

<?xml version="1.0" encoding="utf-8"?>

<document>
  <book>
    <title>"One Title"</title>
  </book>

  <book>
    <title rend="italics">Another Title</title>
  </book>
</document>

и минимальный XSLT

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="2.0" 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        exclude-result-prefixes="xsl">
  <xsl:output omit-xml-declaration="yes" />
  <xsl:template match="/">
    <xsl:apply-templates />
  </xsl:template>

  <xsl:template match="*[@rend='italics']">
    <italics><xsl:apply-templates /></italics>
  </xsl:template>

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

</xsl:stylesheet>

Этот минимальный пример (который, как я думал, создает ситуацию, идентичную той, которую я описал выше), не приводит к неоднозначной ошибке соответствия, но вместо этого приводит к следующему выводу:

<title>"One Title"</title>
<italics>Another Title</italics>

То, что я хотел (в этом минимальном примере) было бы:

<title>"One Title"</title>
<title><italics>Another Title</italics></title>

Я подозреваю, что неправильно понимаю что-то базовое в XSLT или в XPath, но сейчас я в растерянности и буду признателен за любые рекомендации. Большое спасибо.

2 ответа

Решение

Можно ли сделать то, что я пытаюсь здесь, а именно использовать один шаблон для сопоставления атрибутов, основанных на значении (независимо от типа элемента)? Мне интересно, чтобы один шаблон обрабатывал любой элемент, например, с атрибутом "@rend='italics'".

Да, это возможно. Проблема в вашем коде в том, что у вас есть два соответствующих шаблона формы NodeTest[predicate], что оба имеют одинаковый приоритет по умолчанию. Если вы хотите, чтобы один имел приоритет над другим, вы должны добавить priority="X", где X это любое число. То есть:

<xsl:template match="*[@rend='italics']" priority="2">
  <italics><xsl:apply-templates /></italics>
</xsl:template>

Этот минимальный пример (который, как я думал, создает ситуацию, идентичную той, которую я описал выше), не приводит к неоднозначной ошибке соответствия, но вместо этого приводит к следующему выводу

Правильный. Это связано с тем, что в XSLT значения по умолчанию назначаются приоритетам примерно на основе сложности шаблона соответствия. Просто NodeTest имеет более низкий приоритет, чем NodeTest[predicate],

Поскольку атрибут - это просто еще один узел, я подумал, что могу сопоставить его отдельно.

Да, ты можешь. Вы не показали, что вы пытались с атрибутом соответствия, но это должно выглядеть примерно так: match="@rend" или же match="@rend[. = 'italics']", Однако следует помнить, что атрибуты являются специальными узлами. Вам необходимо специально применять шаблоны к атрибутам, чтобы иметь возможность их сопоставлять. Кроме того, узел, имеющий фокус, будет самим узлом атрибута, поэтому вам, возможно, придется пройтись по родительской оси, чтобы получить те же результаты, что и в данный момент.

То, что я хотел (в этом минимальном примере) было бы:

Кажется, что вы хотите, чтобы при совпадении более общего совпадения вы хотели, чтобы конкретное совпадение также применялось к тому же узлу. Любой узел соответствует не более одного соответствующего шаблона. Чтобы один узел соответствовал нескольким шаблонам, вы можете использовать xsl:next-match инструкция. Тем не менее, это работает от конкретного (который сопоставляется первым) до общего (который сопоставляется последним).

В вашем случае вы хотите обратное. Я хотел бы сделать что-то вроде этого, что даст ожидаемый результат (все элементы заголовка сначала соответствуют шаблону заголовка из-за явного приоритета, и только элементы курсива также соответствуют шаблону курсива, добавляя <italics> на выход):

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

<xsl:template match="*[@rend='italics']">
    <italics><xsl:apply-templates /></italics>
</xsl:template>

<xsl:template match="title" priority="2">
    <title><xsl:next-match /></title>
</xsl:template>

Возможно, вы захотите применить подобный шаблон кодирования к вашему большему примеру, в противном случае, <italics> это единственное совпадение, и я думаю, что вы хотите, чтобы и там тоже совпадало, и в правильном порядке (сначала общий, затем конкретный).

Авель уже дал хороший ответ, поэтому я пишу еще один ответ:

...but if I understand you, it is my only real option.

Это далеко не так - как это часто бывает с программированием, есть несколько способов сделать это. Я бы не сказал, что xsl:next-match это уродливо, но это подрывает принцип, который присутствует во многих таблицах стилей, а именно, что каждый узел обрабатывается только один раз и что для него найден один подходящий шаблон.

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

<xsl:template match="title/@*">
  <xsl:element name="{.}">
     <xsl:apply-templates select="../text()"/>
  </xsl:element>
</xsl:template>

Если я правильно понимаю, это то, что вы пытались сделать в первую очередь. Шаблон выше соответствует любому атрибуту title элементы. Затем создается новый элемент, его имя соответствует значению атрибута (в данном случае "курсив"). Наконец, встроенный шаблон для текстовых узлов применяется для обработки текстового содержимого родительского узла атрибута.

стилей

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="2.0" 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
  <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

  <xsl:template match="/document">
    <xsl:copy>
        <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="title/@*">
    <xsl:element name="{.}">
        <xsl:apply-templates select="../text()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="title">
    <title>
        <xsl:choose>
            <xsl:when test="@*">
                <xsl:apply-templates select="@*"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates/>
            </xsl:otherwise>
        </xsl:choose>
    </title>
  </xsl:template>

</xsl:stylesheet>

Как примечание, вам не нужно исключать пространство имен XSLT из дерева результатов, используя exclude-result-prefixes="xsl", Что-нибудь с префиксом xsl: распознается как выполняемая инструкция, и пространство имен по умолчанию исключается из полученного XML.

Вывод XML

<document>
   <title>"One Title"</title>
   <title>
      <italics>Another Title</italics>
   </title>
</document>
Другие вопросы по тегам