Как использовать xpath на узлах с префиксом, но без пространства имен?

У меня есть файл XML, который мне нужно проанализировать. Я не контролирую формат файла и не могу его изменить.

Файл использует префикс (назовите его a), но он нигде не определяет пространство имен для этого префикса. Я не могу использовать xpath запросить узлы с a Пространство имен.

Вот содержимое документа XML

<?xml version="1.0" encoding="UTF-8"?>

<a:root>
  <a:thing>stuff0</a:thing>
  <a:thing>stuff1</a:thing>
  <a:thing>stuff2</a:thing>
  <a:thing>stuff3</a:thing>
  <a:thing>stuff4</a:thing>
  <a:thing>stuff5</a:thing>
  <a:thing>stuff6</a:thing>
  <a:thing>stuff7</a:thing>
  <a:thing>stuff8</a:thing>
  <a:thing>stuff9</a:thing>
</a:root>

Я использую Nokogiri для запроса документа:

doc = Nokogiri::XML(open('text.xml'))
things = doc.xpath('//a:thing')

Сбой дает следующую ошибку:

Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: //a:thing

Из моего исследования я обнаружил, что могу указать пространство имен для префикса в xpath метод:

things = doc.xpath('//a:thing', a: 'nobody knows')

Это возвращает пустой массив.

Что было бы для меня лучшим способом получить нужные мне узлы?

1 ответ

Решение

Проблема заключается в том, что пространство имен не определено должным образом в документе XML. В результате Nokogiri видит имена узлов как "a:root" вместо "a" как пространство имен, а "root" как имя узла:

xml = %Q{
    <?xml version="1.0" encoding="UTF-8"?>
    <a:root>
      <a:thing>stuff0</a:thing>
      <a:thing>stuff1</a:thing>
    </a:root>
}
doc = Nokogiri::XML(xml)
puts doc.at_xpath('*').node_name
#=> "a:root"
puts doc.at_xpath('*').namespace
#=> ""

Решение 1. Укажите имя узла с двоеточием

Одним из решений является поиск узлов с именем "a: thing". Вы не можете сделать //a:thing поскольку XPath будет обрабатывать "а" как пространство имен. Вы можете обойти это, делая //*[name()="a:thing"]:

xml = %Q{
    <?xml version="1.0" encoding="UTF-8"?>
    <a:root>
      <a:thing>stuff0</a:thing>
      <a:thing>stuff1</a:thing>
    </a:root>
}
doc = Nokogiri::XML(xml)
things = doc.xpath('//*[name()="a:thing"]')
puts things
#=> <a:thing>stuff0</a:thing>
#=> <a:thing>stuff1</a:thing>

Решение 2. Изменить XML-документ, чтобы определить пространство имен

Альтернативное решение - изменить файл XML, который вы получаете, чтобы правильно определить пространство имен. Документ будет вести себя с пространствами имен, как и ожидалось:

xml = %Q{
    <?xml version="1.0" encoding="UTF-8"?>
    <a:root>
      <a:thing>stuff0</a:thing>
      <a:thing>stuff1</a:thing>
    </a:root>
}
xml.gsub!('<a:root>', '<a:root xmlns:a="foo">')
doc = Nokogiri::XML(xml)
things = doc.xpath('//a:thing')
puts things
#=> <a:thing>stuff0</a:thing>
#=> <a:thing>stuff1</a:thing>
Другие вопросы по тегам