none
MSXML2.DOMDocumentで 環境依存文字「—」を出力できない RRS feed

  • 質問

  • 質問させていただきます。

    環境は、Win8.1、VS2010+SP1 、
    言語は、C#またはVB.Netです。

    既存のシステムで、DOMを使ってXMLを出力する機能があるのですが、環境依存文字「—」を出力しようとすると次のような例外が発生してしまいます。
    COMException (0x80004005): 文字列を 'shift_jis' エンコードで保存できません。

    全ての環境依存文字で例外が発生するというわけでもなくて、いま分かっているもので「‑、‒、–、—、〼」です。

    サンプルコードを以下に示します。参照設定でCOMのMicrosoft XML,V6.0を参照する必要があります。

    MSXML2.DOMDocument dom = new MSXML2.DOMDocument();
    dom.appendChild(dom.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"shift_jis\"")); // ①
    MSXML2.IXMLDOMNode rootNode = dom.appendChild(dom.createElement("infomation"));
    MSXML2.IXMLDOMNode subNode = dom.createElement("data");
    subNode.text = "—";
    rootNode.appendChild(subNode);
    dom.save(保存先ファイルパス);

    ①で「encoding=\"unicode\""」にすれば正常に出力できるようになることは確認したのですが、そうすると読み込み側のシステムも修正する必要があり、また過去に出力されてきたXMLが読み込めなくなってしまう問題もあり、shift_jisのままにしたいと考えています。

    DOMを使い、encodingもshift_jisのままで、正常に 環境依存文字"‑、‒、–、—、〼"を出力する方法はないでしょうか。

    御存知の方いれば、ご助言をお願いします。

    2014年9月24日 4:22

回答

  • 最悪、domレベルでは、UNICODEで扱えばいいのでは。
    saveをMemoryStreamあたりに向けて、stringにすれば、
    あとは、replaceして回るだけの話でしょう。


    jzkey

    • 回答としてマーク HappyHill415 2014年9月25日 5:31
    2014年9月24日 7:16
  • > 言語は、C#またはVB.Netです。

    とのことなので、MSXML を使うのは止めて、.NET Framework の System.Xml 名前空間の XmlDocument クラスを使って書き直すということはできないのでしょうか? 

    XmlWriter を使ってうまく行くそうなので、XmlDocument.Save メソッド (XmlWriter) メソッドを使えば望みの結果になると思いますが。

    以下のような感じです。

    XmlDocument doc = new XmlDocument();
    XmlDeclaration declaration = doc.CreateXmlDeclaration("1.0", "shift_jis", null);
    XmlElement info = doc.CreateElement("information");
    doc.AppendChild(declaration);
    doc.AppendChild(info);
    XmlElement data = doc.CreateElement("data");
    data.InnerText = "—";
    info.AppendChild(data);
    
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = true;
    settings.Encoding = Encoding.GetEncoding("Shift_JIS");
    XmlWriter writer = XmlWriter.Create("sample.xml", settings);
    doc.Save(writer);
    writer.Close();

    結果、sample.xml の内容は以下のようになります。

    <?xml version="1.0" encoding="shift_jis"?>
    <information>
      <data>&#x2014;</data>
    </information>

    • 回答としてマーク HappyHill415 2014年9月25日 5:31
    2014年9月24日 8:19
  • 保存については、本当は.NETの仕組みで書き直す方がおすすめですが、どうしても修正のコストやリスクを避けたいなら、

    ・MSXMLでいったんUnicode系のエンコーディングで保存して、.NETの仕組みでそれを読み込み、Shift_JISで出力しなおす

    ・MSXMLでxml全体を文字列として取得する機能があるならそれを利用して、.NETの仕組みで取り込んでから、Shift_JISで出力する

    というような方法もあります。

    読み込み側のシステムについては、単に読み込みプログラムがロードできるかという問題だけでなく(これはそもそもまっとうなXMLライブラリならできるのが当たり前)、その読み込みプログラムの処理にて、Shift_JIS以外の文字を想定していない部分で問題がないか、問題を起こさないかということ、そこからさらに別のファイルやシステムへの出力でShift_JIS以外の文字が問題を起こさないか(出力形式がShift_JISのテキストやCSVなどだったりしたらそこでアウト、DB出力で項目のエンコードがShift_JISとかでもアウト)、そこから先のシステム、他システムなどがShift_JIS以外の文字で問題を起こさないか、というように、先の方まで考慮する必要があります。

    これまでShift_JIS前提だったシステムでは、この先のどこかで詰まってしまうことが多いですので、見落としが起きないように十分注意してください。

    ※まあ、XMLは何らかのライブラリでロードするのが普通ですので、出力をShift_JISにしなければならないこと自体疑問ではあるのですが(XMLで扱う場合、普通はエンコーディングがShift_JISじゃないことは問題ではなく、Shift_JIS以外の文字が含まれること自体が問題になるパターンが多いので)
    • 編集済み なちゃ 2014年9月24日 14:36
    • 回答としてマーク HappyHill415 2014年9月25日 5:31
    2014年9月24日 14:32

