tutorial texto secuencias recorrer instrucciones curso con buscar altova xpath groovy namespaces xml-namespaces htmlunit

secuencias - xpath buscar texto



Seleccione el espacio de nombres predeterminado en XPath con HtmlUnit (2)

Quiero analizar un feed de Feedburner con HtmlUnit. El feed es este: http://feeds.feedburner.com/alcoanewsreleases

De este feed quiero leer todos los nodos de los elementos , por lo que normalmente un //item XPath debería hacer el truco. Desafortunadamente eso no funciona en este caso.

fragmento de código groovy:

def page = webClient.getPage("http://feeds.feedburner.com/alcoanewsreleases") def elements = page.getByXPath("//item")

Muestra del feed 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&amp;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&amp;pageID=20100104006194en</feedburner:origLink> </item> [...SNIP...] </rdf:RDF>

Sospecho que esto es un problema con los espacios de nombres porque este documento tiene 4 espacios de nombres. Los espacios de nombres son

  • (este es el valor predeterminado) xmlns = "http://purl.org/rss/1.0/"
  • xmlns: rdf = "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"

Intenté usar Nokogiri con esto (otro Analizador XML que uso para los scripts de Ruby). Con Nokogiri, podemos usar el elemento XPath //xmlns:item que funciona y devuelve todos los nodos del feed.

He intentado el mismo XPath con HtmlUnit, pero no funciona.

Así que creo que puedo formular mi pregunta como: ¿cómo puedo seleccionar un nodo del espacio de nombres predeterminado con HtmlUnit?

¿Algunas ideas?


De este feed quiero leer todos los nodos de los elementos , por lo que normalmente un //item XPath debería hacer el truco. Desafortunadamente eso no funciona en este caso.

En XPath, eso significa "seleccionar todos los elementos cuyo nombre local es un item que no está en ningún espacio de nombres ". En RSS, los item elemento deben estar en un espacio de nombres. Por lo tanto, lo anterior nunca debería funcionar con un analizador XML compatible y un motor XPath.

Lo que es confuso es que en XML, <item> significa "un elemento llamado item que está en el espacio de nombres predeterminado , es decir, cualquier espacio de nombres predeterminado que esté en el alcance en este lugar en el documento;" mientras que en XPath, "elemento" significa un elemento sin espacio de nombres. (O podría decir que significa un elemento en el espacio de nombres predeterminado, pero a menos que tenga una forma de decirle a XPath cuál es el espacio de nombres predeterminado, el espacio de nombres predeterminado no es el espacio de nombres. Generalmente (¿siempre?) En XPath 1.0 no hay forma para declarar el espacio de nombres predeterminado para expresiones XPath).

La otra cosa confusa para los principiantes es que las correlaciones de prefijo del espacio de nombres en el documento XML fuente no son consideradas significativas por el procesador XPath. Cuando se analiza el documento XML, se crea una estructura de datos que recuerda el nombre y el espacio de nombres de cada elemento (y otros nodos). Los prefijos de espacio de nombres utilizados, incluido el prefijo vacío del espacio de nombres predeterminado, se consideran mera conveniencia sintáctica. Más sobre esto a continuación ...

Con Nokogiri, podemos usar el elemento XPath //xmlns:item que funciona y devuelve todos los nodos del feed.

Sea lo que sea, no es XPath. Tal vez sea una extensión de Nokogiri (una muy conveniente, pero su sintaxis es realmente contra-intuitiva).

Así que creo que puedo formular mi pregunta como: ¿cómo puedo seleccionar un nodo del espacio de nombres predeterminado con HtmlUnit?

Vamos a formularlo como: ¿Cómo puedo seleccionar los elementos del elemento RSS con HtmlUnit? Lo pronuncio de esa manera porque la especificación RSS (en realidad, en general, cualquier especificación de vocabulario XML conforme) no requiere que sus elementos estén en el espacio de nombres predeterminado. Eso sucede en la muestra que recibió, pero el proveedor del servicio podría cambiar eso mañana y seguir siendo perfectamente conforme al RSS. Mañana, el proveedor del servicio podría usar el prefijo de espacio de nombres "rss" para ese espacio de nombres; o cualquier otro prefijo arbitrario. Lo que RSS especifica es en qué espacio de nombres estarán sus elementos: el espacio de nombres cuyo URI es http://purl.org/rss/1.0/ .

