■ XMLのCDATAsectionタグの要素を検索し、変換するには?

[Visual Basic Q & A 掲示板] [過去ログの一覧]


sumomo 2008/01/31(木) 05:46:47 <初心者>
お世話になります。
VB6.0(SP6) MSXML2.DOMDOCUMENT50使用

XMLファイルをパースして、CDATASectionタグ"<![CDATA[文字列]]>"が来た場合は、XMLDOMNODEのnodeTypeをNODE_CDATA_SECTIONからNODE_TEXTに変換したいのですが、以下のような方法で考えてみましたが(コンパイル・動作確認済み)、もう少しスマートに出来る方法はないのでしょうか?

Dim Domdoc     As MSXML2.DOMDocument50
Dim oNodeList  As IXMLDOMNodeList
Dim oNode      As IXMLDOMNode

Set Domdoc = New DOMDocument50

Domdoc.async = True
If (Domdoc.Load(ファイル名)) Then
    Set oNodeList = Domdoc.getElementsByTagName(タグ名)
        For Each oNode In oNodeList
             CDATAreplace oNode
        Next oNode
End If


------CDATAタグを変換する関数--------------------------------------
Public Function CDATAreplace(ByRef xmldNode As IXMLDOMNode) as Boolean

Dim objNode As IXMLDOMNode
Dim repNode As IXMLDOMNode
Dim str     As String

   For Each objNode In xmlNode.childNodes
          Select Case (objNode.nodeType)
              Case NODE_CDATA_SECTION:
                   str ="<![CDATA[" & objNode.text & "]]>"
                   set repNode = Domdoc.createTextNode(str)
                   objNode.parentNode.replaceChild repNode,objNode
              Case Else
          End Select

        If (objNode.hasChildNodes) Then
              CDATAreplace objNode
        End If
   Next
End Function

通りすがり 2008/01/31(木) 08:45:34
http://hpcgi1.nifty.com/MADIA/VBBBS/wwwlng.cgi?print+200801/08010040.txt

ネットでも社会でも同じですが質問しっぱなしで放置するならば、
なかなか回答はつかないと思いますが、いかがでしょう?

魔界の仮面弁士 2008/01/31(木) 13:16:41 <常連>
そのコードは、
    <sample>あいうえこ<![CDATA[文字列]]>かきくけこ</sample>
というデータを、
    <sample>あいうえこ文字列かきくけこ</sample>
ではなく
    <sample>あいうえこ&lt;![CDATA[文字列]]&gt;かきくけこ</sample>
のように書き換えるという意味になりますが、それで良いのですね?


> Public Function CDATAreplace(ByRef xmldNode As IXMLDOMNode) as Boolean
(1) xmldNode という引数は一度も使われていません。
 → xmlNode の間違いではありませんか?

(2) ByRef 宣言されていますが、引数の書き換えは行われていません。
 → ByVal とするべきかと。

(3) 戻り値が Boolean になっていますが、戻り値が設定されていません。
 → Function ではなく、Sub にするべきかと。

> Dim str     As String
間違いではありませんが、VB の「Str関数」と競合する名前なので、
できれば別の変数名を用いる事をお薦めします。

> set repNode = Domdoc.createTextNode(str)
Domdoc という変数は、このプロシージャ内で一度も宣言されていません。
恐らくは、プロシージャ外で宣言された DOMDocument オブジェクトだと思いますが、
引数で渡されたノードを加工するなら、ノードの ownerDocument プロパティを使いましょう。


> NODE_CDATA_SECTIONからNODE_TEXTに変換したいのですが
私は、XSLT で変換してしまう事の方が多いですが……DOM でやるなら、そのコードで良いと思いますよ。

sumomo 2008/02/01(金) 07:16:48 <初心者>
ご回答ありがとうございます。

>通りすがり様
1つの質問にたいし、何もレスしないまま、次の質問をUPするのは確かに非常識でした。大変申し訳ありません。以後気をつけます。

>魔界の仮面弁士様
><sample>あいうえこ&lt;![CDATA[文字列]]&gt;かきくけこ</sample>
>のように書き換えるという意味になりますが、それで良いのですね?
はい。そうです。

>(1) xmldNode という引数は一度も使われていません。
> → xmlNode の間違いではありませんか?
 ⇒間違いでした。

>(2) ByRef 宣言されていますが、引数の書き換えは行われていません。
> → ByVal とするべきかと。
 ⇒ByValだと、
