xml - Xpath 通配符只返回第一个元素

标签 xml xpath schematron

我正在编写一个 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>

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

例如,如果 <zone>有一个属性 map , <zone>不能包含元素 <zone.map> .

因此,前面的 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属性被测试。所以把 <zone.display_name>内部元素 <zone map="" display_name="">不会使架构失败,同时反转 <zone display_name="" map=""> 等属性会触发失败。

如果我理解清楚的话,问题似乎是通配符 @*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

任何的想法?我觉得我很近!

最佳答案

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

我使用了这个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()是你的 friend ,因为它实际上考虑了元素列表。

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

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

关于xml - Xpath 通配符只返回第一个元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43357817/

相关文章:

android - EditText 填充不起作用

php - 需要帮助访问 PHP DOM 元素

xml - 如何正确指定表的 colname?

java - 用java解析高级XML

java - .classpath xml 中的环境变量

Python XML : get direct child nodes

css - 跟踪没有唯一 id 的 webclient 页面表的特定行,并且该表保持并发填充数据

regex - 使用 Schematron 将空格标识为元素中的第一个字符

xpath - XPath:嵌套/复杂条件

c# - 使用 Schematron 验证 XML