web-dev-qa-db-ja.com

SQL ServerでBase64文字列をNVARCHARにデコードすると、正しくない文字が表示されるのはなぜですか?

私はSQL Serverを使用してBase64をデコードする方法を検討しており、オンライン(ここからのいくつか)で多くのソリューションを検索した後、この種の方法に基づいているようです。

SELECT CAST(CAST('Base64StringHere' as XML ).value('.','varbinary(max)') AS VARCHAR(250))

ASCIIテキストがある場合、これは完全に機能します。ただし、次のフランス語のテキストがある場合、おそらくVARCHARの制限が原因で)破損します。

Où est le café le plus proche?
T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8=

そして、次の出力を提供します。

Où est le café le plus proche?

比較的簡単な修正はCASTNVARCHARに変更することだと思いましたが、これは再び破損を引き起こします。

SELECT CAST(CAST('T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8=' as XML ).value('.','varbinary(max)') AS NVARCHAR(250) )

썏₹獥⁴敬挠晡꧃氠⁥汰獵瀠潲档㽥

私の検索エンジンのスキルは私に失敗しているかもしれませんが、私の問題を抱えている他の誰かを見つけることができないようです。

何かご意見は?

4
Geesh_SO

問題は、UTF-8でエンコードされた文字列をBase64にエンコードしたことです。したがって、Base64をデコードすると、元のUTF-8バイトシーケンスが返されます。 SQL Serverは、UTF-16リトルエンディアンをNVARCHARデータだけに使用し、XMLにも使用します。したがって、ùは、ù0xC3および0xB9)の2バイトUTF-8シーケンスの8ビットバージョンです。

幸い、UTF-8でエンコードされた文字列をUTF-16に変換したりしたり、Unicode以外のコードページに変換することもできます(IFコードページ変換されるすべての文字をサポートします)。トリックは、Base64でデコードされたバイトを、テキスト表現で(正しく変換されていない文字でも)XMLに変換することです。このトリックの秘訣は、<?xml ...>宣言(通常は省略)を追加し、ソースエンコーディングを指定する必要があることです。

DECLARE @Base64Value NVARCHAR(500) = N'T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8=';

DECLARE @BinaryValue VARBINARY(500) =
    CONVERT(XML, @Base64Value).value('.','varbinary(max)');

DECLARE @IntermediaryValue VARCHAR(500) = CONVERT(VARCHAR(500), @BinaryValue);

SELECT @BinaryValue, @IntermediaryValue;
-- 0x4FC3B920657374206C6520636166C3A9206C6520706C75732070726F6368653F
-- Oֳ¹ est le cafֳ© le plus proche?


-- This is to NVARCHAR, which will always work:
SELECT CONVERT(NVARCHAR(500),
    CONVERT(XML, '<?xml version="1.0" encoding="UTF-8"?>' +  @IntermediaryValue)
              );
-- Où est le café le plus proche?


-- This is to VARCHAR, but "success" will depend on the Code Page
-- specified by the default Collation of the current Database:
SELECT CONVERT(VARCHAR(500),
    CONVERT(XML, '<?xml version="1.0" encoding="UTF-8"?>' +  @IntermediaryValue)
              );

 -- In a DB with a Latin1_General Collation it works:
 -- Où est le café le plus proche?


 -- In a DB with a Hebrew Collation, it gets the following error:
 /*
   Msg 6355, Level 16, State 1, Line XXXXX
   Conversion of one or more characters from XML to target collation impossible
 */

このトリックは、さまざまなソースエンコーディングから変換する場合にのみ機能することに注意してくださいintoUTF-16リトルエンディアン(SQL ServerのXMLデータ型が文字列を格納する方法です)内部で)。このメソッドを使用して、UTF-16をUTF-8またはSQL Serverでサポートされていないその他のエンコードに変換することはできません。


以下は、上記の手順をカプセル化したインラインTVFです。

GO
CREATE FUNCTION dbo.ConvertBase64EncodedUTF8ToUTF16LE
(
  @Base64EncodedUTF8String VARCHAR(8000)
)
RETURNS TABLE
AS RETURN

    SELECT 
        CONVERT(NVARCHAR(500),
                CONVERT(XML,
                        '<?xml version="1.0" encoding="UTF-8"?>' +
                        CONVERT(VARCHAR(500),
                                CONVERT(XML, @Base64EncodedUTF8String)
                                  .value('.','varbinary(max)')
                               )
                       )
               ) AS [DecodedValue];

GO

そしてテストする:

SELECT *
FROM   dbo.ConvertBase64EncodedUTF8ToUTF16LE(
          'T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8=');
-- Où est le café le plus proche?

セットベースの操作を行う場合は、CROSS APPLYと一緒に使用するだけです。

9
Solomon Rutzky