web-dev-qa-db-ja.com

C#でデフォルト名前空間でXpathを使用する

デフォルトの名前空間を持つXMLドキュメントを持っています。次のように、XpathNavigatorを使用して、Xpathを使用してノードのセットを選択しています。

XmlElement myXML = ...;  
XPathNavigator navigator = myXML.CreateNavigator();
XPathNodeIterator result = navigator.Select("/outerelement/innerelement");

結果が得られません。名前空間を指定していないためだと思われます。 selectに名前空間を含めるにはどうすればよいですか?

59
macleojw

まず、ナビゲーターは必要ありません。 SelectNodes/SelectSingleNodeで十分です。

ただし、名前空間マネージャが必要な場合があります-たとえば:

XmlElement el = ...; //TODO
XmlNamespaceManager nsmgr = new XmlNamespaceManager(
    el.OwnerDocument.NameTable);
nsmgr.AddNamespace("x", el.OwnerDocument.DocumentElement.NamespaceURI);
var nodes = el.SelectNodes(@"/x:outerelement/x:innerelement", nsmgr);
79
Marc Gravell

XPath Visualizerツールを試してみてください。

XPathVisualizer は無料で使いやすいです。

alt text

重要:Windows 7/8を使用していて、ファイル、編集、ヘルプメニューの項目が表示されない場合は、Altキーを押してください。

48
Cheeso

know XMLであり、名前空間などを心配する必要がない場合、特に、簡単なハックソリューションを探している人は、この厄介な「機能」を簡単に回避できます。ファイルを文字列に読み取り、攻撃的な属性を置き換えます。

XmlDocument doc = new XmlDocument();
string fileData = File.ReadAllText(fileName);
fileData = fileData.Replace(" xmlns=\"", " whocares=\"");
using (StringReader sr = new StringReader(fileData))
{
   doc.Load(sr);
}

XmlNodeList nodeList = doc.SelectNodes("project/property");

単一のファイルを扱う場合、デフォルトの名前空間に接頭辞を必要とする他のすべてのナンセンスよりもこれが簡単だと思います。お役に立てれば。

24
Mitselplik

名前空間を持つXMLで.NETで(ナビゲーターまたはSelectNodes/SelectSingleNodeを介して)XPathを使用する場合、以下を行う必要があります。

  • 独自のXmlNamespaceManagerを提供する

  • andXPath式の名前空間にあるすべての要素に明示的にプレフィックスを付けます。

後者は(以下にリンクされているMSソースからの言い換え):XPath 1.0はデフォルトのネームスペース仕様(xmlns = "some_namespace")を無視するためです。したがって、接頭辞なしで要素名を使用すると、名前空間がnullであると見なされます。

そのため、XPathの.NET実装では、XmlNamespaceManagerのString.Emptyプレフィックスを持つ名前空間を無視し、常にnull名前空間を使用します。

詳細については、「 XmlNamespaceManagerおよびUndefinedXsltContextはデフォルトのネームスペースを処理しない 」を参照してください。

デフォルトの名前空間宣言を追加するだけでは古いXPath名前空間を認識できないため、この「機能」は非常に不便です。

20

私の答えは、ブランドンによる以前の答えを拡張したものです。彼の例を使用して、次のように拡張メソッドを作成しました。

static public class XmlDocumentExt
{
    static public XmlNamespaceManager GetPopulatedNamespaceMgr(this System.Xml.XmlDocument xd)
    {
        XmlNamespaceManager nmsp = new XmlNamespaceManager(xd.NameTable);
        XPathNavigator nav = xd.DocumentElement.CreateNavigator();
        foreach (KeyValuePair<string,string> kvp in nav.GetNamespacesInScope(XmlNamespaceScope.All))
        {
            string sKey = kvp.Key;
            if (sKey == "")
            {
                sKey = "default";
            }
            nmsp.AddNamespace(sKey, kvp.Value);
        }

        return nmsp;
    }
}

次に、XML解析コードに1行追加するだけです。

XmlDocument xdCandidate = new XmlDocument();
xdCandidate.Load(sCandidateFile);
XmlNamespaceManager nmsp = xdCandidate.GetPopulatedNamespaceMgr();  // 1-line addition
XmlElement xeScoreData = (XmlElement)xdCandidate.SelectSingleNode("default:ScoreData", nmsp);

ソースXMLファイルから名前空間を読み込むという点で完全に動的であり、XML名前空間の概念を完全に無視しないため、このメソッドは本当に好きです。

6
Kent

