XPath вытягивает более одного матча
Ошибка (BaseX)
Я запускаю запросы в большом наборе данных в BaseX, но один XQuery разбивает мою программу с ошибкой [XPTY0004] Item expected, sequence found: (attribute begin {"6"}, ...).
.
В моем запросе я пытаюсь убедиться, что один элемент приходит перед другим, сравнивая begin
- атрибут, который присутствует в XML - с number()
. Но всякий раз, когда я пытаюсь использовать большинство базовых XQueries (возвращающих совпадающих узлов) в моем наборе данных (например, этот онлайн-инструмент), я получаю ошибку, которая напоминает тот, который у меня был раньше:
[Ошибка] SaxonCE.XSLT20Processor 14: 08: 39.692 SEVERE: XPathException в invokeTransform: последовательность из более чем одного элемента не допускается, поскольку первый аргумент числа() ( "6", "10" )
Итак, я предполагаю, что что-то происходит с братьями node, т.е. существует более одного из этих узлов и что он неясен, какой из них следует сравнивать. Ниже приведены примеры.
Почему имеет значение порядок?
XPath используется в механизме запросов для дерева: лингвистически аннотированные корпуса. В некоторых случаях мы хотим, чтобы узлы соответствовали по порядку, и иногда это не имеет значения. В качестве упрощенного примера: иногда мы хотим сопоставить что-то конкретное, как заинтересованный человек, где важна статья заказа, прилагательное, существительное. В других запросах это не имеет значения, и мы хотим соответствовать фразам, таким как доступное время, где порядок статьи, прилагательное, существительное может быть в любом порядке.
Другими словами, в первом случае порядок элементов должен соблюдаться, во втором он не должен. Вот возможное представление XPath такой конструкции, которая содержит статью, прилагательное и существительное.
node[@cat="np" and node[@pt="art"] and node[@pt="adj"] and node[@pt="n"]]
По умолчанию XPath не заботится о порядке этих элементов и делает жадный поиск, т.е. он также будет соответствовать элементам, таким как доступное время (art
, n
, adj
). Но я хочу переписать вышеупомянутый XPath, чтобы убедиться, что порядок узлов соблюден, и поэтому такая конструкция, как доступное время (art
, n
, adj
), не соответствует соответствующему man (art
, adj
, n
).
# Possible representation of *the time available*
<node id="0" begin="1" cat="np">
<node id="1" begin="1" pt="art" text="the" />
<node id="2" begin="2" pt="n" text="time" />
<node id="3" begin="3" pt="adj" text="available" />
</node>
# Possible representation of *the concerned man*
<node id="0" begin="1" cat="np">
<node id="1" begin="1" pt="art" text="the" />
<node id="2" begin="2" pt="adj" text="concerned" />
<node id="3" begin="3" pt="n" text="man" />
</node>
Один из способов - использовать числовое сравнение атрибута begin
, доступного в корпусе. Это числовое увеличение, поэтому, если мы хотим гарантировать, что порядок XPath не поврежден, мы можем сказать, что числовое значение каждого дочернего элемента node of @cat="np"
должно быть меньше следующего, используя number()
. Но, как я показал выше, это приводит к ошибке - ошибке, которая не возникла бы в простом примерном коде, который я только что показал.
Другой пример.
<node id="0" begin="2">
<node id="1" begin="2">
<node id="2" begin="2"/>
<node id="3" begin="3"/>
</node>
<node id="4" begin="5">
<node id="5" begin="5"/>
</node>
<node id="6" begin="6"/>
</node>
Этот XPath должен соответствовать:
/node/node[number(@begin) < number(../node/@begin)]
Но при использовании процессора XQuery вы получите ошибку, описанную выше. A sequence of more than one item is not allowed as the first argument of number() ("2", "5", ...)
.
Я попробовал решение, предоставленное @Michael Kay, но эта же проблема, похоже, играет.
XQuery
for $node in node[every $n in node[position() lt last()] satisfies (number($n/@begin) lt number($n/following-sibling::node/@begin))]
return $node
Данные
<node id="0" begin="2">
<node id="1" begin="2">
<node id="2" begin="2"/>
<node id="3" begin="3"/>
</node>
<node id="4" begin="5">
<node id="5" begin="5"/>
</node>
<node id="6" begin="6"/>
</node>
Ошибка
SaxonCE.XSLT20Processor 14: 48: 49.809 SEVERE: XPathException в invokeTransform: последовательность из более чем одного элемента не допускается, поскольку первый аргумент числа() ( "5", "6" )
Обновление 19 апреля 2017 года
Сегодня я столкнулся с каким-то неожиданным поведением, что делает решение, предоставленное @har07, недостаточным. Я ошибочно предположил, что предложение not()
оказало влияние только на узлы в XPath (а не на всех узлах в XML). Другими словами, когда предложение not()
добавляется в самый верхний node XPath, все его дочерние элементы в XML будут иметь фиксированный упорядоченный порядок слов. (Теперь, когда я читал его так, это кажется нормальным.) Однако я действительно хочу, чтобы порядок слов был установлен только на узлах, указанных в XPath, а не на других узлах в сопоставлении XML. Надеемся, что пример станет более понятным.
Скажем, что я хочу сопоставить следующий XPath, cat="np"
, содержащий rel="det" pt="vnw" lemma="die"
и по крайней мере два раза rel="mod" pt="adj"
.
//node[@cat="np" and node[@rel="det" and @pt="vnw" and @lemma="die"] and count(node[@rel="mod" and @pt="adj"]) > 1]
но с дополнительным требованием следовать порядку этого XPath, то есть
//node[
@cat="np" and
not(node[
position() < last()
][number(@begin) > following-sibling::node/number(@begin)]) and
node[
@rel="det" and
@pt="vnw" and
@lemma="die"
] and
count(node[
@rel="mod" and
@pt="adj"
]) > 1
]
Итак, rel="det"
должен произойти до двух rel="mod"
в XML. Это отлично работает, и все совпадения верны, но не все ожидаемые совпадения найдены. Причина в том, что строка not()
явно нацелена на все узлы XML, а не на узлы, определенные в XPath. В случае, если внизу строки найдено node, которое не придерживается правила not
, совпадения не будет, даже если этот node не указан в XPath. Вышеупомянутый XPath, например, не будет соответствовать следующему XML, потому что внутри cat="np"
существует node, атрибут begin которого больше, чем его следующий родной брат, что не допускается правилом not
.
<node begin="4" cat="np" id="8" rel="obj1">
<node begin="4" id="9" pos="det" pt="vnw" rel="det" word="die" lemma="die" />
<node begin="5" id="10" pos="adj" pt="adj" rel="mod" word="veelzijdige" />
<node begin="6" id="11" pos="adj" pt="adj" rel="mod" word="getalenteerde" />
<node begin="7" id="12" pos="noun" pt="n" rel="hd" word="figuren" />
<node begin="8" id="31" index="1" rel="obj1" />
<node begin="2" id="32" index="2" rel="obj2" />
</node>
Однако мне бы хотелось, чтобы этот cat="np"
совпадал и делал функцию not()
менее агрессивной, т.е. требовали, чтобы узлы, указанные в XPath (в этом примере rel="det" pt="vnw" lemma="die"
и два узла rel="mod" pt="adj"
), следуют требование порядка, в котором атрибут begin должен быть меньше, чем следующий элемент структуры XPath. Другие элементы внутри cat="np"
, которые не были указаны в XPath, имеют атрибут, который больше, чем его следующий брат.
Обратите внимание, что последний элемент структуры XPath (который будет соответствовать id="11"
в примере XML) необязательно должен иметь атрибут begin, который ниже, чем следующий node в XML (который не указан в XPath).
Как и прежде, меня особенно интересует, как решить эту проблему с помощью чистого XPath, но альтернативы XQuery также приветствуются. Предпочтительно, как функция, которая принимает структуру XPath как входную, и применяет "порядок слов" к ее самой верхней node и всем ее потомкам. Пример кода и использование с XPath, показанным здесь в качестве примера, рекомендуется.
Ответы
Ответ 1
Что касается исключения a-sequence-of-more-the-item-is-not-allowed, которое вы видите, обратите внимание, что XPath 2.0 и выше и XQuery поддерживают вызов функции на шаге пути (.../number()
). Тем не менее, вы можете называть number()
на индивидуальном node
, передавая один атрибут begin
за раз, чтобы избежать исключения:
/node/node[number(@begin) < ../node/number(@begin)]
Однако выражение предиката, используемое в вышеприведенном XPath, оценивается как true
, когда по крайней мере есть один атрибут sibling node
с атрибутом begin
, превышающим атрибут begin
текущего node
, что, похоже, не соответствует желаемое поведение.
Вы можете применить одно и то же исправление к предлагаемому XQuery, но, видимо, возникла другая аналогичная проблема из-за того, что lt
используется для сравнения значения с последовательностью значений (чтобы быть ясным, я имею в виду второй lt
в предлагаемом XQuery). Вы можете попробовать следующее, слегка измененное, XQuery:
for $node in node[
every $n in node[position() lt last()]
satisfies not($n/following-sibling::node[number(@begin) lt number($n/@begin)])
]
return $node
"Один из способов - использовать числовое сравнение атрибута begin
, доступного в корпусе. Числовое увеличение, поэтому, если мы хотим, чтобы порядок XPath был не поврежден, мы можем сказать, что числовое значение каждого дочернего элемента node of @cat="np"
должно быть меньше следующего с помощью number()
."
Если я правильно понимаю это, вы можете использовать следующий XPath:
/node/node[
not(
node[position() < last()]
[number(@begin) > following-sibling::node/number(@begin)]
)
]
demo
XPath должен возвращать все элементы 2 уровня node
, где для каждого дочернего node
, за исключением последнего в текущем 2-м уровне node
, ни один из следующих-sibling node
не имеет числового значения begin
, чем у текущего дочернего node
.
Учитывая следующий пример XML:
<node id="0" begin="2">
<node id="0" begin="1" cat="np">
<node id="1" begin="1" pt="art" text="the" />
<node id="2" begin="3" pt="n" text="time" />
<node id="3" begin="2" pt="adj" text="available" />
</node>
<node id="0" begin="1" cat="np">
<node id="1" begin="1" pt="art" text="the" />
<node id="2" begin="2" pt="adj" text="concerned" />
<node id="3" begin="3" pt="n" text="man" />
</node>
</node>
Будет выбран только второй node
, поскольку это единственный 2-й уровень node
, который имеет значения атрибута begin
в порядке возрастания:
<node id="0" begin="1" cat="np">
<node id="1" begin="1" pt="art" text="the"/>
<node id="2" begin="2" pt="adj" text="concerned"/>
<node id="3" begin="3" pt="n" text="man"/>
</node>
Обновление 19 апреля 2017 года:
"... Тем не менее, мне бы хотелось, чтобы этот cat="np"
совпадал и делал функцию not()
менее агрессивной, т.е. требовали только узлы, указанные в XPath (в этом примере rel="det" pt="vnw" lemma="die"
и два узла rel="mod" pt="adj"
) следуют требованиям порядка, в которых атрибут begin должен быть меньше, чем следующий элемент структуры XPath."
Затем нам нужно добавить еще один предикат указать те узлы в not()
, где мы проверяем требование порядка атрибута:
node[(@rel="det" and @pt="vnw" and @lemma="die") or (@rel="mod" and @pt="adj")]
[position() < last()]
[number(@begin) >
following-sibling::node[(@rel="det" and @pt="vnw" and @lemma="die") or (@rel="mod" and @pt="adj")]/number(@begin)
]
Таким образом, полное выражение будет выглядеть следующим образом:
//node[@cat="np" and
not(node[(@rel="det" and @pt="vnw" and @lemma="die") or (@rel="mod" and @pt="adj")]
[position() < last()]
[number(@begin) >
following-sibling::node[
(@rel="det" and @pt="vnw" and @lemma="die") or (@rel="mod" and @pt="adj")
]/number(@begin)
]
)
and node[@rel="det" and @pt="vnw" and @lemma="die"]
and count(node[@rel="mod" and @pt="adj"]) > 1
]
demo
Ответ 2
Часть вашего вопроса, который, я думаю, я понимаю, таков:
Скажем, что я хочу сопоставить XML, где каждый прямой дочерний корень имеет атрибут, который меньше, чем следующий брат.
<node id="0" begin="2">
<node id="1" begin="2">
<node id="2" begin="2"/>
<node id="3" begin="3"/>
</node>
<node id="4" begin="5">
<node id="5" begin="5"/>
</node>
<node id="6" begin="6"/>
</node>
Этот XPath должен соответствовать:
/node/node[number(@begin) < number(../node/@begin)]
Теперь ясно, почему это дает вам ошибку. Внутри предиката ..
выбирает node с id = 0, у этого есть три дочерних узла (с идентификаторами 1, 4 и 6), и каждый из них имеет атрибут @begin
, поэтому number(../node/@begin)
выбирает последовательность из трех атрибутов.
Ваш запрос никоим образом не связан с требованием прозы, а именно
где каждый прямой дочерний элемент корня имеет атрибут begin, который меньше, чем следующий родной брат
Условие для этого было бы
node [каждый $n в node [position() lt last()] удовлетворяет (число ($ n/@begin) lt number ($ n/next-sibling:: node/@begin)]
Ответ 3
с точки зрения вашего рекурсивного запроса на поиск:
Использование //node[@pt=("art" or "adj" or "n")]/ancestor::*
выполняет поиск с внутренних уровней дерева xml. В вашем примере xml это вернет (для каждой группы элементов) каждый верхний уровень рекурсивным образом.
Для получения дополнительной информации: http://www.w3.org/TR/xpath-30/