utf-8形式のXMLファイルをDOMにLoadするには?

解決


sumomo  2008-01-30 07:54:20  No: 100166  IP: 192.*.*.*

お世話になります。
VB6.0(SP6)で、MSXML2.DOMDocument50を利用して、encoding="utf-8"形式のXMLファイルをloadしようとしています。

その際に、タグのvalueが" "のものが?で表示されてしまいます。
どのようにすれば、正常に半角空白として認識されるでしょうか。
※ は半角空白

---XMLファイル(C:\text.xml)-----
<?xml version="1.0" encoding="utf-8"?>
<sample>&#160;</sample>

----VB-------
dim domdoc50 as MSXML2.DOMDocument50

set domdoc50 = new MSXML2.DOMDocument50

if domdoc50.load("C\text.xml") then
    debug.print domdoc50.xml
end if 

---実行結果----
<sample>?</sample>

編集 削除
いな  2008-01-30 10:34:41  No: 100167  IP: 192.*.*.*

そのxmlファイルは本当にUTF-8の文字コードで記載されていますか?

編集 削除
YuO  2008-01-30 11:17:31  No: 100168  IP: 192.*.*.*

単に,debug.printでチェックしているからではないでしょうか。

6.0までの32bit版VBは,内部はUnicodeですが入出力系はShift_JIS (日本語版の場合) です。
U+00A0 NO-BREAK SPACEはShift_JISの範囲内に無い文字なので,?になっているのだと思います。

編集 削除
sumomo  2008-02-01 06:58:10  No: 100169  IP: 192.*.*.*

回答がおそくなり申し訳ありません。
ありがとうございます。

>6.0までの32bit版VBは,内部はUnicodeですが入出力系はShift_JIS (日本語版の場合) です。
>U+00A0 NO-BREAK SPACEはShift_JISの範囲内に無い文字なので,?になっているのだと思います。
なるほど。
確かに、domdoc50.load("C\text.xml")で読み込んだXMLを
domdoc50.save("C:\test2.xml")という風にファイルに出力すると
?は表示されません。
以下のようになります。
---XMLファイル(C:\text.xml)-----
<?xml version="1.0" encoding="utf-8"?>
<sample>&#160;</sample>

---XMLファイル(C:\text2.xml)-----
<?xml version="1.0" encoding="utf-8"?>
<sample> </sample>


それでは、domdoc50.load("C\text.xml")で読み込んだXMLを、Shift_JISの文字コードで編集したあと、encording = utf-8形式で出力したいのですができないのでしょうか?

編集 削除
魔界の仮面弁士  2008-02-01 11:11:56  No: 100170  IP: 192.*.*.*

> Shift_JISの文字コードで編集したあと
それは具体的には、どういう意味でしょうか?

VB6 の String 型は、内部的には UTF-16 相当のバイナリとして格納されています。
Left / Mid / Right / Replace をはじめとする関数群も、それを前提に処理しますので、
Shift_JIS でエンコードされたデータを処理する事はできません。

StrConv を使うなどして、UTF-16 以外の(Shift_JISなどの)バイナリを、String 変数に
押し込めることは可能ですが、その場合、VB 標準の関数は使えません。

Print # ステートメントや Print メソッドは、String を「Shift_JIS」として出力し、
Line Input # ステートメントや InputBox 関数は、「Shift_JIS」でデータを得ますが、
それは、String の内部が UTF-16 である事を前提に、Unicode → ANSI 変換を行っているだけです。


> encording = utf-8形式で出力したいのですができないのでしょうか?
Save メソッドの引数に、「ファイル名を示す String」ではなく、
「UTF-8 エンコードの Stream」を渡せば OK です。

Set stm = New ADODB.Stream
stm.Type = adTypeText
stm.Charset = "UTF-8"
stm.Open
doc.save stm
stm.SaveToFile "C:\sample.xml", adSaveCreateOverWrite
stm.Close

ただしこの方法の場合、<?xml verion="1.0" encoding="***"?> の部分の
書き換えは行われませんので、この部分は出力前に編集しておく必要があります。

編集 削除
sumomo  2008-02-02 00:28:34  No: 100171  IP: 192.*.*.*

>魔界の仮面弁士様
ご回答ありがとうございます。

>> Shift_JISの文字コードで編集したあと
>それは具体的には、どういう意味でしょうか?
⇒すいません。具体的に処理したいことは以下のことです。Shift_JISでは
  なくUnicode(utf-16)でした。。。
  ①encording='utf-8'のXMLファイルをVB6.0上でMSXMLパーサ(DOM)で読み込む。
  ②読み込んだXMLをVB6.0上でMSXMLパーサ(DOM)で編集して、nodeの追加、削除などを行う)する。
  ③編集したXMLを、UTF-8形式のXMLファイルとして出力する。
