Главная » XSLT » Выполнение теоретико-множественных операций над наборами узлов с использованием семантики значений

0

Задача

Требуется найти объединение, пересечение, разность или симметрическую разность двух наборов узлов. Однако теперь равенство определено не как иден­тичность наборов узлов, а как функция от значений узлов.

Решение XSLT 1.0

Потребность в этом решении может возникнуть при работе с несколькими документами. Рассмотрим два документа с одной и той же DTD-схемой, в ко­торых не может быть элементов с одинаковыми значениями. С точки зрения XSLT, элементы, взятые из разных документов, различаются, даже если у них одинаковые пространства имен, атрибуты и текстовые значения. См. примеры 9.1 – 9.4.

Пример 9.1. peoplel.xml

<people>

<person name="Brad York" age="38" sex="m" smoker="yes"/> <person name="Charles Xavier" age="32" sex="m" smoker="no"/> <person name="David Willimas" age="33" sex="m" smoker="no"/> </people>

Пример 9.2. people2.xml

<people>

<person name="Al Zehtooney" age="33" sex="m" smoker="no"/> <person name="Brad York" age="38" sex="m" smoker="yes"/> <person name="Charles Xavier" age="32" sex="m" smoker="no"/> </people>

Пример 9.3. Неудачная попытка воспользоваться операцией объединения в XSLT для устранения дубликатов

<xsl:template match="/"> <people>

<xsl:copy-of select="//person | document(‘people2.xml’)//person"/>

</people> </xsl:template>

<people>

<person name="Brad York" age="38" sex="m" smoker="yes"/> <person name="Charles Xavier" age="32" sex="m" smoker="no"/> <person name="David Willimas" age="33" sex="m" smoker="no"/> <person name="Al Zehtooney" age="33" sex="m" smoker="no"/> <person name="Brad York" age="38" sex="m" smoker="yes"/> <person name="Charles Xavier" age="32" sex="m" smoker="no"/> </people>

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

Следующая таблица стилей – это повторно используемая реализация объеди­нения, пересечения и разности множеств для случая, когда понятие равенства ос­новано на семантике значений. Идея в том, что импортирующая таблица переоп­ределит шаблон, для которого mode="vset:element-equality". Тем самым она сможет определить семантику равенства, имеющую смысл для конкретных входных данных.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:vset="http:/www.ora.com/XSLTCookbook/namespaces/vset">

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<!– Реализация равенства элементов по умолчанию. Переопределить в импортирующей таблице стилей, если необходимо. –> <xsl:template match="node() | @*" mode="vset:element-equality"> <xsl:param name="other"/> <xsl:if test=". = $other">

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

<!– По умолчанию для проверки членства в множестве используется равенство элементов. Вряд ли придется часто переопределять этот шаблон в импортирующей таблице стилей. –>

<xsl:template match="node() | @*" mode="vset:member-of"> <xsl:param name="elem"/> <xsl:variable name="member-of"> <xsl:for-each select=".">

<xsl:apply-templates select="." mode="vset:element-equality">

<xsl:with-param name="other" select="$elem"/> </xsl:apply-templates> </xsl:for-each> </xsl:variable>

<xsl:value-of select="string($member-of)"/> </xsl:template>

<!– Объединение двух множеств вычисляется с помощью paвенcтвa "по значению". –> <xsl:template name="vset:union">

<xsl:param name="nodes1" select="/.." /> <xsl:param name="nodes2" select="/.." /> <!– для внyтpеннегo использования –>

<xsl:param name="nodes" select="$nodes1 | $nodes2" /> <xsl:param name="union" select="/.." /> <xsl:choose>

<xsl:when test="$nodes">

<xsl:variable name="test">

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

<xsl:with-param name="elem" select="$nodes[1]" /> </xsl:apply-templates> </xsl:variable>

<xsl:call-template name="vset:union">

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

select="$union | $nodes[1][not(string($test))]" /> </xsl:call-template> </xsl:when> <xsl:otherwise>

<xsl:apply-templates select="$union" mode="vset:union" /> </xsl:otherwise> </xsl:choose> </xsl:template>

<!– По умолчанию вoзвpaщaетcя копия объединения. Пеpеoпpеделить в импopтиpyющей таблице стилей, если нужно получить pезyльтaт как "oбpaтный вызов". –>

<xsl:template match="/ | node() | @*" mode="vset:union">

<xsl:copy-of select="."/> </xsl:template>