すべての返信

  • > encodingもshift_jisのままで、正常に 環境依存文字"‑、‒、–、—、〼"を出力する方法はないでしょうか。

    「環境依存文字」ではなくて Shift_JIS に無い文字を Shift_JIS エンコーディングで扱いたいということですよね? "‑、‒、–、—、〼" をメモ帳にコピペして ANSI (=Shift_JIS)  で保存するような話で、unicode にする以外手は無いのではないですか?

    ハズレでしたら失礼しました。

    2014年9月24日 5:23
  • XML的には文字参照でできるでしょうけれども、MSXMLでの出力と、既存の読み込み側システムが対応できるのかが問題ですね。

    おそらくはできない気がします(MSXMLが同課は知らないですが、既存の読み込みシステムは大いに怪しい気がします)。

    2014年9月24日 5:34
  • 回答ありがとうございます。

    DOMを使わず、XmlWriterでテストした際のコードです。

    var settings = new XmlWriterSettings()
    {
      Encoding = Encoding.GetEncoding("Shift_JIS"),
      Indent = true,
      OmitXmlDeclaration = false,
    };
    var writer = XmlWriter.Create("保存先ファイルパス", settings);
    writer.WriteStartElement("infomation");
    writer.WriteElementString("data", "—");
    writer.WriteEndElement();
    writer.Flush();
    writer.Close();

    このコードの場合、出力結果が下のようになりました。

    <?xml version="1.0" encoding="shift_jis"?>
    <infomation>
      <data>&#x2014;</data>
    </infomation>

    問題の部分をコードに置換することで、shift_jisのままでも正常に出力させているようでした。
    IEで表示するとバッチリ「—」と表示されます。
    これと同じような出力がDOMでもできればいいんですが・・・。

    2014年9月24日 5:56
  • このコードの場合、出力結果が下のようになりました。

    <?xml version="1.0" encoding="shift_jis"?>
    <infomation>
      <data>&#x2014;</data>
    </infomation>

    問題の部分をコードに置換することで、shift_jisのままでも正常に出力させているようでした。
    IEで表示するとバッチリ「—」と表示されます。
    これと同じような出力がDOMでもできればいいんですが・・・。

    分かっているような気もしますが念のため補足です。

    これが文字参照による出力です。

    で、MSXMLによる出力と、読み込み側のシステムがこれに対応できているか?というのが上述のお話です。

    2014年9月24日 6:06
  • 最悪、domレベルでは、UNICODEで扱えばいいのでは。
    saveをMemoryStreamあたりに向けて、stringにすれば、
    あとは、replaceして回るだけの話でしょう。


    jzkey

    • 回答としてマーク HappyHill415 2014年9月25日 5:31
    2014年9月24日 7:16
  • MSXMLのsaveメソッドのドキュメント

    XML_BAD_ENCODING
    The value returned if the document contains a character that does not belong in the specified encoding. The character must use a numeric entity reference. For example, the Japanese Unicode character 20013 does not fit into the encoding Windows-1250 (the Central European alphabet) and therefore must be represented in markup as the numeric entity reference &#20013; or &#x4E2D; This version of save does not automatically convert characters to the numeric entity references.

    とありました。ということは subNode.text = "—"; というコード自体が誤りで

    subNode.text = "&#x2014;";


    と書く必要があるかもしれません。

    ところでなぜMSXMLを使うのでしょうか。System.XmlやSystem.Xml.Linqを使った方が楽では?

    特に後者は便利で、

    using (var writer = XmlWriter.Create("a.txt", new XmlWriterSettings { Encoding = Encoding.Default, Indent = true })) {
      var dom = new XDocument(new XElement("infomation", new XElement("data", "—")));
      dom.Save(writer);
    }

    で質問文の意図するXMLを生成できます。

    • 編集済み 佐祐理 2014年9月24日 7:51
    2014年9月24日 7:35
  • subNode.text = "&#x2014;";

    と書く必要があるかもしれません。

    &はテキストノードに格納される時点で&amp;に変換されます。

    DOMDocument上はUnicodeで扱われているようなのでテキストノードにShift_JIS範囲外の設定することは問題ありません。

    エンコーディング変換されるのはsave内なので、saveを使わず、DOMDocument::xmlプロパティを参照してUnicodeな文字列を取得し、それに対して変換をかける必要があるでしょう。.NETならEncoderFallback/EncoderFallbackBufferを作成するのが多分一番実装が楽です。

    // というのまでは調べてたけど、なちゃさんが提起している読み込み側の問題について質問者さんが確認するまで待とうと思ってたら回答が続いちゃってるので。

    2014年9月24日 7:45
  • あーその場合はよりXMLDOMに忠実に subNode.appendChild( dom.createTextNode("&#x2014;") ); ですかね。MSXMLを触る気がなくてすみません…。
    2014年9月24日 7:56
  • > 言語は、C#またはVB.Netです。

    とのことなので、MSXML を使うのは止めて、.NET Framework の System.Xml 名前空間の XmlDocument クラスを使って書き直すということはできないのでしょうか? 

    XmlWriter を使ってうまく行くそうなので、XmlDocument.Save メソッド (XmlWriter) メソッドを使えば望みの結果になると思いますが。

    以下のような感じです。

    XmlDocument doc = new XmlDocument();
    XmlDeclaration declaration = doc.CreateXmlDeclaration("1.0", "shift_jis", null);
    XmlElement info = doc.CreateElement("information");
    doc.AppendChild(declaration);
    doc.AppendChild(info);
    XmlElement data = doc.CreateElement("data");
    data.InnerText = "—";
    info.AppendChild(data);
    
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = true;
    settings.Encoding = Encoding.GetEncoding("Shift_JIS");
    XmlWriter writer = XmlWriter.Create("sample.xml", settings);
    doc.Save(writer);
    writer.Close();

    結果、sample.xml の内容は以下のようになります。

    <?xml version="1.0" encoding="shift_jis"?>
    <information>
      <data>&#x2014;</data>
    </information>

    • 回答としてマーク HappyHill415 2014年9月25日 5:31
    2014年9月24日 8:19
  • みなさま、質問に対して議論していただきありがとうございます。

    なちゃさまのおっしゃるように、読み込み側が正常に読めるのか確認が必要でした。
    読み込み側の動作を確認してみます。

    SurferOnWwwさま、サンプルコードを提示していただきありがとうございます。
    MSXMLが使えないとなった場合はこれしかないのかもしれません。
    ただ修正個所が広範囲になるなぁ・・と身構えてしまいます。

    jzkeyさまの方法は思いつきませんでした。
    王道ではないですが、影響範囲は最小に抑えられそうな気がします。
    これも実現可能か調べてみます。

    これら確認できましたらまた投稿いたします。

    ありがとうございました。

    2014年9月24日 9:34
  • 保存については、本当は.NETの仕組みで書き直す方がおすすめですが、どうしても修正のコストやリスクを避けたいなら、

    ・MSXMLでいったんUnicode系のエンコーディングで保存して、.NETの仕組みでそれを読み込み、Shift_JISで出力しなおす

    ・MSXMLでxml全体を文字列として取得する機能があるならそれを利用して、.NETの仕組みで取り込んでから、Shift_JISで出力する

    というような方法もあります。

    読み込み側のシステムについては、単に読み込みプログラムがロードできるかという問題だけでなく(これはそもそもまっとうなXMLライブラリならできるのが当たり前)、その読み込みプログラムの処理にて、Shift_JIS以外の文字を想定していない部分で問題がないか、問題を起こさないかということ、そこからさらに別のファイルやシステムへの出力でShift_JIS以外の文字が問題を起こさないか(出力形式がShift_JISのテキストやCSVなどだったりしたらそこでアウト、DB出力で項目のエンコードがShift_JISとかでもアウト)、そこから先のシステム、他システムなどがShift_JIS以外の文字で問題を起こさないか、というように、先の方まで考慮する必要があります。

    これまでShift_JIS前提だったシステムでは、この先のどこかで詰まってしまうことが多いですので、見落としが起きないように十分注意してください。

    ※まあ、XMLは何らかのライブラリでロードするのが普通ですので、出力をShift_JISにしなければならないこと自体疑問ではあるのですが(XMLで扱う場合、普通はエンコーディングがShift_JISじゃないことは問題ではなく、Shift_JIS以外の文字が含まれること自体が問題になるパターンが多いので)
    • 編集済み なちゃ 2014年9月24日 14:36
    • 回答としてマーク HappyHill415 2014年9月25日 5:31
    2014年9月24日 14:32
  • 最初に提示したサンプルコードの最後の行を、以下のように書き換えることでShiftJISでXMLを書き出せました。

    //dom.save(保存先ファイルパス);

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(dom.xml);

    XmlWriterSettings settings = new XmlWriterSettings()
    {
        Indent = true,
        Encoding = Encoding.GetEncoding("Shift_JIS")
    };

    using (XmlWriter writer = XmlWriter.Create(保存先ファイルパス, settings))
    {
        doc.Save(writer);
        writer.Close();
    }

    一旦ファイルに保存しなくても、dom.xmlプロパティでXML全体を取得できたので、これをXmlDocumentにロードして保存しました。

    読み込み側の動作検証はまだできていないのですが、当初の目的は果たせたので、これでスレッドを閉じさせていただこうと思います。

    助かりました。ありがとうございました。

    2014年9月25日 5:30
  • MSXML を使わない方向で解決したようなので、今更ですが:

    MSXML2.DOMDocument dom = new MSXML2.DOMDocument();

    上記で生成されるのは、MSXML3 以下のバージョンの DOM です(2.0 互換)。

    MSXML6 を使われているのであれば、バージョン番号付きの方を利用してみて下さい。

    今回の場合、4.0 以降のDOM、たとえば MSXML6 の DOM を使うことで、
    「&#8209;、&#8210;、&#8211;、&#8212;、&#12348;」形式で出力させることができました。
    「&#x2011;、&#x2012;、&#x2013;、&#x2014;、&#x303c;」形式では無いですが、
    今回の意図は満たせるかと。

    //MSXML2.DOMDocument dom = new MSXML2.DOMDocument();
    MSXML2.DOMDocument60 dom = new MSXML2.DOMDocument60();

    ※本題とは関係ないですが、要素名のスペルが気になってます。<information /> では無いのかな…。

    2014年9月26日 7:29