If (Domdoc.Load(ファイル名)) Then
    Set oNodeList = Domdoc.getElementsByTagName(タグ名)
        For Each oNode In oNodeList
             CDATAreplace oNode
        Next oNode
End If
の後の処理で、oNodeListを利用しようとしたときに、CDATAreplaceメソッド
で処理した内容が反映されないような気がするのですが、間違いでしょうか?

>引数で渡されたノードを加工するなら、ノードの ownerDocument プロパティを使いましょう。
⇒なるほど。ownerDocumentプロパティはそういう使い方ができるのですね。

> NODE_CDATA_SECTIONからNODE_TEXTに変換したいのですが
>私は、XSLT で変換してしまう事の方が多いですが……DOM でやるなら、そのコードで良いと思いますよ。
⇒ただ、NODE_CDATA_SECTIONからNODE_TEXTに変換したものを、編集後に、
 再度NODE_CDATA_SECTIONに変換しなおす必要があり、そこで悩んでいます。
 NODE_CDATA_SECTIONからNODE_TEXTへは、NODEのnodeTypeでNODE_CDATA_SECTION
 を探すことができるのですが、一旦TEXTNODEになった"&lt;![CDATA[文字列]]&gt;"を再度NODE_CDATA_SECTIONになおすには、Replace("&lt;![CDATA[文字列]]&gt;","&lt;![CDATA[","<![CDATA[")とReplace("&lt;![CDATA[文字列]]&gt;","]]&gt;","]]>")で変換するしかないのでしょうか?

これは結構危険な気がして、なにか他の方法がないか悩んでします。

魔界の仮面弁士 2008/02/01(金) 10:56:42 <常連>
> 処理した内容が反映されないような気がするのですが、間違いでしょうか?
私は反映された事を確認してから投稿したのですが、間違いでしょうか? (T-T)

Debug.Print Domdoc.xml とか、
Debug.Print oNodeList(0).xml などで。


引数宣言に ByRef を使うのであれば、どこかに
 Set xmlNode = 新しいオブジェクト
があるはずで、それが無いならば ByVal で良いと思っています。

確認ですが、ByVal / ByRef の件について、下記のコードを実行したとき、
それぞれの TextBox の内容を、正しく想像できますか?
(その想像は、実際の実行結果と一致していましたか?)

'---------
Option Explicit

Sub Form_Load()
 Text1.Text = ""
 Text2.Text = ""
 Text3.Text = ""
 Text4.Text = ""
End Sub

Sub Command1_Click()
 Dim a As TextBox, b As TextBox
 
 Set a = Text1
 Set b = Text2
 ByValMethod a
 ByRefMethod b
 a.Text = "あいうえお"
 b.Text = "かきくけこ"
End Sub

Sub ByValMethod(ByVal X As TextBox)
 X.Text = "ByVal"
 Set X = Text3
End Sub

Sub ByRefMethod(ByRef X As TextBox)
 X.Text = "ByRef"
 Set X = Text4
End Sub


> 再度NODE_CDATA_SECTIONに変換しなおす必要があり、
(CDATA セクションを処理できない処理系を介する必要があるのかな…?)

その実装だと、元の XML が
<TEST>
 <sample>あいうえこ<![CDATA[文字列]]>かきくけこ</sample>
 <sample>さしすせそ&lt;![CDATA[ストリング]]&gt;たちつてと</sample>
</TEST>
だった場合、CDATA → テキスト → CDATA という変換を辿ると、
<TEST>
 <sample>あいうえこ<![CDATA[文字列]]>かきくけこ</sample>
 <sample>さしすせそ<![CDATA[ストリング]]>たちつてと</sample>
</TEST>
となり、元のデータとは異なる内容になりますが、それで構わないのですね?


> "&lt;![CDATA[文字列]]&gt;"
生データとしてはそうなりますが、DOM を介してアクセスする分には
"<![CDATA[文字列]]>"として得られるはずです。

> で変換するしかないのでしょうか?
や、テキストノードの文字列を Replace しても駄目ですよ。
(パーサを用いず、テキストファイルとして、直接書き換えるならいけますけど)

パーサ経由なら、テキストノードから <![CDATA[ と ]]> で挟まれた範囲を探し、
その部分を、CDATAノードに置換するという手順になるかと。