<!– Пеpеcечение двух множеств вычисляется с помощью paвенcтвa "по значению". –>

<xsl:template name="vset:intersection"> <xsl:param name="nodes1" select="/.."/> <xsl:param name="nodes2" select="/.."/> <!– для внyтpеннегo использования –> <xsl:param name="intersect" select="/.."/>

<xsl:choose>

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

<xsl:apply-templates select="$intersect" mode="vset:intersection"/> </xsl:when>

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

<xsl:apply-templates select="$intersect" mode="vset:intersection"/> </xsl:when> <xsl:otherwise>

<xsl:variable name="test1">

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

<xsl:with-param name="elem" select="$nodes1[1]"/> </xsl:apply-templates> </xsl:variable> <xsl:variable name="test2">

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

<xsl:with-param name="elem" select="$nodes1[1]"/> </xsl:apply-templates> </xsl:variable> <xsl:choose>

<xsl:when test="string($test1) and not(string($test2))"> <xsl:call-template name="vset:intersection"> <xsl:with-param name="nodes1"

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

select="$intersect | $nodes1[1]"/> </xsl:call-template> </xsl:when> <xsl:otherwise>

<xsl:call-template name="vset:intersection"> <xsl:with-param name="nodes1"

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

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

<xsl:template match="/ | node() | @*" mode="vset:intersection">

<xsl:copy-of select="."/> </xsl:template>

<!– Разность двух множеств (node1 – nodes2) вычисляется с помощью равенства "по значению". –> <xsl:template name="vset:difference">

<xsl:param name="nodes1" select="/.."/> <xsl:param name="nodes2" select="/.."/> <!– для внутреннего использования –> <xsl:param name="difference" select="/.."/>

<xsl:choose>

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

<xsl:apply-templates select="$difference" mode="vset:difference"/> </xsl:when>

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

<xsl:apply-templates select="$nodes1" mode="vset:difference"/> </xsl:when> <xsl:otherwise> <xsl:variable name="test1">

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

<xsl:with-param name="elem" select="$nodes1[1]"/> </xsl:apply-templates> </xsl:variable> <xsl:variable name="test2">

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

<xsl:with-param name="elem" select="$nodes1[1]"/> </xsl:apply-templates> </xsl:variable> <xsl:choose>

<xsl:when test="string($test1) or string($test2)"> <xsl:call-template name="vset:difference"> <xsl:with-param name="nodes1"

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

select="$nodes2"/> <xsl:with-param name="difference" select="$difference"/> </xsl:call-template> </xsl:when> <xsl:otherwise>

<xsl:call-template name="vset:difference"> <xsl:with-param name="nodes1"

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

select="$nodes2"/> <xsl:with-param name="difference"

select="$difference | $nodes1[1]"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template>

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

<xsl:template match="/ | node() | @*" mode="vset:difference">

<xsl:copy-of select="."/> </xsl:template>

Эти рекурсивные шаблоны реализованы исходя из следующих определений:

Union(nodes1,nodes2)

Объединение включает все узлы из набора nodes2 плюс те узлы из набора nodes1, которые не входят в nodes2.

Intersection(nodes1,nodes2)

Пересечение включает все узлы из набора nodes1, которые входят также в набор nodes2.

Difference(nodes1,nodes2)

Разность включает все узлы из набора nodes1, которые не входят в набор

nodes2.

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

Имея набор теоретико-множественных операций с семантикой значений, можно получить для файлов peoplel.xml и people2.xml требуемый результат, вос­пользовавшись такой таблицей стилей:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:vset="http:/www.ora.com/XSLTCookbook/namespaces/vset">

<xsl:import href="set.ops.xslt"/>

<xsl:template match="/"> <people>

<xsl:call-template name="vset:union">

<xsl:with-param name="nodes1" select="//person"/>

<xsl:with-param name="nodes2" select="document(‘people2.xml’)//person"/> </xsl:call-template> </people> </xsl:template>

<!– Считаем, что два элемента person равны, если совпадают атрибуты name — >

<xsl:template match="person" mode="vset:element-equality"> <xsl:param name="other"/> <xsl:if test="@name = $other/@name">

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

</xsl:stylesheet>

XSLT 2.0

Главное усовершенствование, введенное в XSLT 2.0, – полноценные функции и последовательности. Это устраняет необходимость рекурсии и трюка с обратным вызо­вом, а определения получаются более элегантными и удобными для применения. При этом функции vset:element-equality и vset:member-of по-прежнему можно переопределять в импортирующей таблице стилей, добиваясь нужного поведения.

