Использование XPath для извлечения элементов XOM из документов с ненужными пространствами имен

Я пытаюсь проанализировать некоторый HTML, возвращаемый внешней системой с помощью XOM. HTML-код выглядит следующим образом:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<body>
  <div>
    Help I am trapped in a fortune cookie factory
  </div>
</body>
</html>

(На самом деле это значительно запутаннее, но в нем есть объявление DOCTYPE и объявления пространства имен и языка, а приведенный выше HTML демонстрирует ту же проблему, что и настоящий HTML.)

Что я хочу сделать, так это извлечь содержимое <div>, но объявление пространства имен, похоже, сбивает с толку XPath. Если я удалю объявление пространства имен (вручную, из файла), следующий код найдет <div> без проблем:

Document document = ...
Nodes divs = document.query("//div");

Но с пространством имен возвращаемый Nodes имеет размер 0.

Хорошо, а как насчет того, чтобы удалить пространство имен программно?

Element rootElement = document.getRootElement();
rootElement.removeNamespaceDeclaration(rootElement.getNamespacePrefix());

...похоже, что это должно работать, но ничего не делает. Из javadoc:

Этот метод удаляет только дополнительные пространства имен, добавленные с помощью addNamespaceDeclaration..

Хорошо, подумал я, предоставлю пространство имен для запроса:

XPathContext context = 
    XPathContext.makeNamespaceContext(document.getRootElement());
Nodes divs = document.query("//div", context);

Размер по-прежнему нулевой.

Как насчет создания контекста пространства имен вручную?

XPathContext context = context = new XPathContext(
     rootElement.getNamespacePrefix(), rootElement.getNamespaceURI());
Nodes divs = document.query("//div", context);

Конструктор XPathContext взрывается:

nu.xom.NamespaceConflictException: 
    XPath expressions do not use the default namespace

Итак, я ищу либо:

  1. способ заставить этот запрос работать, или
  2. способ программно удалить объявления пространств имен или
  3. объяснение правильного подхода, предполагая, что оба они неверны.

Обновление: на основе ответа Льва Левицкого и Часто задаваемые вопросы о Jaxen Я придумал следующий хак:

XPathContext context = new XPathContext(
    "foo", 
    document.getRootElement().getNamespaceURI());
Nodes divs = document.query("//foo:div");

Мне все еще кажется, что это немного безумно, но, думаю, Джаксен хочет, чтобы ты поступал именно так.


Обновление №2. Как указано ниже и по всему Интернет, это не вина Джаксена; это просто XPath является XPath.

Итак, хотя этот хак работает, я все же хотел бы убрать объявление пространства имен. Желательно, не доходя до XSLT.


person David Moles    schedule 12.03.2012    source источник
comment
Именно так XPath работает с пространствами имен, это не зависит от Jaxen: если вы хотите сопоставить что-то с пространством имен, вы должны использовать явный префикс в XPath.   -  person MiMo    schedule 13.03.2012
comment
Да, при дальнейшем чтении я вижу это. Так что ладно, Джаксен не виноват, но он все равно кажется немного сумасшедшим. Или, в лучшем случае, педантичен и рассчитан в первую очередь на максимальную корректность в нереальных вариантах использования.   -  person David Moles    schedule 15.03.2012


Ответы (2)


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

Nodes divs = document.query("//{http://www.w3.org/1999/xhtml}div");

или используя префиксы, которые сопоставляются с соответствующими пространствами имен (я думаю, для этого предназначено NamespaceContext, но в вашем запросе нет префиксов).

К сожалению, я не знаю, как это реализовано на Java, но могу привести пример на Python, если это поможет.

person Lev Levitsky    schedule 12.03.2012

Ты можешь написать:

Nodes divs = document.query("//*[local-name()='div' and namespace-uri()='http://www.w3.org/1999/xhtml']");
person peter.murray.rust    schedule 02.04.2013