です。

>Save メソッドの引数に、「ファイル名を示す String」ではなく、
>「UTF-8 エンコードの Stream」を渡せば OK です。

>Set stm = New ADODB.Stream
>stm.Type = adTypeText
>stm.Charset = "UTF-8"
>stm.Open
>doc.save stm
>stm.SaveToFile "C:\sample.xml", adSaveCreateOverWrite
>stm.Close
>ただしこの方法の場合、<?xml verion="1.0" encoding="***"?> の部分の
>書き換えは行われませんので、この部分は出力前に編集しておく必要があり>ます。
⇒ありがとうございます。今から試してみます。試した結果をまた記載します。

編集 削除
sumomo  2008-02-02 09:03:00  No: 100172  IP: 192.*.*.*

おせわになります。

>> Shift_JISの文字コードで編集したあと
>それは具体的には、どういう意味でしょうか?
>⇒すいません。具体的に処理したいことは以下のことです。Shift_JISでは
>  なくUnicode(utf-16)でした。。。
>  ①encording='utf-8'のXMLファイルをVB6.0上でMSXMLパーサ(DOM)で読み込む。
>  ②読み込んだXMLをVB6.0上でMSXMLパーサ(DOM)で編集して、nodeの追加、削除などを行う)する。
>  ③編集したXMLを、UTF-8形式のXMLファイルとして出力する。
です。
すいません。訂正です。

  ①encording='utf-8'のXMLファイルをVB6.0上でMSXMLパーサ(DOM)で読み込む。
  ②読み込んだXMLをVB6.0上でMSXMLパーサ(DOM)で編集して、nodeの追加、削除などを行う)する。
  ③DomDocument.xmlでString変数に代入。
  ④String変数に格納された文字列をDOMでload。
  ⑤DOMでloadしたstring文字列を、UTF-8形式のXMLファイルとして出力する。
でした。

編集 削除
魔界の仮面弁士  2008-02-02 12:50:23  No: 100173  IP: 192.*.*.*

>(1)encording='utf-8'のXMLファイルをVB6.0上でMSXMLパーサ(DOM)で読み込む。
>(2)読み込んだXMLをVB6.0上でMSXMLパーサ(DOM)で編集して、nodeの追加、削除などを行う)する。
ここまでは良いとして。

>(3)DomDocument.xmlでString変数に代入。
>(4)String変数に格納された文字列をDOMでload。
この部分は、何のために必要なのでしょう?

>(5)DOMでloadしたstring文字列を、UTF-8形式のXMLファイルとして出力する。
文字列を UTF-8 のテキストファイルとして出力したいのであれば、
ADODB.Stream にWriteText して、それを SaveToFile すれば OK。

編集 削除
sumomo  2008-02-03 05:53:00  No: 100174  IP: 192.*.*.*

ご回答ありがとうございます。

>(3)DomDocument.xmlでString変数に代入。
>(4)String変数に格納された文字列をDOMでload。
>この部分は、何のために必要なのでしょう?
⇒別の関数に引数として渡す際にStringにしました。
(関数で要求されているのがStringのため)

気になる点としては、UTF-8形式のXMLファイルをDOMに読み込ませたときに、
DOMが、UTF-8形式のXMLファイルをVB6.0で使用されているUnicode(UTF-16)に
自動的に変換しているのかという点です。
自動的に変換していないのであれば、DOMの各ノードに対して、nodeValueなどにstringの文字列を直接代入するのはNGで、変換が必要かと思います。

>①encording='utf-8'のXMLファイルをVB6.0上でMSXMLパーサ(DOM)で読み込む。

>(5)DOMでloadしたstring文字列を、UTF-8形式のXMLファイルとして出力する。
>文字列を UTF-8 のテキストファイルとして出力したいのであれば、
>ADODB.Stream にWriteText して、それを SaveToFile すれば OK。
⇒教えていただいた方法で試したところ上手くいきました。
  ありがとうございます。

編集 削除
魔界の仮面弁士  2008-02-03 21:13:21  No: 100175  IP: 192.*.*.*

> DOMが、UTF-8形式のXMLファイルをVB6.0で使用されているUnicode(UTF-16)に
> 自動的に変換しているのかという点です。

String 型に、生の UTF-8 のデータが渡される事はありません。

COM の世界においては、String 型は基本的に Unicode データとして
管理されますが、内部的には、これは UCS-2 (UTF-16) 相当のバイナリです。

