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

Search ancestor-or-self axis to find first non empty attribute value

在使用 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>

Expected/Hopeful/Eventual 输出:

<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>

这将跳过所有具有隐藏属性的标签,并将剩余标签的非名称属性移动到使用名称属性值创建的新元素的子标签,同时跳过具有空字符串值的非名称属性。在 name 属性值为空字符串的情况下,我打算使用 ancestor-or-self 轴来查找第一个非空的 name 属性值,递归地搜索祖先知道每个 proto 标签都将具有一个非空的 name 属性值,如果搜索到此为止。

有关获取第一个祖先或自身非空属性名称值的语法的任何帮助(它当前 returns 是否为 @name 的布尔值!= '')和任何 xslt stylistic/good 练习评论表示赞赏。

听起来你要找的是

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

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

A != value其实就是"of all the nodes selected by A, there is at least one that is not equal to value."

在没有 @name 属性的元素上,将没有节点 select 由子表达式 @name 编辑,因此 != 一般比较将失败.在具有值为空字符串的 @name 属性的元素上,再次比较将失败,因为只有一个节点 select 由 @name 编辑,并且等于 ''。在具有非空 @name 属性的元素上,比较将成功。

另外,你好像不想

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

而是

           <xsl:element name="{name(.)}">

换句话说,您希望 "fake entry" 元素的名称是源属性的名称,而不是 name 属性的值。正确的? (无论如何,因为你在 for-each 中 selecting 一个属性,当前节点是一个属性,所以 @name 将无法 select 任何东西,因为属性节点不能有属性。)

在研究你所拥有的东西时,我想到了这个:

<?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}">

但是您确实需要进行一些重组以使这项工作以可维护的方式进行 - 尝试更多地考虑使用模板而不是 for-each 循环 - 它会给你一个更加模块化和灵活的设计。在上面的样式表中,有一个模板来匹配根节点 (/packet)、没有隐藏属性的原型节点、有填充名称的字段节点、没有的字段节点和没有隐藏属性的属性t 一个 'name' 属性。

想要提供我的最终解决方案(几乎完全基于 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>