2012-10-03 71 views
8

Por razones heredadas, tenemos una columna VARCHAR2 en nuestra base de datos Oracle 10, donde la codificación de caracteres se establece en AL32UTF8, que contiene algunos valores que no son UTF-8. Los valores son siempre en uno de estos conjuntos de caracteres:¿Cómo puedo convertir los valores de Oracle VARCHAR2 a UTF-8 de una lista de posibles codificaciones?

  • US-ASCII
  • UTF-8
  • CP1252
  • América-1

He escrito una función de Perl para arregla valores rotos fuera de la base de datos. Para un valor de esta columna de base de datos, recorre esta lista de codificaciones e intenta convertir el valor a UTF-8. Si la conversión falla, intenta la siguiente codificación. El primero en convertir sin error es el valor que guardamos. Ahora, me gustaría replicar esta funcionalidad dentro de la base de datos para que cualquiera pueda usarla.

Sin embargo, todo lo que puedo encontrar para esto es CONVERT function, que nunca falla, pero inserta un carácter de reemplazo para los caracteres que no reconoce. Entonces, de acuerdo con lo que puedo decir, no hay forma de saber cuándo falló la conversión.

para ello, tengo dos preguntas:

  1. ¿Hay alguna interfaz existente que trata de convertir una cadena en uno de la lista de codificaciones, volviendo la primera que tiene éxito?
  2. Y si no, ¿hay alguna otra interfaz que indique fallas si no puede convertir una cadena a una codificación? Si es así, entonces podría escribir la función anterior.

ACTUALIZACIÓN:

Como referencia, he escrito esta función PostgreSQL en PL/pgSQL que hace exactamente lo que necesito:

CREATE OR REPLACE FUNCTION encoding_utf8(
    bytea 
) RETURNS TEXT LANGUAGE PLPGSQL STRICT IMMUTABLE AS $$ 
DECLARE 
    encoding TEXT; 
BEGIN 
    FOREACH encoding IN ARRAY ARRAY[ 
     'UTF8', 
     'WIN1252', 
     'LATIN1' 
    ] LOOP 
     BEGIN 
      RETURN convert_from($1, encoding); 
     EXCEPTION WHEN character_not_in_repertoire OR untranslatable_character THEN 
      CONTINUE; 
     END; 
    END LOOP; 
END; 
$$; 

Me encantaría saber cómo hacer el equivalente en Oracle.

Respuesta

6

, gracias a la información clave acerca de los caracteres no válidos en UTF-8 desde @collapsar , así como algunas excavaciones de un compañero de trabajo, he llegado a esto:

CREATE OR REPLACE FUNCTION reencode(string IN VARCHAR2) RETURN VARCHAR2 
AS 
    encoded VARCHAR2(32767); 
    type array_t IS varray(3) OF VARCHAR2(15); 
    array array_t := array_t('AL32UTF8', 'WE8MSWIN1252', 'WE8ISO8859P1'); 
BEGIN 
    FOR I IN 1..array.count LOOP 
     encoded := CASE array(i) 
      WHEN 'AL32UTF8' THEN string 
      ELSE CONVERT(string, 'AL32UTF8', array(i)) 
     END; 
     IF instr(
      rawtohex(
       utl_raw.cast_to_raw(
        utl_i18n.raw_to_char(utl_raw.cast_to_raw(encoded), 'utf8') 
       ) 
      ), 
      'EFBFBD' 
     ) = 0 THEN 
      RETURN encoded; 
     END IF; 
    END LOOP; 
    RAISE VALUE_ERROR; 
END; 

Cu En verdad, nunca llega a WE8ISO8859P1: WE8MSWIN1252 convierte todos y cada uno de los 800 valores malos que tengo sin queja. Lo mismo no es cierto para mis implementaciones de Perl o PostgreSQL, donde CP1252 falla para algunos valores, pero ISO-8859-1 tiene éxito. Aún así, los valores de Oracle parecen adecuados y parecen ser Unicode válidos (probados al cargarlos en PostgreSQL), por lo que no me puedo quejar. Esto será lo suficientemente bueno para desinfectar mis datos, creo.

+0

lo que sucede en su código es que * primero * intente convertir sus datos de entrada a al32utf8 llamando a 'convertir', y luego verifique si la operación fue exitosa. sin embargo, para conjuntos de caracteres orientados a bytes, lo que sucede es que cp1252 es, cada codificación tiene la longitud de exactamente 1 byte, la conversión a unicode nunca fallará. por lo tanto, su verificación tendrá éxito y la función 'reencode' saldrá. tenga en cuenta que es imposible distinguir entre conjuntos de caracteres de origen codificados por bytes mediante una conversión exitosa a unicode, necesita información contextual para hacerlo. Saludos. – collapsar

+0

(cont.). 1.) técnicamente, mi enunciado solo se aplica a los conjuntos de caracteres (codificados en bytes) cuyos glifos se incorporan a unicode. No conozco ningún juego de caracteres que no cumpla con este criterio (sugerencias apreciadas). 2.) para identificar el juego de caracteres de origen, puede 2a) en el caso particular de latin-1 vs cp1252 comprobar los bytes que no están mapeados en glifos en latin-1 (0x7f-0x9f) o 2b) en verificación general para secuencias en lugar de caracteres individuales. ejemplo: A4 -> EURO (latin-15)/CURRENCY (cp1252). esto último no ocurrirá después de los números en los textos ordinarios, por lo que ' A4 'indicaría latin-15. – collapsar

+0

Lamentablemente, no tengo información contextual, así que estoy limpiando algunas cosas viejas. Unos pocos miles convertidos a CP1252 de más de mil millones de registros es algo con lo que podemos vivir. – theory

2

para comprobar si su base de datos contiene la columna no válido UTF-8 usa la siguiente consulta:

select CASE 
      INSTR (
        RAWTOHEX (
         utl_raw.cast_to_raw (
          utl_i18n.raw_to_char (
           utl_raw.cast_to_raw (<your_column>) 
           , 'utf8' 
         ) 
        ) 
       ) 
       , 'EFBFBD' 
      ) 
     WHEN 0 THEN 'OK' 
     ELSE 'FAIL' 
     END 
    from <your_table> 
     ; 

dado que su juego de caracteres db es AL32UTF8.

tenga en cuenta que EF BF BD representa un illegal encoding in utf-8.

como todos los otros conjuntos de caracteres que indica que están orientados por byte, la transformación a Unicode nunca fallará, pero posiblemente produzca diferentes puntos de código. sin información contextual, la determinación automatizada del juego de caracteres fuente real no será posible.

mejores saludos, Carsten

ps: nombres de Oracle para juegos de caracteres: CP1252 ->WE8MSWIN1252 LATIN-1 ->WE8ISO8859P1

+0

Sí, no sabemos cuál era el juego de caracteres original, así que solo quiero que los valores UTF-8 estén limpios. Según su sugerencia, así como una implementación inicial por parte de un compañero de trabajo, he creado una función que creo que está bastante cerca de lo que necesito. Lo publicaré en una respuesta separada. – theory

Cuestiones relacionadas