XPath wildcard возвращает только первый элемент



Я пишу 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() один элемент, как указано в этом ответе.



Итак, как я могу на самом деле проверить, что для каждого атрибута нет эквивалентного элемента в а дети?



Это вложенный цикл, который может быть представлен в псевдокоде следующим образом:



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


Есть идеи? Я чувствую, что я так близко!

605   1  

1 ответ:

Я, наконец, получил его работу с помощью переменной.

Я использовал этот схематрон:

<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() - Ваш друг, потому что он фактически учитывает списки элементов.

Если вы застряли, попробуйте перевернуть проблему вверх дном. Я перебирал атрибуты, потом снова дети, в то время как сейчас я зацикливаюсь на каждом элементе, а затем проверяю атрибуты их родителей.

Если вы хотите использовать информацию, которая находится в контексте родителя, но застряли внутри [] close, используйте переменную, чтобы получить значение.
Например, если вы попробуете ../@*[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>

Спасибо Эйрикру ут-Ленди за это!

Comments

    Ничего не найдено.