2010-03-26 42 views
30

¿Cuál es el XPath (en C# API a XDocument.XPathSelectElements (xpath, nsman) si es importante) para consultar todos los MyNodes desde este documento?¿Cómo uso XPath con un espacio de nombres predeterminado sin prefijo?

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
    <MyNode xmlns="lcmp" attr="true"> 
    <subnode /> 
    </MyNode> 
</configuration> 
  • me trataron /configuration/MyNode que es erróneo porque no tiene en cuenta el espacio de nombres.
  • Intenté /configuration/lcmp:MyNode que es incorrecto porque lcmp es el URI, no el prefijo.
  • me trataron /configuration/{lcmp}MyNode que fracasó debido a Additional information: '/configuration/{lcmp}MyNode' has an invalid token.

EDIT: no se puede utilizar mgr.AddNamespace("df", "lcmp"); como algunos de los que responden 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?

+0

No está claro qué es lo que quiere lograr exactamente. ¿Cuáles son los criterios que determinan qué nodos estás buscando? ¿Estás buscando elementos basados ​​en su espacio de nombres? En ese caso, su código conocería el espacio de nombres. En cuanto a {my uri} es la "sintaxis XPath", ¿en qué parte de la especificación XPath 1.0 cree que se ha definido esa sintaxis? Y si coloca el URI de espacio de nombres entre llaves o si pasa el URI de espacio de nombres al método AddNamespace no debería importarle a su código C#, en ambos casos, el URI de espacio de nombres debe estar disponible como una cadena. –

+0

@Martin: sí quiero especificar el espacio de nombres en XPath, pero solo tengo el URI del espacio de nombres y no hay prefijo del espacio de nombres. Miré más de cerca en donde 'inventé' el {} y podría haber descremado incorrectamente ... Lo obtuve de esta referencia: http://www.jclark.com/xml/xmlns.htm. Gracias por señalar eso. Por supuesto, incluso si no es válido, parece una cosa útil poder hacer fácilmente ...;) –

+0

Scott, tendrá que elegir cualquier prefijo permitido que desee, asociarlo con el URI de espacio de nombres que tiene utilizando AddNamespace (prefix, namespaceURI) y usa el prefijo elegido en tu expresión XPath. Así es como funciona XPath, al menos XPath 1.0. El prefijo no tiene que existir en absoluto en el XML de entrada o puede ser diferente del utilizado en el XML de entrada, la selección del elemento se realizará en función de la coincidencia del espacio de nombres, no del prefijo. –

Respuesta

34

El elemento configuration está en el espacio de nombre sin nombre, y el MyNode está vinculado al espacio de nombres lcmp sin un prefijo de espacio de nombres.

Este XPATH declaración le permitirá abordar el factor MyNode sin haber declarado el espacio de nombres lcmp o usa un prefijo de espacio en su XPATH:

/configuration/*[namespace-uri()='lcmp' and local-name()='MyNode'] 

Coincide con cualquier elemento que es un hijo de configuration y luego utiliza un archivador de predicado con las funciones namespace-uri() y local-name() para restringirlo al elemento MyNode.

Si no sabe cuál de espacio de nombres URI será utilizado para los elementos, a continuación, puede hacer que el XPATH más genérico y simplemente igualar en el local-name():

/configuration/*[local-name()='MyNode'] 

Sin embargo, se corre el riesgo de emparejar diferentes elementos en diferentes vocabularios (vinculados a diferentes espacios de nombres-uri) que usan el mismo nombre.

+0

@Mads: Ah, interesante, no sabía sobre la sintaxis "[namespace-uri() = 'lcmp'" ... debería funcionar, y si es así (lo intentaré el lunes) marcaré esto como respuesta. ¿Sabe si el "/ configuration/{lcmp} MyNode" es realmente correcto y simplemente no es compatible con C#? –

+0

@Scott No, la sintaxis que intentaba usar no es una declaración XPATH válida y no es compatible con ninguna implementación de la que tenga conocimiento. Aunque puede expandirse a ese QName, no puede abordarlo de esa manera en su declaración XPATH. –

+0

Funcionó como un amuleto, muchas gracias. –

12

Es necesario utilizar 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); 
    } 
+2

Sí, creo que eso funcionaría, pero no puedo hacer eso. Dado que el código de análisis XML es independiente del archivo XML real y de los espacios de nombres que utiliza, mgr.AddNamespace ("df", "lcmp"); es una línea imposible de escribir ... –

+2

Pero tu código de análisis no puede ser independiente de los nombres de los elementos, ¿verdad?El espacio de nombre se considera parte del nombre, por lo que ignorarlo es un diseño mediocre, pero si está seguro de que no habrá conflictos de nombres de espacios, puede hacer algo como "configuration/* [local-name() = 'MyNode']" –

+0

Scott, por favor explicar cómo se supone que su código identifica el elemento si no se conoce el URI del espacio de nombres ¿Cuál es su código que busca exactamente, elementos con el nombre local "MyNode" en cualquier espacio de nombres? Luego usa la sugerencia de Oleg. De lo contrario, explique con más detalle qué elementos exactamente está buscando. –

4

Aquí hay un ejemplo de cómo hacer que el espacio de nombres disponibles para la expresión XPath en el XPathSelectElements método de extensión:

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

@Dan: Sí, creo que funciona, pero requiere la codificación física de todos los espacios de nombres usados ​​... mientras que solo puedo controlar el XPath - ver mi comentario bajo la respuesta de @Martin Honnen. –

5

XPath es (deliberadamente) no diseñado para el caso en el que desea utilizar la misma expresión XPath para algunos espacios de nombres desconocidos que solo viven 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#.

La razón de esta dificultad se expresa mejor en la XML namespaces especificaciones:

Tenemos la visión de las aplicaciones de lenguaje de marcado extensible (XML) en un único documento XML puede contener elementos y atributos (aquí referido como un " vocabulario de marcado ") que se definen y utilizan 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 vocabularios de marcas múltiples, 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 conflictos 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 que se utiliza para asegurarse de que sabe lo que su documento está hablando: es que <head> elemento hablando del preámbulo de un documento XHTML o somebodies cabeza 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 poder 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 utilizar 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 se puede explorar el eje de espacios de nombres XPath (en XPath 1.0), usar la función (en XPath 2.0) o use expresiones como Oleg's "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 - saber el namespaceURI?

Así que resulta que su 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 echar un vistazo a this answer, 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, 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 su lugar, XPath usa sus propios prefijos de espacio de nombres. Algo así 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 algún 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.

+0

@Andrew: SÍ se el espacio de nombres de antemano y puedo ponerlo en el XPath. Lo que no sé es el prefijo del espacio de nombres, que es el que se usa cuando dice algo como "/ configuration/lcmp: MyNode". "/ configuration/{lcmp} MyNode" parece ser la sintaxis adecuada para usar el URI de espacio de nombres en lugar de un prefijo, pero C# no parece ser compatible con la notación {}. Y no tengo un prefijo –

+0

Ah, ya veo. Escribiré una nueva respuesta; básicamente, solo necesita saber que el prefijo del espacio de nombres en su documento XML no tiene nada en común con el prefijo del espacio de nombres en la expresión XPath, aparte de que ambos deben mapearse en la misma nsURI. –

+0

Escritura de edición muy informativa y detallada, pero no creo que realmente aborde mi pregunta, que es: ¿qué XPath encuentra ese nodo? Además, ¿está diciendo que si el XML DID especifica un prefijo (que no), entonces la consulta XPath para encontrar que no podría usarlo? –

0

I como @ Mads-Hansen, su respuesta, tan bien que Escribí estos miembros de utilidad general de clase:

/// <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); 
     } 
    } 
Cuestiones relacionadas