obtener - xpath c#
¿Cómo uso XPath con un espacio de nombres predeterminado sin prefijo? (6)
Aquí hay un ejemplo de cómo hacer que el espacio de nombres esté disponible para la expresión XPath en el método de extensión XPathSelectElements:
using System;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Xml;
namespace XPathExpt
{
class Program
{
static void Main(string[] args)
{
XElement cfg = XElement.Parse(
@"<configuration>
<MyNode xmlns=""lcmp"" attr=""true"">
<subnode />
</MyNode>
</configuration>");
XmlNameTable nameTable = new NameTable();
var nsMgr = new XmlNamespaceManager(nameTable);
// Tell the namespace manager about the namespace
// of interest (lcmp), and give it a prefix (pfx) that we''ll
// use to refer to it in XPath expressions.
// Note that the prefix choice is pretty arbitrary at
// this point.
nsMgr.AddNamespace("pfx", "lcmp");
foreach (var el in cfg.XPathSelectElements("//pfx:MyNode", nsMgr))
{
Console.WriteLine("Found element named {0}", el.Name);
}
}
}
}
¿Qué es el XPath (en C # API para XDocument.XPathSelectElements (xpath, nsman) si es importante) para consultar todos los MyNodes desde este documento?
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<MyNode xmlns="lcmp" attr="true">
<subnode />
</MyNode>
</configuration>
- Intenté
/configuration/MyNode
que está mal porque ignora el espacio de nombres. - Intenté
/configuration/lcmp:MyNode
que es incorrecto porquelcmp
es el URI, no el prefijo. - Intenté
/configuration/{lcmp}MyNode
que falló porqueAdditional information: ''/configuration/{lcmp}MyNode'' has an invalid token.
EDITAR: No puedo usar mgr.AddNamespace("df", "lcmp");
como algunos de los contestadores han sugerido. Eso requiere que el programa de análisis XML conozca todos los espacios de nombres que planeo usar antes de tiempo. Como esto se aplica a cualquier archivo de origen, no sé en qué espacios de nombres agregar los prefijos manualmente. Parece que {my uri}
es la sintaxis de XPath, pero Microsoft no se molestó en implementar eso ... ¿cierto?
Ejemplo con Xpath 2.0 + una biblioteca:
using Wmhelp.XPath2;
doc.XPath2SelectElements("/*:configuration/*:MyNode");
Ver :
El elemento de configuration
se encuentra en el espacio de nombres sin nombre, y MyNode está vinculado al espacio de nombres lcmp
sin un prefijo de espacio de nombres.
Esta instrucción XPATH le permitirá abordar el elemento MyNode
sin haber declarado el espacio de nombres lcmp
o usar un prefijo de espacio de nombres en su XPATH:
/configuration/*[namespace-uri()=''lcmp'' and local-name()=''MyNode'']
Coincide con cualquier elemento que sea un elemento secundario de la configuration
y luego utiliza un archivador de predicado con namespace-uri()
y local-name()
para restringirlo al elemento MyNode
.
Si no sabe qué namespace-uri se usará para los elementos, puede hacer que el XPATH sea más genérico y que coincida con el local-name()
:
/configuration/*[local-name()=''MyNode'']
Sin embargo, corre el riesgo de combinar diferentes elementos en diferentes vocabularios (vinculados a diferentes espacios de nombres-uri) que usan el mismo nombre.
Me gusta @ mads-hansen, su respuesta, tan bien que escribí estos miembros de utilidad general:
/// <summary>
/// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
/// </summary>
/// <param name="childElementName">Name of the child element.</param>
/// <returns></returns>
public static string GetLocalNameXPathQuery(string childElementName)
{
return GetLocalNameXPathQuery(namespacePrefixOrUri: null, childElementName: childElementName, childAttributeName: null);
}
/// <summary>
/// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
/// </summary>
/// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param>
/// <param name="childElementName">Name of the child element.</param>
/// <returns></returns>
public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName)
{
return GetLocalNameXPathQuery(namespacePrefixOrUri, childElementName, childAttributeName: null);
}
/// <summary>
/// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
/// </summary>
/// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param>
/// <param name="childElementName">Name of the child element.</param>
/// <param name="childAttributeName">Name of the child attribute.</param>
/// <returns></returns>
/// <remarks>
/// This routine is useful when namespace-resolving is not desirable or available.
/// </remarks>
public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName, string childAttributeName)
{
if (string.IsNullOrEmpty(childElementName)) return null;
if (string.IsNullOrEmpty(childAttributeName))
{
return string.IsNullOrEmpty(namespacePrefixOrUri) ?
string.Format("./*[local-name()=''{0}'']", childElementName)
:
string.Format("./*[namespace-uri()=''{0}'' and local-name()=''{1}'']", namespacePrefixOrUri, childElementName);
}
else
{
return string.IsNullOrEmpty(namespacePrefixOrUri) ?
string.Format("./*[local-name()=''{0}'']/@{1}", childElementName, childAttributeName)
:
string.Format("./*[namespace-uri()=''{0}'' and local-name()=''{1}'']/@{2}", namespacePrefixOrUri, childElementName, childAttributeName);
}
}
Necesita usar un XmlNamespaceManager de la siguiente manera:
XDocument doc = XDocument.Load(@"../../XMLFile1.xml");
XmlNamespaceManager mgr = new XmlNamespaceManager(new NameTable());
mgr.AddNamespace("df", "lcmp");
foreach (XElement myNode in doc.XPathSelectElements("configuration/df:MyNode", mgr))
{
Console.WriteLine(myNode.Attribute("attr").Value);
}
XPath no está (deliberadamente) diseñado para el caso en el que desee utilizar la misma expresión XPath para algunos espacios de nombres desconocidos que solo residen en el documento XML. Se espera que conozca el espacio de nombres con anticipación, declare el espacio de nombres en el procesador XPath y use el nombre en su expresión. Las respuestas de Martin y Dan muestran cómo hacer esto en C #.
El motivo de esta dificultad se expresa mejor en la especificación de espacios de nombres XML :
Visualizamos aplicaciones de Lenguaje de marcado extensible (XML) donde un único documento XML puede contener elementos y atributos (en adelante, un "vocabulario de marcado") definidos y utilizados por múltiples módulos de software. Una motivación para esto es la modularidad: si existe un vocabulario de marcado que es bien conocido y para el cual hay disponible un software útil, es mejor volver a utilizar este marcado en lugar de reinventarlo.
Dichos documentos, que contienen múltiples vocabularios de marcado, plantean problemas de reconocimiento y colisión. Los módulos de software deben ser capaces de reconocer los elementos y atributos que están diseñados para procesar, incluso frente a las "colisiones" que ocurren cuando el marcado destinado a otro paquete de software utiliza el mismo nombre de elemento o nombre de atributo.
Estas consideraciones requieren que las construcciones de documentos tengan nombres construidos para evitar choques entre nombres de diferentes vocabularios de marcado. Esta especificación describe un mecanismo, espacios de nombres XML, que lo logra asignando nombres expandidos a elementos y atributos.
Es decir, se supone que los espacios de nombres deben usarse para asegurarse de que sabe de qué está hablando su documento: ¿está el elemento <head>
hablando sobre el preámbulo de un documento XHTML o la cabeza de alguien en un documento AnatomyML? Nunca se "supone" que sea agnóstico sobre el espacio de nombres y es prácticamente lo primero que debe definir en cualquier vocabulario de XML.
Debería ser posible hacer lo que quiera, pero no creo que se pueda hacer en una sola expresión XPath. En primer lugar, debe hurgar en el documento y extraer todos los namespaceURI, luego agréguelos al gestor del espacio de nombres y luego ejecute la expresión XPath real que desea (y necesita saber algo sobre la distribución de los espacios de nombres en el documento en este punto, o tienes muchas expresiones para ejecutar). Creo que probablemente sea mejor usar algo que no sea XPath (por ejemplo, una API similar a DOM o SAX) para encontrar las URL de espacios de nombres, pero también podría explorar el eje de espacios de nombres de XPath (en XPath 1.0), usar el namespace-uri-from-QName
Función namespace-uri-from-QName
(en XPath 2.0) o expresiones como "configuration/*[local-name() = ''MyNode'']"
. De todos modos, creo que tu mejor opción es intentar evitar escribir XPath agnóstico en el espacio de nombres. ¿Por qué no conoce su espacio de nombres antes de tiempo? ¿Cómo vas a evitar emparejar cosas que no pretendas igualar?
Editar: ¿conoces el namespaceURI?
Entonces resulta que tu pregunta nos confundió a todos. Aparentemente conoce el URI del espacio de nombres, pero no conoce el prefijo del espacio de nombres que se usa en el documento XML. De hecho, en este caso, no se utiliza ningún prefijo de espacio de nombres y el URI se convierte en el namspace predeterminado donde se define. La clave para saber es que el prefijo elegido (o la falta de un prefijo) es irrelevante para su expresión XPath (y el análisis XML en general). El atributo prefix / xmlns es solo una forma de asociar un nodo con un URI de espacio de nombres cuando el documento se expresa como texto. Es posible que desee echarle un vistazo a esta respuesta , donde trato de aclarar los prefijos del espacio de nombres.
Debería tratar de pensar en el documento XML de la misma manera que el analizador lo piensa: cada nodo tiene un URI de espacio de nombres y un nombre local. Las reglas de prefijo / herencia de espacios de nombres simplemente guardan escribir el URI muchas veces. Una forma de escribir esto es en notación Clark: es decir, usted escribe { http://www.example.com/namespace/example } LocalNodeName, pero esta notación generalmente solo se usa para la documentación: XPath no sabe nada acerca de esta notación.
En cambio, XPath usa sus propios prefijos de espacio de nombres /ns1:root/ns2:node
como /ns1:root/ns2:node
. Pero estos están completamente separados y no tienen nada que ver con los prefijos que pueden usarse en el documento XML original. Cualquier implementación de XPath tendrá una forma de mapear sus propios prefijos con URI de espacio de nombres. Para la implementación de C # usted usa un XmlNamespaceManager
, en Perl usted proporciona un hash, xmllint toma argumentos de línea de comando ... Entonces, todo lo que necesita hacer es crear un prefijo arbitrario para el URI de espacio de nombres que conoce y usar este prefijo en la expresión XPath . No importa qué prefijo use, en XML solo le importa la combinación del URI y el localName.
La otra cosa para recordar (a menudo es una sorpresa) es que XPath no hereda el espacio de nombres. Debe agregar un prefijo para cada uno que tenga un espacio de nombre, independientemente de si el espacio de nombre proviene de la herencia, un atributo xmlns o un prefijo del espacio de nombres. Además, aunque siempre debe pensar en términos de URI y localNames, también hay formas de acceder al prefijo desde un documento XML. Es raro tener que usar estos.