<xsl:stylesheet version="2.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:xs="http://www.w3.org/2 0 01/XMLSchema"

xmlns:vset="http:/www.ora.com/XSLTCookbook/namespaces/vset">

<!– Реализация равенства элементов по умолчанию. Переопределить в импортирующей таблице стилей, если необходимо. –> <xsl:function name="vset:element-equality" as="xs:boolean"> <xsl:param name="item1" as="item()?"/> <xsl:param name="item2" as="item()?"/> <xsl:sequence select="$item1 = $item2"/> </xsl:function>

<!– По умолчанию для проверки членства в множестве используется равенство элементов. Вряд ли придется часто переопределять этот шаблон в импортирующей таблице стилей. –>

<xsl:function name="vset:member-of" as="xs:boolean"> <xsl:param name="set" as="item()*"/>

<xsl:param name="elem" as="item()"/> <xsl:variable name="member-of" as="xs:boolean*" select="for $test in $set

return if (vset:element-equality($test, $elem)) then true() else ()"/> <xsl:sequence select="not(empty($member-of))"/> </xsl:function>

<!– Объединение двух множеств вычисляется с помощью равенства "по значению". –>

<xsl:function name="vset:union" as="item()*"> <xsl:param name="nodes1" as="item()*" /> <xsl:param name="nodes2" as="item()*" /> <xsl:sequence select="$nodes1, for $test in $nodes2

return if (vset:member-of($nodes1,$test)) then () else $test"/>

</xsl:function>

<!– Пересечение двух множеств вычисляется с помощью равенства "по значению". –>

<xsl:function name="vset:intersection" as="item()*"> <xsl:param name="nodes1" as="item()*" /> <xsl:param name="nodes2" as="item()*" /> <xsl:sequence select="for $test in $nodes1

return if (vset:member-of($nodes2,$test)) then $test else ()"/>

</xsl:function>

<!– Разность двух множеств (node1 – nodes2) вычисляется с помощью равенства "по значению". –>

<xsl:function name="vset:difference" as="item()*"> <xsl:param name="nodes1" as="item()*" /> <xsl:param name="nodes2" as="item()*" />

<xsl:sequence select="for $test in $nodes1 return if (vset:member- of($nodes2,$test)) then () else $test"/> </xsl:function>

</xsl:stylesheet>

Обсуждение

Быть может, вам кажется, что равенство – вещь тривиальная; два предмета либо равны, либо нет. Однако в программировании (как и в политике), равенство зависит от того, кто сравнивает. В типичном документе элемент ассоциируется

с уникально идентифицируемым объектом. Например, один абзац <p>…</p> отличается от любого другого абзаца в том же документе, даже если их содержи­мое одинаково. Поэтому теоретико-множественные операции, в основе которых лежат уникальные идентификаторы элементов, определены корректно. Но, если операции XSLT производятся над несколькими документами или над элемента­ми, возникшими в результате применения команды xsl:copy, то нужно более аккуратно определять, какие элементы мы хотим считать равными.

Вот несколько примеров запросов, в которых требуется семантика значений: 1. Есть два документа из разных пространств имен. В примерах 9.5 – 9.8 мы ищем (локальные) имена элементов, общие для обоих документов и уни­кальные в своих пространствах имен.

Пример 9.5. doc1.xml

<doc xmlns:doc1="doc1" xmlns="doc1"> <chapter label="1">

<section label="1"> <p>

Давным-давно…

</p>

</section> </chapter> <chapter label="2">

<note to="editor"^ все еще жду своего аванса в сумме $10 0 0 0 0.</note>

<section label="1"> <p>

… и с тех пop они жили счастливо.

</p> </section> </chapter> </doc>

Пример 9.6. doc2.xml

<doc xmlns:doc1="doc2" xmlns="doc2"> <chapter label="1"> <section label="1">

<sub>

<p>

Давным-давно…

<ref type="footnote" label="1"/> </p>

</sub>

<fig>Figure1</fig> </section>

<footnote label="1">

Hey diddle diddle. </footnote> </chapter> <chapter label="2">

<section label="1"> <p>

… и с тех пор они жили счастливо.

</p> </section> </chapter> </doc>

Пример 9.7. unique-element-names.xslt

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:doc1="doc1" xmlns:doc2="doc2"

