Buscar XDocument usando LINQ sin conocer el espacio de nombres


81

¿Hay alguna forma de buscar un XDocument sin conocer el espacio de nombres? Tengo un proceso que registra todas las solicitudes SOAP y encripta los datos confidenciales. Quiero encontrar cualquier elemento basado en el nombre. Algo como, dame todos los elementos donde el nombre es CreditCard. No me importa cuál sea el espacio de nombres.

Mi problema parece estar en LINQ y requiere un espacio de nombres xml.

Tengo otros procesos que recuperan valores de XML, pero conozco el espacio de nombres para estos otros procesos.

XDocument xDocument = XDocument.Load(@"C:\temp\Packet.xml");
XNamespace xNamespace = "http://CompanyName.AppName.Service.Contracts";

var elements = xDocument.Root
                        .DescendantsAndSelf()
                        .Elements()
                        .Where(d => d.Name == xNamespace + "CreditCardNumber");

Realmente quiero tener la capacidad de buscar xml sin conocer los espacios de nombres, algo como esto:

XDocument xDocument = XDocument.Load(@"C:\temp\Packet.xml");
var elements = xDocument.Root
                        .DescendantsAndSelf()
                        .Elements()
                        .Where(d => d.Name == "CreditCardNumber")

Esto no funcionará porque no conozco el espacio de nombres de antemano en el momento de la compilación.

¿Cómo se puede hacer esto?

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Request xmlns="http://CompanyName.AppName.Service.ContractA">
        <Person>
            <CreditCardNumber>83838</CreditCardNumber>
            <FirstName>Tom</FirstName>
            <LastName>Jackson</LastName>
        </Person>
        <Person>
            <CreditCardNumber>789875</CreditCardNumber>
            <FirstName>Chris</FirstName>
            <LastName>Smith</LastName>
        </Person>
        ...

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Request xmlns="http://CompanyName.AppName.Service.ContractsB">
        <Transaction>
            <CreditCardNumber>83838</CreditCardNumber>
            <TransactionID>64588</FirstName>
        </Transaction>      
        ...

Consulte esta respuesta de otra pregunta: stackoverflow.com/questions/934486/…
MonkeyWrench

Respuestas:


90

Como Adam precisa en el comentario, XName se puede convertir en una cadena, pero esa cadena requiere el espacio de nombres cuando hay uno. Es por eso que la comparación de .Name con una cadena falla, o por qué no puede pasar "Persona" como parámetro al Método XLinq para filtrar por su nombre.
XName consta de un prefijo (el espacio de nombres) y un LocalName. El nombre local es lo que desea consultar si ignora los espacios de nombres.
Gracias Adam :)

No puede poner el Nombre del nodo como parámetro del método .Descendants (), pero puede consultar de esa manera:

var doc= XElement.Parse(
@"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
  <Request xmlns=""http://CompanyName.AppName.Service.ContractA"">
    <Person>
        <CreditCardNumber>83838</CreditCardNumber>
        <FirstName>Tom</FirstName>
        <LastName>Jackson</LastName>
    </Person>
    <Person>
        <CreditCardNumber>789875</CreditCardNumber>
        <FirstName>Chris</FirstName>
        <LastName>Smith</LastName>
    </Person>
   </Request>
   </s:Body>
</s:Envelope>");

EDITAR: mala copia / pasado de mi prueba :)

var persons = from p in doc.Descendants()
              where p.Name.LocalName == "Person"
              select p;

foreach (var p in persons)
{
    Console.WriteLine(p);
}

Funciona para mi...


5
Podría ayudar a dar una explicación de por qué su respuesta es como es: Name es un XName, y XName resulta ser convertible en una cadena, por lo que la comparación de .Name con una cadena falla con la consulta del autor de la pregunta. XName consta de un prefijo y un nombre local, y el nombre local es lo que desea consultar si ignora los espacios de nombres.
Adam Sills

eso fue en el comentario que he puesto en la respuesta de somerockstar. Puedo agregar esto para mayor claridad, tiene razón
Stéphane

