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
Есть идеи? Я чувствую, что я так близко!
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