xmlns:vset="http:/www.ora.com/XSLTCookbook/namespaces/vset" extension-element-prefixes="vset">

<xsl:import href="set.ops.xslt"/>

<xsl:output method="text" />

<xsl:template match="/">

<xsl:text>&#xa,’Общие элементы: </xsl:text> <xsl:call-template name="vset:intersection"> <xsl:with-param name="nodes1" select="//*"/>

<xsl:with-param name="nodes2" select="document(‘doc2.xml’)//*"/> </xsl:call-template>

<xsl:text>&#xa,Элементы, имеющиеся только в doc1: </xsl:text> <xsl:call-template name="vset:difference">

<xsl:with-param name="nodes1" select="//*"/>

<xsl:with-param name="nodes2" select="document(‘doc2.xml’)//*"/> </xsl:call-template>

<xsl:text>&#xa,Элементы, имеющиеся только в doc2: </xsl:text> <xsl:call-template name="vset:difference">

<xsl:with-param name="nodes1" select="document(‘doc2.xml’)//*"/> <xsl:with-param name="nodes2" select="//*"/> </xsl:call-template> <xsl:text>&#xa;</xsl:text>

<xsl:template match="*" mode="vset:intersection"> <xsl:value-of select="local-name(.)"/> <xsl:if test="position() != last()">

<xsl:text>, </xsl:text> </xsl:if> </xsl:template>

<xsl:template match="*" mode="vset:difference"> <xsl:value-of select="local-name(.)"/> <xsl:if test="position() != last()">

<xsl:text>, </xsl:text> </xsl:if> </xsl:template>

<xsl:template match="doc1:* | doc2:*" mode="vset:element-equality"> <xsl:param name="other"/>

<xsl:if test="local-name(.) = local-name($other)">

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

</xsl:stylesheet>

Пример 9.8. Результат

Общие элементы: doc, chapter, section, p

Элементы, имеющиеся только в doc1: note

Элементы, имеющиеся только в doc2: sub, ref, fig, footnote

2. Документ на языке Visio XML состоит из главных форм, экземпляров глав­ных форм и определенных пользователем форм, для которых главной фор­мы не существует. Требуется получить данные обо всех уникальных формах. С точки зрения этого запроса две формы считаются равными, если выполня­ется хотя бы одно из следующих условий:

a.У обеих форм есть атрибут ©Master, и значения этого атрибута совпадают.

b.Хотя бы у одной формы нет атрибута ©Master, но все геометрические элементы Geom равны. Два элемента Geom считаются равными, если равны все атрибуты всех их потомков.

В противном случае формы не равны.

Для реализации запроса можно взять пересечение множества всех форм с са­мим собой при описанной выше интерпретации равенства1.

Можно также воспользоваться шаблоном vset:union с параметром nodes:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:vxd="urn:schemas-microsoft-com:office:visio" xmlns:vset="http:/www.ora.com/XSLTCookbook/namespaces/vset" extension-element-prefixes="vset">

<xsl:import href="set.ops.xslt"/>

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/"> <UniqueShapes>

<xsl:call-template name="vset:intersection">

<xsl:with-param name="nodes1" select="//vxd:Pages/*/*/vxd:Shape"/> <xsl:with-param name="nodes2" select="//vxd:Pages/*/*/vxd:Shape"/> </xsl:call-template> </UniqueShapes> </xsl:template>

<xsl:template match="vxd:Shape" mode="vset:intersection">

<xsl:copy-of select="." /> </xsl:template>

<xsl:template match="vxd:Shape" mode="vset:element-equality"> <xsl:param name="other"/> <xsl:choose>

<xsl:when test="@Master and $other/@Master and @Master = $other/@Master"> <xsl:value-of select="true()"/> </xsl:when>

<xsl:when test="not(@Master) or not($other/@Master)"> <xsl:variable name="geom1">

<xsl:for-each select="vxd:Geom//*/@*"> <xsl:sort select="name()"/> <xsl:value-of select="."/> </xsl:for-each> </xsl:variable> <xsl:variable name="geom2">

<xsl:for-each select="$other/vxd:Geom//*/@*"> <xsl:sort select="name()"/> <xsl:value-of select="."/> </xsl:for-each> </xsl:variable>

<xsl:if test="$geom1 = $geom2">

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

</xsl:when> </xsl:choose> </xs l:template>

</xsl:stylesheet>

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

По теме:

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