Oracle: преобразование объектов xml в поле varchar2 в символы utf-8

У меня есть поле в таблице, которое содержит XML-сущности для специальных символов, поскольку таблица находится в латинице-1. Например. «Hallöle slovenčina» («ö» в латинице-1, но «č» в «slovenčina» должно было быть преобразовано в сущность каким-либо приложением, которое сохраняет значения в базе данных)

Теперь мне нужно экспортировать таблицу в файл с кодировкой utf-8, преобразовав объекты XML в их исходные символы.

Есть ли функция в Oracle, которая могла бы справиться с этим для меня, или мне действительно нужно создать для этого огромную карту ключей/значений?

Любая помощь приветствуется.

РЕДАКТИРОВАТЬ: я нашел функцию DBMS_XMLGEN.convert, но она работает только с <, > и &. Не на &#NNN; :-(


person mawimawi    schedule 02.02.2012    source источник
comment
в этой ссылке dbms_xmlgen.convert преобразовал файл '. Может быть, потому что это таблица xml вместо varchar2?   -  person    schedule 07.02.2012
comment
Сергей, это определенно поле типа varchar2: CREATE TABLE mytable (tid INTEGER NOT NULL, zitat VARCHAR2 (2000) NOT NULL);   -  person mawimawi    schedule 07.02.2012


Ответы (3)


Я считаю, что проблема с dbms_xmlgen заключается в том, что технически существует только пять объектов XML. В вашем примере есть числовой объект HTML, который соответствует Unicode:

http://theorem.ca/~mvcorks/cgi-bin/unicode.pl.cgi?start=0100&end=017F

В Oracle есть функция UNISTR, которая здесь полезна:

select unistr('sloven\010dina') from dual;

Я преобразовал 269 в его шестнадцатеричный эквивалент 010d в приведенном выше примере (в Юникоде это U+010D). Однако вы можете передать десятичное число и выполнить преобразование следующим образом:

select unistr('sloven\' || replace(to_char(269, 'xxx'), ' ', '0') || 'ina') from dual;

РЕДАКТИРОВАТЬ: решение PL/SQL:

Вот пример, который я подготовил для вас. Это должно перебирать и заменять любые вхождения для каждой строки, которую вы выбираете из своей таблицы (таблиц).

create table html_entities (
    id NUMBER(3),
    text_row VARCHAR2(100)
);

INSERT INTO html_entities 
VALUES (1, 'Hallöle sloven&#269;ina &#266; &#250;');

INSERT INTO html_entities 
VALUES (2, 'I like the letter &#266;');

INSERT INTO html_entities 
VALUES (3, 'Nothing to change here.');

DECLARE
    v_replace_str NVARCHAR2(1000);
    v_fh UTL_FILE.FILE_TYPE;       
BEGIN
    --v_fh := utl_file.fopen_nchar(LOCATION IN VARCHAR2, FILENAME IN VARCHAR2, OPEN_MODE IN VARCHAR2, MAX_LINESIZE IN BINARY_INTEGER);

    FOR v_rec IN (select id, text_row from html_entities) LOOP
        v_replace_str := v_rec.text_row;
        WHILE (REGEXP_INSTR(v_replace_str, '&#[0-9]+;') <> 0) LOOP
            v_replace_str := REGEXP_REPLACE(
                v_replace_str, 
                '&#([0-9]+);',
                unistr('\' || replace(to_char(to_number(regexp_replace(v_replace_str, '.*?&#([0-9]+);.*$', '\1')), 'xxx'), ' ', '0')),
                1,
                1
            );
        END LOOP;

        -- utl_file.put_line_nchar(v_fh, v_replace_str);
        dbms_output.put_line(v_replace_str);

    END LOOP;
    --utl_file.fclose(v_fh);
END;
/

Обратите внимание, что я заглушил вызовы функции UTL_FILE для записи строк NVARCHAR (расширенный набор символов Oracle) в файл на сервере базы данных. dbms_output, хотя и отлично подходит для отладки, похоже, не поддерживает расширенные символы, но это не должно быть проблемой, если вы используете UTL_FILE для записи в файл. Вот DBMS_OUTPUT:

Hallöle slovencina C ú
I like the letter C
Nothing to change here.
person Dan A.    schedule 07.02.2012
comment
unistr выглядит интересной функцией, но поскольку в моей таблице много строк с сущностями для разных символов, все они в формате &#N; (где N обозначает одну или несколько десятичных цифр), этот подход не кажется мне осуществимым. - person mawimawi; 07.02.2012
comment
@mawimawi - я понимаю, что это не полное решение, но это хорошее начало. Я вижу, что MK расширил мое решение с помощью регулярных выражений. Если REGEXP_REPLACE не имеет глобальной функции замены (было бы обидно, если бы ее не было), вы можете использовать хранимую процедуру - цикл по курсору и цикл для каждой строки, пока вы больше не найдете десятичный объект, используя REGEXP_INSTR функции, а также REGEXP_REPLACE. Если позже у меня будет время, а у вас все еще нет решения, я могу написать пример кода. - person Dan A.; 07.02.2012
comment
если ты найдешь время, это будет для меня находкой. - person mawimawi; 08.02.2012
comment
Спасибо за помощь Danimal37! Выглядит фантастически! - person mawimawi; 08.02.2012
comment
И благодарю вас! Моя скромная репутация только что сделала большой скачок! :) - person Dan A.; 09.02.2012
comment
С правами только для чтения я пытаюсь запустить это упражнение по замене регулярных выражений на SELECT {reg-ex...} as "corrected values" FROM ... как однострочный, то есть я не пытаюсь изменить значения в как бы в исходной таблице, но только в выходной таблице. При первой попытке это вернуло ошибку invalid number. То, о чем я прошу, достаточно ясно и легко сделать? - person nutty about natty; 25.05.2020

Вы также можете просто использовать пакет интернационализации:

UTL_I18N.unescape_reference ('текст')

Отлично работает при изменении этих html-объектов на обычные символы (например, очистка после перемещения базы данных с iso 8859P1 на UTF-8)

person Olafur Tryggvason    schedule 11.03.2013

Это, вероятно, следует сделать на PL/SQL, которого я не знаю, но я хотел посмотреть, как далеко я смогу зайти с чистым SQL. Это заменяет только первое вхождение кода, поэтому вам придется как-то запускать его несколько раз.

select regexp_replace(s, '&#([0-9]+);', u) from
(select s, unistr('\0' || REPLACE(TO_CHAR(TO_NUMBER(c), 'xxxx'), ' ', '')) u from
(select s, regexp_replace(s, '.*&#([0-9]+);.*', '\1') c from
(select 'Hallöle sloven&#269;ina' s from dual)))

Или менее читаемый, но более удобный:

SELECT 
REGEXP_REPLACE(s, '&#([0-9]+);', unistr('\0' || REPLACE(TO_CHAR(TO_NUMBER(regexp_replace(s, '.*?&#([0-9]+);.*$', '\1', 1, 1)), 'xxxx'), ' ', '')), 1, 1) 
FROM
(SELECT 'Hallöle sloven&#269;ina &#269; &#278;' s FROM DUAL)

Эта (обновленная) версия правильно заменяет первое вхождение. Применять его нужно до тех пор, пока все они не будут заменены.

person MK.    schedule 07.02.2012
comment
выглядит отлично! но разве нет флага для функции regexp_replace, которая выполняет замену несколько раз в строке? Я почти уверен, что perl, python и другие языки поддерживают такой флаг. в противном случае мне придется много раз заменять фразы, которые, например, на польском или словацком языках :-( - person mawimawi; 07.02.2012
comment
Да, можно заменить все, но проблема в том, что вы не заменяете их каждый раз одной и той же строкой. Вам нужно извлечь код, преобразовать его в юникод и заменить этот код символом юникода. Возможно, каким-то образом это можно сделать с помощью чистого SQL, но это определенно выходит за рамки моего уровня знаний SQL. - person MK.; 07.02.2012
comment
Замена 269 на 160 возвращает ошибку must be followed by four hexadecimal characters](oracleerror.com/2016/07/30/) (в 11g). Любая идея, почему и как решить? Мои строки XML содержат многие из этих &#160;, среди прочего. Спасибо! - person nutty about natty; 25.05.2020