поиск последовательных братьев и сестер с помощью XPath


Вот простой момент для эксперта XPath! :)

Структура документа:

<tokens>
  <token>
    <word>Newt</word><entityType>PROPER_NOUN</entityType>
  </token>
  <token>
    <word>Gingrich</word><entityType>PROPER_NOUN</entityType>
  </token>
  <token>
    <word>admires</word><entityType>VERB</entityType>
  </token>
  <token>
    <word>Garry</word><entityType>PROPER_NOUN</entityType>
  </token>
  <token>
    <word>Trudeau</word><entityType>PROPER_NOUN</entityType>
  </token>
</tokens>

Игнорируя семантическую невероятность документа, я хочу вытащить [["Ньют", "Гингрич"], ["Гарри", "Трюдо"]], то есть: когда есть две лексемы подряд, чьи entityTypes являются PROPER_NOUN, я хочу извлечь слова из этих двух лексем.

Я добрался до:

"//token[entityType='PROPER_NOUN']/following-sibling::token[1][entityType='PROPER_NOUN']"

... что доходит до нахождения второго из двух последовательных токенов PROPER_NOUN, но я не конечно, как заставить его испустить первый токен вместе с ним.

Некоторые примечания:

  • я не возражаю против более высокоуровневой обработки наборов узлов (например, в Ruby / Nokogiri), если это упрощает задачу.
  • в том случае, если существует три или более последовательных лексемы PROPER_NOUN (назовем их A, B, C), в идеале я хотел бы выделить [A, B], [B, C].

Обновление

Вот мое решение, использующее функции Ruby более высокого уровня. Но я устал от всех этих XPath хулиганы пинают песок мне в лицо, и я хотел бы знать, как это делают настоящие кодеры XPath!

def extract(doc)
  names = []
  sentences = doc.xpath("//tokens")
  sentences.each do |sentence| 
    tokens = sentence.xpath("token")
    prev = nil
    tokens.each do |token|
      name = token.xpath("word").text if token.xpath("entityType").text == "PROPER_NOUN"
      names << [prev, name] if (name && prev)
      prev = name
    end
  end
  names
end
4 4

4 ответа:

Я бы сделал это в два шага. Первым шагом является выбор набора узлов:

//token[entityType='PROPER_NOUN' and following-sibling::token[1][entityType='PROPER_NOUN']]
Это дает вам все token s, которые начинают пару из 2 слов. Затем, чтобы получить фактическую пару, повторите список узлов и извлеките ./word и following-sibling::token[1]/word

