XPath,平面层次结构和停止条件

时间:2011-07-28 21:06:16

标签: xml xslt xpath flat

我需要从非常糟糕的XML构造Start对象。我为一个案例制作了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>

假设我有第二个“开始”节点对象(来自第一个XML示例)。现在我想直接在这个节点后面得到“a”和“b”元素。但是,如果我从这个节点(带有follow-sibling)对“b”节点进行相对查询,我将获得第3个起始节点下的节点。是否可以说“在此节点后面找到节点X但在节点Y上停止(返回null)”?

我知道我可以使用“|”或多个查询,但这不是我想要的(尽管它也可能解决我的问题)。

谢谢。

3 个答案:

答案 0 :(得分:4)

如果你使用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>

答案 1 :(得分:3)

假设上下文是特定的<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>

答案 2 :(得分:1)

假设输入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>

Testet与XQilla,但每个其他XQuery处理器都应该产生相同的结果。