web-dev-qa-db-fra.com

XPath select node avec espace de noms

C'est un .vbproj et ressemble à ceci

<Project DefaultTargets="Build" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <ProjectGuid>15a7ee82-9020-4fda-a7fb-85a61664692d</ProjectGuid>

tout ce que je veux, c'est le ProjectGuid, mais cela ne fonctionne pas quand un espace de noms est présent ...

 Dim xmlDoc As New XmlDocument()
 Dim filePath As String = Path.Combine(mDirectory, name + "\" + name + ".vbproj")
 xmlDoc.Load(filePath)
 Dim value As Object = xmlDoc.SelectNodes("/Project/PropertyGroup/ProjectGuid")

que puis-je faire pour résoudre ce problème?

64
Peter

La meilleure façon de faire des choses comme celle-ci (IMHO) est de créer un gestionnaire d’espace de nommage. Cela peut être utilisé en appelant SelectNodes pour indiquer quelles URL d'espace de nom sont connectées à quels préfixes. Je configure normalement une propriété statique qui renvoie une instance adéquate comme celle-ci (c'est C #, vous devrez traduire):

private static XmlNamespaceManager _nsMgr;
public static XmlNamespaceManager NsMgr
{
  get
  {
    if (_nsMgr == null)
    {
      _nsMgr = new XmlNamespaceManager(new NameTable());
      _nsMgr.AddNamespace("msb", "http://schemas.Microsoft.com/developer/msbuild/2003");
    }
    return _nsMgr;
  }
}

Je n'inclus qu'un seul espace de noms ici, mais vous pouvez en avoir plusieurs. Ensuite, vous pouvez sélectionner dans le document comme ceci:

Dim value As Object = xmlDoc.SelectNodes("/msb:Project/msb:PropertyGroup/msb:ProjectGuid", NsMgr)

Notez que tous les éléments sont dans l'espace de noms spécifié. 

44
Teun D

Je serais probablement enclin à aller avec Bartek* solution d'espace de noms, mais une solution générale de xpath est:

//*[local-name()='ProjectGuid']

** puisque la réponse de Bartek a disparu, je recommande Teun's (qui est en réalité plus approfondie) *

63
annakata

Ce problème a été ici plusieursfoisdéjà .

Soit vous travaillez avec des expressions XPath indépendantes de l’espace de nom (non recommandé en raison de sa maladresse et du risque de résultats faux positifs - <msb:ProjectGuid> et <foo:ProjectGuid> sont les mêmes pour cette expression):

XmlNamespaceManager pour enregistrer l'URI de l'espace de noms afin d'inclure un préfixe d'espace de nom dans votre XPath:

Dim xmlDoc As New XmlDocument()
xmlDoc.Load(Path.Combine(mDirectory, name, name + ".vbproj"))

Dim nsmgr As New XmlNamespaceManager(xmlDoc.NameTable)
nsmgr.AddNamespace("msb", "http://schemas.Microsoft.com/developer/msbuild/2003")

Dim xpath As String = "/msb:Project/msb:PropertyGroup/msb:ProjectGuid"
Dim value As Object = xmlDoc.SelectNodes(xpath, nsmgr)
27
Tomalak

Il vous suffit d'enregistrer ces espaces de noms XML et de les associer à un préfixe pour que la requête fonctionne. Créez et transmettez un gestionnaire d'espaces de noms comme second paramètre lors de la sélection des nœuds:

Dim ns As New XmlNamespaceManager ( xmlDoc.NameTable )
ns.AddNamespace ( "msbuild", "http://schemas.Microsoft.com/developer/msbuild/2003" )
Dim value As Object = xmlDoc.SelectNodes("/msbuild:Project/msbuild:PropertyGroup/msbuild:ProjectGuid", ns)
3
baretta

Une solution consiste à utiliser les extensions + NameSpaceManager.
Le code est dans VB mais est très facile à traduire en C #.

Imports System.Xml
Imports System.Runtime.CompilerServices

Public Module Extensions_XmlHelper

    'XmlDocument Extension for SelectSingleNode
    <Extension()>
    Public Function _SelectSingleNode(ByVal XmlDoc As XmlDocument, xpath As String) As XmlNode
        If XmlDoc Is Nothing Then Return Nothing

        Dim nsMgr As XmlNamespaceManager = GetDefaultXmlNamespaceManager(XmlDoc, "x")
        Return XmlDoc.SelectSingleNode(GetNewXPath(xpath, "x"), nsMgr)
    End Function

    'XmlDocument Extension for SelectNodes
    <Extension()>
    Public Function _SelectNodes(ByVal XmlDoc As XmlDocument, xpath As String) As XmlNodeList
        If XmlDoc Is Nothing Then Return Nothing

        Dim nsMgr As XmlNamespaceManager = GetDefaultXmlNamespaceManager(XmlDoc, "x")
        Return XmlDoc.SelectNodes(GetNewXPath(xpath, "x"), nsMgr)
    End Function


    Private Function GetDefaultXmlNamespaceManager(ByVal XmlDoc As XmlDocument, DefaultNamespacePrefix As String) As XmlNamespaceManager
        Dim nsMgr As New XmlNamespaceManager(XmlDoc.NameTable)
        nsMgr.AddNamespace(DefaultNamespacePrefix, XmlDoc.DocumentElement.NamespaceURI)
        Return nsMgr
    End Function

    Private Function GetNewXPath(xpath As String, DefaultNamespacePrefix As String) As String
        'Methode 1: The easy way
        Return xpath.Replace("/", "/" + DefaultNamespacePrefix + ":")

        ''Methode 2: Does not change the nodes with existing namespace prefix
        'Dim Nodes() As String = xpath.Split("/"c)
        'For i As Integer = 0 To Nodes.Length - 1
        '    'If xpath starts with "/", don't add DefaultNamespacePrefix to the first empty node (before "/")
        '    If String.IsNullOrEmpty(Nodes(i)) Then Continue For
        '    'Ignore existing namespaces prefixes
        '    If Nodes(i).Contains(":"c) Then Continue For
        '    'Add DefaultNamespacePrefix
        '    Nodes(i) = DefaultNamespacePrefix + ":" + Nodes(i)
        'Next
        ''Create and return then new xpath
        'Return String.Join("/", Nodes)
    End Function

End Module

Et pour l'utiliser:

Imports Extensions_XmlHelper

......
Dim FileXMLTextReader As New XmlTextReader(".....")
FileXMLTextReader.WhitespaceHandling = WhitespaceHandling.None
Dim xmlDoc As XmlDocument = xmlDoc.Load(FileXMLTextReader)
FileXMLTextReader.Close()
......
Dim MyNode As XmlNode = xmlDoc._SelectSingleNode("/Document/FirstLevelNode/SecondLevelNode")

Dim MyNode As XmlNodeList = xmlDoc._SelectNodes("/Document/FirstLevelNode/SecondLevelNode")

......
0