DOBON.NET DOBON.NETプログラミング掲示板過去ログ

XML形式の文字列Stringを使ってxmlNodeを追加したい

環境/言語:[VB.Net 2005]
分類:[.NET]

今あるXmlDocumentにXML文字列を使って新規ノードを追加したいです。

  Private Sub AddNode(ByVal NodeName As String)
    Dim ParentNodes As XmlNodeList = myXML.GetElementsByTagName("mtc:tests")  'myXMLは現在あるXMLDocumentです。
    'pNodeは追加する親ノードになります。
    Dim pNode As XmlNode = ParentNodes(0)
    '新規にnewNodeを作ります。
    Dim newNode As XmlNode = myXML.CreateElement("mtc:test", myXML.GetNamespaceOfPrefix("mtc"))  '・・・(1)
    'アトリビュートを追加します。
    Dim attrClass As XmlAttribute = myXML.CreateAttribute("mtc:class")
    attrClass.Value = NodeName  '引数でmtc:classの値を設定します。
    Dim attrVersion As XmlAttribute = myXML.CreateAttribute("mtc:version")
    attrVersion.Value = "1.0"
    newNode.Attributes.Append(attrClass)
    newNode.Attributes.Append(attrVersion)
    '引数の名前でXMLの文字列を取り込み、innerXMLにセットします。
    newNode.InnerXml = getTestXML(NodeName)
End Sub

