none
XmlLite IXmlWriterでサロゲートペアを含む文字列を扱いたい RRS feed

  • 質問

  • 環境:

    • Visual Studio Professional 2017 15.9.20 (C++)
    • Windows 10 Pro 1909 日本語版

    やりたいこと:

    xmlliteのIXmlWriterを使ってXMLファイルを作成します。このとき要素の値に任意の文字列を書きたいと思っています。このとき「任意の文字列」にはサロゲートペアを含むwchar_t配列が含まれます。

    質問:

    IXmlWriterのWriteString()のドキュメントでは「Surrogate pair characters cannot be split across multiple buffers when this method is called. Therefore, surrogate pairs require special handling. The XML specification defines the valid ranges for surrogate pairs. To write a surrogate pair, see WriteSurrogateCharEntity.」という記載がありますが、具体的にWriteSurrogateCharEntity()を使って何をどうすればよいのかドキュメントに記載がなく、サンプルなども見つかりません。

    何か参考になる情報があればご紹介下さい。あるいは実際にこれを使って組んだことがある方がいらっしゃったらアドバイスを頂きたく。

    UTF16/サロゲートペアについて知識がないので、何か頓珍漢なことを言っていたらすみません。

    2020年3月10日 8:49

回答

  • 単純にwchar_t配列を順番に見て行ってサロゲートペアを見つけたらWriteSurrogateCharEntiryで書き込むだけでは?

    //IXmlWriter *pWriter
    const wchar_t* p;
    p = L"えもじ😀";//U+1F600
    while (*p != L'\0' && SUCCEEDED(hr))
    {
    	if (0xD800 <= *p && *p <= 0xDFFF)
    	{//上位サロゲートなら
    
    		//hr = pWriter->WriteSurrogateCharEntity(0, 0);
    		if (0xDC00 <= p[1] && p[1] <= 0xDFFF)
    		{//次が下位サロゲートなら
    			hr = pWriter->WriteSurrogateCharEntity(*(p+1), *p);
    		}
    		else
    		{
    			hr = WC_E_XMLCHARACTER;
    		}
    		++++p;
    	}
    	else
    	{
    		hr = pWriter->WriteCharEntity(*p);
    		++p;
    	}
    }

    #sjisで書き出してみたけどWriteStringでも自動で数値文字参照にしてくれるっぽい


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2020年3月10日 12:48 不要なコードの削除忘れを修正
    • 回答としてマーク Takahiro Toyama 2020年3月10日 12:57
    2020年3月10日 9:50

