XSLT技术实现:如何跳过缺失指定子元素的节点?
问题描述
我编写的程序可将周期性测量数据转换为XML格式,但有时会出现测量失败的情况(如连接失败)。为保证输出一致性,程序仍会输出内容,此时原始值标记为U(代表undefined,未定义),转换器生成XML时会省略value元素。
sample元素的内容模型如下(省略大部分细节):
<!ELEMENT sample (label, value?, unit?, thresholds?, range?)> <!ATTLIST sample label CDATA #REQUIRED>
可见value是可选元素,以下是不含value的示例:
<sample label="in"> <label>in</label> <range min="0"/> </sample>
我需要将该XML转换为特定输出格式,希望跳过内部不含value节点的sample节点,但不知如何实现。
sample元素所在的内容模型:
<!ELEMENT perf_data (sample+, warn_labels?, crit_labels?)> <!ATTLIST perf_data count CDATA #REQUIRED>
目前XSL使用的选择器为:
<xsl:template match="perf_data/sample[@label]">
实际示例上下文(两个无value的sample节点):
<perf_data count="2"> <sample label="in"> <label>in</label> <range min="0"/> </sample> <sample label="out"> <label>out</label> <range min="0"/> </sample> </perf_data>
直接使用match="perf_data/sample[@label]/value"会导致转换失败。
补充示例
输入XML(std_dev节点不含value)
<?xml version="1.0" encoding="utf-8"?> <MonitoringOutput id="id-18895" version="0.2"> <perf_data count="3"> <sample label="last"> <label>last</label> <value>0.508798</value> <range min="0"/> </sample> <sample label="std_dev"> <label>std_dev</label> <range min="0"/> </sample> <sample label="avg"> <label>avg</label> <value>0.508798</value> <range min="0"/> </sample> </perf_data> </MonitoringOutput>
现有XSLT代码
<xsl:template match="/MonitoringOutput[@version]"> <xsl:comment><xsl:value-of select="name(.)" /> v<xsl:value-of select="@version" /> id=<xsl:value-of select="@id" /> (PRTG.xsl v0.0.2)</xsl:comment> <xsl:variable name="main.node" select="." /> <prtg> <xsl:choose> <xsl:when test="perf_data[@count > 0]"> <xsl:apply-templates select="$main.node/perf_data"/> </xsl:when> <xsl:otherwise> <Error>1</Error> </xsl:otherwise> </xsl:choose> </prtg> </xsl:template> <xsl:template match="perf_data[@count > 0]"> <!-- no parsing error --> <xsl:variable name="main.node" select=".." /> <xsl:variable name="perf.node" select="." /> <xsl:apply-templates select="$perf.node/sample"/> </xsl:template> <xsl:template match="perf_data/sample[@label]"> <result> <xsl:apply-templates /> </result> </xsl:template> <xsl:template match="sample/label"> <Channel><xsl:value-of select="."/></Channel> </xsl:template> <xsl:template match="sample/range[@*]"> <xsl:for-each select="@*"> <xsl:comment>range: <xsl:value-of select="name(.)" />="<xsl:value-of select="."/>"</xsl:comment> </xsl:for-each> </xsl:template> <xsl:template match="sample/value"> <Value><xsl:value-of select="."/></Value> </xsl:template>
解决方案
有两种简单的方式可以实现跳过不含value的sample节点:
方法一:修改节点选择条件
在perf_data模板中,修改apply-templates的选择器,只选中包含value子元素的sample节点:
<xsl:template match="perf_data[@count > 0]"> <!-- no parsing error --> <xsl:variable name="main.node" select=".." /> <xsl:variable name="perf.node" select="." /> <!-- 仅处理包含value子元素的sample --> <xsl:apply-templates select="$perf.node/sample[value]"/> </xsl:template>
这种方式从源头上过滤掉不需要处理的节点,只对符合条件的sample执行后续模板。
方法二:添加空模板跳过节点
添加一个匹配**不含value的sample**的空模板,优先级高于原有模板,这样这类节点不会输出任何内容:
<!-- 空模板:跳过所有不含value的sample节点 --> <xsl:template match="perf_data/sample[@label][not(value)]" /> <!-- 原有模板:仅处理含value的sample节点 --> <xsl:template match="perf_data/sample[@label]"> <result> <xsl:apply-templates /> </result> </xsl:template>
因为XSLT会优先匹配更具体的模板,所以不含value的sample会被空模板匹配,不产生输出;而含value的sample会走原有模板生成<result>节点。
内容的提问来源于stack exchange,提问作者U. Windl
相关产品推荐
相关产品推荐

