.net - leer - ¿Por qué es necesario XmlNamespaceManager?
xpath c# ejemplos (6)
Debe registrar los pares URI / prefijo en la instancia XmlNamespaceManager para que SelectSingleNode () sepa a qué nodo particular "nodeName" se refiere: el de "http://someplace.org" o el de "http: //otherplace.net ".
Tenga en cuenta que el nombre del prefijo concreto no importa cuando está haciendo la consulta XPath. Yo creo que esto también funciona:
Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("any", "http://someplace.org")
nsmgr.AddNamespace("thing", "http://otherplace.net")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//thing:nodeName", nsmgr)
SelectSingleNode () solo necesita una conexión entre el prefijo de su expresión XPath y el URI del espacio de nombres.
He llegado a la conclusión de por qué , al menos en .Net Framework, es necesario usar un XmlNamespaceManager
para manejar los espacios de nombres (o el bastante tosco y detallado [local-name()=...
Predicado / función XPath / lo que sea) al realizar consultas XPath. Entiendo por qué los espacios de nombres son necesarios o al menos beneficiosos, pero ¿por qué es tan complejo?
Para consultar un documento XML simple (sin espacios de nombres) ...
<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode>
<nodeName>Some Text Here</nodeName>
</rootNode>
... se puede usar algo como doc.SelectSingleNode("//nodeName")
(que coincidiría <nodeName>Some Text Here</nodeName>
)
Misterio n. ° 1 : mi primera molestia : si lo entiendo correctamente, es simplemente agregar una referencia de espacio de nombres a la etiqueta principal / raíz (ya sea que se use como parte de una etiqueta de nodo hijo o no) así:
<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode xmlns="http://someplace.org">
<nodeName>Some Text Here</nodeName>
</rootNode>
... requiere varias líneas adicionales de código para obtener el mismo resultado:
Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("ab", "http://s+omeplace.org")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//ab:nodeName", nsmgr)
... esencialmente soñando con un prefijo inexistente (" ab
") para encontrar un nodo que ni siquiera usa un prefijo. ¿Cómo esto tiene sentido? ¿Qué está mal (conceptualmente) con doc.SelectSingleNode("//nodeName")
?
Misterio n.º 2 : digamos que tienes un documento XML que usa prefijos:
<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode xmlns:cde="http://someplace.org" xmlns:feg="http://otherplace.net">
<cde:nodeName>Some Text Here</cde:nodeName>
<feg:nodeName>Some Other Value</feg:nodeName>
<feg:otherName>Yet Another Value</feg:otherName>
</rootNode>
... Si lo entiendo correctamente, tendría que agregar ambos espacios de nombres a XmlNamespaceManager
, para hacer una consulta para un solo nodo ...
Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("cde", "http://someplace.org")
nsmgr.AddNamespace("feg", "http://otherplace.net")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//feg:nodeName", nsmgr)
... ¿Por qué, en este caso, necesito (conceptualmente) un administrador de espacio de nombres?
** SE HA REDUIDO en los comentarios a continuación **
Editar agregado: Mi pregunta revisada y refinada se basa en la aparente redundancia del XmlNamespaceManager en lo que creo que es la mayoría de los casos y el uso del administrador del espacio de nombres para especificar un mapeo del prefijo al URI:
Cuando el mapeo directo del prefijo del espacio de nombres ("cde") al URI del espacio de nombres ("http://someplace.org") se establece explícitamente en el documento de origen:
...<rootNode xmlns:cde="http://someplace.org"...
¿Cuál es la necesidad conceptual de un programador para recrear esa asignación antes de realizar una consulta?
El motivo es simple No se requiere conexión entre los prefijos que usa en su consulta XPath y los prefijos declarados en el documento xml. Para dar un ejemplo, los siguientes xmls son semánticamente equivalentes:
<aaa:root xmlns:aaa="http://someplace.org">
<aaa:element>text</aaa:element>
</aaa:root>
vs
<bbb:root xmlns:bbb="http://someplace.org">
<bbb:element>text</bbb:element>
</bbb:root>
La consulta " ccc:root/ccc:element
" hará coincidir ambas instancias siempre que haya una asignación en el administrador del espacio de nombres para eso.
nsmgr.AddNamespace("ccc", "http://someplace.org")
La implementación de .NET no se preocupa por los prefijos literales usados en el xml solo que hay un prefijo definido para el literal de la consulta y que el valor del espacio de nombres coincide con el valor real del documento. Esto requiere tener expresiones de consulta constantes incluso si los prefijos varían entre documentos consumidos y es la implementación correcta para el caso general.
El punto básico (como lo señala Kev, arriba ), es que el URI del espacio de nombres es la parte importante del espacio de nombres, en lugar del prefijo del espacio de nombres, el prefijo es una "conveniencia arbitraria".
En cuanto a por qué necesita un administrador de espacio de nombres, en lugar de que exista alguna magia que funcione usando el documento, puedo pensar en dos razones.
Motivo 1
Si solo se permitiera agregar declaraciones de espacios de nombres a documentElement, como en los ejemplos, sería trivial seleccionar selectSingleNode para usar solo lo que esté definido.
Sin embargo, puede definir prefijos de espacio de nombres en cualquier elemento de un documento, y los prefijos de espacio de nombres no están vinculados de forma exclusiva a ningún espacio de nombres dado en un documento. Considera el siguiente ejemplo
<w xmlns:a="mynamespace">
<a:x>
<y xmlns:a="myOthernamespace">
<z xmlns="mynamespace">
<b:z xmlns:b="mynamespace">
<z xmlns="myOthernamespace">
<b:z xmlns:b="myOthernamespace">
</y>
</a:x>
</w>
En este ejemplo, ¿qué le gustaría que devolvieran //z
, //a:z
y //b:z
? ¿Cómo expresarías eso sin algún tipo de administrador de espacio de nombres externo?
Motivo 2
Le permite reutilizar la misma expresión XPath para cualquier documento equivalente, sin necesidad de saber nada sobre los prefijos de espacio de nombres en uso.
myXPathExpression = "//z:y"
doc1.selectSingleNode(myXPathExpression);
doc2.selectSingleNode(myXPathExpression);
doc1:
<x>
<z:y xmlns:z="mynamespace" />
</x>
doc2:
<x xmlns"mynamespace">
<y>
</x>
Para lograr este último objetivo sin un gestor de espacio de nombres, debería inspeccionar cada documento, creando una expresión XPath personalizada para cada uno.
Este hilo me ayudó a entender el tema de los espacios de nombres mucho más claramente. Gracias. Cuando vi el código de Jez , lo intenté porque parecía una solución mejor de la que había programado. Descubrí algunas deficiencias con eso, sin embargo. Tal como está escrito, solo se ve en el nodo raíz (pero los espacios de nombres se pueden enumerar en cualquier lugar) y no maneja los espacios de nombres predeterminados. Traté de abordar estos problemas modificando su código, pero fue en vano.
Aquí está mi versión de esa función. Utiliza expresiones regulares para encontrar las asignaciones del espacio de nombres en todo el archivo; trabaja con espacios de nombres predeterminados, dándoles el prefijo arbitrario ''ns''; y maneja múltiples ocurrencias del mismo espacio de nombres.
private XmlNamespaceManager CreateNamespaceManagerForDocument(XmlDocument document)
{
var nsMgr = new XmlNamespaceManager(document.NameTable);
// Find and remember each xmlns attribute, assigning the ''ns'' prefix to default namespaces.
var nameSpaces = new Dictionary<string, string>();
foreach (Match match in new Regex(@"xmlns:?(.*?)=([/x22/x27])(.+?)/2").Matches(document.OuterXml))
nameSpaces[match.Groups[1].Value + ":" + match.Groups[3].Value] = match.Groups[1].Value == "" ? "ns" : match.Groups[1].Value;
// Go through the dictionary, and number non-unique prefixes before adding them to the namespace manager.
var prefixCounts = new Dictionary<string, int>();
foreach (var namespaceItem in nameSpaces)
{
var prefix = namespaceItem.Value;
var namespaceURI = namespaceItem.Key.Split('':'')[1];
if (prefixCounts.ContainsKey(prefix))
prefixCounts[prefix]++;
else
prefixCounts[prefix] = 0;
nsMgr.AddNamespace(prefix + prefixCounts[prefix].ToString("#;;"), namespaceURI);
}
return nsMgr;
}
Por lo que puedo decir, no hay una buena razón para que necesite definir manualmente un XmlNamespaceManager
para obtener nodos abc
-prefixed si tiene un documento como este:
<itemContainer xmlns:abc="http://abc.com" xmlns:def="http://def.com">
<abc:nodeA>...</abc:nodeA>
<def:nodeB>...</def:nodeB>
<abc:nodeC>...</abc:nodeC>
</itemContainer>
Microsoft simplemente no pudo molestarse en escribir algo para detectar que xmlns:abc
ya se había especificado en un nodo padre. Podría estar equivocado, y si es así, me gustaría recibir comentarios sobre esta respuesta para poder actualizarla.
Sin embargo, esta publicación del blog parece confirmar mi sospecha. Básicamente, dice que necesita definir manualmente un XmlNamespaceManager
y iterar manualmente a través de los atributos xmlns:
agregando cada uno al administrador del espacio de nombres. No sé por qué Microsoft no pudo hacer esto automáticamente.
Aquí hay un método que creé basado en esa publicación de blog para generar automáticamente un XmlNamespaceManager
basado en los xmlns:
atributos de un XmlDocument
fuente:
/// <summary>
/// Creates an XmlNamespaceManager based on a source XmlDocument''s name table, and prepopulates its namespaces with any ''xmlns:'' attributes of the root node.
/// </summary>
/// <param name="sourceDocument">The source XML document to create the XmlNamespaceManager for.</param>
/// <returns>The created XmlNamespaceManager.</returns>
private XmlNamespaceManager createNsMgrForDocument(XmlDocument sourceDocument)
{
XmlNamespaceManager nsMgr = new XmlNamespaceManager(sourceDocument.NameTable);
foreach (XmlAttribute attr in sourceDocument.SelectSingleNode("/*").Attributes)
{
if (attr.Prefix == "xmlns")
{
nsMgr.AddNamespace(attr.LocalName, attr.Value);
}
}
return nsMgr;
}
Y lo uso así:
XPathNavigator xNav = xmlDoc.CreateNavigator();
XPathNodeIterator xIter = xNav.Select("//abc:NodeC", createNsMgrForDocument(xmlDoc));
Yo respondo al punto 1:
Establecer un espacio de nombre predeterminado para un documento XML todavía significa que los nodos, incluso sin un prefijo de espacio de nombre, es decir:
<rootNode xmlns="http://someplace.org">
<nodeName>Some Text Here</nodeName>
</rootNode>
ya no están en el espacio de nombres "vacío". Todavía necesita alguna forma de referenciar estos nodos usando XPath, por lo que crea un prefijo para referenciarlos, incluso si está "compuesto".
Para responder el punto 2:
<rootNode xmlns:cde="http://someplace.org" xmlns:feg="http://otherplace.net">
<cde:nodeName>Some Text Here</cde:nodeName>
<feg:nodeName>Some Other Value</feg:nodeName>
<feg:otherName>Yet Another Value</feg:otherName>
</rootNode>
Internamente en el documento de la instancia, los nodos que residen en un espacio de nombres se almacenan con su nombre de nodo y su nombre de espacio de nombre largo, se llama (en el lenguaje W3C) un nombre expandido .
Por ejemplo, <cde:nodeName>
se almacena esencialmente como <http://someplace.org:nodeName>
. Un prefijo de espacio de nombres es una conveniencia arbitraria para los humanos, de modo que cuando escribimos XML o tenemos que leerlo no tenemos que hacer esto:
<rootNode>
<http://someplace.org:nodeName>Some Text Here</http://someplace.org:nodeName>
<http://otherplace.net:nodeName>Some Other Value</http://otherplace.net:nodeName>
<http://otherplace.net:otherName>Yet Another Value</http://otherplace.net:otherName>
</rootNode>
Cuando se busca un documento XML, no se busca por el prefijo amigable, la búsqueda se realiza por URI de espacio de nombres, por lo que debe informar a XPath sobre sus espacios de nombres a través de una tabla de espacio de nombres pasada usando XmlNamespaceManager
.