Usar Xpath con el espacio de nombre predeterminado en C #

Tengo un documento XML con un espacio de nombres predeterminado. Estoy usando un XPathNavigator para seleccionar un conjunto de nodos usando Xpath de la siguiente manera:

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

No recibo ningún resultado: supongo que es porque no estoy especificando el espacio de nombres. ¿Cómo puedo incluir el espacio de nombres en mi selección?

Primero, no necesita un navegador; SelectNodes / SelectSingleNode debería ser suficiente.

Sin embargo, puede necesitar un administrador de espacio de nombres, por ejemplo:

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

Es posible que desee probar una herramienta Visualizador XPath para ayudarlo a pasar.

XPathVisualizer es gratuito, fácil de usar.

texto alternativo

IMPORTANTE: si está usando Windows 7/8 y no ve los ítems Archivo, Edición y Menú de Ayuda, presione la tecla ALT.

Para cualquiera que busque una solución de hackeo rápido, especialmente en aquellos casos en que conoce el XML y no necesita preocuparse por los espacios de nombres y todo eso, puede evitar esta pequeña “característica” molesta simplemente leyendo el archivo en una cadena y reemplazando el atributo ofensivo:

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

Encuentro esto más fácil que todo el otro sin sentido que requiere un prefijo para un espacio de nombres predeterminado cuando trato con un solo archivo. Espero que esto ayude.

Al usar XPath en .NET (a través de un navegador o SelectNodes / SelectSingleNode) en XML con espacios de nombres, necesita:

  • Proporcione su propio XmlNamespaceManager

  • y prefije explícitamente todos los elementos en la expresión XPath, que están en el espacio de nombres.

El último es (parafraseado desde MS source linked más abajo): porque XPath 1.0 ignora las especificaciones de espacio de nombres predeterminadas (xmlns = “some_namespace”). Entonces cuando usas el nombre del elemento sin prefijo, asume el espacio de nombre nulo.

Es por eso que la implementación .NET de XPath ignora el espacio de nombres con el prefijo String.Empty en XmlNamespaceManager y siempre usa espacio de nombres nulo.

Consulte XmlNamespaceManager y UndefinedXsltContext no maneja el espacio de nombres predeterminado para obtener más información.

Encuentro esta “característica” muy inconveniente porque no puedes hacer que el espacio de nombres de XPath viejo sea compatible simplemente al agregar una statement de espacio de nombres predeterminada, pero así es como funciona.

Puede usar la statement XPath sin usar XmlNamespaceManager de la siguiente manera:

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

Esa es una forma simple de seleccionar elementos dentro de XML con el espacio de nombres predeterminado definido.

El punto es usar:

 namespace-uri() = '' 

que encontrará el elemento con el espacio de nombres predeterminado sin usar prefijos.

En caso de que los espacios de nombres difieran para el elemento externo e innerelevación

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

Encontré un problema similar con un espacio de nombres predeterminado en blanco. En este ejemplo XML, tengo una combinación de elementos con prefijos de espacio de nombres, y un solo elemento (DataBlock) sin:

    7   Value   Value     

Intenté usar un XPath que funcionaba en XPath Visualizer, pero no funcionaba en mi código:

  XmlDocument doc = new XmlDocument(); doc.Load( textBox1.Text ); XPathNavigator nav = doc.DocumentElement.CreateNavigator(); XmlNamespaceManager nsman = new XmlNamespaceManager( nav.NameTable ); foreach ( KeyValuePair 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; } 

Lo reduje al elemento “DataBlock” de XPath, pero no pude hacerlo funcionar excepto simplemente por el comodín del elemento 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; } 

Después de mucho headscratching y Google (que me aterrizó aquí) decidí abordar el espacio de nombres predeterminado directamente en mi cargador de XmlNamespaceManager cambiándolo a:

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

Así que ahora “predeterminado” y “” apuntan al mismo espacio de nombres. Una vez que hice esto, el XPath “/ src: SRCExample / default: DataBlock / a: DocID / a: IdID” devolvió mis resultados como yo quería. Espero que esto ayude a aclarar el problema para otros.

Mi respuesta extiende la respuesta anterior de Brandon. Usé su ejemplo para crear un método de extensión de la siguiente manera:

 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 kvp in nav.GetNamespacesInScope(XmlNamespaceScope.All)) { string sKey = kvp.Key; if (sKey == "") { sKey = "default"; } nmsp.AddNamespace(sKey, kvp.Value); } return nmsp; } } 

Luego, en mi código de análisis XML, simplemente agrego una sola línea:

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

Me gusta mucho este método porque es completamente dynamic en términos de cargar los espacios de nombres del archivo XML de origen, y no ignora por completo el concepto de espacios de nombres XML, por lo que se puede usar con XML que requiere múltiples espacios de nombres para la desconexión.

En mi caso, agregar un prefijo no era práctico. Demasiado xml o xpath se determinaron en tiempo de ejecución. Eventualmente amplié los métodos en XmlNode. Esto no ha sido optimizado para el rendimiento y probablemente no maneje todos los casos, pero hasta ahora me funciona.

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

Luego, en tu código solo usa algo como

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

Espero que esto ayude

Usé el enfoque hacky-pero-útil descrito por SpikeDog arriba. Funcionó muy bien hasta que arrojé una expresión xpath que utilizaba las tuberías para combinar varias rutas.

Así que lo reescribí usando expresiones regulares, y pensé que lo compartiría:

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

O, si alguien debería usar un XPathDocument, como yo:

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

En este caso, es probable que la causa del problema sea la resolución del espacio de nombres, pero también es posible que su expresión XPath no sea correcta en sí misma. Es posible que desee evaluarlo primero.

Aquí está el código usando un 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);