Cada mes (en teoría), la DNIT (ex SET) publica un listado de RUCs. A la fecha, está disponible en este enlace. Supuestamente la codificación de caracteres es UTF-8, pero si se inspecciona detenidamente cada archivo, se pueden encontrar barbaridades como “AGÿ¼ERO” en lugar de “AGÜERO”, “QUIÿ⿿ONEZ” en lugar de “QUIÑÓNEZ”, entre muchos, muuuuchos otros casos. Quien aborda excelentemente este tema es el colega dschulz, en el README de su proyecto de aplicación de escritorio para depurar este listado. Está disponible en su repositorio en Github.
En este caso particular, se tiene un formulario en Oracle Forms que se encarga de subir los archivos de RUC al servidor, luego procesa tales archivos línea por línea e inserta los registros nuevos en la base de datos, o los actualiza si corresponde. Como los archivos supuestamente están en UTF-8, y la codificación de caracteres de la base de datos es un superset de ISO 8859-1 (o similar), entonces se necesita convertir la codificación de cada archivo antes de proceder a su lectura. El usuario solía olvidar convertir manualmente el archivo antes de subirlo. Los archivos se suben vía webutil.
Cabe destacar que esto no evita al 100% los errores de conversión de caracteres. Solo se muestran los reemplazos en las cadenas más frecuentes que se han podido identificar.
Las expresiones en
sedfueron probadas en archivos utf-8, por lo tanto puede que no funcione al 100% en archivos con otras codificaciones de caracteres. Es cuestión de probar y hacer los ajustes correspondientes de ser necesario.
Dado que desde Forms es posible ejecutar comandos del sistema operativo (con el comando HOST()), y el servidor de aplicaciones es Linux, se elaboró un script con los comandos a continuación.
/bin/sed: Para efectuar operaciones de reemplazo y limpieza.
/usr/bin/file: Para verificar el charset del archivo subido. file -b --mime-encoding
/usr/bin/iconv: Para efectuar el reemplazo de una codificación a otra. iconv -f <charset_a_reemplazar> -t <charset_de_reemplazo> -o <ruta_archivo_resultante> -c
Nótese que se debe escribir la ruta completa de cada comando para utilizarlo (probablemente se necesiten hacer configuraciones adicionales para no escribir las rutas completas, pero explicarlo no es el objetivo en este post 😄)
El script en resumen hace lo siguiente: en el archivo especificado, se efectúan varios reemplazos como para limpiar lo más posible el contenido del archivo. Si el charset es utf-8, se realiza la conversión de UTF-8 a ISO-8859-1, sino, se hace la conversión del charset original a ISO-8859-1. Para ambos casos, se vuelca el resultado en un archivo nuevo.
Lo convertido aquí es el resultado de sed, es por ello que se encadena la salida de este comando (con el pipe (|)) a iconv.
Se utilizan los equivalentes en hexadecimal de los caracteres para evitar problemas de codificación en la terminal, además de contemplar ciertos caracteres no imprimibles.
Los detalles adicionales del script están descritos como comentario. Fueron utilizadas 4 variables de tipo VARCHAR2 (la primera de 3000 caracteres, las demás de 200):
'convertido_' || P_NOMBRE_ARCHIVO;/** Dado que con el parámetro -c en iconv se descartan los caracteres que no pueden ser convertidos (como las tildes (´)),
*** primeramente se procede a hacer ciertos reemplazos con sed (y se lo aprovecha además para hacer una que otra limpieza).
*** Si no se agrega el parámetro mencionado previamente, el archivo de salida se trunca al encontrarse un caracter que no puede convertirse.
***
*** SINTAXIS DE EXPRESIONES: s/<expresión_regular>/<texto_de_reemplazo>/<banderas_adicionales>
*** /g reemplaza todas las coincidencias en la línea, no solo la primera.
*** Los \x indican el valor en hexadecimal: cada caracter está representado por dos números hex.
***
*** s/'E¨/'\xC3\x8B/g: reemplaza "'E¨" por "Ë" (caso: KO'E¨ por KO'Ë)
*** s/´´/"/g: reemplaza un par de tildes (´´) por doble comilla (caso: "hola´´ por "hola")
*** s/\xC2\x80//g: elimina el caracter no imprimible PAD.
*** s/½/ 1\/2/g: reemplaza ½ por 1/2.
*** s/\xC3\x83\xC2\xBF\xC3\x82\xC2\xBC/\xC3\x9C/g: reemplaza la cadena ÿ¼ por Ü.
*** s/``/"/g: reemplaza un par de acentos graves por doble comilla (caso: ``hola" por "hola")
*** s/´/'/g: reemplaza la tilde por la comilla (CHR(39)) (casos: apellidos que deberían llevar apóstrofe y colocaron una tilde en su lugar)
*** s/¨/"/g: reemplaza la diéresis por doble comilla (caso: ¨9 de julio¨ por "9 de julio")
***
***/
V_SCRIPT := '(/bin/sed -e "s/'|| CHR(39) || 'E¨/'|| CHR(39) || '\xC3\x8B/g" ' ||
' -e ''s/´´/"/g'' ' ||
' -e "s/\xC2\x80//g" ' ||
' -e "s/½/ 1\/2/g" ' ||
' -e "s/\xC3\x83\xC2\xBF\xC3\x82\xC2\xBC/\xC3\x9C/g" '||
' -e ''s/``/"/g'' ' ||
' -e "s/´/'|| CHR(39) || '/g" ' ||
' -e ''s/¨/"/g'' '||P_RUTA_SUBIR_ARCHIVOS||P_NOMBRE_ARCHIVO||') | ' ||
'([ "$(/usr/bin/file -b --mime-encoding '||P_RUTA_SUBIR_ARCHIVOS||P_NOMBRE_ARCHIVO||')" = "utf-8" ] && ' ||
'(/usr/bin/iconv -f UTF-8 -t ISO-8859-1 -o '||P_RUTA_SUBIR_ARCHIVOS||P_NOMBRE_ARCHIVO_CONVERTIDO || ' -c) || ' ||
'(/usr/bin/iconv -f $(/usr/bin/file -b --mime-encoding '||P_RUTA_SUBIR_ARCHIVOS||P_NOMBRE_ARCHIVO||') ' ||
'-t ISO-8859-1 '||
'-o '||P_RUTA_SUBIR_ARCHIVOS||P_NOMBRE_ARCHIVO_CONVERTIDO || ' -c))';
HOST(V_SCRIPT);
Si algo no está descrito aquí, es porque puede responderse con man 😉