Выберите пространство имен по умолчанию в XPath с помощью HtmlUnit
Я хочу проанализировать канал Feedburner с помощью HtmlUnit. Фид такой: http://feeds.feedburner.com/alcoanewsreleases
Из этого канала я хочу прочитать все узлы элементов, поэтому обычно //item
XPath должен сделать свое дело. К сожалению, это не работает в этом случае.
Отличный фрагмент кода:
def page = webClient.getPage("http://feeds.feedburner.com/alcoanewsreleases")
def elements = page.getByXPath("//item")
Образец XML-канала:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss1full.xsl"?>
<?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://purl.org/rss/1.0/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
[...SNIP...]
<item rdf:about="http://www.alcoa.com/global/en/news/news_detail.asp?newsYear=2011&pageID=20110518006002en">
<title>Chris L. Ayers Named President, Alcoa Global Primary Products</title>
<dc:date>2011-05-18</dc:date
<link>http://feedproxy.google.com/~r/alcoanewsreleases/~3/PawvdhpJrkc/news_detail.asp</link>
<description>NEW YORK--(BUSINESS WIRE)--Alcoa (NYSE:AA) announced today that Chris L. Ayers has been named President of Alcoa’s Global Primary Products (GPP) business, effective May 18, 2011. Ayers, previously Chief Operating Officer of GPP, succeeds John Thuestad, who will be handling special projects for the Company. Ayers joined Alcoa in February 2010 as Chief Operating Officer of Alcoa Cast, Forged and Extruded Products, a new position. He was elected a Vice President of Alcoa in April 2010 and Executive</description>
<feedburner:origLink xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">http://www.alcoa.com/global/en/news/news_detail.asp?newsYear=2010&pageID=20100104006194en</feedburner:origLink>
</item>
[...SNIP...]
</rdf:RDF>
Я подозреваю, что это проблема с пространствами имен, потому что этот документ имеет 4 пространства имен. Пространства имен
- (это значение по умолчанию) xmlns = "http://purl.org/rss/1.0/"
- XMLNS: РДФ ="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- XMLNS: dc = "http://purl.org/dc/elements/1.1/"
- XMLNS: FeedBurner = "http://rssnamespace.org/feedburner/ext/1.0"
Я попытался использовать Nokogiri с этим (другой парсер XML, который я использую для скриптов ruby). С Nokogiri я мог бы просто нам XPath //xmlns:item
который работает и возвращает все узлы из канала.
Я пробовал тот же XPath с HtmlUnit, но он не работает.
Поэтому я думаю, что могу сформулировать свой вопрос следующим образом: Как я могу выбрать узел из пространства имен по умолчанию с помощью HtmlUnit?
Есть идеи?
2 ответа
Из этого канала я хочу прочитать все узлы элементов, поэтому обычно
//item
XPath должен сделать свое дело. К сожалению, это не работает в этом случае.
В XPath это означает "выбрать все элементы, локальное имя которых item
которые не находятся в пространстве имен ". В RSS, item
элементы должны быть в пространстве имен. Таким образом, вышеприведенное никогда не должно работать с соответствующим синтаксическим анализатором XML и механизмом XPath.
Что сбивает с толку то, что в XML, <item>
означает "элемент с именем item, который находится в пространстве имен по умолчанию, т. е. любое пространство имен по умолчанию находится в области действия в этом месте в документе"; тогда как в XPath "элемент" означает элемент без пространства имен. (Или, можно сказать, это означает элемент в пространстве имен по умолчанию, но если у вас нет способа сообщить XPath, что такое пространство имен по умолчанию, пространство имен по умолчанию не является пространством имен. Обычно (всегда?) В XPath 1.0 нет никакого способа объявить пространство имен по умолчанию для выражений XPath.)
Еще одна запутанная вещь для начинающих заключается в том, что сопоставления префиксов пространства имен в исходном XML-документе не считаются значимыми процессором XPath. Когда XML-документ анализируется, создается структура данных, которая запоминает имя и пространство имен каждого элемента (и других узлов). Используемые префиксы пространства имен, включая пустой префикс пространства имен по умолчанию, считаются простым синтаксическим удобством. Подробнее об этом ниже...
С Nokogiri я мог бы просто нам XPath
//xmlns:item
который работает и возвращает все узлы из канала.
Что бы это ни было, это не XPath. Может быть, это расширение к Nokogiri (очень удобное, но его синтаксис действительно нелогичен).
Поэтому я думаю, что могу сформулировать свой вопрос следующим образом: Как я могу выбрать узел из пространства имен по умолчанию с помощью HtmlUnit?
Давайте сформулируем это следующим образом: Как я могу выбрать элементы элемента RSS с помощью HtmlUnit? Я выражаю это таким образом, потому что спецификация RSS (фактически вообще любая соответствующая спецификация словаря XML) не требует, чтобы ее элементы были в пространстве имен по умолчанию. Это верно для образца, который вы получили, но поставщик услуг может изменить это завтра и при этом полностью соответствовать RSS. Завтра поставщик услуг может использовать префикс пространства имен "rss" для этого пространства имен; или любой другой произвольный префикс. Что RSS указывает, так это то, в каком пространстве имен будут его элементы: пространство имен, чей URI http://purl.org/rss/1.0/
,
Это все равно, что спросить: "Как мне написать функцию (в Javascript, C, Java и т. Д.), Которая может сообщить мне значение переменной a
?"Обычно функция не имеет представления, какое имя переменной использовалось для чего в вызывающей программе. Все, что она знает, это значения ее аргументов. Если вы вызываете sqrt(4)
вы получите тот же ответ, что и с a = 4; sqrt(a)
или же rumpelstiltzkin = 4; sqrt(rumpelstiltzkin)
, Понятно, что имя аргумента-переменной не оказывает прямого влияния на результат вызова функции. Это просто должно быть имя переменной, которая содержит правильное значение. Если компилятор жаловался, потому что вы написали b = 4; return sqrt(b)
Вместо того, чтобы использовать a
можно подумать, что компилятор был чокнутым. Не следует заботиться об именах переменных, если вы используете допустимые идентификаторы.
Точно так же, при обработке RSS мы не должны заботиться о том, какой префикс пространства имен используется, если это префикс, который идентифицирует правильное пространство имен. Это может быть без префикса (который определяет пространство имен по умолчанию).
В XPath 2.0 вы можете использовать подстановочное пространство имен. Это очень удобно, если вы знаете, что вам не понадобятся пространства имен для устранения неоднозначности. В этом случае вы можете выбрать //*:item
, Однако я не думаю, что HTMLUnit поддерживает XPath 2.0. Также в средах XPath 2.0, таких как XSLT 2.0, вы можете указать пространство имен по умолчанию для выражений XPath, но это не поможет вам в HTMLUnit.
Итак, у вас есть несколько вариантов:
- Используйте выражение XPath, которое игнорирует пространства имен, например
//*[local-name() = 'item']
,
или же
- Надежный способ: зарегистрируйте префикс пространства имен для
http://purl.org/rss/1.0/
и используйте его в своем выражении XPath://rss:item
, Тогда возникает вопрос: как зарегистрировать префикс пространства имен в HTMLUnit и передать его процессору XPath? Я быстро просмотрел документы и не нашел возможности сделать это.
Предостережение: я должен добавить, что вышесказанное касается соответствия процессоров XPath. Я понятия не имею, что процессор XPath HTMLUnit использует. Есть некоторые процессоры XPath, которые игнорируют спецификации и делают мир более запутанным для всех.
Я видел здесь, что кто-то использовал следующий синтаксис для элементов в пространстве имен по умолчанию в HTMLUnit:
//:item
Но я бы не рекомендовал этого по трем причинам:
Это не правильный XPath, поэтому вы не можете ожидать, что он будет работать с другими программами.
Он будет работать только в каналах RSS, которые объявляют пространство имен RSS в качестве пространства имен по умолчанию. RSS-каналы, использующие префикс пространства имен, приведут к сбою вышеописанного.
Это удержит вас от изучения того, как реально работают пространства имен XML, и поможет сохранить статус-кво инструментов, которые не поддерживают пространства имен должным образом.
HTMLUnit в первую очередь предназначен для HTML, поэтому неполная обработка XML понятна. Но утверждение о поддержке XPath, а затем не предоставление способов объявления префиксов пространства имен является ошибкой. HTMLUnit использует пакет XPath, который кажется частью Xalan-J. В этом пакете есть способы обеспечить сопоставления пространства имен для XPath, но я не знаю, предоставляет ли HTMLUnit эту функциональность.
Это звучит достаточно знакомо, и я уверен, что в прошлом я успешно использовал пространства имен и XPath с HtmlUnit, но, конечно, я не могу найти код. Я подозреваю, что это должно быть только с HTML-страницами: page
ссылка в вашем примере является XmlPage
который имеет ряд методов, специфичных для пространств имен, каждый из которых выдает исключение "еще не реализовано" при использовании.:-(
Текущая версия (2.8) HtmlUnit уже почти год, так что, возможно, за это время была проделана некоторая работа по поддержке пространств имен XML. Список рассылки "HtmlUnit Users" будет местом, чтобы узнать.
А пока как всегда есть обходной путь:
final XmlPage page = webClient.getPage("http://feeds.feedburner.com/alcoanewsreleases");
// no good
List elements = page.getByXPath("//item");
System.out.println( elements.size() ) ;
// ugly, but it works
DomElement de = (DomElement)page.getFirstByXPath( "//rdf:RDF" );
List<DomNode> items = new ArrayList<DomNode>() ;
for( DomNode dn : de.getChildNodes() )
{
String name = dn.getLocalName() ;
if( ( name != null ) && ( name.equals( "item" ) ) )
items.add( dn ) ;
}
System.out.println( "found " + items.size() ) ;
Ох, боже, Ява больно после работы в Scala...;-)