つまり通常は、元ファイルの文字コードが何であるかを意識する必要は無く、
MSXML にロードされた XML データが、nodeValue プロパティ等を通じて
「String 型」で受け渡しされる際には、その内部データは、常に
UTF-16 の Unicode データとなっている、という事です。


ただしこれは、MSXML の内部データが、常に UTF-16 固定であるという意味ではありません。

MSXML の load メソッドは、ファイル名やURLを表す「文字列(String または Byte配列)」だけではなく、
他にも XML データを持った「ストリーム(IStream, ISequentialStream, IPersistStream)」を渡す事が
できるようになっています。(ASP の Request オブジェクトや、ADODB の Stream オブジェクトなど)

MSXML がストリームから読み込みを行った場合には(または、ストリームが MSXML に保存した場合には)、
ストリーム自体が UTF-8 であった場合には、Call .Save("C:\sample.xml") の結果は、元ストリームと同じく
UTF-8 形式の文字列で出力されます。(元が euc-jp なら、euc-jp のまま)

そして、出力結果の文字コードを変えたい場合は、先に示した例のように、Save メソッドの引数を
「文字列」ではなく、「ストリーム」にしてやれば OK です。


> nodeValueなどにstringの文字列を直接代入するのはNGで、変換が必要かと思います。
上記のような理由から、そうした変換は不要です。