sumomo 2008/02/02(土) 01:41:07 <初心者>
>私は反映された事を確認してから投稿したのですが、間違いでしょうか? (T-T)
>Debug.Print Domdoc.xml とか、
>Debug.Print oNodeList(0).xml などで。
⇒申し訳ありません。確かに反映されてました。
 CDATAreplace関数の引数としてByValでxmlNodeを定義した場合、
  xmlNodeのスコープ(有効期間)は、CDATAreplace関数が呼び出し
 元に制御を返すまでだと思います。
 そうすると、CDATAreplace関数が呼び出し元に制御を返した
 タイミングで、xmlNodeは破棄され、呼び出し元で引数として
  渡したoNode(下の呼び出し例)には変更内容が反映されないの
 ではないかと考えていたのですが、確かに反映されていました。。。
  引数宣言でByValを使った時には、呼び出し元で引数として渡した
 oNodeと、ByValで受け取ったxmlNodeは、メモリ上まったく別の領域
 だと思っていました。(つまり、xmlNodeは、oNodeを別の領域にコピー
 したもの)
 魔界の仮面弁士さんの例題の結果は思ったとおりだったのですが。
 うーん。。。もうちょっと、調べて頭を整理してみます。。。
    
Dim Domdoc     As MSXML2.DOMDocument50
Dim oNodeList  As IXMLDOMNodeList
Dim oNode      As IXMLDOMNode
Set Domdoc = New DOMDocument50

Domdoc.async = True
If (Domdoc.Load(ファイル名)) Then
    Set oNodeList = Domdoc.getElementsByTagName(タグ名)
        For Each oNode In oNodeList
             CDATAreplace oNode
        Next oNode
End If

>(CDATA セクションを処理できない処理系を介する必要があるのかな…?)
その通りです。

>その実装だと、元の XML が
><TEST>
> <sample>あいうえこ<![CDATA[文字列]]>かきくけこ</sample>
> <sample>さしすせそ&lt;![CDATA[ストリング]]&gt;たちつてと</sample>
></TEST>
>だった場合、CDATA → テキスト → CDATA という変換を辿ると、
><TEST>
> <sample>あいうえこ<![CDATA[文字列]]>かきくけこ</sample>
> <sample>さしすせそ<![CDATA[ストリング]]>たちつてと</sample>
></TEST>
>となり、元のデータとは異なる内容になりますが、それで構わないのですね?
うーん・・・。確かに。それはマズイですね。DOMでは対処
できないのでしょうか・・・。

魔界の仮面弁士 2008/02/02(土) 13:24:43 <常連>
> xmlNodeのスコープ(有効期間)は、CDATAreplace関数が呼び出し
> 元に制御を返すまでだと思います。
これは正しいです。

> つまり、xmlNodeは、oNodeを別の領域にコピーしたもの
これは半分正解、半分不正解です。

まず、操作対象となるオブジェクト実体(ノードのインスタンス)は、常に 1 つだけです。
一つのオブジェクトを、oNode という変数と、xmlNode という引数の 2箇所から
参照しているだけであって、ノードそのものが 2 つになったわけではありません。

ByVal の場合、oNode と xmlNode は異なる変数です。(参照情報がコピーされている)
ByRef の場合、oNode と xmlNode は同一の変数であると考えてみてください。

これは実際に、
 Debug.Print VarPtr(oNode), ObjPtr(oNode)
と、
 Debug.Print VarPtr(xmlNode), ObjPtr(xmlNode)
の結果を比較して見ると、イメージが掴めるかと思います。

VarPtr 関数は、変数のアドレスを返します。
ObjPtr 関数は、参照先のオブジェクトのアドレスを返します。

ByVal と ByRef のいずれであっても、ObjPtr の結果は同一ですが、
VaPtr の結果は、ByVal の場合は不一致、ByRef で一致することが分かるかと。


> うーん・・・。確かに。それはマズイですね。DOMでは対処
> できないのでしょうか・・・。
ロジックにさえ問題が無ければ、DOM でも SAX でも 直接ファイルを編集でも、
正しく実装できるかと思いますよ。操作手法とは無関係かと。


実際のところ、そちらで必要としている
>>(CDATA セクションを処理できない処理系を介する必要があるのかな…?)
>その通りです。
という処理系が、どういう働きをするのか分かりませんが、たとえば、
以下のような変換でも良いなら、正しく復元できるかと思います。