次のようにXmlNamespaceManagerを使用せずにXPathステートメントを使用できます。

...
navigator.Select("//*[ local-name() = 'innerelement' and namespace-uri() = '' ]")
...

これは、デフォルトの名前空間が定義されたXML内の要素を選択する簡単な方法です。

ポイントは以下を使用することです:

namespace-uri() = ''

プレフィックスを使用せずにデフォルトの名前空間を持つ要素を見つけます。

空のデフォルト名前空間で同様の問題が発生しました。このXMLの例では、名前空間プレフィックスを持つ要素と、次のものを含まない単一の要素(DataBlock)が混在しています。

<src:SRCExample xmlns="urn:some:stuff:here" xmlns:src="www.test.com/src" xmlns:a="www.test.com/a" xmlns:b="www.test.com/b">
 <DataBlock>
  <a:DocID>
   <a:IdID>7</a:IdID>
  </a:DocID>
  <b:Supplimental>
   <b:Data1>Value</b:Data1>
   <b:Data2/>
   <b:Extra1>
    <b:More1>Value</b:More1>
   </b:Extra1>
  </b:Supplimental>
 </DataBlock>
</src:SRCExample>

XPath Visualizerで機能するXPathを使用しようとしましたが、コードでは機能しませんでした。

  XmlDocument doc = new XmlDocument();
  doc.Load( textBox1.Text );
  XPathNavigator nav = doc.DocumentElement.CreateNavigator();
  XmlNamespaceManager nsman = new XmlNamespaceManager( nav.NameTable );
  foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
    nsman.AddNamespace( nskvp.Key, nskvp.Value );
  }

  XPathNodeIterator nodes;

  XPathExpression failingexpr = XPathExpression.Compile( "/src:SRCExample/DataBlock/a:DocID/a:IdID" );
  failingexpr.SetContext( nsman );
  nodes = nav.Select( failingexpr );
  while ( nodes.MoveNext() ) {
    string testvalue = nodes.Current.Value;
  }

XPathの「DataBlock」要素に絞り込みましたが、DataBlock要素を単純にワイルドカード化しない限り、機能させることができませんでした。

  XPathExpression workingexpr = XPathExpression.Compile( "/src:SRCExample/*/a:DocID/a:IdID" );
  failingexpr.SetContext( nsman );
  nodes = nav.Select( failingexpr );
  while ( nodes.MoveNext() ) {
    string testvalue = nodes.Current.Value;
  }

多くのヘッドスクラッチとグーグル(ここに着陸しました)の後、XmlNamespaceManagerローダーのデフォルトの名前空間を次のように変更して直接取り組むことにしました。

  foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
    nsman.AddNamespace( nskvp.Key, nskvp.Value );
    if ( nskvp.Key == "" ) {
      nsman.AddNamespace( "default", nskvp.Value );
    }
  }

したがって、「デフォルト」と「」は同じ名前空間を指します。これを行うと、XPath "/ src:SRCExample/default:DataBlock/a:DocID/a:IdID"は、私が望んでいたように結果を返しました。うまくいけば、これは他の人のために問題を明確にするのに役立ちます。

5
Brandon

名前空間がouterelementとinnerelementで異なる場合

XmlNamespaceManager manager = new XmlNamespaceManager(myXmlDocument.NameTable);
                            manager.AddNamespace("o", "namespaceforOuterElement");
                            manager.AddNamespace("i", "namespaceforInnerElement");
string xpath = @"/o:outerelement/i:innerelement"
// For single node value selection
XPathExpression xPathExpression = navigator.Compile(xpath );
string reportID = myXmlDocument.SelectSingleNode(xPathExpression.Expression, manager).InnerText;

// For multiple node selection
XmlNodeList myNodeList= myXmlDocument.SelectNodes(xpath, manager);
5
Rashmi Pandit

私の場合、プレフィックスを追加することは実用的ではありませんでした。実行時に決定されたxmlまたはxpathが多すぎます。最終的に、XmlNodeのメソッドを拡張しました。これはパフォーマンスのために最適化されておらず、おそらくすべてのケースを処理するわけではありませんが、これまでのところ私のために働いています。

    public static class XmlExtenders
{

    public static XmlNode SelectFirstNode(this XmlNode node, string xPath)
    {
        const string prefix = "pfx";
        XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
        string prefixedPath = GetPrefixedPath(xPath, prefix);
        return node.SelectSingleNode(prefixedPath, nsmgr);
    }