すべての返信

  • 単純にwchar_t配列を順番に見て行ってサロゲートペアを見つけたらWriteSurrogateCharEntiryで書き込むだけでは?

    //IXmlWriter *pWriter
    const wchar_t* p;
    p = L"えもじ😀";//U+1F600
    while (*p != L'\0' && SUCCEEDED(hr))
    {
    	if (0xD800 <= *p && *p <= 0xDFFF)
    	{//上位サロゲートなら
    
    		//hr = pWriter->WriteSurrogateCharEntity(0, 0);
    		if (0xDC00 <= p[1] && p[1] <= 0xDFFF)
    		{//次が下位サロゲートなら
    			hr = pWriter->WriteSurrogateCharEntity(*(p+1), *p);
    		}
    		else
    		{
    			hr = WC_E_XMLCHARACTER;
    		}
    		++++p;
    	}
    	else
    	{
    		hr = pWriter->WriteCharEntity(*p);
    		++p;
    	}
    }

    #sjisで書き出してみたけどWriteStringでも自動で数値文字参照にしてくれるっぽい


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2020年3月10日 12:48 不要なコードの削除忘れを修正
    • 回答としてマーク Takahiro Toyama 2020年3月10日 12:57
    2020年3月10日 9:50
  • gekka様

    お返事ありがとうございます。自分で文字コードを確認してサロゲートかどうかチェックして処理するのですね。そうするとサロゲートペア有りのシステムではWriteString()は使ってはいけないということになるかと思いますが、間違っていたら教えてください。

    あと、ご提示いただいたコードで1点質問がございます。上位サロゲートを見つけた段階でWriteSurrogateCharEnity(0, 0)を行っていますが、これはどんな意味があるのでしょうか?docs.microsoft.comを読む限りですと入力値が許容範囲にないのでストリームには効果を与えずエラーになるように思いますし、その下のelse文でWC_E_XMLCHARACTERをhrに設定しているのでループ的にも影響がないように思うのですが、いかがでしょうか。

    2020年3月10日 12:21
  • WriteSurrogateCharEnity(0, 0)を行っていますが、これはどんな意味があるのでしょう

    デバッグで遊んだ残骸なので不要なコードです

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2020年3月10日 12:44
  • WriteSurrogateCharEnity(0, 0)を行っていますが、これはどんな意味があるのでしょう

    デバッグで遊んだ残骸なので不要なコードです

    承知しました。解決しましたので回答としてマークしました。

    重ねましてご回答ありがとうございます。

    2020年3月10日 12:58
  • サロゲートペアとは名前の通り、上位サロゲートと下位サロゲートとの2文字のペアとなるものです。Wikipediaに上げられている例ですと「𠮷」の字は、wchar_tで表現する場合 L"\uD842\uDFB7" ですが、UTF-8で書き出される場合、 u8"\xF0\xA0\xAE\xB7" となります。ペアが分割されてしまうと単純にはこのような変換が行えません。

    ドキュメントにある注記は、ペアを分割して扱えないという意味です。

    そこで確認ですが、サロゲートペアを分割して扱いたいのでしょうか? Noであれば気にする必要はないかと。

    2020年3月10日 13:00
  • 佐祐理様

    いつもお世話になっております。お返事ありがとうございます。

    「サロゲートペアを分割して扱いたいのでしょうか?」と言われると、たぶんNOなのではないかと思います...

    サロゲートペアを分割して扱う必要性が私の知識不足のため不明なのですが、最終的にやりたいことはC++オブジェクトの直列化であり、メモリ上にUTF16LEで保持しているヌル終端の文字列をXMLファイルにUTF8で書き出したいだけです。

    そしてUTF16LE→UTF8の変換は(IXmlWriter::WriteString()等のI/Fを通じて)IXmlWriterにやらせたいと思っています。

    上記のような文脈でこのスレッドで質問したい内容を言い直すと、
    「IXmlWriter::WriteString()は特定の入力文字列についてUTF16LE→UTF8変換が正しく行えないことがあるのか、あるならその場合どう対処すればよいのか」
    ということになるかと思います。

    以下のようなコードを組んで実行し、出力されるXMLファイルは期待通りになっていることは確認できました。

    #include <stdio.h>
    #include <exception>
    #include <Shlwapi.h>
    #pragma comment(lib, "Shlwapi.lib")
    #include <xmllite.h>
    #pragma comment(lib, "xmllite.lib")
    
    #define CHECK_FAILED(x) \
    	if(FAILED(x)) \
    	{ \
    		throw std::exception(#x); \
    	}
    
    struct ComPtrClose
    {
    	IUnknown* obj_;
    	~ComPtrClose()
    	{
    		this->obj_->Release();
    	}
    };
    
    int main()
    {
    	try
    	{
    		IStream* stream = nullptr;
    		CHECK_FAILED(::SHCreateStreamOnFileA("D:\\created1.xml", STGM_CREATE | STGM_WRITE, &stream));
    		ComPtrClose closeStream { stream };
    
    		IXmlWriter* writer = nullptr;
    		CHECK_FAILED(::CreateXmlWriter(__uuidof(IXmlWriter), (void**)&writer, nullptr));
    		ComPtrClose closeWriter { writer };
    
    		CHECK_FAILED(writer->SetOutput(stream));
    
    		CHECK_FAILED(writer->SetProperty(XmlWriterProperty_Indent, TRUE));
    
    		CHECK_FAILED(writer->WriteStartDocument(::XmlStandalone::XmlStandalone_Yes));
    		{
    			CHECK_FAILED(writer->WriteStartElement(L"", L"DataAttrs", L""));
    
    			CHECK_FAILED(writer->WriteStartElement(L"", L"DataAttr", L""));
    			CHECK_FAILED(writer->WriteString(L"𠮷😀"));
    			CHECK_FAILED(writer->WriteFullEndElement());
    
    			CHECK_FAILED(writer->WriteFullEndElement());
    		}
    		CHECK_FAILED(writer->WriteEndDocument());
    		CHECK_FAILED(writer->Flush());
    	}
    	catch(std::exception& ex)
    	{
    		printf("exception: %s\n", ex.what());
    	}
    
    	printf("終了します\n");
    	getchar();
    	return 0;
    }
    

    2020年3月11日 0:52