Página 1 de 1

Ayuda - contar filas y columnas en archivos de Excel

Publicado: 18 Oct 2017, 13:17
por Jonny
Hola,

A ver si alguien sabe echarme una mano con esto, porque aunque lo tengo casi resuelto, no es la mejor forma.

Estoy haciendo un programa, que me ayude en el trabajo, ya que he de manejar ficheros CSV enormes, donde hay miles de productos en cada uno, con información como PN, fabricante, marca, precio, características, imágenes, stock...

Todo esto son productos de distribuidores, que he de agregar a Prestashop. Pero antes de agregar los productos, por ejemplo, he de clasificar las imágenes: unas proporcionadas por el distribuidor, y otras descargadas de una base de datos de productos online.

Al ser tantísimos productos, y tener más de 100.000 imágenes que organizar, ya que hay Thumbs, originales, de calidad alta, media o baja ... hacerlo a mano es imposible sin tardar ... Años ;)

¡Además de que es pesadísimo!

Por eso, estoy haciendo un programa que me ayude con todo esto, y finalmente, cree un CSV, con los productos de todos los CSV con los que he de trabajar, teniendo en cuenta varios factores, como cantidad de stock de un mismo producto en más de un CSV, precio más bajo del mismo producto en varios CSV ...

Pero el problema es, que he decidido usar la librería Excel.au3 para eso, porque aunque he visto códigos para usar ficheros CSV con AutoIT, o no me han terminado de funcionar, o no hacen exactamente lo que necesito; pero además, el problema es el mismo que estoy teniendo, sin usar esos códigos: habiendo programado yo lo que necesito. Y es que al ser ficheros de entre 5 y 8 MB, el programa tarda muchísimo en procesar el contenido: casi tres cuartos de hora, y no es más que parte del procesamiento.

Con Excel.au3 podría solucionar en parte esto, ya que ahorraría código. Pero el problema es que no veo como contar las filas y columnas de un archivo.

trabajo con AutoIT 3.3.8.1, que tiene una versión de Excel.au3 vieja, a la que le faltan funciones respecto a la última versión. Pero he estado mirando la librería Excel.au3 que viene con la última versión de AutoIT, y tampoco veo funciones para contar las filas y columnas.

Lo más que veo es una función para leer celdas entre un rango, pero como no sé entre que valores estará el rango (porque los archivos de los distribuidores van cambiando constantemente) eso no me sirve.

En el foro inglés de AutoIT encontré la UDF CSV.au3, que tiene una función en teoría para contar los elementos de una columna o fila (o al menos serviría, porque devuelve los elementos en un array). Pero me encontré con que no funciona, por culpa de como están hechos los CSV (que seguramente estarán bien hechos, pero engaña a la función de la UDF). Y es que, en la columna "Características" hay saltos de línea, para que no aparezca todo el texto en la misma línea; así, que la función, que parece que hace un StringSplit("cadena", @CRLF) para obtener las filas, devuelve un valor erróneo.

Esto finalmente, lo he solucionado haciéndome yo mismo una función que cuente las filas y las columnas, que no está terminada pero casi: lo más importante y difícil está hecho, que es la parte que limpia los saltos de línea entre comillas.

esto hubiera sido muy fácil, y hubiera funcionado muy rápido, si lo hubiera podido hacer sólo con una ER... Lo intenté, y aunque me costó hacer la ER ¡lo conseguí! :)

Pero me di cuenta de por qué no funcionaba:

Resulta, que sí, eliminaba los saltos de línea entre comillas ¡pero toooooooodos!. y es que, entre comillas, no es sólo lo que se entiende por un campo (o celda) sino que algo como

Código: Seleccionar todo

celda 1;celda 2;"esta es 
la tercera celda";celda 4;celda 5;"esta es 
la sexta celda";celda 7
celda 8;celda 9;"esta es 
la décima celda";celda 11;celda 12;"¡esta es 
la celda 12+1!";celda 14
no tiene sólo entre comillas
"esta es
la tercera celda"

"esta es
la sexta celda"

"esta es
la décima celda"

"¡esta es
la celda 12+1!"
Sino que
;celda 4;celda 5;

;celda 7
celda 8;celda 9;

;celda 11;celda 12;
también está entre comillas ... ¿Se le puede decir a una ER que esa parte no la quiero?. Supongo que no, y de poderse no sé como hacerlo.

