c#
  • xpath
  • .net-2.0
  • html-agility-pack
  • case-sensitive
  • 2012-02-05 14 views 6 likes 
    6

    Quando usoHtmlAgilityPack XPath ignorando

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

    non funziona, ma quando io uso lo stesso caso che ha utilizzato nel documento originale funziona bene:

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

    Quindi la domanda è come posso impostare il caso ignorando?

    +0

    XPath è volutamente maiuscole e minuscole? – CarneyCode

    +0

    @Carnotaurus Sì. – Tomalak

    risposta

    4

    Se è necessaria una soluzione più completa, è possibile scrivere una funzione di estensione per il processore XPath che eseguirà un confronto senza distinzione tra maiuscole e minuscole. È un bel po 'di codice, ma lo scrivi solo una volta.

    Dopo aver implementato l'estensione è possibile scrivere la query come segue

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

    Dove Extensions:CaseInsensitiveComparison è la funzione di estensione implementata nel campione qui sotto.

    NOTA: questo non è ben testato Ho appena lanciato insieme per questa risposta in modo che la gestione degli errori ecc. È inesistente!

    Quello che segue è il codice per il contesto XSLT personalizzato che fornisce uno o più appendici funzioni

    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 
        } 
    } 
    

    È quindi possibile utilizzare la funzione di estensione di cui sopra nelle query XPath, ecco un esempio per il nostro caso

    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

    può essere applicato al nome del nodo? –

    8

    Se il valore effettivo è un caso sconosciuto, penso che sia necessario utilizzare translate. Credo che sia:

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

    Questo è l'hack, ma è l'unica opzione in XPath 1.0 (tranne l'opposto di maiuscole).

    2

    Questo è come lo faccio:

    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

    Il tuo approccio non è così universale come quello di Chris Taylor. La risposta di Chris prende in considerazione ogni combinazione del caso di Char. – kseen

    +1

    @kseen Lo so ma davvero, è possibile che qualcuno metta qualcosa come "KeYwOrDs"? Ci sono tre modi in comune, e se qualcuno scrive meta-nomi come questo dubito che sia in grado di analizzare qualcosa da quel documento HTML. Questa è una soluzione fuori dalla scatola che richiede due linee di codice e funziona bene per la maggior parte dei casi, ma tutto dipende dalle tue esigenze. – formatc

    +0

    Cerco di mantenere la regola "non fidarti mai dell'input dell'utente" e ti consiglio anche io. – kseen

    1

    usare alternativa ° e nuova sintassi LINQ che dovrebbe sostenere caso di corrispondenza insensitive:

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

    Ma bisogna fare un controllo nullo brutta per gli attributi al fine di evitare un NullReferenceException ...

    Problemi correlati