Является ли xsl:sequence всегда непустым?

Я не понимаю вывод из этой таблицы стилей:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:apply-templates select="root/sub"/>
    </xsl:template>

    <xsl:template match="sub">
        <xsl:variable name="seq">
            <xsl:sequence select="*" />
        </xsl:variable>

        <xsl:message>
            <xsl:value-of select="@id" />
            <xsl:text>: </xsl:text>
            <xsl:value-of select="count($seq)" />
        </xsl:message>
    </xsl:template>
</xsl:stylesheet>

применительно к следующему XML:

<root>
    <sub id="empty" />
    <sub id="one"><one/></sub>
    <sub id="two"><one/><one/></sub>
    <sub id="three"><one/><one/><one/></sub>
</root>

Вывод, написанный xsl:message элемент, это:

empty: 1
one: 1
two: 1
three: 1

Я ожидал этого вместо этого:

empty: 0
one: 1
two: 2
three: 3

Почему count($seq) всегда возвращать 1 в этом случае? Как бы вы изменили определение переменной, чтобы я потом смог проверить ее на пустоту? (Просто <xsl:variable name='seq' select='*' /> вернул бы ожидаемый ответ, но не вариант... хочу поменять between переменная в этом шаблоне, и проверьте ее на пустоту позже).

3 ответа

Решение

Позвольте мне попытаться ответить на вопрос "почему" на ваш вопрос.

Если вы напишите следующее заявление:

<xsl:variable name="x" select="*" />

переменная $x содержит последовательность дочерних узлов текущего узла. Там нет неявного родительского узла в $x потому что вы используете select, Теперь рассмотрим следующее:

<xsl:variable name="x">
    <content />
    <content />
</xsl:variable>

где переменная $x содержит последовательность одного узла: родительский узел content, Вот, count($x) всегда даст вам 1, Чтобы получить сумму content элементы, вам нужно сосчитать детей $x неявный корневой узел: count($x/content),

Как правило, вы можете помнить, что: если xsl:variable сам по себе пустой элемент с select атрибут, набор результатов будет назначен переменной. Если вы используете xsl:variable без select атрибут, вы всегда создаете неявный родительский элемент с переменной, а содержимое переменной - дочерние элементы под ним.

То же самое относится к вашему примеру с xsl:sequence в детстве xsl:variable, Имея непустой xsl:variable вы создаете неявного родителя для последовательности, поэтому вы всегда получаете 1 если вы посчитаете саму переменную.

Если вам нужно и то, и другое: пачка утверждений внутри xsl:variable, но поведение select атрибут, вы можете обойти это, используя следующее:

<xsl:variable name="x" select="$y/*" />

<xsl:variable name="y">
    <xsl:sequence select="foo" />
</xsl:variable>

который теперь даст ожидаемую сумму для count($x) Но спорным является ли это действительно полезным или делает ваш код более понятным.

Это работает так, как вы можете ожидать, если вы либо измените объявление переменной на:

<xsl:variable name="seq" select="*"/>

или объявите тип переменной, используя атрибут "as":

<xsl:variable name="seq" as="item()*">
        <xsl:sequence select="*" />
</xsl:variable>

Не указание какой-либо информации о типе часто приводит к удивительным результатам в XSLT 2.0. Если вы используете Saxon, вы можете вывести, как Saxon интерпретирует таблицу стилей, используя атрибут расширения объяснения:

    <xsl:template match="sub" saxon:explain="yes" xmlns:saxon="http://saxon.sf.net/">
    <xsl:variable name="seq">
        <xsl:sequence select="*" />
    </xsl:variable>

    <xsl:message>
        <xsl:value-of select="@id" />
        <xsl:text>: </xsl:text>
        <xsl:value-of select="count($seq)" />
    </xsl:message>
</xsl:template>

Как вы можете видеть, Saxon создает узел документа из последовательности:

Optimized expression tree for template at line 6 in :
                    let $seq[refCount=1] as document-node() :=
                      document-constructor
                        child::element()
                    return
                      message

Вы выбираете подчиненные узлы, а затем подсчитываете каждый узел - так что это всегда будет 1. Вам нужно сосчитать детей, например:

<xsl:value-of select="count($seq/*)" />

даст вам ожидаемый результат.

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