如何在 XSLT 中使用 XPath 获取元素数组,包括缺失的元素?
Posted
技术标签:
【中文标题】如何在 XSLT 中使用 XPath 获取元素数组,包括缺失的元素?【英文标题】:How can I get array of elements, including missing elements, using XPath in XSLT? 【发布时间】:2012-03-27 18:33:57 【问题描述】:给定以下符合 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
也一样,不知道能不能搞定
[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。
【问题讨论】:
也许 XPath 不是解决这个问题的方法。也许您想使用更健壮的东西,例如 JAXB。 我对 xpath 的替代品持开放态度。你怎么能用 jaxb 来表达呢? 这个问题 - 更新未定义。请编辑并提供与编辑对应的新源 XML 文档。如果不提供正确的 XML 文档,您怎么能指望人们猜出您的想法呢? 由于您没有提供与最新更新相对应的 XML 文档,我倾向于认为您不知道自己在说什么。 所以想象一下上面没有<div>
元素的原始html(不是XML 文档)代码,你将如何使用xpath 来返回这个问题中描述的预期结果。请在我们之前关于使用 xpath 处理 html 文档的讨论中重燃您的记忆:***.com/questions/4250925/…
+1 给@DimitreNovaatchev 和Kirill Polishchuk,当没有共同的div
父母时,我不明白“缺失元素”是什么意思。 a
、b
和 c
是否只是随机分布在整个文档中,但如果在一个父节点中少于 3 个,则缺少一个?
【参考方案1】:
XPath 中没有空值。这里有一个半相关的问题也解释了这一点:http://www.velocityreviews.com/forums/t686805-xpath-query-to-return-null-values.html
实际上,您有三个选择:
-
根本不使用 XPath。
使用这个:
//a | //div[not(a)]
,如果其中没有a
,它将返回div
元素,并让您的Java代码处理任何div
's返回为'没有a
元素存在' .根据上下文,这甚至可以让您在需要时输出更有用的内容,因为您将可以访问 div 的全部内容,例如错误“在 div 中找不到 a
元素(某些标识符)” .
使用 XSLT 预处理您的 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
元素,其中在下一个h1
元素或文档末尾之前不存在后续a
元素。正如 Dimitre 指出的那样,这仅在您从 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
元素的计数与下一个a
前面的所有h1
元素的计数不同。在 XPath 中可能有更有效的方法,但如果它比这更做作,我绝对建议不要使用 XPath。
【讨论】:
//a | //div[not(a)]
的情况下,是否会先运行|
的左侧,然后运行右侧
不,节点应该按文档顺序返回。你会得到<a>a1</a>
、<div><b>b2</b></div>
、<a>a3</a>
谢谢你回答了我最初比较关心的问题的第一部分。但是对上面的第二个例子有什么想法吗?
好吧,这有 8 票...我认为选择这个作为答案是公平的,我已经阅读了所有其他答案,但她列出的 xpath 表达式就足够了。我赞成以下所有其他答案,我认为它们同样好,但我认为公平地说,Flynn1179 是第一个建议使用 not()
函数的人,所以最终我正在使用他的解决方案。我也会在不久的将来使用 xslt 解决方案。【参考方案2】:
第一个问题的解决方案:
这个 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)
]
第三组:
/*/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
-th 组(从评估$k-th 表达式中选择的节点)包含a
,则使用其字符串值生成所需数组的$k
-th 项——否则生成null
对于想要的数组的$k
-th 项。
这是对上述 XPath 表达式的基于 XSLT 的验证:
<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
换句话说,在第一个和第二个/*/h1
之间的a
元素是/*/h1[1]
的兄弟元素之后的a
元素和/*/h1[1]
的兄弟元素之前的a
元素的交集/*/h1[2]
.
此表达式仅对/*/h1
元素的最后一个之后的a
元素有问题。这就是为什么我们添加一个额外的谓词,它使用以下布尔表达式检查下一个 /*/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"]
更新2:
现在 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>
这种转换实现了两种不同的解决方案。不同之处在于使用什么元素来表示“null”。在第一种情况下,它是 h1
元素。不建议这样做,因为任何 h1
已经有自己的含义,不同于“表示 null”。第二种解决方案使用特殊的my:null
元素来表示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
的对应数组时。
【讨论】:
@DimitreNovaatchev,这是一个非常详细的答案,但它没有回答问题的第一部分。我将问题更改为接受 XSLT 解决方案。在这里没有人反对你 Dimitre,很明显你知道很多,你有我的尊重,但也许是出于某种原因,无论是有意还是无意,这种轻微的居高临下/被动的攻击性确实会降低你答案的质量,我从不质疑他们,但你的态度有时肯定会以错误的方式消失,但这一切都很好,当时我添加了第二部分,在我看来这是个好主意。 Kim Jong Woo:恕我直言,从你的问题中仍然不清楚到底想要什么。忘记人们需要猜测真正想要什么的时间,但如果问题被明确地陈述,你将不会得到最好的答案。事实上,像基里尔这样的优秀专家已经删除了他的答案并且再也没有回来——想想这个,这可能意味着什么。 仍然没有解决问题的第一部分。 金钟宇:第一部分是什么?您从第一部分恢复,导致两个人删除了他们的好答案,现在要求“第一部分”???什么是“问题”?你从来没有明确地解释过这一点。 删除大部分对话,@Flynn,Dimitre。请不要使用 cmets 讨论投票。选民信息是私密的是有原因的,而这种无用的对话是其中很大一部分原因。【参考方案3】:我建议您使用以下内容,它可能会被重写为 xsl:function,其中父节点名称(此处: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>
【讨论】:
以上是关于如何在 XSLT 中使用 XPath 获取元素数组,包括缺失的元素?的主要内容,如果未能解决你的问题,请参考以下文章