Главная » XSLT » Сравнение наборов узлов на равенство по значению

0

Задача

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

Решение

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

<xsl:template name="vset:equal-text-values"> <xsl:param name="nodes1" select="/.."/> <xsl:param name="nodes2" select="/.."/> <xsl:choose>

<!– Пустые наборы узлов имеют равные значения –> <xsl:when test="not($nodes1) and not($nodes2)"> <xsl:value-of select="true()"/> </xsl:when>

<!– Наборы из разного числа узлов не могут иметь равные значения –> <xsl:when test="count($nodes1) != count($nodes2)"/>

<!– Если элемент из набора nodes1 присутствует в наборе nodes2, то наборы узлов имеют одинаковые значения, если то же самое можно сказать о наборах, из которых общий элемент удален. –> <xsl:when test="$nodes1[1] = $nodes2"> <xsl:call-template name="vset:equal-text-values">

<xsl:with-param name="nodes1" select="$nodes1[position()>1]"/> <xsl:with-param name="nodes2"

select="$nodes2[not(. = $nodes1[1])]"/>

</xsl:call-template> </xsl:when> <xsl:otherwise/> </xsl:choose> </xsl:template>

Мы выбрали для этого шаблона имя equal-text-values, чтобы подчерк­нуть контекст, в котором он должен применяться. Именно, равенство значений озна­чает равенство текстовых значений. Очевидно, что этот шаблон не даст правильного результата, если равенство определено на основе значений атрибутов или еще бо­лее сложного критерия. Однако, в нем есть и более тонкая ошибка. Молчаливо предполагается, что сравниваемые наборы узлов – настоящие множества (то есть не содержат дубликатов) относительно сравнения по текстовому значению. Иногда это неверно. Рассмотрим следующий XML-документ, описывающий чи­тателей, взявших книги в библиотеке:

<?xml version="1.0" encoding="UTF-8"? <library> <book>

<name>High performance Java programming.</name> <borrowers>

<borrower>James Straub</borower> </borrowers> </book> <book>

<name>Exceptional C++</name> <borrowers>

<borrower>Steven Levitt</borower> </borrowers> </book> <book>

<name>Design Patterns</name> <borrowers>

<borrower>Steven Levitt</borower> <borrower>James Straub</borower> <borrower>Steven Levitt</borower> </borrowers> </book> <book>

<name>The C++ Programming Language</name> <borrowers>

<borrower>James Straub</borower> <borrower>James Straub</borower> <borrower>Steven Levitt</borower> </borrowers> </book> </library>

Если имя читателя появляется более одного раза, это просто означает, что он несколько раз брал книгу. Если нужно найти книги, которые брали одни и те же люди, то вы, наверное, согласитесь, что Design Patterns и The C+ + Programming Language должны быть найдены. Однако, если в реализации такого запроса воспользоваться шаблоном vset:equal-text-values, то результат будет иным, поскольку в нем предполагается, что множества не содержат дубликатов.

Чтобы разрешить дубликаты, можно модифицировать vset:equal-text- values следующим образом:

<xsl:template name="vset:equal-text-values-ignore-dups"> <xsl:param name="nodes1" select="/.."/> <xsl:param name="nodes2" select="/.."/> <xsl:choose>

<!– Пустые нaбopы узлов имеют paвные значения –> <xsl:when test="not($nodes1) and not($nodes2)"> <xsl:value-of select="true()"/> </xsl:when>

<!– Если элемент из Ha6opa nodes1 пpиcyтcтвyет в нaбopе nodes2, то Ha6opbi узлов имеют одинаковые значения, если то же самое можно сказать о Ha6opax, из ra^phix общий элемент удален. –> <!– удалить эту строку

<xsl:when test="count($nodes1) != count($nodes2)"/> –> <xsl:when test="$nodes1[1] = $nodes2">

<xsl:call-template name="vset:equal-text-values"> <xsl:with-param name="nodes1"

select="$nodes1[not(. = $nodes1[1])]"/> <xsl:with-param name="nodes2" select="$nodes2[not(. = $nodes1[1])]"/>       </xsl:call-template>

</xsl:when> <xsl:otherwise/> </xsl:choose>

</xsl:template>

