wake-up-neo.com

Verwenden von Xpath mit Standardnamensraum in C #

Ich habe ein XML-Dokument mit einem Standardnamensraum. Ich verwende einen XPathNavigator, um eine Gruppe von Knoten mit Xpath wie folgt auszuwählen: 

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

Ich bekomme keine Ergebnisse zurück: Ich gehe davon aus, dass ich den Namespace nicht spezifiziere. Wie kann ich den Namespace in meine Auswahl aufnehmen?

58
macleojw

Erstens: Sie brauchen keinen Navigator. SelectNodes/SelectSingleNode sollte ausreichen.

Möglicherweise benötigen Sie jedoch einen Namespace-Manager, zum Beispiel:

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);
77
Marc Gravell

Möglicherweise möchten Sie ein XPath-Visualizer-Tool ausprobieren, um Ihnen zu helfen. 

XPathVisualizer ist kostenlos und einfach zu benutzen. 

 alt text

WICHTIG: Wenn Sie Windows 7/8 verwenden und keine Elemente des Menüs Datei, Bearbeiten und Hilfe anzeigen, drücken Sie bitte die ALT-Taste.

48
Cheeso

Für alle, die nach einer schnellen Hack-Lösung suchen, insbesondere in den Fällen, in denen Sie das XML kennen und sich nicht um Namespaces und all das kümmern müssen Sie können dieses nervige kleine "Feature" umgehen, indem Sie einfach die Datei in eine Zeichenfolge einlesen und das anstößige Attribut ersetzen:

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");

Ich finde das einfacher als alle anderen Unsinnigen, die ein Präfix für einen Standard-Namespace benötigen, wenn ich mit einer einzelnen Datei arbeite. Hoffe das hilft.

24
Mitselplik

Wenn Sie XPath in .NET (über einen Navigator oder SelectNodes/SelectSingleNode) in XML mit Namespaces verwenden, müssen Sie Folgendes tun:

  • stellen Sie Ihren eigenen XmlNamespaceManager zur Verfügung

  • und stellen explizit alle Elemente im XPath-Ausdruck voran, die sich im Namespace befinden.

Letzteres ist (von MS-Quelle unten umschlossen): weil XPath 1.0 Standard-Namespace-Spezifikationen ignoriert (xmlns = "some_namespace"). Wenn Sie also den Elementnamen ohne Präfix verwenden, wird ein Null-Namespace angenommen.

Aus diesem Grund ignoriert die .NET-Implementierung von XPath den Namespace mit dem Präfix String.Empty in XmlNamespaceManager und verwendet immer einen NULL-Namespace.

Siehe XmlNamespaceManager und UndefinedXsltContext behandeln den Standardnamespace nicht, um weitere Informationen zu erhalten.

Ich finde dieses "Feature" sehr unpraktisch, da Sie den alten XPath-Namespace nicht durch einfaches Hinzufügen von Standard-Namespace-Deklarationen aktivieren können. Dies funktioniert jedoch so.

19

Sie können die XPath-Anweisung ohne XmlNamespaceManager wie folgt verwenden:

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

Dies ist eine einfache Methode zum Auswählen eines Elements in XML mit einem definierten definierten Namespace .

Der Punkt ist zu verwenden: 

namespace-uri() = ''

welches das Element mit dem Standard-Namespace findet, ohne Präfixe zu verwenden.

Meine Antwort erweitert die vorherige Antwort von Brandon. Ich habe sein Beispiel verwendet, um eine Erweiterungsmethode wie folgt zu erstellen:

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;
    }
}

Dann füge ich in meinem XML-Code nur eine Zeile hinzu:

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

Diese Methode gefällt mir sehr, da sie in Bezug auf das Laden der Namespaces aus der XML-Quelldatei vollständig dynamisch ist und das Konzept der XML-Namespaces nicht vollständig außer Acht lässt. Dies kann mit XML verwendet werden, das mehrere Namespaces für die Dekonfliktion erfordert.

5
Kent

Bei einem leeren Standardnamespace ist ein ähnliches Problem aufgetreten. In diesem Beispiel-XML habe ich eine Mischung aus Elementen mit Namespace-Präfixen und einem einzelnen Element (DataBlock) ohne:

<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>

Ich habe versucht, einen XPath zu verwenden, der in XPath Visualizer funktioniert, aber nicht in meinem Code:

  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;
  }

Ich habe es auf das "DataBlock" -Element des XPath eingegrenzt, konnte es aber nicht funktionieren lassen, außer durch einfaches Platzieren des DataBlock-Elements:

  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;
  }

Nach vielem Headscratching und Googeln (was mich hier gelandet hat) entschied ich mich, den Standard-Namespace direkt in meinem XmlNamespaceManager-Loader anzupacken, indem ich ihn in Folgendes änderte:

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

"Default" und "" zeigen nun auf den gleichen Namespace. Sobald ich dies getan habe, gab der XPath "/ src: SRCExample/default: DataBlock/a: DocID/a: IdID" meine Ergebnisse genau so wieder, wie ich es wollte. Hoffentlich hilft dies, das Problem für andere zu klären.

5
Brandon

Falls sich die Namespaces für äußere Elemente und innere Elemente unterscheiden

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

In meinem Fall war das Hinzufügen eines Präfixes nicht praktikabel. Zu viel von XML oder Xpath wurde zur Laufzeit ermittelt. Schließlich habe ich die Methoden auf XmlNode erweitert. Dies wurde nicht für die Leistung optimiert, und es wird wahrscheinlich nicht jeden Fall handhaben, aber es funktioniert bisher für mich.

    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;
    }
}

Dann verwenden Sie in Ihrem Code einfach so etwas

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

Hoffe das hilft

3
SpikeDog

Oder wenn jemand ein XPathDocument wie ich verwenden sollte:

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

Ich habe den hackigen, aber nützlichen Ansatz verwendet, den SpikeDog oben beschrieben hat. Es hat sehr gut funktioniert, bis ich einen Xpath-Ausdruck darauf warf, der Pipes verwendete, um mehrere Pfade zu kombinieren.

Also schrieb ich es mit regulären Ausdrücken um und dachte, ich würde es teilen:

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] Wenn Sie eine XML-Datei ohne Präfix im Namespace haben:

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

sie haben diese Problemumgehung:

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] Wenn Sie eine XML-Datei mit einem Präfix im Namespace haben:

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

Benutze das:

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

Natürlich können Sie bei Bedarf eine Namensraumverwaltung verwenden:

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);

Ich denke, es ist der einfachste Weg, den Code in den meisten Fällen zum Laufen zu bringen.

Ich hoffe, diese Hilfe hilft, dieses Microsoft-Problem zu lösen…

0
Corto

In diesem Fall ist es wahrscheinlich die Namensraumauflösung, die die Ursache des Problems ist, es ist jedoch auch möglich, dass Ihr XPath-Ausdruck an sich nicht korrekt ist. Vielleicht möchten Sie es zuerst bewerten.

Hier ist der Code mit einem 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