Usando XPath para seleccionar atributos con comodines

c# html html-agility-pack xpath

Pregunta

Tengo el código HTML que necesito analizar, y estoy usando C # y Html Agility Pack Library para hacer la selección de nodos. Mi html se verá algo así como:

<input data-translate-atrr-placeholder="FORGOT_PASSWORD.FORM.EMAIL">

o:

<h1 data-translate="FORGOT_PASSWORD.FORM.EMAIL"></h1>

donde data-translate-attr-**** es el nuevo patrón de atributos que necesito encontrar

Podría usar algo como esto:

//[contains(@??,'data-translate-attr')]

pero desafortunadamente, eso solo buscará valor DENTRO de un atributo. ¿Cómo busco el atributo en sí mismo, con un comodín?

Actualización: @Mathias Muller

HtmlAgilityPack.HtmlDocument htmlDoc    
// this is the old code (returns nodes)
var nodes = htmlDoc.DocumentNode.SelectNodes("//@data-translate");  
// these suggestions return no nodes using the same data
var nodes = htmlDoc.DocumentNode.SelectNodes("//@*[contains(name(),'data-translate')]");  
var nodes = htmlDoc.DocumentNode.SelectNodes("//@*[starts-with(name(),'data-translate')]");

Actualización 2

Esto parece ser un problema de Html Agility Pack más que un problema de XPath, usé Chrome para probar mis expresiones XPath y todo lo siguiente funcionó en Chrome pero no en Html Agility Pack:

//@*[contains(local-name(),'data-translate')]
//@*[starts-with(name(),'data-translate')]
//attribute::*[starts-with(local-name(.),'data-translate')]

Mi solución

Terminé haciendo cosas a la antigua usanza ...

var nodes = htmlDoc.DocumentNode.SelectNodes("//@*");

if (nodes != null) {
    foreach (HtmlNode node in nodes) {
        if (node.HasAttributes) {
            foreach (HtmlAttribute attr in node.Attributes) {
                if (attr.Name.StartsWith("data-translate")) {
                    // code in here to handle translation node
                }
            }
        }
    }
}

Respuesta aceptada

Utilice las funciones XPath que contains() o starts-with() . Necesitas una expresión XPath como

//@*[contains(name(),'data-translate')]

o quizás

//@*[starts-with(name(),'data-translate')]

que en realidad recupera los nodos de atributo . Arriba, el @* es el atributo comodín que estaba buscando.


Respuesta popular

en lugar de usar el name() , use local-name() como:

var nodes = htmlDoc.DocumentNode.SelectNodes("//@*[starts-with(local-name(),'data-translate')]");

la diferencia es que name() debe darle el nombre del atributo con un prefijo como un espacio de nombres en xml, y local-name() emitirá ese prefijo si está allí, en su caso name() y local-name() debería funciona de la misma manera porque es html y no hay espacios de nombres, pero parece que no lo hacen y es probable que sea un error.

Prueba:

    var html = "<h3 x='foo'></h3>";
    var doc = new HtmlAgilityPack.HtmlDocument();
    doc.LoadHtml(html);
    var ElementByName = doc.DocumentNode.SelectSingleNode("//*[name()='h3']");                //Works
    var ElementByLocalName = doc.DocumentNode.SelectSingleNode("//*[local-name()='h3']");     //Works
    var ElementByAttributeLocalName = doc.DocumentNode.SelectSingleNode("//*[@*[local-name()='x']]"); //Works
    var ElementByAttributeName = doc.DocumentNode.SelectSingleNode("//*[@*[name()='x']]");  //Does NOT

    //Mathias Way
    var ElementByAttributeLocalName_ = doc.DocumentNode.SelectSingleNode("//@*[local-name() = 'x']"); //Works
    var ElementByAttributeName_ = doc.DocumentNode.SelectSingleNode("//@*[name() = 'x']");  //Does NOT


Related

Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow