2012-02-05 7 views
6

Когда я используюHtmlAgilityPack XPath случае игнорирования

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

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

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

Таким образом, вопрос как я могу задать игнорирование случая?

+0

XPath намеренно чувствителен к регистру? – CarneyCode

+0

@ Карнотавр Да. – Tomalak

ответ

4

Если вам нужно более комплексное решение, вы можете написать функцию расширения для процессора 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 
    } 
} 
+0

это можно применить к имени узла? –

8

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

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

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

2

Это, как я это делаю:

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; 
+0

Ваш подход не настолько универсален, как у Криса Тейлора. Ответ Криса привлекает к себе внимание любой комбинации случая персонажа. – kseen

+1

@ kseen Я знаю, но действительно, возможно ли кому-то поставить что-то вроде «KeYwOrDs»? Это три общих способа, и если кто-то пишет мета-имя, то я сомневаюсь, что вы сможете разобрать что-нибудь из этого документа HTML. Это решение не из коробки, которое требует двух строк кода и хорошо работает для большинства случаев, но все зависит от вашего требования. – formatc

+0

Я пытаюсь сохранить правило «никогда не доверяю пользовательскому вводу», и я приветствую вас. – kseen

1

в качестве альтернативы использовать тыс е новый синтаксис 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 ...

Смежные вопросы