XPath, плоская иерархия и условие остановки - PullRequest
3 голосов
/ 29 июля 2011

Мне нужно построить объекты Start из очень плохого XML.Я сделал парсер SAX для одного случая, но он грязный, и я хотел бы попробовать XPath.

У меня есть следующий XML:

<doc>
    <start/>
    <a/>
    <b/>
    <item/>
    <item/>
    <item/>

    <start/>
    <item/>
    <item/>
    <item/>

    <start/>
    <b/>
    <item/>
    <item/>
    <item/>

</doc>

Однако я бы очень хотел этот документ (который я надеваю'):

<doc>
    <start>
        <a/>
        <b/>
        <item/>
        <item/>
        <item/>
    <start/>

    <start>
        <item/>
        <item/>
        <item/>
    <start/>

    <start>
        <b/>
        <item/>
        <item/>
       <item/>
    <start/>

</doc>

Предположим, что у меня есть 2-й объект "стартового" узла (из 1-го примера XML).Теперь я хотел бы получить элементы "a" и "b" непосредственно после этого узла.Однако, если я сделаю относительный запрос от этого узла (с последующим братом) для узла "b", я получу узел под 3-м стартовым узлом.Можно ли сказать «найти узел X, следующий за этим узлом, но остановиться на узле Y (вернуть ноль)»?

Я знаю, что могу использовать «|»или несколько запросов, но это не то, что я хочу (хотя это может решить и мою проблему).

Спасибо.

Ответы [ 3 ]

4 голосов
/ 29 июля 2011

Если вы используете XSLT 1.0, вы также можете сгруппировать соседних братьев и сестер, используя ключ xsl:key, что упрощает выражения XPath:

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

    <xsl:key name="k_adjChild"
        match="/*/*[not(self::start)]"
        use="generate-id(preceding-sibling::start[1])"
        />

    <xsl:template match="doc">
        <doc>
            <xsl:apply-templates select="start"/>
        </doc>
    </xsl:template>

    <xsl:template match="start">
        <xsl:copy>
            <xsl:copy-of select="key('k_adjChild', generate-id())" />
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>
3 голосов
/ 29 июля 2011

Предполагая, что контекст является конкретным элементом <start>, этот XPath будет выбирать все узлы между текущим <start> и следующим <start>.

following-sibling::node()[not(self::start)]
                         [generate-id(preceding-sibling::start[1]) 
                           = generate-id(current())]

Этот XSLT применяет этот XPathдля группировки контента по элементам <start>.

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

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="doc">
        <xsl:copy>
            <xsl:apply-templates select="@*|start" />
        </xsl:copy>
    </xsl:template>

    <!--for each start element, copy it,
        apply templates for it's attributes(in case any exist)
        and for nodes() that are following-siblings
        who's first preceeding-sibling is this start element-->
    <xsl:template match="start">
        <xsl:copy>
            <xsl:apply-templates select="@*
                | following-sibling::node()[not(self::start)]
                    [generate-id(preceding-sibling::start[1]) 
                      = generate-id(current())]" />
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>
1 голос
/ 29 июля 2011

Предполагая, что входной XML находится в файле in.xml, этот скрипт XQuery делает то, что вам нужно:

(:
  This library function can be found here:
  http://www.xqueryfunctions.com/xq/functx_index-of-node.html
:)
declare namespace functx = "http://www.functx.com"; 
declare function functx:index-of-node($nodes as node()* ,
$nodeToFind as node() )  as xs:integer* 
{     
  for $seq in (1 to count($nodes))
  return $seq[$nodes[$seq] is $nodeToFind]
};

(:
  Recursively calculate the start elements with the other elements between
  as childs.
  Take the first two indices of $positions and create a start element
  with the elements of $elements with positions between these two indices.
  Then remove the first index of $position and do the recursive call.
  Input:
    $positions: Sequence with start element indices (belongs to $elements)
    $elements: Element sequence
  Output:
    Sequence of start elements with child elements
:)
declare function local:partition($positions as xs:integer*, 
    $elements as element()*) as element()* 
{
  let $len := count($positions)
  return
    if($len gt 1)
    then (
      let $first := $positions[1]
      let $second := $positions[2]
      let $rest := subsequence($positions, 2)
      return
        ( element start
          {
            subsequence($elements, $first + 1, $second - $first - 1)
          },
          local:partition($rest, $elements)
        )
    ) 
    else if($len eq 1)
    then (
          element start
          {
            subsequence($elements, $positions[1] + 1)
          }
    )
    else () 
};

(: Input document :)
let $input-doc := doc('in.xml')

(: Sequence of all child elements of root element doc :)
let $childs := $input-doc/doc/node()[. instance of element()]

(: Sequence with the indices of the start elements in $childs :)
let $positions := for $s in $input-doc/doc/start 
                  return functx:index-of-node($childs, $s)

return 
  <doc>
  {
    local:partition($positions, $childs)
  }
  </doc>

Вывод:

<doc>
  <start>
    <a/>
    <b/>
    <item/>
    <item/>
    <item/>
  </start>
  <start>
    <item/>
    <item/>
    <item/>
  </start>
  <start>
    <b/>
    <item/>
    <item/>
    <item/>
  </start>
</doc>

Тестирование с XQilla но любой другой процессор XQuery должен давать тот же результат.

...