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/