Es como preguntar, "¿Cómo escribo una función (en Javascript, C, Java, etc.) que me puede decir el valor de la variable a ?" Por lo general, una función no tiene idea de qué nombre de variable se utilizó para qué en la persona que llama. Todo lo que sabe son los valores de sus argumentos. Si llama a sqrt(4) , obtendrá la misma respuesta que con a = 4; sqrt(a) a = 4; sqrt(a) o rumpelstiltzkin = 4; sqrt(rumpelstiltzkin) rumpelstiltzkin = 4; sqrt(rumpelstiltzkin) . Claramente, el nombre del argumento variable no tiene un efecto directo en el resultado de la llamada a la función. Simplemente debe ser el nombre de una variable que tenga el valor correcto. Si un compilador se quejó porque escribió b = 4; return sqrt(b) b = 4; return sqrt(b) lugar de usar a , pensarías que el compilador estaba loco. Se supone que no le importan los nombres de variables siempre que use identificadores válidos.

De la misma manera, cuando procesamos RSS, no debemos preocuparnos sobre qué prefijo de espacio de nombres se usa, siempre que sea un prefijo que identifique el espacio de nombres correcto. No podría ser un prefijo (que identifica el espacio de nombres predeterminado).

En XPath 2.0, puede comodín el espacio de nombres. Esto es muy útil si sabes que no vas a necesitar espacios de nombres para la desambiguación. En ese caso, puede seleccionar //*:item . Sin embargo, no creo que HTMLUnit sea compatible con XPath 2.0. También en entornos XPath 2.0 como XSLT 2.0, puede especificar un espacio de nombres predeterminado para expresiones XPath, pero eso no lo ayudará en HTMLUnit.

Entonces tienes un par de opciones:

  • Use una expresión XPath que ignore los espacios de nombres, como //*[local-name = ''item''] .

o

  • La forma robusta: Registre un prefijo de espacio de nombres para http://purl.org/rss/1.0/ y http://purl.org/rss/1.0/ en su expresión XPath: //rss:item . La pregunta es: ¿cómo registrar un prefijo de espacio de nombres en HTMLUnit y pasarlo al procesador XPath? Eché un vistazo rápido a los documentos y no encontré ninguna facilidad para hacerlo.

Advertencia: Debo añadir que lo anterior está relacionado con la conformidad de los procesadores XPath. No tengo idea de qué procesador XPath usa HTMLUnit. Hay algunos procesadores XPath que ignoran las especificaciones y hacen que el mundo sea más confuso para todos.

Vi aquí que alguien usó la siguiente sintaxis para los elementos en el espacio de nombres predeterminado en HTMLUnit:

//:item

Pero no lo recomendaría, por tres razones:

  1. No es válido XPath, por lo que no puede esperar que funcione con otros programas.

  2. Solo funcionará en los canales RSS que declaren que el espacio de nombres RSS es el espacio de nombre predeterminado. Las fuentes RSS que usan un prefijo de espacio de nombres harán que lo anterior falle.

  3. Te impedirá aprender cómo funcionan realmente los espacios de nombres XML, y ayudará a preservar el status quo de las herramientas que no son compatibles con los espacios de nombres.

HTMLUnit está diseñado principalmente para HTML, por lo que el manejo incompleto de XML es comprensible. Pero afirmar que es compatible con XPath y luego no proporciona formas de declarar prefijos de espacio de nombres es un error . HTMLUnit usa un paquete XPath que parece ser parte de Xalan-J. Ese paquete tiene formas de proporcionar asignaciones de espacios de nombres a XPath , pero no sé si HTMLUnit expone esa funcionalidad.


Esto suena lo suficientemente familiar como para estar bastante seguro de que he usado espacios de nombres y XPath con éxito con HtmlUnit en el pasado, pero por supuesto no puedo encontrar el código. Sospecho que debe haber sido solo con páginas HTML: la referencia de page en su ejemplo es una XmlPage que tiene una cantidad de métodos específicos para los espacios de nombres, todos arrojan una excepción "no implementada aún" cuando se usan. :-(

La versión actual (2.8) de HtmlUnit tiene casi un año de antigüedad, por lo que es posible que ya se haya realizado algún trabajo para admitir espacios de nombres XML. La lista de correo "Usuarios de HtmlUnit" sería el lugar para averiguarlo.

Mientras tanto, como siempre, hay una solución alternativa:

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() ) ;

Oh, chico, Java es doloroso después de trabajar en Scala ... ;-)