Así, que se me ocurrió otra forma: Contar las comillas que hay en total en el archivo, y dando por ehcho que sólo van a haber bloques de comillas
"esta es
la tercera celda"
una vez obtenido el total de comillas, sólo habría que tratarlas en parejas y quitar ahora sí, con una ER, los saltos de línea que haya entre cada dos comillas.

Bueno: ¿parece fácil verdad?.
Pues no lo ha sido tanto, al menos como yo lo he hecho, y no se me ha ocurrido una forma mejor.

Funciona perfecto, y tampoco es que haya tirado demasiado código. Pero dentro de un año, fácil de entender; si no es por que está cada línea documentada ... ¡No lo será!.

Os pongo el código que lee el archivo y quita las comillas, porque la función no está terminada. Pero esto es lo más importante:

Código: Seleccionar todo

Cat_ProvCountRC()

   Func Cat_ProvCountRC() ;Función "Cat_ProvCountRC()".


      Local $Buff_HandPF ;Buffer para almacenar el handle del archivo de proveedor.
      Local $Buff_ResultReadPF ;Buffer para almacenar el contenido del archivo de proveedor.
      Local $Buff_CountQuotesPF[1][2] ;Buffer para almacenar la cantidad de comillas (") en el contenido del archivo de proveedor, y la posición de cada una de ellas en este. 1ª dimensión (inicia en 1): [#º]=Indice de cada posición de comillas (") en el archivo de proveedor. Segunda dimensión (inicia en 1): [1]=Posición en el archivo de proveedor, de las comillas (") del índice actual de la 1ª dimensión. *[0][0]=Cantidad de elementos de la 1ª dimensión del array.
       $Buff_CountQuotesPF[0][0]=(Ubound($Buff_CountQuotesPF, 1)-1) ;Ajusta la cantidad inicial de elementos de la primera dimensión del array del buffer para almacenar la cantidad de comillas (") en el contenido del archivo de proveedor, y la posición de cada una de ellas en este, en "[0][0]". *Se resta "1", para ajustar correctamente la cantidad de elementos del array, en el bucle que busca comillas (") en el archivo de proveedor, ya que el array es de base "[1]".
      Local $Check_QuotesInPF ;Busca comillas (") en el contenido del archivo de proveedor.
      Local $Count_LoopExec=1 ;Contador de iteraciones del bucle para buscar comillas (") en el contenido del archivo de proveedor.
      Local $Buff_AddNewTXT_PF ;Buffer para almacenar el contenido del archivo de proveedor, sin los carácteres "Chr(0)", "@CR", "@LF", "@CRLF", "@TAB" o "\v" entre bloques de comillas ("").
      Local $Buff_PFSize ;Buffer para almacenar el tamaño del contenido del archivo de proveedor.

     $Buff_HandPF=FileOpen(@ScriptDir&"\Archivo.csv", 0) ;Abre el archivo de proveedor, en modo "sólo lectura".
      If $Buff_HandPF=-1 Then Return 0 ;Si ocurrió un error al abrir el archivo de proveedor, en modo "sólo lectura" - Devuelve código de error ("0").
     $Buff_ResultReadPF=FileRead($Buff_HandPF) ;Lee todo el contenido del archivo de proveedor.
      If (@Error=1 Or @Extended=0) Then ;Si ocurrió un error al leer todo el contenido del archivo de proveedor.
       FileClose($Buff_HandPF) ;Cierra el archivo de proveedor. *No se comprueba si ocurre ningún error, ya que a continuación se devuelve código de error ("0").
       Return 0 ;Devuelve código de error ("0").
      EndIf ;End -> Si ocurrió un error al leer todo el contenido del archivo de proveedor.
     If FileClose($Buff_HandPF)=0 Then Return 0 ;Si ocurrió un error al cerrar el archivo de proveedor - Devuelve código de error ("0").
     $Buff_PFSize=StringLen($Buff_ResultReadPF) ;Almacena el tamaño del contenido del archivo de proveedor, en el buffer.
     While 1 ;Bucle para contar la cantidad de comillas ("") que contiene el archivo de proveedor.
      $Check_QuotesInPF=StringInSTR($Buff_ResultReadPF, """", 0, $Count_LoopExec, 1) ;Busca comillas (") en el contenido del archivo de proveedor.
       If $Check_QuotesInPF=0 Then ExitLoop ;Si no se encontraron comillas (") en el contenido del archivo de proveedor - Sale del bucle.
      ReDim $Buff_CountQuotesPF[(Ubound($Buff_CountQuotesPF, 1)+1)][2] ;Añade un elemento a la primera dimensión del array del buffer para almacenar la cantidad de comillas (") en el contenido del archivo de proveedor, y la posición de cada una de ellas en este.
      $Buff_CountQuotesPF[(Ubound($Buff_CountQuotesPF, 1)-1)][1]=$Check_QuotesInPF ;Almacena la posición de las comillas encontradas en el contenido del archivo de proveedor indicado en "$Arg_ProvIndexConfig", en el índice de búsqueda actual, en el buffer.
      $Count_LoopExec+=1 ;Suma "1" al contador de iteraciones del bucle para buscar comillas (") en el contenido del archivo de proveedor.
     Wend ;End -> Bucle para contar la cantidad de comillas ("") que contiene el archivo de proveedor.
     $Buff_CountQuotesPF[0][0]=(Ubound($Buff_CountQuotesPF, 1)-1) ;Ajusta la cantidad actual de elementos de la primera dimensión del array del buffer para almacenar la cantidad de comillas (") en el contenido del archivo de proveedor, y la posición de cada una de ellas en este, en "[0][0]".
     If Mod($Buff_CountQuotesPF[0][0], 2)<>0 Then Return 0 ;Si la cantidad de comillas (") que contiene el archivo de proveedor es impar - Devuelve código de error ("0").
     For $I=1 To $Buff_CountQuotesPF[0][0] Step +2 ;Recorre todas las posiciones de comillas (") en el contenido del archivo de proveedor.
      If $I=1 Then ;Si el bucle está en la primera iteración.
       If $Buff_CountQuotesPF[$I][1]>1 Then ;Si la posición de las comillas (") de apertura del primer bloque de comillas ("") del contenido del archivo de proveedor no es la primera.
        $Buff_AddNewTXT_PF=StringMid($Buff_ResultReadPF, 1, ($Buff_CountQuotesPF[$I][1]-1)) ;Almacena el contenido previo al primer bloque de comillas ("") del contenido del archivo de proveedor, en el buffer.
       EndIf ;End -> Si la posición de las comillas (") de apertura del primer bloque de comillas ("") del contenido del archivo de proveedor no es la primera.
        ElseIf $I>1 Then ;Si el bucle está en una iteración posterior a la primera.
         If (($Buff_CountQuotesPF[$I][1]-1)>$Buff_CountQuotesPF[($I-1)][1]) Then ;Si la posición de las comillas (") actuales es más de una unidad superior a la posición de las comillas (") anteriores.
          $Buff_AddNewTXT_PF&=StringMid($Buff_ResultReadPF, ($Buff_CountQuotesPF[($I-1)][1]+1), ($Buff_CountQuotesPF[$I][1]-($Buff_CountQuotesPF[($I-1)][1]+1))) ;Almacena el contenido previo al bloque actual de comillas ("") del contenido del archivo de proveedor, en el buffer.
         EndIf ;End -> Si la posición de las comillas (") actuales es más de una unidad superior a la posición de las comillas (") anteriores.
      EndIf ;End -> Si el bucle está en la primera iteración.
      $Buff_AddNewTXT_PF&=StringRegExpReplace(StringMid($Buff_ResultReadPF, $Buff_CountQuotesPF[$I][1], (($Buff_CountQuotesPF[($I+1)][1]+1)-$Buff_CountQuotesPF[$I][1])), "(\x22(?:\x01-\x08|\x0B-\x0C|\x0E-\x21|\x23-\xFF*)?\x00|\x0A|\x0D|\t|\v+(?:\x01-\x08|\x0B-\x0C|\x0E-\x21|\x23-\xFF*)?\x22)", "{BS}", 0) ;Elimina los carácteres "Chr(0)", "@CR", "@LF", "@CRLF", "@TAB" o "\v" entre el bloque actual de comillas (""), del contenido del archivo de proveedor.
       If @Error<>0 Then Return 0 ;Si ocurrió un error al eliminar los carácteres "Chr(0)", "@CR", "@LF", "@CRLF", "@TAB" o "\v" entre el bloque actual de comillas (""), del contenido del archivo de proveedor - Devuelve código de error ("0").
      If $I=($Buff_CountQuotesPF[0][0]-1) Then ;Si el índice de iteraciones del bucle es una unidad inferior a la cantidad de comillas (") que contiene el archivo de proveedor.
       If ($Buff_PFSize>=($Buff_CountQuotesPF[($I+1)][1]+1)) Then ;Si el último carácter del contenido del archivo de proveedor no son comillas (").
        $Buff_AddNewTXT_PF&=StringMid($Buff_ResultReadPF, ($Buff_CountQuotesPF[($I+1)][1]+1)) ;Almacena todo el contenido posterior al último bloque de comillas ("") del contenido del archivo de proveedor, en el buffer.
       EndIf ;End -> Si el último carácter del contenido del archivo de proveedor no son comillas (").
       $Buff_ResultReadPF="" ;Elimina todo el contenido leído del archivo de proveedor, del buffer, para liberar memoria.
       ExitLoop ;Sale del bucle.
        Else ;Si el índice de iteraciones del bucle es más de una unidad inferior a la cantidad de comillas (") que contiene el archivo de proveedor.
         ContinueLoop ;Continúa con la siguiente iteración del bucle.
      EndIf ;End -> Si el índice de iteraciones del bucle es una unidad inferior a la cantidad de comillas (") que contiene el archivo de proveedor.
     Next ;End -> Recorre todas las posiciones de comillas (") en el contenido del archivo de proveedor.
     $Buff_AddNewTXT_PF=StringRegExpReplace($Buff_AddNewTXT_PF, "({BS})+", "{BS}", 0) ;Reemplaza etiquetas "{BS}" consecutivas, por una sola, entre bloques de comillas (""), del contenido del archivo de proveedor.
      If @Error<>0 Then Return 0 ;Si ocurrió un error al reemplazar etiquetas "{BS}" consecutivas, por una sola, entre bloques de comillas (""), del contenido del archivo de proveedor - Devuelve código de error ("0").

     ;Hasta aquí la limpieza de saltos de línea entre bloques de comillas.


     ;*A continuación se guarda el contenido del archivo de proveedor en un fichero de texto, para poder ver el resultado, y se muestra un mensaje para notificar que el proceso ha terminado correctamente.
      fileopen(@Scriptdir&"\Archivo.txt", 1)
      filewrite(@Scriptdir&"\Archivo.txt", $Buff_AddNewTXT_PF)

      msgbox(0, "", "Fin del programa")


     ;Cierra el programa.
      Exit


   EndFunc ;End -> Función "Cat_ProvCountRC()".
He dejado todos los comentarios porque imagino que así no tendréis que romperos tanto los cuernos para entender el código. Y de paso, si alguien ha de hacer algo parecido, este código quizás le sea útil.

*No pongo los ficheros CSV ya que son del trabajo ;)

Pero si alguien puede encontrar ficheros CSV muy pesados, verá que efectivamente, tarda un montón en terminar la función. De hecho, a veces termina por colgarse el programa (parece incluso que no responda, porque si la aplicación tiene GUI, se queda congelada).

Con ficheros CSV pequeños funciona perfectamente, y no tarda en procesar el contenido.

Entiendo que procesar casi seis MB de datos no es cualquier cosa, y debe tardar ¡Pero 45 minutos es muchísimo! :)

Al código anterior sólo le falta dividir el contenido del CSV por saltos de línea para obtener las filas, y luego, por ejemplo, la primera fila, dividirla por el separador ;, para obtener la cantidad de columnas.

Esto no debería suponer mucho más tiempo extra de procesamiento, pero luego, tratar el archivo sí supondrá otro montón de rato si utilizo un bucle For por ejemplo; igual con la función de la librería de Excel para leer por rangos no tarda tanto... Pero me temo, que en cualquier caso, debo de usar el código anterior para contar las filas y las columnas del archivo.

¿A alguien se le ocurre algo mejor?

Salu2!

Re: Ayuda - contar filas y columnas en archivos de Excel

Publicado: 18 Oct 2017, 18:02
por Dany
Hola Jonny. Me gustaría revisar y ver si puedo hacer algo. serias tan amable de compartir un archivo CSV de ejemplo. O un script para crear un archivo de prueba aleatorio.



Saludos :smt027

Re: Ayuda - contar filas y columnas en archivos de Excel

Publicado: 20 Oct 2017, 10:28
por Jonny
Aquí tienes uno. Es sobre el que he estado trabajando para hacer el código que puse en el post anterior.

Comprimido parece que no ocupa, pero cuando lo descomprimas, verás que son cinco megas y pico ;)

Salu2!

Re: Ayuda - contar filas y columnas en archivos de Excel

Publicado: 28 Oct 2017, 15:56
por Dany
Usando la UDF de excel puedes hacer esto.

Código: Seleccionar todo

#include <Excel.au3>

Local $oExcel = _Excel_Open(False)
Local $sWorkbook = @ScriptDir & "\productos.csv"
Local $oWorkbook = _Excel_BookOpen($oExcel, $sWorkbook)
Local $iColumns=$oExcel.ActiveSheet.UsedRange.Columns.Count
Local $iRows=$oExcel.ActiveSheet.UsedRange.Rows.Count
_Excel_BookClose($oWorkbook, False)
_Excel_Close($oExcel)


ConsoleWrite($iColumns & @CRLF)
ConsoleWrite($iRows & @CRLF)


Saludos :smt027

Re: Ayuda - contar filas y columnas en archivos de Excel

Publicado: 07 Nov 2017, 16:42
por Jonny
Vaya, ¡lo probaré en cuanto tenga un ratillo!.

¿Como has sabido lo de

Código: Seleccionar todo

Local $iColumns=$oExcel.ActiveSheet.UsedRange.Columns.Count
Local $iRows=$oExcel.ActiveSheet.UsedRange.Rows.Count
No lo he visto en la documentación de la librería, en ninguna función. ¿Es el código de alguna función de la UDF?.

¿en el código anterior ha de haber alguna hoja activa en el archivo?. Lo digo porque si es así, va a ser más complicado el código, si hay que controlar la hoja activa.

¿Por que usas dos veces la función _Excel_Open()?. ¿No basta con

Código: Seleccionar todo

Local $sWorkbook = @ScriptDir & "\productos.csv"
Local $oWorkbook = _Excel_BookOpen($sWorkbook)
Y lo mismo con _Excel_BookClose() y _Excel_Close() ?.

Salu2!

Re: Ayuda - contar filas y columnas en archivos de Excel

Publicado: 08 Nov 2017, 20:23
por Dany
No se si existe interno en la UDF probablemente si. Lo conozco porque es muy usado en VBA. Y pues como es el mismo objeto de Excel entonces es soportado.


_Excel_Open() es para crear la instancia de Excel solamente.



_Excel_BookOpen es para abrir el archivo de Excel. El codigo funciona el la hoja activa por defecto. si quieres hacerlo con otra hoja debes activarla. Probablemente en la ayuda si consigas algo para eso.


Saludos :smt027

Re: Ayuda - contar filas y columnas en archivos de Excel

Publicado: 09 Nov 2017, 11:03
por Jonny
O sea, que existen más objetos de los que usa la UDF.

Al menos como función, no he visto ese objeto. Y si lo usa la UDF, será en funciones para leer filas o columnas por rangos, que como dije, no me sirve.

¿Y por qué creas una instancia de Excel y luego abres el libro?. Yo sólo abro el libro y en principio me funciona bien lo que he programado con la UDF de Excel.

Lo digo por si es realmente necesario lo de crear la instancia y luego abrir el libro, o no, para que funcione el código que pusiste.

Pues lo de la hoja activa ... Habiendo tantísima información no sé, si estaría toda en la misma hoja?. Supongo que sí: probaré el código para ver si me devuelve correctamente la cantidad de filas y columnas sin tener que cambiar la hoja activa.

Por cierto, aprovechando el tema:

Esta parte, con tu código supongo que quedará resuelta, porque me devolverá la cantidad de filas y columnas al instante, en vez de tardar muchísimo como con el código que puse yo.

Pero igualmente, el programa tardará mucho en procesar los archivos CSV para hacer cualquier cosa, ya que es muchísima información.

Ayer estuve hechando un vistazo por el foro, y vi temas, donde se hablaba de los diccionarios con tablas hash. Vi ejemplos y explicaciones de Ximorro muy buenas sobre eso, y pensé que igual podría usarlo para el programa que estoy haciendo. Pero no sé muy bien como, ya que en este caso no se trata de buscar un valor de la forma "clave-valor", sino de leer todo el contenido, y compararlo con el de otro archivo, con el contenido de un directorio, descargar imágenes cuyo link aparece en el CSV que se está leyendo ...

Y como no conozco los diccionarios, no sé cuantas cosas se pueden llegar a hacer con ellos, y si lo que necesito podría hacerse.

¿Qué te parece?.

Salu2!