2012-10-03 4 views
8

Per motivi legacy, abbiamo una colonna VARCHAR2 nel nostro database Oracle 10, in cui la codifica dei caratteri è impostata su AL32UTF8, che contiene alcuni valori non UTF-8. I valori sono sempre in uno di questi set di caratteri:Come posso convertire i valori Oracle VARCHAR2 in UTF-8 da un elenco di possibili codifiche?

  • US-ASCII
  • UTF-8
  • CP1252
  • Latin-1

Ho scritto una funzione di Perl per correggere i valori danneggiati al di fuori del database. Per un valore da questa colonna del database, scorre in questo elenco di codifiche e tenta di convertire il valore in UTF-8. Se la conversione fallisce, prova la prossima codifica. Il primo a convertire senza errori è il valore che manteniamo. Ora, vorrei replicare questa funzionalità all'interno del database in modo che chiunque possa usarlo.

Tuttavia, tutto ciò che posso trovare per questo è lo CONVERT function, che non fallisce mai, ma inserisce un carattere di sostituzione per i caratteri che non riconosce. Quindi non c'è modo, per quanto posso dire, di sapere quando la conversione è fallita.

Perciò, ho due domande:

  1. c'è qualche interfaccia esistente che cerca di convertire una stringa in una di elenco delle codifiche, restituendo il primo che riesce?
  2. E se no, c'è qualche altra interfaccia che indica un errore se non è in grado di convertire una stringa in una codifica? Se è così, allora potrei scrivere la funzione precedente.

UPDATE:

Per avere un riferimento, ho scritto questa funzione PostgreSQL in PL/pgSQL che fa esattamente quello che mi serve:

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; 
$$; 

avrei caro piace sapere come fare l'equivalente in Oracle.

risposta

6

grazie alle informazioni chiave sui caratteri illegali in UTF-8 da @collapsar , così come alcuni scavi da un collega, ho inventato questo:

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 non riesce mai a WE8ISO8859P1: WE8MSWIN1252 converte ogni singolo elenco di circa 800 valori errati che ho senza lamentarmi. Lo stesso non vale per le mie implementazioni Perl o PostgreSQL, dove CP1252 fallisce per alcuni valori ma ISO-8859-1 ha esito positivo. Tuttavia, i valori di Oracle sembrano adeguati e sembrano essere validi Unicode (testato caricandoli in PostgreSQL), quindi non posso lamentarmi. Questo sarà abbastanza buono per disinfettare i miei dati, penso.

+0

ciò che accade nel tuo codice è che tu * prima * cerchi di convertire i tuoi dati di input in al32utf8 con il comando 'convert', quindi verifica se l'operazione ha avuto successo. tuttavia, per i set di caratteri orientati ai byte, che cp1252 capita di essere, ogni codifica ha la lunghezza di esattamente 1 byte: la conversione in unicode non fallirà mai. quindi il tuo controllo avrà successo e la funzione 'reencode' uscirà. si noti che non è possibile distinguere tra i set di caratteri codificati in byte mediante una conversione corretta in unicode: per farlo è necessario disporre di informazioni contestuali. Saluti. – collapsar

+0

(continua). 1.) tecnicamente, la mia affermazione si applica solo ai set di caratteri (codificati in byte) i cui glifi sono incorporati in unicode. non sono a conoscenza di alcun charset che non soddisfi questo criterio (suggerimenti apprezzati). 2.) per identificare il set di caratteri sorgente, si può 2a.) Nel caso particolare di latin-1 vs cp1252 controllare per byte che non sono mappati ai glifi in latin-1 (0x7f-0x9f) o 2b.) In generale per sequenze invece di singoli caratteri. esempio: A4 -> EURO (latin-15)/VALUTA (cp1252). quest'ultimo non si verifica dopo i numeri nei testi ordinari, quindi " A4" indica latino-15. – collapsar

+0

Purtroppo non ho informazioni contestuali e quindi sto solo ripulendo alcune vecchie cose. Alcune migliaia di convertiti in CP1252 su oltre un miliardo di dischi sono qualcosa con cui possiamo convivere. – theory

2

Per verificare se il database contiene colonna non valida utf-8 utilizzare la seguente query:

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> 
     ; 

dato che il set di caratteri db è AL32UTF8.

nota che EF BF BD rappresenta un illegal encoding in utf-8.

poiché tutti gli altri set di caratteri che si indicano sono orientati al byte, la trasformazione in unicode non fallirà mai, ma probabilmente produrrà punti di codice diversi. senza informazioni contestuali la determinazione automatica del set di caratteri sorgente effettivo non sarà possibile.

migliori saluti, Carsten

ps: nomi Oracle per i set di caratteri: CP1252 ->WE8MSWIN1252 LATIN-1 ->WE8ISO8859P1

+0

Sì, non sappiamo quale fosse il set di caratteri originale, quindi voglio solo ottenere i valori UTF-8 puliti. Sulla base del tuo suggerimento, oltre a un'implementazione iniziale da parte di un collega, ho elaborato una funzione che ritengo sia molto simile a ciò di cui ho bisogno. Lo posterò in una risposta separata. – theory