【问题标题】:Xpath wildcard returns only the first elementXpath 通配符只返回第一个元素
【发布时间】:2017-04-11 23:27:43
【问题描述】:

我正在编写一个 schematron 来验证以下 xml 文件:

<root version="1.0">
    <zone map="fields.map" display_name="Fields">
        <zone.rectangles>
            <rectangle h="2" w="2" x="0" y="0" />
        </zone.rectangles>
    </zone>
</root>

我想确保如果声明了任何元素的属性,则该元素不能包含与该属性同名的子元素。

例如,如果&lt;zone&gt; 具有属性map,则&lt;zone&gt; 不能包含元素&lt;zone.map&gt;

因此,前面的xml文件是有效的,但是下面的不是:

无效:

<root version="1.0">
    <zone map="fields.map" display_name="Fields">
        <zone.map>fields.map</zone.map>
        <zone.rectangles>
            <rectangle h="2" w="2" x="0" y="0" />
        </zone.rectangles>
    </zone>
</root>

另一方面,这个是有效的:

有效:

<root version="1.0">
    <zone display_name="Fields">
        <zone.map>fields.map</zone.map>
        <zone.rectangles>
            <rectangle h="2" w="2" x="0" y="0" />
        </zone.rectangles>
    </zone>
</root>

我可以使用这个 schematron 文件:

<schema xmlns="http://purl.oclc.org/dsdl/schematron">
    <pattern>
        <title>Attribute usage</title>
        <!-- Every element that has attributes -->
        <rule context="*[@*]">
            <!-- The name of its children should not be {element}.{attribute} -->
            <assert test="name(*) != concat(name(), '.', name(@*))">
                The attribute <name />.<value-of select="name(@*)" /> is defined twice.
            </assert>
        </rule>
    </pattern>
</schema>

经过无数次不幸的尝试后,我花了大约 4 个小时才让它正常工作,所以我对这个架构非常满意,并开始对其进行更多测试。

看到它只适用于每个元素的第一个属性,我真的很失望。例如,对于 zone 元素,仅测试 map 属性。因此,在 &lt;zone map="" display_name=""&gt; 中放置 &lt;zone.display_name&gt; 元素不会导致架构失败,而反转 &lt;zone display_name="" map=""&gt; 等属性会触发失败。

如果我理解清楚的话,问题似乎是通配符 @* 实际上没有用作 concat(name(), '.', name(@*)) 中的列表,因为 concat() 实际上需要一个字符串,和 name() 单个元素,如 this answer 中所述。

那么我如何才能真正检查每个属性,在子元素中没有等效元素?

这是一个嵌套循环,可以用伪代码表示为:

for attribute in element.attributes:
    for child in element.children:
        if child.name == element.name + "." + attribute.name:
            raise Error

有什么想法吗?我觉得我很近!

【问题讨论】:

    标签: xml xpath schematron


    【解决方案1】:

    我终于通过使用变量让它工作了。

    我用过这个schematron:

    <schema xmlns="http://purl.oclc.org/dsdl/schematron">
        <pattern>
            <title>Attribute usage</title>
            <!-- Elements that contains a dot in their name -->
            <rule context="*[contains(name(), '.')]">
                <!-- Take the part after the dot -->
                <let name="attr_name" value="substring-after(name(), '.')" />
                <!-- Check that there is no parent's attributes with the same name -->
                <assert test="count(../@*[name() = $attr_name]) = 0">
                    The attribute <name /> is defined twice.
                </assert>
            </rule>
        </pattern>
    </schema>
    

    Schematron 确实很强大,但你必须掌握它...

    对这个问题的更通用的回答:

    如果您想遍历通配符*@*,那么count() 是您的朋友,因为它实际上考虑了元素列表。

    如果您发现自己陷入困境,请尝试将问题颠倒过来。我循环遍历属性,然后遍历子元素,而现在我循环遍历每个元素,然后检查其父元素的属性。

    如果您想使用父上下文中的信息,但发现自己被困在 [] 关闭中,请使用变量来获取值。
    例如,如果你尝试../@*[name() = name(..)],它不会做你想做的事情,因为[] 中的name(..) 指的是属性的父级名称,而不是当前上下文元素的名称。
    如果您将值提取为&lt;let name="element_name" value="name()" /&gt;,那么您就可以开始了:../@*[name() = $element_name]

    当您打开方括号时,您无法再访问这些方括号之外的元素,因此请使用变量将它们放入。

    编辑:

    您可以使用current() 函数从括号内获取上下文元素,而无需使用变量。我的最终架构是:

    <schema xmlns="http://purl.oclc.org/dsdl/schematron">
        <pattern>
            <title>Attribute usage</title>
            <!-- Elements that contains a dot in their name -->
            <rule context="*[contains(name(), '.')]">
                <!-- Check that there is no parent's attributes with the same name -->
                <assert test="not(../@*[name() = substring-after(name(current()), '.')])">
                    The attribute <name /> is defined twice.
                </assert>
            </rule>
        </pattern>
    </schema>
    

    感谢 Eiríkr Útlendi!

    【讨论】:

    • 阅读the current() function。当您在 [square-bracket expression] 中并且想要引用作为表达式之前的上下文的元素时,这很有用。这样就不需要使用变量来访问方括号外的元素。
    • count(XYZ) = 0 实际上等价于not(XYZ)。我不明白的是,你的循环在哪里?您的代码没有检查每个属性,是吗?
    • @Tomalak:第一个循环发生在上下文选择器中,第一个通配符 * 在我的文档中的每个元素上。然后第二个循环发生在../@* 的每个父属性上。感谢not() 相当于count() = 0,顺便说一句!
    • 啊,是的,这是有道理的。我没有正确阅读代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-03
    • 1970-01-01
    • 1970-01-01
    • 2011-07-12
    • 2012-11-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多