поиск последовательных братьев и сестер с помощью 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 ответа:
Я бы сделал это в два шага. Первым шагом является выбор набора узлов:
Это дает вам все//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="' '"/> </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"]]