《元ファイル》
<TEST>
 <sample>あいうえこ<![CDATA[文字列]]>かきくけこ</sample>
 <sample>さしすせそ&lt;![CDATA[ストリング]]&gt;たちつてと</sample>
 <sample>なにぬねの&amp;たちつてと</sample>
</TEST>

CDATA → テキスト化

《変換結果》
<TEST>
 <sample>あいうえこ&lt;![CDATA[文字列]]&gt;かきくけこ</sample>
 <sample>さしすせそ&amp:lt;![CDATA[ストリング]]&amp;gt;たちつてと</sample>
 <sample>なにぬねの&amp;amp;たちつてと</sample>
</TEST>

→CDATAに復元

《復元結果》
<TEST>
 <sample>あいうえこ<![CDATA[文字列]]>かきくけこ</sample>
 <sample>さしすせそ&lt;![CDATA[ストリング]]&gt;たちつてと</sample>
 <sample>なにぬねの&amp;たちつてと</sample>
</TEST>

sumomo 2008/02/02(土) 16:29:52 <初心者>
ご回答ありがとうございます。

>まず、操作対象となるオブジェクト実体(ノードのインスタンス)は、常に 1 つだけです。
>一つのオブジェクトを、oNode という変数と、xmlNode という引数の 2箇所から
>参照しているだけであって、ノードそのものが 2 つになったわけではありません。

>ByVal の場合、oNode と xmlNode は異なる変数です。(参照情報がコピーされている)
>ByRef の場合、oNode と xmlNode は同一の変数であると考えてみてください。

>これは実際に、
 Debug.Print VarPtr(oNode), ObjPtr(oNode)
>と、
 Debug.Print VarPtr(xmlNode), ObjPtr(xmlNode)
>の結果を比較して見ると、イメージが掴めるかと思います。
⇒試してみたところ、おっしゃられたとおりで、ByRef/ByValの違いがよくわかりました。
魔界の仮面弁士さんが、ByRef にするなら新しいオブジェクトを代入するときに使うとおっしゃられた意味がやっとわかりました。
ありがとうございます。


>《元ファイル》
><TEST>
> <sample>あいうえこ<![CDATA[文字列]]>かきくけこ</sample>
> <sample>さしすせそ&lt;![CDATA[ストリング]]&gt;たちつてと</sample>
> <sample>なにぬねの&amp;たちつてと</sample>
></TEST>

>CDATA → テキスト化
>《変換結果》
><TEST>
> <sample>あいうえこ&lt;![CDATA[文字列]]&gt;かきくけこ</sample>
> <sample>さしすせそ&amp:lt;![CDATA[ストリング]]&amp;gt;たちつてと></sample>
> <sample>なにぬねの&amp;amp;たちつてと</sample>
></TEST>

>→CDATAに復元

>《復元結果》
><TEST>
> <sample>あいうえこ<![CDATA[文字列]]>かきくけこ</sample>
> <sample>さしすせそ&lt;![CDATA[ストリング]]&gt;たちつてと</sample>
> <sample>なにぬねの&amp;たちつてと</sample>
></TEST>

⇒まさに行いたいことです。

今、私は
最初のCDATA → テキスト化で、最初に提示した関数を使って
《変換結果》
<TEST>
 <sample>あいうえこ&lt;![CDATA[文字列]]&gt;かきくけこ</sample>
 <sample>さしすせそ&amp:lt;![CDATA[ストリング]]&amp;gt;たちつてと</sample>
 <sample>なにぬねの&amp;amp;たちつてと</sample>
</TEST>


CDATAに復元する関数を使って

→CDATAに復元
《復元結果》
<TEST>
 <sample>あいうえこ<![CDATA[文字列]]>かきくけこ</sample>
 <sample>さしすせそ<![CDATA[ストリング]]>たちつてと</sample>
 <sample>なにぬねの&amp;たちつてと</sample>
になってしまいます。
魔界の仮面弁士さんがおっしゃられている通りです。

やはり、CDATA → テキスト化の方法から見直さないと無理なのでしょうか?
うーん。難しいですね。。。。

>ロジックにさえ問題が無ければ、DOM でも SAX でも 直接ファイルを編集でも、
>正しく実装できるかと思いますよ。操作手法とは無関係かと。
⇒ですよね。自分の力の無さをDOMのせいにしてしまいました。すいません。。。

毎週金曜日はポイント最大3倍!さらに4倍のチャンスも!

Programming Library