XPATH или XSL для сопоставления двух наборов узлов с помощью пользовательского сравнения - PullRequest
3 голосов
/ 06 ноября 2008

РЕДАКТИРОВАТЬ: У меня также есть доступ к ESXLT функции.

У меня есть два набора узлов строковых токенов. Один набор содержит такие значения:

/Geography/North America/California/San Francisco
/Geography/Asia/Japan/Tokyo/Shinjuku

Другой набор содержит такие значения:

/Geography/North America/
/Geography/Asia/Japan/

Моя цель - найти «совпадение» между ними. Сопоставление выполняется, когда любая строка в наборе 1 начинается со строки в наборе 2. Например, сопоставление будет выполняться между / Geography / North America / California / San Francisco и / Geography / Северная Америка / потому что строка из набора 1 начинается со строки из набора 2.

Я могу сравнивать строки, используя подстановочные знаки, используя стороннее расширение. Я также могу использовать регулярные выражения внутри Xpath.

Моя проблема в том, как мне структурировать Xpath для выбора, используя функцию между всеми узлами обоих наборов? XSL также является жизнеспособным вариантом.

Это XPATH:

count($set1[.=$set2])

Получил бы количество пересечений между set1 и set2, но это сравнение 1 к 1. Можно ли использовать другие способы сравнения узлов?

РЕДАКТИРОВАТЬ: Я получил это работает, но я обманываю с использованием некоторых других сторонних расширений, чтобы получить тот же результат. Я все еще заинтересован в других методах, чтобы сделать это.

Ответы [ 4 ]

2 голосов
/ 07 ноября 2008

Это:

<xsl:variable name="matches" select="$set1[starts-with(., $set2)]"/>

установит $matches на набор узлов, содержащий каждый узел в $set1, текстовое значение которого начинается с текстового значения узла в $ set2. Это то, что вы ищете, верно?

Edit:

Ну, я просто ошибаюсь по этому поводу. Вот почему.

starts-with ожидает, что два его аргумента будут строками. Если это не так, он преобразует их в строки перед оценкой функции.

Если вы задаете ему набор узлов в качестве одного из аргументов, он использует строковое значение набора узлов, которое является текстовым значением первого узла в наборе. Таким образом, в приведенном выше примере $set2 никогда не ищется; проверяется только первый узел в списке, поэтому предикат найдет только узлы в $set1, начинающиеся со значения первого узла в $set2.

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

<xsl:variable name="hits" select="$set1[. = $set2]"/>

Но этот предикат использует сравнение между наборами узлов, а не между текстовыми значениями.

Идеальный способ сделать это - использовать предикаты. То есть «Я хочу найти каждый узел в $set1, для которого есть узел в $set2, значение которого начинается с ...», и здесь XPath не работает. Начинается с чего? То, что вы хотели бы написать что-то вроде:

<xsl:variable name="matches" select="$set1[$set2[starts-with(?, .)]]"/>

только нет выражения, которое вы можете написать для ?, который будет возвращать узел, в настоящее время тестируемый внешним предикатом. (Если я не пропускаю что-то ослепительно очевидное.)

Чтобы получить то, что вы хотите, вы должны протестировать каждый узел в отдельности:

<xsl:variable name="matches">
  <xsl:for-each select="$set1">
    <xsl:if test="$set2[starts-with(current(), .)]">
      <xsl:copy-of select="."/>
    </xsl:if>
  </xsl:for-each>
</xsl:variable>

Это не очень удачное решение, потому что оно оценивает фрагмент дерева результатов, а не набор узлов. Вам придется использовать функцию расширения (например, msxsl:node-set) для преобразования RTF в набор узлов, если вы хотите использовать переменную в выражении XPath.

1 голос
/ 06 декабря 2008

Существует простое и чистое решение XSLT 1.0 (без расширений) для определения количества совпадений :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>

    <xsl:template match="/">
        <xsl:variable name="vStars">
            <xsl:for-each select="*/regions/*">
                <xsl:for-each select="/*/cities/*[starts-with(.,current())]">
                    <xsl:value-of select="'*'"/>
                </xsl:for-each>
            </xsl:for-each>
        </xsl:variable>

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

Когда это преобразование применяется к следующему документу XML :

<t>
    <cities>
        <city>/Geography/North America/California/San Francisco</city>
        <city>/Geography/Asia/Japan/Tokyo/Shinjuku</city>
    </cities>
    <regions>
        <region>/Geography/North America/</region>
        <region>/Geography/Asia/Japan/</region>
    </regions>
</t>

получен правильный результат :

2

Обратите внимание , что для каждого найденного совпадения создается один символ (звездочка), и все эти звездочки образуют содержимое переменной $vStars. Затем мы просто выводим его string-length().

0 голосов
/ 09 ноября 2008

Последнее от Роберта xsl:variable хорошо для получения фрагмента дерева результатов, содержащего совпадающие текстовые значения, но если (как он предполагает) вы не используете расширения EXSLT или MS для XSLT 1.0 для преобразования RTF в набор узлов, вы можете ' t получить количество совпадающих текстовых узлов.

Вот таблица стилей XSLT, о которой я упоминал в своем предыдущем ответе, которая повторяется в примере входного документа, который я дал, чтобы подсчитать количество текстовых узлов в наборе 1, для которых узел в наборе 2 соответствует части или всем:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

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

  <xsl:template match="/">
    <xsl:call-template name="count-matches">
      <xsl:with-param name="set1-node" select="sets/set[1]/text[1]"/>
      <xsl:with-param name="set2-node" select="sets/set[2]/text[1]"/>
      <xsl:with-param name="total-count" select="0"/>
    </xsl:call-template>
    <xsl:text>
</xsl:text>
  </xsl:template>

  <xsl:template name="count-matches">
    <xsl:param name="set1-node"/>
    <xsl:param name="set2-node"/>
    <xsl:param name="total-count" select="0"/>
    <xsl:variable name="this-count">
      <xsl:choose>
        <xsl:when test="contains($set1-node, $set2-node)">
          <xsl:value-of select="1"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="0"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="$set2-node/following-sibling::text">
        <xsl:call-template name="count-matches">
          <xsl:with-param name="set1-node"
                          select="$set1-node"/>
          <xsl:with-param name="set2-node"
                          select="$set2-node/following-sibling::text[1]"/>
          <xsl:with-param name="total-count"
                          select="$total-count + $this-count"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="$set1-node/following-sibling::text">
        <xsl:call-template name="count-matches">
          <xsl:with-param name="set1-node"
                          select="$set1-node/following-sibling::text[1]"/>
          <xsl:with-param name="set2-node"
                          select="$set2-node/preceding-sibling::text[last()]"/>
          <xsl:with-param name="total-count"
                          select="$total-count + $this-count"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$total-count + $this-count"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

Не очень лаконично, но поскольку XSLT не позволяет программистам присваивать новые значения уже определенным переменным, рекурсия часто бывает необходима. Я не вижу способа в XSLT 1.0 получить счетчик сортировки, запрошенный Заком, используя xsl:for-each.

0 голосов
/ 08 ноября 2008

Полагаю, я не смог заставить XPath выше работать. Я начал со следующего документа XML для инициализации двух наборов узлов:

<?xml version="1.0"?>
<sets>
  <set>
    <text>/Geography/North America/California/San Francisco</text>
    <text>/Geography/Asia/Japan/Tokyo/Shinjuku</text>
  </set>
  <set>
    <text>/Geography/North America/</text>
    <text>/Geography/Asia/Japan/</text>
  </set>
</sets>

Я думаю, что эта таблица стилей должна реализовывать решение Роберта, но я получаю только счет '1':

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="text"/>

  <xsl:template match="/">
    <xsl:variable name="set1" select="sets/set[1]/text/text()"/>
    <xsl:variable name="set2" select="sets/set[2]/text/text()"/>
    <xsl:value-of select="count($set1[starts-with(., $set2)])"/>
    <xsl:text>
</xsl:text>
  </xsl:template>

</xsl:stylesheet>

Я написал таблицу стилей, которая использует рекурсивный шаблон и производит правильное количество «2» с заданным входным документом, но это гораздо менее элегантно, чем ответ Роберта. Если бы я только мог заставить работать XPath - всегда желая учиться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...