ただし、(文字コードではなく)文字集合の点から言えば、例えば Shift_JIS ファイルの場合、
http://support.microsoft.com/kb/170559/ja
http://support.microsoft.com/kb/286776/ja
のように、Unicode との間での変換上の問題が発生する可能性があります。
(UTF-8 の場合は、UTF-16 文字集合が同じであるため、そうした問題は発生しません。

ちなみに loadXML メソッドは、UTF-16 バイナリを持った String を受け取れますが
データ中の <?xml version="1.0" encoding="〜"?> の encoding 指定を無視します。

編集 削除
sumomo  2008-02-10 14:26:27  No: 100176  IP: 192.*.*.*

ご回答ありがとうございます。投稿が遅くなりまして申し訳ありません。

これまで、内部的な文字コードをあまり意識することが無かったため、
今回の変換の問題は非常に自分のためになっています。
魔界の仮面弁士さんの解説が非常に丁寧で理解の手助けになってます。
ありがとうございます。

このトピックの最初で起こっていた"&#160;"⇒"?"になってしまう件がどうして発生するのかという原因は今、やっと整理できた状態です。
(いまさらですが・・・)

まず、"&#160;"は、UTF-8,16には存在するが、Shift_JISには存在しない。
そのため、UTF-8から、Shift_JIS形式のファイルに出力しようとすると、
変換できず"?"として出力されてしまう。

UTF-8⇒UTF-16(VB6.0の内部文字コード)⇒Shift_JIS(ファイル出力)⇒UTF-16(VB6.0の内部文字コード)⇒UTF-8という文字コードの変換を行った際には、
Shitf_JISに変換するタイミングで?として出力されてしまうので、その後UTF-8に変換しなおしても?のままになってしまう。

上記の課題を解決するには、"&#160;"をShift_JISに変換する際に、何か違う文字(たとえば"nbsp")に置き換える必要があり、Shift_JIS⇒UNICODEに変換する際も、置き換えた文字を"&#160;"に変換しなおす必要がある。

というところまでは、整理できました。
今、その解決策を模索中です。

一応途中経過のみお伝えします。。。

編集 削除
exp  2008-04-11 12:48:20  No: 100177  IP: 192.*.*.*

調査を重ねているうちに、本掲示板にたどり着きました。。。
私も「XMLファイルのUTF-8形式での編集、新規作成」を
VB6.0(SP6)にてコーディングを行っておりますが、
以下の内容がどうもうまく行きません。。。

■文面抜粋です
> encording = utf-8形式で出力したいのですができないのでしょうか?
Save メソッドの引数に、「ファイル名を示す String」ではなく、
「UTF-8 エンコードの Stream」を渡せば OK です。

Set stm = New ADODB.Stream
stm.Type = adTypeText
stm.Charset = "UTF-8"
stm.Open
doc.save stm
stm.SaveToFile "C:\sample.xml", adSaveCreateOverWrite
stm.Close

上記を実行すると、MSXML2.Domdocument30のsaveメソッド
(doc.save stmの部分)で引数不正のエラーにてこけます。

MSXMLは XML Version,3.0
ADODBはMicrosoft ActiveX Data Objects2.5
を参照しています。

xmlsdk30ヘルプも確認しましたが、IStream(Streamとは違う?)も
引数として可能のような(英語なので。。)内容が書かれていました。

申し訳ありませんが、どなたかお分かりになる方はいらっしゃいませんでしょうか?
あるいは他のやり方でUTF-8形式での作成方法をご存知の方いらっしゃいましたらご教授の程宜しくお願い申し上げます。

編集 削除
魔界の仮面弁士  2008-04-11 13:27:59  No: 100178  IP: 192.*.*.*

> 私も「XMLファイルのUTF-8形式での編集、新規作成」を
> VB6.0(SP6)にてコーディングを行っておりますが、
問題となるのは、「編集」というよりも「保存」時ではありませんか?

DomDocument.Save メソッドでの保存は、ロード中の XML データの
<?xml version='1.0' encoding='euc-jp'?>
などの encoding 指定に引きずられますので、注意が必要です。

文字コードを指定して保存したいのであれば、
  doc.Save stm
の代わりに、
  stm.WriteText doc.xml
の構文を試してみてください。これは、先の投稿に記述した
>>>> ADODB.Stream にWriteText して、それを SaveToFile すれば OK。
の処理に相当します。

xml プロパティを使う方法の場合、<?xml version="1.0"?> 部の
encoding 指定が付きませんが、UTF-8 (あるいは UTF-16)の場合は、
encoding 指定が不要なので、特に問題はないでしょう。
  

> 上記を実行すると、MSXML2.Domdocument30のsaveメソッド
> (doc.save stmの部分)で引数不正のエラーにてこけます。
その際の、具体的なエラーメッセージは何ですか?

たとえば、doc.parseError.errorCode <> 0 となる、異常な XML が
ロードされた状態の DOM を Save しようとした場合、
doc.parseError.reason 相当のエラーが発生する事があります。

あるいは、保存時のコードにしても、
  doc.Save stm
ならば正常に動作するコードも、
  doc.Save(stm)
にすると、エラー438の
「オブジェクトは、このプロパティまたはメソッドをサポートしていません。」
が発生する事があります。


> IStream(Streamとは違う?)も
ADODB の Stream オブジェクトや、MSXML の DOMDocument オブジェクトは、
いずれも、「IStream インターフェイス」を実装した COM クラスです。

編集 削除
exp  2008-04-11 14:04:24  No: 100179  IP: 192.*.*.*

魔界の仮面弁士様

早速のご教授ありがとうございました。

>DomDocument.Save メソッドでの保存は、ロード中の XML データの
><?xml version='1.0' encoding='euc-jp'?>
>などの encoding 指定に引きずられますので、注意が必要です。
なるほど。という事は、encoding='euc-jp'と指定されたファイルをDomDocument.Loadして、編集、DomDocument.Saveした場合はファイル元々の形式がなんであっても(euc-jpでなくても)、euc-jp形式になるという事ですかねー
ま、いろいろ試してみます。

>文字コードを指定して保存したいのであれば、
>  doc.Save stm
>の代わりに、
>  stm.WriteText doc.xml
>の構文を試してみてください。これは、先の投稿に記述した
>>>> ADODB.Stream にWriteText して、それを SaveToFile すれば OK。
>の処理に相当します。
上記方法ではうまく行きました。
ご記載の通り、encoding=の文字列が消えるようですね。

>その際の、具体的なエラーメッセージは何ですか?
「実行時エラー '5'
プロシージャの呼び出し、または引数が不正です。」
です。
doc.parseError.errorCodeの内容も調べましたが、0でした。
また、
doc.Save stm
でも
Call doc.Save(stm)
でも、同じエラーメッセージになってしまいます。。。

すみません。誠に恐れ入りますが、宜しくお願いいたします。

編集 削除
魔界の仮面弁士  2008-04-11 15:23:02  No: 100180  IP: 192.*.*.*

# ここは sumomo さんの質問に対するスレッドなので、expさんの別質問が
# 繋がるのはあまり好ましくは無いのですが、無関係というわけでも無いので、
# このまま回答を続けておきます。

> DomDocument.Loadして、
私が提示したのは Load ではなく LoadXml ですが、それはともかくとして:

注意が必要なのは、元データの文字コードは、特に関係無いという事です。
DOM 自体は、XML データを常に Unicode として処理しますので。

問題となるのは、encoding 指定です。

Set doc = New MSXML2.DOMDocument30
doc.loadXML "<?xml version='1.0' encoding='iso-2022-jp'?><あ/>"
doc.save "C:\sample1.xml"   'JISファイルとして保存される

doc.replaceChild _
    doc.createProcessingInstruction( _
    "xml", "version='1.0' encoding='euc-jp'"), _
    doc.childNodes(0)
doc.save "C:\sample2.xml"   'EUCファイルとして保存される

なお、本題からは外れるので詳細は省きますが、XSLT 変換で
ストリーム出力する場合には、また別の変換規則が働くようです。


>>> MSXMLは XML Version,3.0
>>> ADODBはMicrosoft ActiveX Data Objects2.5
>>> を参照しています。
>「実行時エラー '5'
> プロシージャの呼び出し、または引数が不正です。」

MSXML3 + ADODB 2.5 および
MSXML6 + ADODB 2.8 の両方で試しましたが、当方では再現しませんでした。

Private Sub Command1_Click()
  Dim doc As MSXML2.DOMDocument30
  Set doc = New MSXML2.DOMDocument30
  doc.async = False
  doc.loadXML "<?xml version='1.0' encoding='UTF-8'?><祈/>"

  Dim stm As ADODB.Stream
  Set stm = New ADODB.Stream
  stm.Type = adTypeText
  stm.Charset = "UTF-8"
  stm.Open
  doc.save stm
  stm.SaveToFile "C:\sample.xml", adSaveCreateOverWrite
  stm.Close
End Sub

編集 削除
exp  2008-04-11 16:53:58  No: 100181  IP: 192.*.*.*

お世話になります

># ここは sumomo さんの質問に対するスレッドなので、expさんの別質問が
># 繋がるのはあまり好ましくは無いのですが、無関係というわけでも無いので、
># このまま回答を続けておきます。
申し訳ありません。助かります。

>>>> MSXMLは XML Version,3.0
>>>> ADODBはMicrosoft ActiveX Data Objects2.5
>>>> を参照しています。
>>「実行時エラー '5'
>> プロシージャの呼び出し、または引数が不正です。」

>MSXML3 + ADODB 2.5 および
>MSXML6 + ADODB 2.8 の両方で試しましたが、当方では再現しませんでした。
ご提示いただいたソースを試しましたが、やはりエラー。
ところが、OSを2000からXPに換えて試したところうまく行きました。
今は原因は不明ですが、何とかなりそうです。

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

編集 削除
魔界の仮面弁士  2008-04-11 17:34:37  No: 100182  IP: 192.*.*.*

幾らなんでも、他の人のスレッドに割り込んで便乗質問した上に、
勝手に解決チェックを付けてしまうというのは、如何なものかと。

# もっとも、sumomoさんはもう見ていないとは思いますけれどね。(^_^;)

編集 削除
exp  2008-04-11 18:21:05  No: 100183  IP: 192.*.*.*

お世話になります。

>幾らなんでも、他の人のスレッドに割り込んで便乗質問した上に、
>勝手に解決チェックを付けてしまうというのは、如何なものかと。

># もっとも、sumomoさんはもう見ていないとは思いますけれどね。(^_^;)

あ、、そうですね。困りました。
よく考えたら、もともとの質問者のsumomoさんがチェックするもの
ですよね。
ホント考慮不足でした。誠にすみませんでした。
もうチェックははずせないんっすよね?!

sumomoさん本当にごめんなさい!!

編集 削除
sumomo  2008-04-16 14:07:23  No: 100184  IP: 192.*.*.*

投稿が放置状態になっており大変申し訳ありません。

処理したかった内容と結果です。
①encording='utf-8'のXMLファイルをVB6.0上でMSXMLパーサ(DOM)で読み込む。
②読み込んだXMLをVB6.0上でMSXMLパーサ(DOM)で編集して、nodeの追加、削除などを行う)する。
③DomDocument.xmlでString変数に代入。
④String変数に格納された文字列をDOMでload。
⑤DOMでloadxmlしたstring文字列を、UTF-8形式のXMLファイルとして出力する。

結果
*Test.xml*
<?xml version='1.0' encoding='UTF-8'?>
<TEST>test</TEST>

*output.xml*
<?xml version='1.0'?>
<TEST><TEST2>aaaa</TEST2></TEST>

Private Sub Command1_Click()
Dim doc As MSXML2.DOMDocument50
Dim output as String
Set doc = New MSXML2.DOMDocument50

doc.async = False
doc.load "C:\Test.xml" 
output = doc.xml
'methodメソッドに処理依頼
'(ここでは、文字列"<TEST><TEST2>aaaa</TEST2></TEST>"が返り値として返ってくるとする) 
output = method(output)
output =  "<?xml version='1.0' encoding='UTF-8'?>" & output
doc.loadxml output
doc.save "C:\output.xml"

編集 削除