    public static XmlNodeList SelectAllNodes(this XmlNode node, string xPath)
    {
        const string prefix = "pfx";
        XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
        string prefixedPath = GetPrefixedPath(xPath, prefix);
        return node.SelectNodes(prefixedPath, nsmgr);
    }

    public static XmlNamespaceManager GetNsmgr(XmlNode node, string prefix)
    {
        string namespaceUri;
        XmlNameTable nameTable;
        if (node is XmlDocument)
        {
            nameTable = ((XmlDocument) node).NameTable;
            namespaceUri = ((XmlDocument) node).DocumentElement.NamespaceURI;
        }
        else
        {
            nameTable = node.OwnerDocument.NameTable;
            namespaceUri = node.NamespaceURI;
        }
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(nameTable);
        nsmgr.AddNamespace(prefix, namespaceUri);
        return nsmgr;
    }

    public static string GetPrefixedPath(string xPath, string prefix)
    {
        char[] validLeadCharacters = "@/".ToCharArray();
        char[] quoteChars = "\'\"".ToCharArray();

        List<string> pathParts = xPath.Split("/".ToCharArray()).ToList();
        string result = string.Join("/",
                                    pathParts.Select(
                                        x =>
                                        (string.IsNullOrEmpty(x) ||
                                         x.IndexOfAny(validLeadCharacters) == 0 ||
                                         (x.IndexOf(':') > 0 &&
                                          (x.IndexOfAny(quoteChars) < 0 || x.IndexOfAny(quoteChars) > x.IndexOf(':'))))
                                            ? x
                                            : prefix + ":" + x).ToArray());
        return result;
    }
}

次に、コードで次のようなものを使用します

        XmlDocument document = new XmlDocument();
        document.Load(pathToFile);
        XmlNode node = document.SelectFirstNode("/rootTag/subTag");

お役に立てれば

3
SpikeDog

または、誰かが私のようなXPathDocumentを使用する必要がある場合:

XPathDocument xdoc = new XPathDocument(file);
XPathNavigator nav = xdoc.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nav.NameTable);
nsmgr.AddNamespace("y", "http://schemas.Microsoft.com/developer/msbuild/2003");
XPathNodeIterator nodeIter = nav.Select("//y:PropertyGroup", nsmgr);
1
Zak

上記のSpikeDogで説明されている、ハックですが有用なアプローチを使用しました。パイプを使用して複数のパスを結合するxpath式を投げるまで、非常にうまく機能しました。

そこで、正規表現を使用して書き直し、共有すると思いました。

public string HackXPath(string xpath_, string prefix_)
{
    return System.Text.RegularExpressions.Regex.Replace(xpath_, @"(^(?![A-Za-z0-9\-\.]+::)|[A-Za-z0-9\-\.]+::|[@|/|\[])(?'Expression'[A-Za-z][A-Za-z0-9\-\.]*)", x =>
                {
                    int expressionIndex = x.Groups["Expression"].Index - x.Index;
                    string before = x.Value.Substring(0, expressionIndex);
                    string after = x.Value.Substring(expressionIndex, x.Value.Length - expressionIndex);
                    return String.Format("{0}{1}:{2}", before, prefix_, after);
                });
}
1
Dan

1]名前空間にプレフィックスのないXMLファイルがある場合:

<bookstore xmlns="http://www.contoso.com/books">
…
</bookstore>

この回避策があります:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
// ignore the namespace as there is a single default namespace:
reader.Namespaces = false;
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");

2]名前空間に接頭辞を持つXMLファイルがある場合:

<bookstore xmlns:ns="http://www.contoso.com/books">
…
</bookstore>

これを使って:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");

もちろん、必要に応じて名前空間管理を使用できます。

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(reader.NameTable);
nsmgr.AddNamespace("ns", "http://www.contoso.com/book");
XPathNodeIterator nodes = navigator.Select("//book", nsmgr);

ほとんどの場合、コードを機能させる最も簡単な方法だと思います。

このマイクロソフトの問題を解決する助けになることを願っています…

0
Corto

この場合、おそらく名前空間の解決が問題の原因ですが、XPath式自体が正しくない可能性もあります。最初に評価することをお勧めします。

XPathNavigatorを使用するコードは次のとおりです。

//xNav is the created XPathNavigator.
XmlNamespaceManager mgr = New XmlNamespaceManager(xNav.NameTable);
mgr.AddNamespace("prefix", "http://tempuri.org/");

XPathNodeIterator result = xNav.Select("/prefix:outerelement/prefix:innerelement", mgr);
0
Cerebrus