Muchas gracias por la ayuda rápida. Con suerte, esto ayudará a alguien más.
Mike Barlow - BarDev

Espero que así sea, me quedé atascado en el mismo problema la primera vez que usé XLinq :)
Stéphane

1
@ MikeBarlow-BarDev lo hizo ;-)
Simon_Weaver

88

Puede tomar el espacio de nombres del elemento raíz:

XDocument xDocument = XDocument.Load(@"C:\temp\Packet.xml");
var ns = xDocument.Root.Name.Namespace;

Ahora puede obtener todos los elementos deseados fácilmente usando el operador más:

root.Elements(ns + "CreditCardNumber")

Esta parece ser la mejor respuesta, ya que aún le permite usar la mayoría de las LINQoperaciones.
Ehtesh Choudhury

6
Esta respuesta solo es aceptable si ninguno de los elementos se encuentra en un espacio de nombres diferente al del documento raíz. Sí, es fácil conocer el espacio de nombres si solo lo solicita al documento raíz, pero es más complicado pedir cualquier elemento de un nombre de pila, independientemente del espacio de nombres en el que se encuentre el elemento. Por eso considero que la respuesta está usando XElement. Name.LocalName (generalmente a través de linq) son más generalizados.
Caleb Holt

Esta respuesta no es lo suficientemente general.
ceztko

14

Creo que encontré lo que buscaba. Puedes ver en el siguiente código que hago la evaluación Element.Name.LocalName == "CreditCardNumber". Esto pareció funcionar en mis pruebas. No estoy seguro de si es una buena práctica, pero la usaré.

XDocument xDocument = XDocument.Load(@"C:\temp\Packet.xml");
var elements = xDocument.Root.DescendantsAndSelf().Elements().Where(d => d.Name.LocalName == "CreditCardNumber");

Ahora tengo elementos donde puedo cifrar los valores.

Si alguien tiene una mejor solución, por favor proporcione. Gracias.


Esa es una solución perfecta si no conoce el espacio de nombres ni le importa. ¡Gracias!
SeriousM

2

Si sus documentos XML siempre definen el espacio de nombres en el mismo nodo ( Requestnodo en los dos ejemplos dados), puede determinarlo haciendo una consulta y viendo qué espacio de nombres tiene el resultado:

XDocument xDoc = XDocument.Load("filename.xml");
//Initial query to get namespace:
var reqNodes = from el in xDoc.Root.Descendants()
               where el.Name.LocalName == "Request"
               select el;
foreach(var reqNode in reqNodes)
{
    XNamespace xns = reqNode.Name.Namespace;
    //Queries making use of namespace:
    var person = from el in reqNode.Elements(xns + "Person")
                 select el;
}

2

Hay un par de respuestas con métodos de extensión que se han eliminado. No estoy seguro de por qué. Aquí está mi versión que funciona para mis necesidades.

public static class XElementExtensions
{
    public static XElement ElementByLocalName(this XElement element, string localName)
    {
        return element.Descendants().FirstOrDefault(e => e.Name.LocalName == localName && !e.IsEmpty);
    }
}

El IsEmptyes filtrar nodos conx:nil="true"

Puede haber sutilezas adicionales, así que utilícelo con precaución.


¡Encantador! Gracias Simon. Yo casi diría esto debería ser la respuesta correcta única .... si usted está haciendo esto una vez y luego se va a realizar 100 veces y todas las otras respuestas son bastante torpe en comparación con: el.ElementByLocalName ( "foo") .
Tim Cooper

-7

Simplemente use el método Descendents:

XDocument doc = XDocument.Load(filename);
String[] creditCards = (from creditCardNode in doc.Root.Descendents("CreditCardNumber")
                        select creditCardNode.Value).ToArray<string>();

3
eso no funcionará ya que el parámetro descendiente solicita un XName, y el XName tiene como prefijo un espacio de nombres aquí.
Stéphane
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.