2014-04-23 27 views
5

Piszę prosty program skrobania ekranu w języku C#, dla którego muszę wybrać wszystkie dane wejściowe umieszczone w jednym formularzu o nazwie "aspnetForm" (na stronie są 2 formularze i nie chcę danych wejściowych od innych), a wszystkie dane wejściowe w tej formie znajdują się w różnych tabelach, div lub po prostu na pierwszym poziomie podrzędnym tego formularza.Wybór XPath w HTMLAgilityPack nie działa zgodnie z oczekiwaniami

Więc napisany naprawdę proste zapytanie XPath:

//form[@id='aspnetForm']//input 

Działa jako oczekiwane we wszystkich przeglądarkach, które testowałem (Chrome, IE, Firefox) - zwraca to, co chcę.

Ale w HTMLAgilityPack to w ogóle nie działa - SelectNodes po prostu zawsze zwraca NULL.

Te zapytania, które napisałem dla testów, działają dobrze, ale nie zwracają tego, co chcę. Najpierw należy wybrać wszystkie wejścia, że ​​są pierwszymi-Childs dla mojej postaci i forma drugiego prostu wrócić za:

//form[@id='aspnetForm']/input 
//form[@id='aspnetForm'] 

Tak, wiem, że mogę po prostu wymienić na węzły z ostatniego zapytania, lub dokonać innego selectNodes na jego wynik, ale naprawdę nie chcę tego robić. Chcę użyć tego samego zapytania, co w przeglądarkach.

Czy XPath jest obecnie uszkodzony w HTMLAgilityPack? Czy istnieją jakieś alternatywne implementacje XPath dla C#?

UPDATE: Kod Test:

using HtmlAgilityPack; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 

namespace HtmlAGPTests 
{ 
    [TestClass] 
    public class XPathTests 
    { 
     private const string html = 
       "<form id=\"aspnetForm\">" + 
       "<input name=\"first\" value=\"first\" />" + 
       "<div>" + 
        "<input name=\"second\" value=\"second\" />" + 
       "</div>" + 
       "</form>"; 

     private static HtmlNode GetHtmlDocumentNode() 
     { 
      var document = new HtmlDocument(); 
      document.LoadHtml(html); 
      return document.DocumentNode; 
     } 

     [TestMethod] 
     public void TwoLevelXpathTest()  // fail - nodes is NULL actually. 
     { 
      var query = "//form[@id='aspnetForm']//input"; // what i want 
      var documentNode = GetHtmlDocumentNode(); 

      var inputNodes = documentNode.SelectNodes(query); 

      Assert.IsTrue(inputNodes.Count == 2); 
     } 

     [TestMethod] 
     public void TwoSingleLevelXpathsTest()  // works 
     { 
      var formQuery = "//form[@id='aspnetForm']"; 
      var inputQuery = "//input"; 
      var documentNode = GetHtmlDocumentNode(); 

      var formNode = documentNode.SelectSingleNode(formQuery); 
      var inputNodes = formNode.SelectNodes(inputQuery); 

      Assert.IsTrue(inputNodes.Count == 2); 
     } 

     [TestMethod] 
     public void SingleLevelXpathTest()  // works 
     { 
      var query = "//form[@id='aspnetForm']"; 
      var documentNode = GetHtmlDocumentNode(); 

      var formNode = documentNode.SelectSingleNode(query); 

      Assert.IsNotNull(formNode); 
     } 

    } 
} 
+0

. NET ma wbudowaną implementację XPath, która jest używana przez HtmlAgilityPack (HAP nie implementuje własnego silnika XPath). I faktycznie XPath używany przez HAP działał dobrze dla mnie, więc sugeruję podejrzenie czegoś innego. – har07

+0

Spróbuj zapisać 'HtmlDocument', a następnie sprawdź, czy zapisany plik zawiera oczekiwany format HTML. – har07

+1

@ har07, zawiera - testowałem to przed opublikowaniem pytania. Również metody "brudnego obejścia" działają, więc nie stanowi to problemu z wprowadzaniem danych. Dodano kod testowy do zapytania, więc możesz go przetestować samodzielnie - to NIE działa zgodnie z oczekiwaniami. – rufanov

Odpowiedz

4

Nieoczekiwane zachowanie w teście wystąpić, ponieważ html zawiera <form> element. Tutaj jest związane dyskusja:

Ariman. „Odkryłem, że po parsowania dowolny węzeł nie ma żadnych węzłów potomnych Wszystkie węzły, które powinny znajdować się wewnątrz postaci (, etc.) są tworzone, jak to ., a następnie rodzeństwa dzieci

VikciaR: "w formacie HTML postać opis znacznik mogą zachodzić na siebie, tak Htmlagilitypack uchwyt ten węzeł trochę innego ..."

[CodePlex discussion : No child nodes for FORM objects]

I jak sugeruje VikciaR tam, spróbuj zmodyfikować kod inicjalizacji testu tak: notatka

private static HtmlNode GetHtmlDocumentNode() 
{ 
    var document = new HtmlDocument(); 
    document.LoadHtml(html); 

    //execute this line once 
    HtmlNode.ElementsFlags.Remove("form"); 

    return document.DocumentNode; 
} 

niepożądane:inputQuery wartość w metodzie badania TwoSingleLevelXpathsTest() powinny być .//input. Zwróć uwagę na kropkę (.) na początku, aby wskazać, że zapytanie to dotyczy bieżącego węzła. W przeciwnym razie przeszuka od korzenia, ignorując poprzednią formQuery (bez kropki, możesz zmienić formQuery na coś, o ile nie zwróci wartości null, inputQuery zawsze zwróci ten sam wynik).

+2

Nieco dziwne zachowanie domyślnie .. Ale w każdym razie, dziękuję! To działa! – rufanov