Ayuda - contar filas y columnas en archivos de Excel
Publicado: 18 Oct 2017, 13:17
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
no tiene sólo entre comillas
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
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:
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!
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
Sino que"esta es
la tercera celda"
"esta es
la sexta celda"
"esta es
la décima celda"
"¡esta es
la celda 12+1!"
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.;celda 4;celda 5;
;celda 7
celda 8;celda 9;
;celda 11;celda 12;
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
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."esta es
la tercera celda"
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()".
*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!