Использование XmlStarlet ( http://xmlstar.sourceforge.net/ - удивительный инструмент для быстрого управления xml) командная строка

xml sel -t -m "//token[entityType='PROPER_NOUN' and following-sibling::token[1][entityType='PROPER_NOUN']]" -v word -o "," -v "following-sibling::token[1]/word" -n /tmp/tok.xml 

Дача

Newt,Gingrich
Garry,Trudeau

XmlStarlet также скомпилирует эту командную строку в xslt, соответствующий бит is

  <xsl:for-each select="//token[entityType='PROPER_NOUN' and following-sibling::token[1][entityType='PROPER_NOUN']]">
    <xsl:value-of select="word"/>
    <xsl:value-of select="','"/>
    <xsl:value-of select="following-sibling::token[1]/word"/>
    <xsl:value-of select="'&#10;'"/>
  </xsl:for-each>

Используя Nokogiri, это может выглядеть примерно так:

#parse the document
doc = Nokogiri::XML(the_document_string)

#select all tokens that start 2-word pair
pair_starts = doc.xpath '//token[entityType = "PROPER_NOUN" and following-sibling::token[1][entityType = "PROPER_NOUN"]]'

#extract each word and the following one
result = pair_starts.each_with_object([]) do |node, array|
  array << [node.at_xpath('word').text, node.at_xpath('following-sibling::token[1]/word').text]
end

Это выражение XPath 1.0 :

   /*/token
      [entityType='PROPER_NOUN'
     and
       following-sibling::token[1]/entityType = 'PROPER_NOUN'
      ]
       /word

Выбирает все "первые в паре существительное-слова"

Это выражение XPath :

/*/token
  [entityType='PROPER_NOUN'
 and
   preceding-sibling::token[1]/entityType = 'PROPER_NOUN'
  ]
   /word

Выбирает все "вторые в паре существительное-слова"

Вам придется создать фактические пары, берущие K-й узел каждого из двух полученных результирующих наборов узлов.

Проверка на основе XSLT :

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:copy-of select=
  "/*/token
      [entityType='PROPER_NOUN'
     and
       following-sibling::token[1]/entityType = 'PROPER_NOUN'
      ]
       /word
  "/>
==============
  <xsl:copy-of select=
   "/*/token
      [entityType='PROPER_NOUN'
     and
       preceding-sibling::token[1]/entityType = 'PROPER_NOUN'
      ]
       /word
  "/>
 </xsl:template>
</xsl:stylesheet>

Просто вычисляет два выражения XPath и выводит результаты этих двух оценок (использование подходящего разделителя для визуализации конца первого результата и начала второго результата).

При применении к предоставленному XML-документу:

<tokens>
  <token>
    <word>Newt</word><entityType>PROPER_NOUN</entityType>
  </token>
  <token>
    <word>Gingrich</word><entityType>PROPER_NOUN</entityType>
  </token>
  <token>
    <word>admires</word><entityType>VERB</entityType>
  </token>
  <token>
    <word>Garry</word><entityType>PROPER_NOUN</entityType>
  </token>
  <token>
    <word>Trudeau</word><entityType>PROPER_NOUN</entityType>
  </token>
</tokens>

Вывод: :

<word>Newt</word>
<word>Garry</word>
==============
  <word>Gingrich</word>
<word>Trudeau</word>

И комбинирование (сжатие) двух результатов (которые вы укажете в своем любимом PL):

["Newt", "Gingrich"]

И

["Garry", "Trudeau"]

Когда то же самое преобразование применяется к этому XML документ (Обратите внимание, что теперь у нас есть один триппл):

<tokens>
  <token>
    <word>Newt</word><entityType>PROPER_NOUN</entityType>
  </token>
  <token>
    <word>Gingrich</word><entityType>PROPER_NOUN</entityType>
  </token>
  <token>
    <word>Rep</word><entityType>PROPER_NOUN</entityType>
  </token>
  <token>
    <word>admires</word><entityType>VERB</entityType>
  </token>
  <token>
    <word>Garry</word><entityType>PROPER_NOUN</entityType>
  </token>
  <token>
    <word>Trudeau</word><entityType>PROPER_NOUN</entityType>
  </token>
</tokens>

Теперь результат :

<word>Newt</word>
<word>Gingrich</word>
<word>Garry</word>
==============
  <word>Gingrich</word>
<word>Rep</word>
<word>Trudeau</word>

И сжатие двух результатов приводит к правильному, желаемому конечному результату:

["Newt", "Gingrich"],

["Gingrich", "Rep"],

И

["Garry", "Trudeau"]

Обратите Внимание :

Искомый результатможет быть получен с помощью одного выражения XPath 2.0. Дайте мне знать, если вы заинтересованы в решении XPath 2.0.

XPath возвращает узел или набор узлов,но не возвращает группы. Таким образом, вы должны определить начало каждой группы, а затем захватить остальные.

first = "//token[entityType='PROPER_NOUN' and following-sibling::token[1][entityType='PROPER_NOUN']]/word"
next = "../following-sibling::token[1]/word"

doc.xpath(first).map{|word| [word.text, word.xpath(next).text] }

Вывод:

[["Newt", "Gingrich"], ["Garry", "Trudeau"]]

Один только XPath недостаточно силен для этой задачи. Но это очень просто в XSLT:

<xsl:for-each-group select="token" group-adjacent="entityType">
  <xsl:if test="current-grouping-key="PROPER_NOUN">
     <xsl:copy-of select="current-group">
     <xsl:text>====</xsl:text>
  <xsl:if>
</xsl:for-each-group>