Html Agility Pack - lectura div InnerText en tabla




 Mi problema es que no puedo obtener div InnerText de la tabla. He extraído con éxito diferentes tipos de datos, pero no sé cómo leer div de la tabla. 

 En la siguiente imagen, he resaltado div, y necesito obtener InnerText de ella, en este caso, el número 3. 

 [Haga clic aquí para ver la primera imagen](http://cl.ly/image/0B3F2J3n2Q2C) 

 Estoy tratando de lograr esto usando la siguiente ruta: 


```
"//div[@class='kal']//table//tr[2]/td[1]/div[@class='cipars']"

```
  Pero estoy obteniendo el siguiente error: 

 [Haga clic aquí para ver la imagen del mensaje de error](http://cl.ly/image/2X10323V0y0o) 

 Suponiendo que el resto del código esté escrito correctamente, ¿podría alguien dirigirme en la dirección correcta? He estado tratando de resolver esto, pero no puedo obtener ningún resultado.
Entonces, su problema es que confía en posiciones dentro de su XPath. Si bien esto puede estar bien en algunos casos, no está aquí, porque está esperando que el **primer** `td` en un `tr` dado tenga un `div` con la clase. 

 Mirando la fuente en Chrome, muestra que no siempre es así. Puede ver esto comparando el elemento "1" en el calendario, a "2" y "3". Notará que el elemento "1" tiene una serie de elementos a su alrededor, que los demás no. 

 Su consulta XPath original no devuelve un elemento, es por eso que está recibiendo el error. En el caso de que la consulta XPath que proporcione a HtmlAgilityPack no produzca un elemento DOM, devolverá un valor nulo. 

 Ahora, debido a que no ha mostrado su código completo, no sé cómo se está ejecutando este código. Sin embargo, supongo que está intentando recorrer todos los elementos del calendario. En cualquier caso, tiene varias formas de hacer esto, pero le mostraré que con el selector de XPath `descendant` , *puede* agarrar todo el lote de una vez: 


```
//div[@class='kal']//table//descendant::div[@class='cipars']

```
  Esto devolverá *todos* los elementos del calendario (es decir, de 1 a 30). 

 Sin embargo, para obtener todos los elementos en una fila en particular, simplemente puede pegar ese `tr` en la consulta: 


```
//div[@class='kal']//table//tr[3]/descendant::div[@class='cipars']

```
  Esto devolvería 2 a 8 (la segunda fila de elementos del calendario). 

 Para apuntar a uno específico, bueno, tendrá que hacer una suposición sobre el código fuente del sitio web. Parece que cada `div` "cipars" tiene un ancestro de `td` con `datums` clase ... así que para obtener el valor "3" de tu pregunta: 


```
//div[@class='kal']//table//tr[3]//td[@class='datums'][2]/div[@class='cipars']

```
  Esperemos que esto sea suficiente para mostrar al menos el problema. 

 **Editar** 

 Aunque tiene un problema de XPath, también tiene otro problema. 

 El sitio está creado muy extrañamente. El calendario se carga de una manera extraña. Cuando llego a esa URL, el calendario se crea mediante algún Javascript que llama a un servicio web XML (escrito en PHP) que luego calcula la `table` completa que se usará para el calendario. 

 Debido a que esto es Javascript (código del lado del cliente), HtmlAgilityPack no lo ejecutará. Por lo tanto, HtmlAgilityPack ni siquiera "ve" la tabla. Por lo tanto, las consultas en su contra vuelven como "no encontradas" (null). 

 Formas de evitar esto: 1) Use una herramienta que llame a los scripts. Con esto, me refiero a cargar un navegador. Una gran herramienta para usar para esto se llama [Selenium](http://docs.seleniumhq.org/) . Esta será probablemente la mejor solución general porque significa que se llamará a todos los scripts utilizados por el sitio. Aún puede usar XPath con él, por lo que sus consultas no cambiarán. 

 La segunda forma es enviar una solicitud al *mismo* servicio web que la página. Esto es básicamente para obtener de vuelta el *mismo* HTML que la página está recibiendo, y el uso *que* con HtmlAgilityPack. ¿Como hacemos eso? 

 Bueno, usted puede POST fácilmente datos a un servicio web usando C #. Solo por facilidad de uso, he robado el código de [esta pregunta SO](https://stackoverflow.com/questions/4718420/sending-post-data-with-c-sharp) . Con esto, podemos enviar la misma solicitud que la página y recuperar el mismo HTML. 

 Entonces, para enviar algunos datos POST, generamos un método así ... 


```
public static string SendPost(string url, string postData)
{
    string webpageContent = string.Empty;

    byte[] byteArray = Encoding.UTF8.GetBytes(postData);

    HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
    webRequest.Method = "POST";
    webRequest.ContentType = "application/x-www-form-urlencoded";
    webRequest.ContentLength = byteArray.Length;

    using (Stream webpageStream = webRequest.GetRequestStream())
    {
        webpageStream.Write(byteArray, 0, byteArray.Length);
    }

    using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse())
    {
        using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
        {
            webpageContent = reader.ReadToEnd();
        }
    }

    return webpageContent;
}

```
  Podemos llamarlo así: 


```
string responseBody = SendPost("http://lekcijas.va.lv/lekcijas_request.php", "nodala=IT&kurss=1&gads=2013&menesis=9&c_dala=");

```
  ¿Cómo conseguí esto? Bueno, el archivo `php` que estamos llamando es el servicio web que es la página, y los datos POST también lo son. La forma en que descubrí qué datos envía al servicio es mediante la depuración del Javascript (usando la Consola de desarrollador de Chrome), **pero** puede notar que es casi lo mismo que está en la URL. Eso parece ser intencional. 

 El `responseBody` que se devuelve es el **HTML físico** de *solo* la `table` para el calendario. 

 ¿Qué hacemos con eso ahora? Lo cargamos en HtmlAgilityPack, porque puede aceptar HTML puro. 


```
var document = new HtmlDocument();
document.LoadHtml(webpageContent);

```
  Ahora, pegamos ese XPath original en: 


```
var node = document.DocumentNode.SelectSingleNode("//div[@class='kal']//table//tr[3]//td[@class='datums'][2]/div[@class='cipars']");

```
  Ahora, imprimimos lo que esperamos sea "3": 


```
Console.WriteLine(node.InnerText);

```
  Mi salida, ejecutándolo localmente, es de hecho: **3** . 

 Sin embargo, aunque esto le ayude a superar el problema que está teniendo, asumo que el resto del sitio es así. Si este es el caso, es posible que aún puedas solucionarlo utilizando la técnica anterior, pero las herramientas como Selenium se crearon por esta misma razón.
Se puede extraer información de archivos, utilizando un DTP (Data Transformation Package), en el Enterprise Manager, selecciona tu servidor, y después la opción: Data Transformation Services, sobre la opción Local Package, oprime el botón derecho y luego la opción New Package. Un DTP, se crea en forma gráfica, solo necesitas seleccionar el icono de archivo de texto fuente y el icono Microsoft Ole DB Provider for SQL Server. Posteriormente seleccionas oprimiendo la tecla Control, el icono de archivo de texto fuente y después sin soltarla el icono de Microsoft Ole DB provider for SQL Server. En la parte de arriba oprime la opción: Transform Data Task, aparecerá una flecha entre ambas conexiones. Le debes de dar doble click sobre esta flecha y especificar las opciones de Origen y destino. Así como que campos corresponden con las columnas del archivo de texto. Por ultimo para ejecutarlo oprime la flecha verde. La información que quieres extraer, ¿está organizada en líneas? ¿Puedes aportar un ejemplo, real o ficticio, de un html como los que quieres tratar? Tienes que usar la función BUSCARV para que en función de un valor busques en la primera columna de tu otra tabla, e indicándole el número de columna consigas el dato deseado.

Otra opción es usar las funciones INDICE e COINCIDIR.

En Internet y Youtube hay muchos tutoriales, el propio Excel tiene también ejemplos.

Desafortunadamente con la información que proporcionas no veo como orientarte de manera más específica. *Nota: la respuesta se proporcionó en un comentario en la pregunta original. Solo le estoy agregando un poco aquí, para hacerlo más visible y claro.* 





 Entity Framework obviamente asigna clases de C # a tablas de bases de datos a través de propiedades que representan las distintas columnas en cada tabla. Ahora, si el tipo de datos de una de estas propiedades no es compatible con el tipo de datos de su columna correspondiente en la base de datos, obtendrá una `System.InvalidCastException` cuando intente recuperar algo de esa tabla, incluso si la propiedad no coincide no está directamente involucrada en la búsqueda 

 En la pregunta, la consulta fue: 


```
User user = context.Users.First(i => i.UserId == id);

```
  No importa que tanto `id` como su propiedad correspondiente `UserId` sean válidas y compatibles entre sí aquí, porque lo que se está buscando es una instancia de toda la clase de `User` . 

 Aparentemente, el tipo de alguna otra propiedad en el `User` es incompatible con el tipo una columna con el mismo nombre en la tabla de `user` en la base de datos, por lo que el intento de mapearlo arroja la excepción anterior. necesit&aacute;s tener una <strong>tarjeta aceleradora</strong> para que d&eacute; esos efectos.<br/>despu&eacute;s ejecut&aacute;s:<br/>[code:1]$ sudo aptitude install 3ddesktop[/code:1] <br/><br/>para iniciar el efecto de varias formas, prueba la que m&aacute;s te guste:<br/>[code:1]<br/>$ 3ddesk --mode=flip<br/>$ 3ddesk --mode=linear<br/>$ 3ddesk --mode=cylinder<br/>$ 3ddesk --mode=priceisright<br/>$ 3ddesk --mode=carousel<br/>$ 3ddesk --mode=viewmaster[/code:1]<br/><br/>&iquest;c&oacute;mo llenar tu fondo de escritorio de agua?<br/>[code:1]$ sudo aptitude install xdesktopwaves[/code:1] Para volver a agregar una tabla a su modelo, primero deberá eliminar la tabla de su modelo. (se puede ver una lista de tablas en el árbol [model.Store] (vea el panel ' **Explorador de modelos** '; puede abrirla desde el menú del botón derecho del mouse). Cuando ejecute 'Actualizar modelo desde la base de datos ...' la tabla aparecerá en la pestaña 'Agregar' en el primer paso del 'Asistente de actualización'. 

 Pasos para completar: 

1. Cierra tu modelo en Visual Studio.
2. Abra su archivo .edmx en un editor de texto.
3. Busque y elimine los elementos de la entidad XML (vea las notas a continuación).
4. Abre tu modelo en Visual Studio.
5. Haga clic en Actualizar modelo de la base de datos.

 Para eliminar todas las referencias a una tabla en su Modelo: 

* En el elemento 'EntityContainer', elimine todos los elementos secundarios 'EntitySet' que tienen el atributo 'Nombre' establecido en el valor [TableNameToReAdd].
* En el elemento 'EntityContainer', elimine todos los elementos secundarios 'AssociationSet' donde exista un elemento 'End' que tengan su atributo 'EntitySet' establecido en el valor [TableNameToReAdd].
* En el elemento 'EntityContainer', elimine todos los elementos secundarios 'EntityType' donde tengan el atributo 'Nombre' establecido en el valor [TableNameToReAdd].
* En el elemento 'EntityContainer', elimine todos los elementos secundarios de 'Asociación' donde exista un elemento 'Fin' que tenga su atributo 'Función' establecido en el valor [TableNameToReAdd]. si vas a consultar cualquier objeto o tabla de sap tienes basicamente 2 vias:

por una consulta select a traves del objeto orecordset de la diapi .

a traves de un select a la db de sap mediante una conexion odbc, ado, jdbc, etc…(dependera del lenguaje usado).

la tabla de condiciones de pago de lo… Bueno lo que dices es fácil de resolver si estas usando tablas nada más cuando inicias la tabla haces algo así  
<table width="1024">  
<tr>  
 <td>  
 </td>  
</tr>  
</table>  
Y si no estas usando tablas te recomendaría que hicieras una tabla con una sola celda un código igual al que te acabo de dar:  
despues de la instruccion <body> lo heces para que se vea algo asi:  
<html>  
<head>  
 <title>  
 </title>  
</head>  
<body>  
<table width="1024">  
<tr>  
 <td>  
 aca metes la informacion de tada la pagina  
 </td>  
</tr>  
</table>  
</body>  
</html>  
Y con esto se te soluciona cuando hagas más pequeña la página esta no se modificara...  
Espero haberte ayudado y espero tener una pregunta tuya pronto nuevamente...  
Roy Rojas  
Webmaster OK. De movida te comento que no será sencillo, pero podemos resolvvero por aproximaciones sucesivas.  
Como me están faltando datos respecto al diseño de tu hoja. Supongo que la base de datos tiene cantidades irregulares de registros para cada persona, pero seguramente puedes saber cuantos tiene el de mayor cantidad de líneas. Asumo, además, que la base está ordenada por ID.  
Una combinación de las funciones INDICE y COINCIDIR funcionan en forma similar al BUSCARV. Pero en el caso que planteas -creo- sería la solución a tu problema.   
Vale decir que, si la función COINCIDIR encuentra el valor buscado (ID) en la primer columna traerá los datos en cada línea de acuerdo a la fila donde la fórmula se encuentre.   
Tal fórmula sería como esta:   
=INDICE(Hoja2!$C$5:$G$20;COINCIDIR($I$5;Hoja2!$C$5:$C$20;0)+FILA()-FILA($I$5);COLUMNA()-COLUMNA($I$5)+1)   
[Considera si sueles usar comas o punto y coma para separar argumentos de las funciones. Yo usé ";"]  
Está fórmula copiada en las celdas inferiores traerá los datos como quieres, pero adolece de un problema, necesitas saber de antemano cuántos registros tienes para el que estas buscando y contar con una cantidad igual de líneas de fórmulas de búsqueda.   
Para evitar ese problema, la siguiente fórmula agrega un control:   
=SI($I$5=INDICE(Hoja2!$C$5:$G$20;COINCIDIR($I$5;Hoja2!$C$5:$C$20;0)+FILA()-FILA($I$5);1);INDICE(Hoja2!$C$5:$G$20;COINCIDIR($I$5;Hoja2!$C$5:$C$20;0)+FILA()-FILA($I$5);COLUMNA()-COLUMNA($I$5)+1);"")   
INDICE muestra un dato de una matriz (C5:G20 de la hoja2) de acuerdo a las coordenadas de fila y columna que se le indique como argumentos.   
Para obtener la coordenada Fila usamos la función COINCIDIR en un rango (C5:C20 de Hoja2) de la misma altura (misma cantidad y ubicación de las líneas) que la base de búsqueda. Al número de fila así obtenido le agregará tantas líneas como diferencia halla entre la fila donde está la fórmula y la fila donde está el dato a buscar. Así, si la fórmula estuviera en la misma fila que el dato, tal diferencia será cero y, por lo tanto, traerá el dato de la misma fila.   
Al copiar esta celda y pegarla en la fila inferior, la diferencia será 1, por lo tanto traerá el valor de la fila siguiente de la base original. Y así sucesivamente.   
De igual modo, para identificar de qué columna traer el dato, restará la columna actual menos la primera.   
De esta manera, la misma fórmula copiada al resto del rango donde debe traer los valores servirá al propósito sin tener que modificarla.   
Aquí vale aclarar que, si no quieres todas las columnas, puedes especificarle exactamente qué columna necesitas, reemplazando la expresión COLUMNA()-COLUMNA($I$5)+1) por el número de la columna a mostrar  
El control que le agregué, verifica que en cualquier fila que se esté buscando se trate de la misma persona que se buscó originalmente.   
Lo único que tendrás que tener en cuenta es tener una cantidad de lineas de esta fórmula de búsqueda igual al mayor número de registros que pueda tener una persona.   
Por ejemplo, supongamos que buscar los datos de los hijos de cada empleado. Si hubiera un fecundo con 10 hijos, tendrás que tener 10 filas con aquella fórmula que te sugerí. Para los que tengan menos de 10, las celdas excedentes no mostrarán nada. Pero si hubiera uno con once o doce, sólo mostrará hasta el décimo.   
Por último, si -eventualmente- necesitaras otro set de estas fórmulas para buscar otro código en otro rango de la hoja "Datos" necesitarás, primero quitarle la referencia absoluta a la celda donde está e primer código (I5).   
Nota que, apropósitamente, todas las referencias a la linea del código dicen $I$5 de forma tal que siempre lean esa celda a cada uno de sus efectos.   
Lo que suelo hacer, entonces, es seleccionar todo el rango de fórmulas (4 columnas por 8 filas) y hago "Edición" | "Reemplazar..." En la casilla Buscar: coloco $I$5 y en la de reemplazar con escribo I5. Finalmente presiono "Reemplazar todo"   
Si bien todas las fórmulas modificadas siguen "apuntando" a la celda I5, esta es ahora una referencia relativa a tal celda. Esto permite que al copiarlas a otro rango lean una posición distinta a I5.   
Por ejemplo, si tu otro código a buscar estuviera en la celda I14 y copias este set de fórmulas a partir de esa misma fila, todas leerán los datos de la celda I14.   
Lo dicho: No es una tarea simple, pero si sigues cuidadosamente las instrucciones, llegarás al objetivo.  
- Ingresa al sitio www.porloschicos.com y, si quieres,   
presiona el botón de donación (es gratis).   
Confío en que lo harás. pues solo la plantilla ojdt. revisa bien la plantilla csv o txt que parece que tiene o detecta más columnas de las que estás pasando…

configura la plantilla de nuevo partiendo de cero o elimina todas las columnas y filas que te sobren…