搜索祖先或自身轴以查找第一个非空属性值

时间:2015-08-10 16:52:34

标签: xslt xpath wireshark

在使用WireShark的pdml输出(此处定义为http://www.nbee.org/doku.php?id=netpdl:pdml_specification)时,我试图将下面相同结构的较大文档转换为下面的示例:

原始XML:

<packet>
    <proto name="geninfo" pos="0" showname="General Information" size="308">
        <field name="num" pos="0" show="2574" showname="Number" value="a0e" size="308"/>
        <!-- more field tags here -->
    </proto>
    <proto name="ip" showname="Internet Protocol Version 4" size="0" pos="0">
        <field name="" show="This is a fake entry created from the metadata" size="308" pos="0" value="">
            <field name="ip.src" showname="Source: 1.2.3.4 (1.2.3.4)" size="0" pos="0" show="1.2.3.4"/>
            <field name="ip.src2" showname="Source: 1.2.3.4 (1.2.3.4)" hide="yes" size="0" pos="0" show="1.2.3.4"/>
            <!-- more field tags here -->
        </field>
        <!-- more field tags here -->
    </proto>
    <!-- more proto tags here -->
</packet>

预期/希望/最终输出:

<packet>
    <geninfo>
        <pos>0</pos>
        <showname>General Information</showname>
        <size>308</size>
        <num>
            <pos>0</pos>
            <show>2574</show>
            <showname>Number</showname>
            <value>a0e</value>
            <size>308</size>
        </num>
        <!-- more transformed field tags here -->
    </geninfo>
    <ip>
        <showname>Internet Protocol Version 4</showname>
        <size>0</size>
        <pos>0</pos>
        <ip>
            <show>This is a fake entry created from the metadata</show>
            <size>308</size>
            <pos>0</pos>
            <ip.src>
                <showname>Source: 1.2.3.4 (1.2.3.4)</showname>
                <size>0</size>
                <pos>0</pos>
                <show>1.2.3.4</show>
            </ip.src>
            <!-- more transformed field tags here -->
        </ip>
        <!-- more transformed field tags here -->
    </ip>
    <!-- more transformed proto tags here -->
</packet>

当前的XSLT:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/transform" version="1.0">
    <xsl:output omit-xml-declaration="yes"/>
    <xsl:template match="*[not(@hide)]">
        <xsl:variable name="nameAttr" select="ancestor-or-self::*[@name][1]/@name!=''"/>
        <xsl:element name="{$nameAttr}">
            <xsl:for-each select="@*[name(.)!='name' and .!='']">
                <xsl:element name="{name()}">
                    <xsl:value-of select="."/>
                </xsl:element>
            </xsl:for-each>
            <xsl:apply-templates/>
        </xsl:element>
    </xst:template>
</xsl:stylesheet>

这会使用hide属性跳过所有标记,并移动剩余的标记&#39;使用name属性值创建的新元素的子标记的非名称属性,同时使用空字符串值跳过非名称属性。在name属性值为空字符串的情况下,我打算使用祖先或自身轴来查找第一个非空名称属性值,以递归方式搜索祖先,知道每个proto标记将具有非空名称属性值,如果搜索到目前为止。

获取第一个祖先或自我非空属性名称值的语法的任何帮助(它当前返回@name!=&#39;&#39;)和任何xslt样式的布尔值/好的做法评论表示赞赏。

3 个答案:

答案 0 :(得分:0)

这听起来像你正在寻找的是

<xsl:variable name="nameAttr" select="ancestor-or-self::*[@name != ''][1]"/>

要了解为什么这会有效,您需要了解XPath的一般比较运算符的工作方式。

A != value实际上意味着在A选择的所有节点中,至少有一个节点不等于值。&#34;

在没有@name属性的元素上,子表达式@name不会选择任何节点,因此!=一般比较将失败。在具有@name属性且其值为空字符串的元素上,再次比较将失败,因为@name仅选择了一个节点,并且它等于{{1 }}。在具有非空''属性的元素上,比较将成功。

此外,看起来你不想要

@name

而是

           <xsl:element name="{@name}">

换句话说,你想要&#34;虚假条目的名称&#34; element是source属性的名称,而不是 <xsl:element name="{name(.)}"> 属性的值。对? (而且,无论如何,由于您在name中选择了一个属性,因此当前节点是一个属性,因此for-each将无法选择任何内容,因为属性节点不能拥有属性。)

答案 1 :(得分:0)

在完成你所拥有的工作的同时,我想出了这个:

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

    <xsl:template match="/packet">
        <packet>
            <xsl:apply-templates />
        </packet>
    </xsl:template>

    <xsl:template match="proto[not(@hide)]">
        <xsl:element name="{@name}">
            <xsl:apply-templates select="*|@*" />
        </xsl:element>
    </xsl:template>

    <xsl:template match="field[@name != '']">
        <xsl:element name="{@name}">
            <xsl:apply-templates select="*|@*"  />
        </xsl:element>
    </xsl:template>

    <xsl:template match="field[@name = '']">
        <xsl:element name="{ancestor::*[@name != ''][1]/@name}">
            <xsl:apply-templates select="*|@*" />
        </xsl:element>
    </xsl:template>

    <xsl:template match="@*[name() != 'name']">
        <xsl:element name="{name()}">
            <xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

与您的原始问题相关,这里是使用非空@name的祖先的XPath:

<xsl:element name="{ancestor::*[@name != ''][1]/@name}">

但是你确实需要进行一些重组才能以可维护的方式完成这项工作 - 尝试考虑使用模板而不是每个循环 - 它将为您提供更加模块化和灵活的设计。在上面的样式表中,有一个匹配根节点(/ packet)的模板,没有hide属性的proto节点,具有填充名称的字段节点,不具有填充名称的字段节点,以及属于'n'的属性“名称”属性。

答案 2 :(得分:0)

想要提供我的最终解决方案(几乎完全基于Dan Field的答案),并且无法将其纳入评论:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output omit-xml-declaration="yes"/>
    <xsl:strip-space elements="*">
    <xsl:template match="packet">
        <xsl:copy>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[(name()='proto' or name()='field') and not(@hide)]">
        <xsl:element name="{ancestor-or-self::*[@name!=''][1]/@name}">
            <xsl:apply-templates select="* | @*"/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="@*[name()!='name' and .!='']">
        <xsl:element name="{name()}">
            <xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="@*[name()='name']"/>
</xsl:stylesheet>