我需要从非常糟糕的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)”?
我知道我可以使用“|”或多个查询,但这不是我想要的(尽管它也可能解决我的问题)。
谢谢。
答案 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处理器都应该产生相同的结果。