HtmlAgilityPack: xpath y regex

c# html-agility-pack regex

Pregunta

Actualmente estoy usando HtmlAgilityPack para buscar cierto contenido a través de una consulta xpath. Algo como esto:

var col = doc.DocumentNode.SelectNodes("//*[text()[contains(., 'foo'] or @*....

Ahora quiero buscar contenido específico en todos los códigos fuente de HTML (= texto, etiquetas y atributos) usando una expresión regular. ¿Cómo se puede lograr esto con HtmlAgilityPack? ¿Puede HtmlAgilityPack manejar xpath + regex o cuál sería la mejor manera de usar una expresión regular y HtmlAgilityPack para buscar?

Respuesta aceptada

El Html Agility Pack utiliza la implementación subyacente de .NET XPATH para su soporte de XPATH. Afortunadamente, XPATH en .NET es totalmente extensible (BTW: es una pena que Microsoft no invierta más en esta magnífica tecnología ...).

Supongamos que tengo este html:

<div>hello</div>
<div>hallo</div>

Aquí hay un código de ejemplo que seleccionará ambos nodos porque compara los nodos con la expresión de expresión regular 'h.llo':

HtmlNodeNavigator nav = new HtmlNodeNavigator("mypage.htm");
foreach (var node in SelectNodes(nav, "//div[regex-is-match(text(), 'h.llo')]"))
{
    Console.WriteLine(node.OuterHtml); // should dump both div elements
}

Funciona porque uso un contexto especial Xslt / XPath donde he definido una nueva función XPATH llamada "regex-is-match". Aquí está el código de utilidad SelectNodes:

public static IEnumerable<HtmlNode> SelectNodes(HtmlNodeNavigator navigator, string xpath)
{
    if (navigator == null)
        throw new ArgumentNullException("navigator");

    XPathExpression expr = navigator.Compile(xpath);
    expr.SetContext(new HtmlXsltContext());

    object eval = navigator.Evaluate(expr);
    XPathNodeIterator it = eval as XPathNodeIterator;
    if (it != null)
    {
        while (it.MoveNext())
        {
            HtmlNodeNavigator n = it.Current as HtmlNodeNavigator;
            if (n != null && n.CurrentNode != null)
            {
                yield return n.CurrentNode;
            }
        }
    }
}

Y aquí está el código de soporte:

    public class HtmlXsltContext : XsltContext
    {
        public HtmlXsltContext()
            : base(new NameTable())
        {
        }

        public override int CompareDocument(string baseUri, string nextbaseUri)
        {
            throw new NotImplementedException();
        }

        public override bool PreserveWhitespace(XPathNavigator node)
        {
            throw new NotImplementedException();
        }

        protected virtual IXsltContextFunction CreateHtmlXsltFunction(string prefix, string name, XPathResultType[] ArgTypes)
        {
            return HtmlXsltFunction.GetBuiltIn(this, prefix, name, ArgTypes);
        }

        public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] ArgTypes)
        {
            return CreateHtmlXsltFunction(prefix, name, ArgTypes);
        }

        public override IXsltContextVariable ResolveVariable(string prefix, string name)
        {
            throw new NotImplementedException();
        }

        public override bool Whitespace
        {
            get { return true; }
        }
    }

    public abstract class HtmlXsltFunction : IXsltContextFunction
    {
        protected HtmlXsltFunction(HtmlXsltContext context, string prefix, string name, XPathResultType[] argTypes)
        {
            Context = context;
            Prefix = prefix;
            Name = name;
            ArgTypes = argTypes;
        }

        public HtmlXsltContext Context { get; private set; }
        public string Prefix { get; private set; }
        public string Name { get; private set; }
        public XPathResultType[] ArgTypes { get; private set; }

        public virtual int Maxargs
        {
            get { return Minargs; }
        }

        public virtual int Minargs
        {
            get { return 1; }
        }

        public virtual XPathResultType ReturnType
        {
            get { return XPathResultType.String; }
        }

        public abstract object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext);

        public static IXsltContextFunction GetBuiltIn(HtmlXsltContext context, string prefix, string name, XPathResultType[] argTypes)
        {
            if (name == "regex-is-match")
                return new RegexIsMatch(context, name);

            // TODO: create other functions here
            return null;
        }

        public static string ConvertToString(object argument, bool outer, string separator)
        {
            if (argument == null)
                return null;

            string s = argument as string;
            if (s != null)
                return s;

            XPathNodeIterator it = argument as XPathNodeIterator;
            if (it != null)
            {
                if (!it.MoveNext())
                    return null;

                StringBuilder sb = new StringBuilder();
                do
                {
                    HtmlNodeNavigator n = it.Current as HtmlNodeNavigator;
                    if (n != null && n.CurrentNode != null)
                    {
                        if (sb.Length > 0 && separator != null)
                        {
                            sb.Append(separator);
                        }

                        sb.Append(outer ? n.CurrentNode.OuterHtml : n.CurrentNode.InnerHtml);
                    }
                }
                while (it.MoveNext());
                return sb.ToString();
            }

            IEnumerable enumerable = argument as IEnumerable;
            if (enumerable != null)
            {
                StringBuilder sb = null;
                foreach (object arg in enumerable)
                {
                    if (sb == null)
                    {
                        sb = new StringBuilder();
                    }

                    if (sb.Length > 0 && separator != null)
                    {
                        sb.Append(separator);
                    }

                    string s2 = ConvertToString(arg, outer, separator);
                    if (s2 != null)
                    {
                        sb.Append(s2);
                    }
                }
                return sb != null ? sb.ToString() : null;
            }

            return string.Format("{0}", argument);
        }

        public class RegexIsMatch : HtmlXsltFunction
        {
            public RegexIsMatch(HtmlXsltContext context, string name)
                : base(context, null, name, null)
            {
            }

            public override XPathResultType ReturnType { get { return XPathResultType.Boolean; } }
            public override int Minargs { get { return 2; } }

            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                if (args.Length < 2)
                    return false;

                return Regex.IsMatch(ConvertToString(args[0], false, null), ConvertToString(args[1], false, null));
            }
        }
    }

La función regex se implementa en una clase llamada RegexIsMatch al final. No es super complicado. Tenga en cuenta que hay una función de utilidad ConvertToString que intenta forzar cualquier "cosa" xpath en una cadena que sea muy útil.

Por supuesto, con esta tecnología, puede definir cualquier función XPATH que necesite con muy poco código (uso esto todo el tiempo para hacer conversiones de mayúsculas / minúsculas ...).


Respuesta popular

Directamente citar ,

Creo que la falla aquí es que HTML es una gramática de tipo 2 de Chomsky (gramática sin contexto) y RegEx es una gramática de tipo 3 de Chomsky (gramática regular) . Dado que una gramática de Tipo 2 es fundamentalmente más compleja que una gramática de Tipo 3 (consulte la jerarquía de Chomsky ), posiblemente no pueda hacer que esto funcione. Pero muchos lo intentarán, algunos reclamarán el éxito y otros encontrarán la falla y lo arruinarán totalmente.

Podría tener sentido usar una expresión regular con algunas partes de un documento HTML. Tratar de usar HtmlAgilityPack para ejecutar una expresión regular en las etiquetas y la estructura de un documento HTML es perverso y, en última instancia, no puede proporcionar una solución universal a su problema.



Related

Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué