【问题标题】:Decoding big base64 encoded blobs解码大的 base64 编码的 blob
【发布时间】:2016-11-29 01:00:03
【问题描述】:

我有一个大小约为 3.5mb 的 xml 文件。我正在对其进行编码并将其传播到另一个需要对其进行解码的数据库。我在迭代中对其进行解码,因为它太大而无法一次解码,但是在某些迭代中它无法解码,我得到了一些乱码。我相信这是因为一些符号可以是 1 个字节,而另一些可以是 2 个字节,有时子字符串将一个 2 个字节的符号切成两半,并且迭代会出现乱码。我想我可以尝试将每个子字符串转换为 clob,因为它在转换失败时会发出警告,如果出现警告,则将子字符串中的数量增加一些数字,但我还没有成功解码这个方法。有什么解决方法吗?

更新

解码成功,有警告检查。您需要做的就是尝试使用dbms_lob.convertblobtoclob 将子字符串转换为clob,检查warning != 0,如果是这样,将偏移量减少1并进行下一次迭代,而不将子字符串写入您的blob。然而,这是非常耗费内存的检查,因为它需要创建 blob 并将该 blob 转换为 clob。有没有更简单的解决方法,也许我遗漏了一些非常明显的东西?

更新

我有一个 xml 文件,其中包含以 base64 编码的付款 xml,以及有关该 xml 的其他数据。 f.e.

<envelope>
  <file_name>a.xml</file_name>
  <...><...>
  <data>BASE64 ENCODED XML FILE</data>
</envelope>

包含几个示例的完整脚本。之前我提到过警告检查的解决方法,但在这个例子中它似乎不起作用,当我更好地考虑它时,它不应该。无论如何,这是脚本:

declare
  l_clob clob := empty_clob();

  function convert_clob_to_blob(
    p_clob in clob) return blob is

    l_dest_offsset number := 1;
    l_src_offsset number := 1;
    l_lang_context number := dbms_lob.default_lang_ctx;
    l_warning number;

    l_result blob;
  begin
    dbms_lob.createtemporary(l_result, false);
    dbms_lob.convertToBlob(
      dest_lob => l_result,
      src_clob => p_clob,
      amount => dbms_lob.lobmaxsize,
      dest_offset => l_dest_offsset,
      src_offset => l_src_offsset,
      blob_csid => dbms_lob.default_csid,
      lang_context => l_lang_context,
      warning => l_warning);

    if l_warning != 0 then
      raise_application_error(-20001, 'sd' || '.convert_blob_to_clob ' || l_warning);
    end if;
    return l_result;
  end;

  function gen_rand_xml return clob is

    l_xml xmltype := xmltype('<envelope><nullnode></nullnode></envelope>');
  begin
    for i in 1..50 loop
        SELECT
          insertXMLafter(
            l_xml,
            '/envelope/nullnode',
            XMLType('<node>' || i || '</node>'))
        INTO
          l_xml
        FROM dual;
    end loop;

    return l_xml.getClobVal();
  end;

  function to_base64(
    p_clob in clob) return clob is

    l_length integer;
    l_offset integer := 1;
    l_amt binary_integer := 600;
    l_buffer varchar2(1800);

    l_result clob := empty_clob();
    l_temp_blob blob;
  begin
    dbms_lob.createtemporary(l_temp_blob, false);
    l_temp_blob := convert_clob_to_blob(p_clob);

    l_length := dbms_lob.getlength(l_temp_blob);
    while l_offset < l_length loop
      l_result := l_result || utl_raw.cast_to_varchar2(utl_encode.base64_encode(dbms_lob.substr(l_temp_blob, l_amt, l_offset)));
      l_offset := l_offset + l_amt;
    end loop;

    return l_result;
  end;

  function from_base64(
    p_clob in clob) return clob is

    l_length integer := dbms_lob.getLength(p_clob);
    l_offset integer := 1;
    l_amt binary_integer := 800;
    l_buffer varchar2(3200);

    l_result clob := empty_clob();
  begin  

    while l_offset <= l_length loop
      l_buffer := replace(replace(dbms_lob.substr(p_clob, l_amt, l_offset), chr(10), null), chr(13), null);
      l_offset := l_offset + l_amt;
      while l_offset <= l_length and mod(dbms_lob.getLength(l_buffer), 4) > 0 loop
        l_buffer := l_buffer || replace(replace(dbms_lob.substr(p_clob, 1, l_offset), chr(10), null), chr(13), null);
        l_offset := l_offset + 1;
      end loop;
      l_result := l_result || utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(l_buffer)));
    end loop;

    return l_result;
  end;

  procedure print_clob( p_clob in clob )
  as
    l_offset number default 1;
  begin
    loop
    exit when l_offset > dbms_lob.getlength(p_clob);
    dbms_output.put_line( dbms_lob.substr( p_clob, 4000, l_offset ) );
    l_offset := l_offset + 4000;
    end loop;
  end;
begin
  l_clob := gen_rand_xml;
  print_clob(from_base64(to_base64(l_clob)));
end;
/

【问题讨论】:

  • 请向我们展示您的代码。为什么要尝试将 BLOB 转换为 CLOB?对于 XML 文件,在 CLOB 中工作应该没问题(除非 XML 是 UTF-8,但您的数据库在“较低”字符集上工作,例如 WE8ISO8859P1 - 但你仍然可以使用 NCLOB
  • 当你使用base64_encode时,那么数据量必须是3的整数倍,即l_amt binary_integer := 1000;不起作用,比如使用l_amt binary_integer := 999;
  • 这比我想象的要难。实际上你必须使用 CONVERTTOBLOB 因为 BASE64 需要 3 个 Bytes (不是字符)并将其转换为 4 Bytes。另一个挑战是您在 BASE64 字符串中可能包含的 NEW_LINE 字符,解码时必须忽略这些字符。请查看更新的答案,对我来说,它甚至可以使用 XMLTYPE('&lt;ñödê&gt;' || i || '&lt;/ñödê&gt;') 之类的字符串
  • 如果您的输入字符串包含特殊字符(即像ñödê 这样的非ASCII),则在解码时必须转换为BLOB。否则我会回来一团糟。
  • 很可能只发生在 UTF-8 / UTF-16 字符集上。使缓冲区更小(例如l_amt binary_integer := 36;),然后您应该会看到它。

标签: oracle plsql base64


【解决方案1】:

我仍然不明白你的意思,我最不明白你为什么要从 BLOB 转换为 CLOB。

无论如何,我的应用程序中也有类似的情况,在 XML 文件中使用 BASE64 编码的 XML。

对于编码和解码,我使用这些功能,也许对您的情况有所帮助。

CREATE OR REPLACE FUNCTION DecodeBASE64(InBase64Char IN OUT NOCOPY CLOB) RETURN CLOB IS

    blob_loc BLOB;
    clob_trim CLOB;
    res CLOB;

    lang_context INTEGER := DBMS_LOB.DEFAULT_LANG_CTX;
    dest_offset INTEGER := 1;
    src_offset INTEGER := 1;
    read_offset INTEGER := 1;
    warning INTEGER;
    ClobLen INTEGER;

    amount INTEGER := 1440; -- must be a whole multiple of 4
    buffer RAW(1440);
    stringBuffer VARCHAR2(1440);

BEGIN

    -- Remove all NEW_LINE from base64 string
    ClobLen := DBMS_LOB.GETLENGTH(InBase64Char);
    DBMS_LOB.CREATETEMPORARY(clob_trim, TRUE);
    LOOP
        EXIT WHEN read_offset > ClobLen;
        stringBuffer := REPLACE(REPLACE(DBMS_LOB.SUBSTR(InBase64Char, amount, read_offset), CHR(13), NULL), CHR(10), NULL);
        DBMS_LOB.WRITEAPPEND(clob_trim, LENGTH(stringBuffer), stringBuffer);
        read_offset := read_offset + amount;
    END LOOP;

    read_offset := 1;
    ClobLen := DBMS_LOB.GETLENGTH(clob_trim);
    DBMS_LOB.CREATETEMPORARY(blob_loc, TRUE);
    LOOP
        EXIT WHEN read_offset > ClobLen;
        buffer := UTL_ENCODE.BASE64_DECODE(UTL_RAW.CAST_TO_RAW(DBMS_LOB.SUBSTR(clob_trim, amount, read_offset)));
        DBMS_LOB.WRITEAPPEND(blob_loc, DBMS_LOB.GETLENGTH(buffer), buffer);
        read_offset := read_offset + amount;
    END LOOP;

    DBMS_LOB.CREATETEMPORARY(res, TRUE);
    DBMS_LOB.CONVERTTOCLOB(res, blob_loc, DBMS_LOB.LOBMAXSIZE, dest_offset, src_offset,  DBMS_LOB.DEFAULT_CSID, lang_context, warning);

    DBMS_LOB.FREETEMPORARY(blob_loc);
    DBMS_LOB.FREETEMPORARY(clob_trim);
    RETURN res;


END DecodeBASE64;




CREATE OR REPLACE FUNCTION EncodeBASE64(InClearChar IN OUT NOCOPY CLOB) RETURN CLOB IS

    dest_lob BLOB;  
    lang_context INTEGER := DBMS_LOB.DEFAULT_LANG_CTX;
    dest_offset INTEGER := 1;
    src_offset INTEGER := 1;
    read_offset INTEGER := 1;
    warning INTEGER;

    amount INTEGER := 1440; -- must be a whole multiple of 3
    buffer RAW(1440);
    res CLOB := EMPTY_CLOB();

BEGIN

    IF DBMS_LOB.GETLENGTH(InClearChar) IS NULL THEN 
        RETURN NULL;
    END IF;

    DBMS_LOB.CREATETEMPORARY(dest_lob, TRUE);
    DBMS_LOB.CONVERTTOBLOB(dest_lob, InClearChar, DBMS_LOB.LOBMAXSIZE, dest_offset, src_offset, DBMS_LOB.DEFAULT_CSID, lang_context, warning);
    LOOP
        EXIT WHEN read_offset >= dest_offset;
        DBMS_LOB.READ(dest_lob, amount, read_offset, buffer);
        res := res || UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(buffer));       
        read_offset := read_offset + amount;
    END LOOP;
    DBMS_LOB.FREETEMPORARY(dest_lob);
    RETURN res;


END EncodeBASE64;

你可以这样使用它

DECLARE
    str VARCHAR2(1000) := '<envelope><file_name>a.xml</file_name><data>some text</data></envelope>';
    base64 VARCHAR2(1000);
BEGIN
    base64 := EncodeBASE64(str);
    DBMS_OUTPUT.PUT_LINE( base64 );

    str :=  DecodeBASE64(base64);
    DBMS_OUTPUT.PUT_LINE( str );

END;

输出:

PGVudmVsb3BlPjxmaWxlX25hbWU+YS54bWw8L2ZpbGVfbmFtZT48ZGF0YT5zb21l
IHRleHQ8L2RhdGE+PC9lbnZlbG9wZT4=
<envelope><file_name>a.xml</file_name><data>some text</data></envelope>

也许这个例子更接近你的用例:

DECLARE
    payment CLOB := '<payment><amout>50 Cent</amout><recipient>Wernfried Domscheit</recipient></payment>';
    envelope XMLTYPE;
    base64 CLOB;

BEGIN

    SELECT 
        XMLELEMENT("envelope", 
            XMLELEMENT("file_name", 'a.xml'),
            XMLELEMENT("data", EncodeBASE64(payment))
        )
    INTO envelope
    FROM dual;
    DBMS_OUTPUT.PUT_LINE( envelope.getclobval() || CHR(13));

    SELECT RETURN_BASE64
    INTO base64
    FROM XMLTABLE('envelope/data' PASSING envelope COLUMNS
        RETURN_BASE64 CLOB PATH '/');       
    DBMS_OUTPUT.PUT_LINE( base64 || CHR(13) );

    payment := DecodeBASE64(base64);
    DBMS_OUTPUT.PUT_LINE( payment );

END;

输出:

<envelope><file_name>a.xml</file_name><data>PHBheW1lbnQ+PGFtb3V0PjUwIENlbnQ8L2Ftb3V0PjxyZWNpcGllbnQ+V2VybmZy
aWVkIERvbXNjaGVpdDwvcmVjaXBpZW50PjwvcGF5bWVudD4=</data></envelope>

PHBheW1lbnQ+PGFtb3V0PjUwIENlbnQ8L2Ftb3V0PjxyZWNpcGllbnQ+V2VybmZy
aWVkIERvbXNjaGVpdDwvcmVjaXBpZW50PjwvcGF5bWVudD4=

<payment><amout>50 Cent</amout><recipient>Wernfried Domscheit</recipient></payment>

为了不浪费内存,当您使用 LOB 时,您应该始终使用IN OUT NOCOPY 子句。这样,LOB 不会被复制以进行过程调用,您只需传递一个指针。

【讨论】:

  • 既然您提到我不需要将其转换为 blob 然后再转换为 clob,我已根据您的示例更正了我的函数。我已经更新了脚本。向脚本添加了随机 xml 生成器,因此您可以看到我所面临的情况。但是,问题仍然相同。从 base64 解码时,我的部分 xml 无法解码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多