Обратите внимание, что проверка на равенство размеров закомментирована, поскольку при наличии дубликатов она была бы лишней. Например, в одном на­боре может быть три вхождения элемента с текстовым значением foo, а в другом – только одно. Если дубликаты игнорируются, то эти наборы следует считать равными. Кроме того, на шаге рекурсии недостаточно просто удалить первый элемент; необходимо удалить все элементы с таким же значением, как у перво­го, как то делается для второго набора. При этом на каждом шаге рекурсии дублика­ты учитываются в полной мере. После таких изменений все проверки на равенство, базирующиеся на текстовом значении, оказываются корректными, но за это прихо­дится платить дополнительной обработкой множеств, которые заведомо не равны.

Описанные выше проверки на равенство не такие общие, как в операциях над множествами значений из рецепта 9.2, поскольку предполагается, что единственный вид равенства, который нас интересует, – это равенство текстовых значений. Можно обобщить шаблон, повторно воспользовавшись рассмотренной ранее тех­никой проверки на членство, основанной на равенстве элементов, которое можно переопределить в импортирующей таблице:

<xsl:template name="vset:equal">

<xsl:param name="nodes1" select="/.."/>

<xsl:param name="nodes2" select="/.."/> <xsl:if test="count($nodes1) = count($nodes2)"> <xsl:call-template name="vset:equal-impl">

<xsl:with-param name="nodes1" select="$nodes1"/> <xsl:with-param name="nodes2" select="$nodes2"/> </xsl:call-template> </xsl:if> </xsl:template>

<!— Зная, что число элементов в обоих наборах одинаково, –> <!– осталось только проверить, что каждый член первого набора –> <!– является и членом второго –> <xsl:template name="vset:equal-impl">

<xsl:param name="nodes1" select="/.."/> <xsl:param name="nodes2" select="/.."/> <xsl:choose>

<xsl:when test="not($nodes1)">

<xsl:value-of select="true()"/> </xsl:when> <xsl:otherwise> <xsl:variable name="test">

<xsl:apply-templates select="$nodes2" mode="vset:member-of">

<xsl:with-param name="elem" select="$nodes1[1]"/> </xsl:apply-templates> </xsl:variable> <xsl:if test="string($test)">

<xsl:call-template name="vset:equal-impl">

<xsl:with-param name="nodes1" select="$nodes1[position() > 1]"/> <xsl:with-param name="nodes2" select="$nodes2"/> </xsl:call-template> </xsl:if> </xsl:otherwise> </xsl:choose> </xsl:template>

Если вы хотите, чтобы обобщенное равенство работало и при наличии дубли­катов, то придется применить более грубый подход, выполнив два прохода по на­борам узлов:

<xsl:template name="vset:equal-ignore-dups"> <xsl:param name="nodes1" select="/.."/> <xsl:param name="nodes2" select="/.."/>

<xsl:variable name="mismatch1"> <xsl:for-each select="$nodes1">

<xsl:variable name="test-elem">

<xsl:apply-templates select="$nodes2" mode="vset:member-of">

<xsl:with-param name="elem" select="."/> </xsl:apply-templates> </xsl:variable>

<xsl:if test="not(string($test-elem))">

<xsl:value-of select=" ‘false’ "/> </xsl:if> </xsl:for-each> </xsl:variable>

<xsl:if test="not($mismatch1)"> <xsl:variable name="mismatch2"> <xsl:for-each select="$nodes2">

<xsl:variable name="test-elem">

<xsl:apply-templates select="$nodes1" mode="vset:member-of">

<xsl:with-param name="elem" select="."/> </xsl:apply-templates> </xsl:variable>

<xsl:if test="not(string($test-elem))">

<xsl:value-of select=" ‘false’ "/> </xsl:if> </xsl:for-each> </xsl:variable>

<xsl:if test="not($mismatch2)">

<xsl:value-of select="true()"/> </xsl:if> </xsl:if> </xsl:template>

В этом шаблоне во время обхода первого набора узлов ищутся элементы, отсутствующие во втором. Если таких элементов не найдено, то переменная $mismatch1 будет равна null. В таком случае нужно повторить проверку, но на этот раз обойти второй набор узлов.

Обсуждение

Необходимость сравнивать множества на равенство часто возникает при вы­полнении запросов. Рассмотрим следующие задачи:

?               найти все книги, написанные одними и теми же авторами;

?               найти всех поставщиков, поставляющих одинаковый набор деталей;

?               найти все семьи, в которых есть дети одного возраста.

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

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии