Как я могу получить массив элементов, включая отсутствующие элементы, используя XPath в XSLT? - PullRequest
8 голосов
/ 10 марта 2012

Учитывая следующий XML-совместимый HTML:

<div>
 <a>a1</a>
 <b>b1</b>
</div>

<div>
 <b>b2</b>
</div>

<div>
 <a>a3</a>
 <b>b3</b>
 <c>c3</c>
</div>

выполнение //a вернет:

[a1,a3]

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

как можно выразить xpath, чтобы получить все элементы A, которые будут возвращать:

[a1, null, a3]

тот же случай для //c, IИнтересно, возможно ли получить

[null, null, c3]

ОБНОВЛЕНИЕ: рассмотрим другой сценарий, в котором нет общих родителей <div>.

<h1>heading1</h1>
 <a>a1</a>
 <b>b1</b>


<h1>heading2</h1>
 <b>b2</b>


<h1>heading3</h1>
 <a>a3</a>
 <b>b3</b>
 <c>c3</c>

ОБНОВЛЕНИЕ: теперь я могу использовать XSLT.

Ответы [ 3 ]

10 голосов
/ 16 марта 2012

В XPath нет нулевого значения.Здесь есть наполовину связанный вопрос, который также объясняет это: http://www.velocityreviews.com/forums/t686805-xpath-query-to-return-null-values.html

Реально, у вас есть три варианта:

  1. Не использовать XPath вообще.
  2. Используйте это: //a | //div[not(a)], которое вернет элемент div, если в нем не было a, и ваш Java-код будет обрабатывать любые div, возвращаемые как 'нет a присутствующего элемента',В зависимости от контекста, это может даже позволить вам вывести что-то более полезное, если потребуется, поскольку у вас будет доступ ко всему содержимому div, например, ошибка: «элемент * a не найден в div (некоторый идентификатор)».
  3. Предварительно обработайте ваш XML с помощью XSLT, который вставляет элементы a в любой элемент div, у которого еще нет элемента с подходящим значением по умолчанию.

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

//a | //h1[not(following-sibling::a) or generate-id(.) != generate-id(following-sibling::a[1]/preceding-sibling::h1[1])]

Это будет соответствовать любым a элементамили любые элементы h1, у которых нет следующего элемента a перед следующим элементом h1 или концом документа.Однако, как отметил Димитр, это работает, только если вы используете его из XSLT, так как generate-id является функцией XSLT.

Если вы не используете его из XLST, вы можете использовать это скорееПридуманная формула:

//a | //h1[not(following-sibling::a) or count(. | preceding-sibling::h1) != count(following-sibling::a[1]/preceding-sibling::h1)]

Работает путем сопоставления h1 элементов, в которых количество самого себя и всех предшествующих элементов h1 не равно количеству всех h1элементы, предшествующие следующему a.Возможно, есть более эффективный способ сделать это в XPath, но если он получится более хитрым, я бы определенно рекомендовал вообще не использовать XPath.

3 голосов
/ 19 марта 2012

Я предлагаю вам использовать следующее, которое может быть переписано в функцию xsl :, где имя родительского узла (здесь: div) параметризовано.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/">
    <root>
        <aList><xsl:copy-of select="$divIncludingNulls//a"/></aList>
        <bList><xsl:copy-of select="$divIncludingNulls//b"/></bList>
        <cList><xsl:copy-of select="$divIncludingNulls//c"/></cList>
    </root>
</xsl:template>

<xsl:variable name="divChild" select="distinct-values(//div/*/name())"/>

<xsl:variable name="divIncludingNulls">
    <xsl:for-each select="//div">
        <xsl:variable name="divElt" select="."/>
        <div>
            <xsl:for-each select="$divChild">
                <xsl:variable name="divEltvalue" select="$divElt/*[name()=current()]"/>
                <xsl:element name="{.}">
                    <xsl:choose>
                        <xsl:when test="$divEltvalue"><xsl:value-of select="$divEltvalue"/></xsl:when>
                        <xsl:otherwise>null</xsl:otherwise>
                    </xsl:choose>
                </xsl:element>
            </xsl:for-each>
       </div>
    </xsl:for-each>
</xsl:variable>

</xsl:stylesheet>

Применимо к

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <div>
     <a>a1</a>
     <b>b1</b>
    </div>

    <div>
     <b>b2</b>
    </div>

    <div>
     <a>a3</a>
     <b>b3</b>
     <c>c3</c>
    </div>
</root>

вывод

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <aList>
        <a>a1</a>
        <a>null</a>
        <a>a3</a>
    </aList>
    <bList>
        <b>b1</b>
        <b>b2</b>
        <b>b3</b>
    </bList>
    <cList>
        <c>null</c>
        <c>null</c>
        <c>c3</c>
    </cList>
</root>
3 голосов
/ 17 марта 2012

Решение для первой проблемы :

Это выражение XPath:

    /*/div/a
|
    /*/div[not(a)]

При сравнении со следующим документом XML:

<t>
    <div>
        <a>a1</a>
        <b>b1</b>
    </div>
    <div>
        <b>b2</b>
    </div>
    <div>
        <a>a3</a>
        <b>b3</b>
        <c>c3</c>
    </div>
</t>

выбирает следующие три узла (a, div, a):

<a>a1</a>
<div>
    <b>b2</b>
</div>
<a>a3</a>

В вашем массиве Java любой выбранный элемент, не являющийся a, должен рассматриваться как (или заменяться)null.


Вот одно решение второй проблемы :

Используйте эти выражения XPath для выбора элементов a из каждогогруппа :

для первой группы:

/*/h1[1]
   /following-sibling::a
      [not(/*/h1[2])
     or
       count(.|/*/h1[2]/preceding-sibling::a)
      =
       count(/*/h1[2]/preceding-sibling::a)
      ]

для второй группы :

/*/h1[2]
   /following-sibling::a
      [not(/*/h1[3])
     or
       count(.|/*/h1[3]/preceding-sibling::a)
      =
       count(/*/h1[3]/preceding-sibling::a)
      ]

и для3-я группа :

/*/h1[3]
   /following-sibling::a
      [not(/*/h1[4])
     or
      count(.|/*/h1[4]/preceding-sibling::a)
      =
       count(/*/h1[4]/preceding-sibling::a)
      ]

В случае, если:

count(/*/h1)

равно $cnt,

, генерировать $cnt такие выражения (для i = 1 to $cnt) и оцените их все.Выбранные узлы по каждому из них либо содержат элемент a, либо нет.Если $k -ая группа (узлы, выбранные из вычисления $ k-го выражения) содержит a, используйте ее строковое значение для генерации $k -го элемента требуемого массива - в противном случае создайте nullдля $k -ого элемента искомого массива.

Вот проверка на основе XSLT вышеуказанных выражений XPath :

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

 <xsl:template match="/">
   <xsl:variable name="vGroup1" select=
   "/*/h1[1]
       /following-sibling::a
          [not(/*/h1[2])
         or
           count(.|/*/h1[2]/preceding-sibling::a)
          =
           count(/*/h1[2]/preceding-sibling::a)
          ]
   "/>

   <xsl:variable name="vGroup2" select=
   "/*/h1[2]
       /following-sibling::a
          [not(/*/h1[3])
         or
           count(.|/*/h1[3]/preceding-sibling::a)
          =
           count(/*/h1[3]/preceding-sibling::a)
          ]
   "/>

   <xsl:variable name="vGroup3" select=
   "/*/h1[3]
       /following-sibling::a
          [not(/*/h1[4])
         or
          count(.|/*/h1[4]/preceding-sibling::a)
          =
           count(/*/h1[4]/preceding-sibling::a)
          ]
   "/>

 Group1:  "<xsl:copy-of select="$vGroup1"/>"

 Group2:  "<xsl:copy-of select="$vGroup2"/>"

 Group3:  "<xsl:copy-of select="$vGroup3"/>"

 </xsl:template>
</xsl:stylesheet>

Когда это преобразование применяется к следующему документу XML (OP не предоставил полный и правильно сформированный документ XML !!!):

<t>
    <h1>heading1</h1>
    <a>a1</a>
    <b>b1</b>

    <h1>heading2</h1>
    <b>b2</b>

    <h1>heading3</h1>
    <a>a3</a>
    <b>b3</b>
    <c>c3</c>
</t>

, три выражения XPathоцениваются и выбираются узлы по каждому из них: :

 Group1:  "<a>a1</a>"

 Group2:  ""

 Group3:  "<a>a3</a>"

Пояснение :

Используется известная формула Кейсиана для пересечения двух наборов узлов:

$ns1[count(. | $ns2) = count($ns2)]

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

Осталось заменить $ns1 и $ns2с выражениями, которые имеют отношение к проблеме.

Мы заменяем $ns1 на:

/*/h1[1]
    /following-sibling::a

и заменяем $ns2 на:

/*/h1[2]
    /preceding-sibling::a

В другихДругими словами, элементы a, которые находятся между первым и вторым /*/h1, являются пересечением элементов a, которые следуют за братьями и сестрами /*/h1[1], и элементами a, которые предшествуют братьям и сестрам /*/h1[2].

Это выражение проблематично только для элементов a, следующих за последним из элементов /*/h1.вот почему мы добавляем дополнительный предикат, который проверяет отсутствие следующего /*/h1 элемента и or это со следующими логическими выражениями.

Наконец, в качестве руководящего примера дляРеализация Java здесь представляет собой полное преобразование XSLT, которое выполняет нечто подобное - создает сериализованный массив и может быть механически преобразовано в соответствующее решение Java :

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

         <my:null>null</my:null>
         <my:Q>"</my:Q>

         <xsl:variable name="vNull" select="document('')/*/my:null"/>
         <xsl:variable name="vQ" select="document('')/*/my:Q"/>

         <xsl:template match="/">
           <xsl:variable name="vGroup1" select=
           "/*/h1[1]
               /following-sibling::a
                  [not(/*/h1[2])
                 or
                   count(.|/*/h1[2]/preceding-sibling::a)
                  =
                   count(/*/h1[2]/preceding-sibling::a)
                  ]
           "/>

           <xsl:variable name="vGroup2" select=
           "/*/h1[2]
               /following-sibling::a
                  [not(/*/h1[3])
                 or
                   count(.|/*/h1[3]/preceding-sibling::a)
                  =
                   count(/*/h1[3]/preceding-sibling::a)
                  ]
           "/>

           <xsl:variable name="vGroup3" select=
           "/*/h1[3]
               /following-sibling::a
                  [not(/*/h1[4])
                 or
                  count(.|/*/h1[4]/preceding-sibling::a)
                  =
                   count(/*/h1[4]/preceding-sibling::a)
                  ]
           "/>

         [<xsl:value-of select=
          "concat($vQ[$vGroup1/self::a[1]],
                  $vGroup1/self::a[1],
                  $vQ[$vGroup1/self::a[1]],
                  $vNull[not($vGroup1/self::a[1])])"/>
          <xsl:text>,</xsl:text>

         <xsl:value-of select=
          "concat($vQ[$vGroup2/self::a[1]],
                  $vGroup2/self::a[1],
                  $vQ[$vGroup2/self::a[1]],
                  $vNull[not($vGroup2/self::a[1])])"/>
          <xsl:text>,</xsl:text>

         <xsl:value-of select=
          "concat($vQ[$vGroup3/self::a[1]],
                  $vGroup3/self::a[1],
                  $vQ[$vGroup3/self::a[1]],
                  $vNull[not($vGroup3/self::a[1])])"/>]
         </xsl:template>
</xsl:stylesheet>

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

     ["a1",null,"a3"]

Update2 :

Теперь OP добавил, чтоон может использовать решение XSLT.Вот один из них:

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

         <xsl:key name="kFollowing" match="a"
              use="generate-id(preceding-sibling::h1[1])"/>

         <my:null/>
         <xsl:variable name="vNull" select="document('')/*/my:null"/>

         <xsl:template match="/*">
           <xsl:copy-of select=
           "h1/following-sibling::a[1]
          |
            h1[not(key('kFollowing', generate-id()))]"/>

           =============================================

           <xsl:apply-templates select="h1"/>

         </xsl:template>

         <xsl:template match="h1">
           <xsl:variable name="vAsInGroup" select=
               "key('kFollowing', generate-id())"/>
           <xsl:copy-of select="$vAsInGroup[1] | $vNull[not($vAsInGroup)]"/>
         </xsl:template>
</xsl:stylesheet>

Это преобразование реализует два разных решения.Разница в том, какой элемент используется для представления «ноль».В первом случае это элемент h1.Это не рекомендуется, потому что любой h1 уже имеет свое собственное значение, которое отличается от «представления нуля».Второе решение использует специальный элемент my:null для представления нуля.

Когда это преобразование применяется к тому же XML-документу, что и выше :

<t>
        <h1>heading1</h1>
        <a>a1</a>
        <b>b1</b>

        <h1>heading2</h1>
        <b>b2</b>

        <h1>heading3</h1>
        <a>a3</a>
        <b>b3</b>
        <c>c3</c>
</t>

каждое из двух выражений XPath (содержащее ссылки XSLT key()) оцениваются и выводятся выбранные узлы (выше и ниже "========" соответственно):

<a>a1</a>
<h1>heading2</h1>
<a>a3</a>

           =============================================

           <a>a1</a>
<my:null xmlns:my="my:my" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
<a>a3</a>

Замечание по производительности :

Поскольку используются ключи, это решение будет значительно более эффективным, когда выполняется более одного поиска - например, когда необходимо создать соответствующие массивы для a, b и c.

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