Function getTestXML(NodeName) As String
  Dim s As String = ""
    s += "<mtc:values mtc:class=""attributes"">"
    s += "  <mtc:value mtc:class=""target"" />"
    s += "</mtc:values>"
    s += "<mtc:tables mtc:class=""result"">"
    s += "<mtc:table mtc:class=""" & NodeName & """" mtc:index=""0"">"
    s += "  <mtc:columns mtc:class=""result"">"
    s += "    <mtc:colums mtc:class="username" mtc:columnkey=""1"" />
    s += "    <mtc:colums mtc:class="useraddr" mtc:columnkey=""2"" />
         '・・・いろいろ続く。テーブルレイアウトのイメージ
    s += "  <mtc:rows mtc:class=""result"" />"
    s += "</mtc:table>"  
  getTestXML = s
End Function

実行すると、
newNode.InnerXml = getTestXML(NodeName)
の行でエラーになり、
{"'mtc' is an undeclared namespace. Line 1, position 2."}
のメッセージを得ます。"mtc"のnamespaceは myXML上に設定されています。
(1)の行で追加したいxmlNodeに(myXML.GetNamespaceOfPrefix("mtc")を設定していてもだめみたいです。

どうすればXML文字列からxmlNodeを作り、親のxmlNodeに追加することができるでしょうか?
> Dim newNode As XmlNode = myXML.CreateElement("mtc:test", myXML.GetNamespaceOfPrefix("mtc"))
XmlDocunentのノードレベルではまだXML名前空間は定義されていません(XmlDocumentはルート要素のさらに親のノードです)。
ParentNode辺りからXML名前空間を取ってくるべきでしょう。

> newNode.InnerXml = getTestXML(NodeName)
この辺、いささか複雑ですが。
XmlDocument.CreateElement(name, namespaceURI)で作成された要素は、指定されたXML名前空間に属しますしOuterXmlなどを取得すればちゃんと<prefix:localName xmlns:prefix="namespaceURI"/>という文字列を返しますが、このXmlElementオブジェクト自体は、Attributesプロパティの中にxmlns:prefixを持ちません(後から明示的にAttributesにAppendすることは可能です)。
このXmlElementにInnerXmlを設定するとき、XML名前空間を解決するために、XMLを読み取る前にそのXmlElementから先祖ノードにさかのぼってXML名前空間の定義を探索しますが、このときに使用するのがそれぞれのノードのAttributesプロパティの中でxmlnsプリフィクス持ちのもの、ということになっています。
つまり、CreateElementで作成しただけの要素ではこの探索に引っかからず、そのXML名前空間の定義は無視されるわけです。
さらにCreateElementしたばかりの要素では親ノードをさかのぼろうにもさかのぼれず、結果としてその要素はXML名前空間の定義を持たないことになり、その後InnerXmlで設定するXMLを読み取る段になって「プリフィクスに該当するXML名前空間が未定義だよ!」ってことになります。

解決策としては、いくつか考えられます。

1) InnerXmlを設定するより前に、CreateElementで作成した要素をXmlDocumentのいずれかのノードにAppendしておく。
 CreateElementした要素のXML名前空間は使用できませんが、その祖先要素でXML名前空間が定義されているなら、XML名前空間の探索の祖先ノードにさかのぼる過程で見つけられるでしょう。
 CreateElementではなく、LoadやLoadXmlでロードした部分の各要素には、Attributesにxmlns:prefix属性が追加されています。

2) CreateElement(name, namespaceURI)した後、Attributesにxmlns:prefix属性もAppendしておく。
 こうしておけばInnerXmlのXML名前空間の探索時に問題なく取得できます。

3) InnerXmlを使わず、XmlDocument.ReadNodeから一つずつXmlElementをAppendChildしていく。
 ややこしいので解説は控えます。
 キーワードはXmlReaderSettings.ConformanceLevel、XmlParserContext、XmlNamespaceManager、XmlDocument.ReadNodeといった辺り。
ありがとうございます。

指摘されて確認したのですが、myXML.GetNamespaceOfPrefix("mtc")
の戻り値が"" でnamespaceを取得できていないようです。
そこで先にappendしてからInnerTextを指定したらエラーは出なくなりました。
しかし、appendしたノードのタグではprefixがありません。

そこで気がついたのですが、親ノードのInnerTextにXML文字列を追加すればそのまま新規のノードが作れるんじゃないかと。
getTestXML関数側で、始まりと終わりにタグ(<mtc:test></mtc:test>)を追加する必要がありますが、試したところ、うまくいきそうです。

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



■No30674に返信(Hongliangさんの記事)
>>Dim newNode As XmlNode = myXML.CreateElement("mtc:test", myXML.GetNamespaceOfPrefix("mtc"))
> XmlDocunentのノードレベルではまだXML名前空間は定義されていません(XmlDocumentはルート要素のさらに親のノードです)。
> ParentNode辺りからXML名前空間を取ってくるべきでしょう。
>
>>newNode.InnerXml = getTestXML(NodeName)
> この辺、いささか複雑ですが。
> XmlDocument.CreateElement(name, namespaceURI)で作成された要素は、指定されたXML名前空間に属しますしOuterXmlなどを取得すればちゃんと<prefix:localName xmlns:prefix="namespaceURI"/>という文字列を返しますが、このXmlElementオブジェクト自体は、Attributesプロパティの中にxmlns:prefixを持ちません(後から明示的にAttributesにAppendすることは可能です)。
> このXmlElementにInnerXmlを設定するとき、XML名前空間を解決するために、XMLを読み取る前にそのXmlElementから先祖ノードにさかのぼってXML名前空間の定義を探索しますが、このときに使用するのがそれぞれのノードのAttributesプロパティの中でxmlnsプリフィクス持ちのもの、ということになっています。
> つまり、CreateElementで作成しただけの要素ではこの探索に引っかからず、そのXML名前空間の定義は無視されるわけです。
> さらにCreateElementしたばかりの要素では親ノードをさかのぼろうにもさかのぼれず、結果としてその要素はXML名前空間の定義を持たないことになり、その後InnerXmlで設定するXMLを読み取る段になって「プリフィクスに該当するXML名前空間が未定義だよ!」ってことになります。
>
> 解決策としては、いくつか考えられます。
>
> 1) InnerXmlを設定するより前に、CreateElementで作成した要素をXmlDocumentのいずれかのノードにAppendしておく。
>  CreateElementした要素のXML名前空間は使用できませんが、その祖先要素でXML名前空間が定義されているなら、XML名前空間の探索の祖先ノードにさかのぼる過程で見つけられるでしょう。
>  CreateElementではなく、LoadやLoadXmlでロードした部分の各要素には、Attributesにxmlns:prefix属性が追加されています。
>
> 2) CreateElement(name, namespaceURI)した後、Attributesにxmlns:prefix属性もAppendしておく。
>  こうしておけばInnerXmlのXML名前空間の探索時に問題なく取得できます。
>
> 3) InnerXmlを使わず、XmlDocument.ReadNodeから一つずつXmlElementをAppendChildしていく。
>  ややこしいので解説は控えます。
>  キーワードはXmlReaderSettings.ConformanceLevel、XmlParserContext、XmlNamespaceManager、XmlDocument.ReadNodeといった辺り。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板