HtmlAgilityPack XPath игнорирует регистр - PullRequest
6 голосов
/ 05 февраля 2012

Когда я использую

SelectSingleNode("//meta[@name='keywords']")

это не работает, но когда я использую тот же случай, который использовался в оригинальном документе, он работает хорошо:

SelectSingleNode("//meta[@name='Keywords']")

Итак, вопрос в том, как установить регистр игнорирования?

Ответы [ 4 ]

8 голосов
/ 05 февраля 2012

Если фактическое значение неизвестно, я думаю, что вы должны использовать перевод.Я считаю, что это:

SelectSingleNode("//meta[translate(@name,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='keywords']")

Это хак, но это единственный вариант в XPath 1.0 (за исключением противоположного прописному).

4 голосов
/ 05 февраля 2012

Если вам нужно более комплексное решение, вы можете написать функцию расширения для процессора XPath, которая будет выполнять сравнение без учета регистра.Это немного кода, но вы пишете его только один раз.

После реализации расширения вы можете написать свой запрос следующим образом

"//meta[@name[Extensions:CaseInsensitiveComparison('Keywords')]]"

Где Extensions:CaseInsensitiveComparison - реализованная функция расширенияв приведенном ниже примере.

ПРИМЕЧАНИЕ: это не очень хорошо протестировано. Я просто собрал его вместе для этого ответа, поэтому обработка ошибок и т. д. не существует!

Ниже приведен код для пользовательского контекста XSLT, который предоставляет одну или несколько функций расширения

using System;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Xml;
using HtmlAgilityPack;

public class XsltCustomContext : XsltContext
{
  public const string NamespaceUri = "http://XsltCustomContext";

  public XsltCustomContext()
  {
  }

  public XsltCustomContext(NameTable nt) 
    : base(nt)
  {    
  }

  public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] ArgTypes)
  {
    // Check that the function prefix is for the correct namespace
    if (this.LookupNamespace(prefix) == NamespaceUri)
    {
      // Lookup the function and return the appropriate IXsltContextFunction implementation
      switch (name)
      {
        case "CaseInsensitiveComparison":
          return CaseInsensitiveComparison.Instance;
      }
    }

    return null;
  }

  public override IXsltContextVariable ResolveVariable(string prefix, string name)
  {
    return null;
  }

  public override int CompareDocument(string baseUri, string nextbaseUri)
  {
    return 0;
  }

  public override bool PreserveWhitespace(XPathNavigator node)
  {
    return false;
  }

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

  // Class implementing the XSLT Function for Case Insensitive Comparison
  class CaseInsensitiveComparison : IXsltContextFunction
  {
    private static XPathResultType[] _argTypes = new XPathResultType[] { XPathResultType.String };
    private static CaseInsensitiveComparison _instance = new CaseInsensitiveComparison();

    public static CaseInsensitiveComparison Instance
    {
      get { return _instance; }
    }      

    #region IXsltContextFunction Members

    public XPathResultType[] ArgTypes
    {
      get { return _argTypes; }
    }

    public int Maxargs
    {
      get { return 1; }
    }

    public int Minargs
    {
      get { return 1; }
    }

    public XPathResultType ReturnType
    {
      get { return XPathResultType.Boolean; }
    }

    public object Invoke(XsltContext xsltContext, object[] args, XPathNavigator navigator)
    {                
      // Perform the function of comparing the current element to the string argument
      // NOTE: You should add some error checking here.
      string text = args[0] as string;
      return string.Equals(navigator.Value, text, StringComparison.InvariantCultureIgnoreCase);        
    }
    #endregion
  }
}

Затем вы можете использовать вышеуказанную функцию расширения в своих запросах XPath, вот пример для нашего случая

class Program
{
  static string html = "<html><meta name=\"keywords\" content=\"HTML, CSS, XML\" /></html>";

  static void Main(string[] args)
  {
    HtmlDocument doc = new HtmlDocument();
    doc.LoadHtml(html);

    XPathNavigator nav = doc.CreateNavigator();

    // Create the custom context and add the namespace to the context
    XsltCustomContext ctx = new XsltCustomContext(new NameTable());
    ctx.AddNamespace("Extensions", XsltCustomContext.NamespaceUri);

    // Build the XPath query using the new function
    XPathExpression xpath = 
      XPathExpression.Compile("//meta[@name[Extensions:CaseInsensitiveComparison('Keywords')]]");

    // Set the context for the XPath expression to the custom context containing the 
    // extensions
    xpath.SetContext(ctx);

    var element = nav.SelectSingleNode(xpath);

    // Now we have the element
  }
}
2 голосов
/ 13 мая 2012

Вот как я это делаю:

HtmlNodeCollection MetaDescription = document.DocumentNode.SelectNodes("//meta[@name='description' or @name='Description' or @name='DESCRIPTION']");

string metaDescription = MetaDescription != null ? HttpUtility.HtmlDecode(MetaDescription.FirstOrDefault().Attributes["content"].Value) : string.Empty;
1 голос
/ 14 мая 2012

В качестве альтернативы используйте новый синтаксис Linq, который должен поддерживать сопоставление без учета регистра:

        node = doc.DocumentNode.Descendants("meta")
            .Where(meta => meta.Attributes["name"] != null)
            .Where(meta => string.Equals(meta.Attributes["name"].Value, "keywords", StringComparison.OrdinalIgnoreCase))
            .Single();

Но вы должны выполнить уродливую нулевую проверку атрибутов, чтобы предотвратить NullReferenceException ...

...