Wyszukaj XDocument za pomocą LINQ bez znajomości przestrzeni nazw


81

Czy istnieje sposób na przeszukiwanie XDocument bez znajomości przestrzeni nazw? Mam proces, który rejestruje wszystkie żądania SOAP i szyfruje poufne dane. Chcę znaleźć wszystkie elementy na podstawie nazwy. Coś w stylu, podaj mi wszystkie elementy, na których nazwa to CreditCard. Nie obchodzi mnie, czym jest przestrzeń nazw.

Wydaje się, że mój problem dotyczy LINQ i wymaga przestrzeni nazw xml.

Mam inne procesy, które pobierają wartości z XML, ale znam przestrzeń nazw dla tych innych procesów.

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

Naprawdę chcę mieć możliwość wyszukiwania xml bez wiedzy o przestrzeniach nazw, coś takiego:

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

To nie zadziała, ponieważ nie znam wcześniej przestrzeni nazw w czasie kompilacji.

Jak można to zrobić?

<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>      
        ...

Sprawdź tę odpowiedź z innego pytania: stackoverflow.com/questions/934486/ ...
— MonkeyWrench

Odpowiedzi:


90

Jak Adam precyzuje w komentarzu, XName można zamienić na ciąg, ale ten ciąg wymaga przestrzeni nazw, jeśli taka istnieje. Dlatego porównanie .Name z łańcuchem nie powiedzie się lub dlaczego nie można przekazać „Person” jako parametru do metody XLinq, aby przefiltrować ich nazwę.
XName składa się z prefiksu (przestrzeni nazw) i nazwy lokalnej. Nazwa lokalna jest tym, o co chcesz zapytać, jeśli ignorujesz przestrzenie nazw.
Dziękuję Adam :)

Nie możesz umieścić nazwy węzła jako parametru metody .Descendants (), ale możesz zapytać w ten sposób:

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

EDYCJA: zła kopia / przeszłość z mojego testu :)

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

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

To działa dla mnie ...


5
Może pomóc wyjaśnienie, dlaczego twoja odpowiedź jest taka, jaka jest: Name to XName, a XName po prostu można zamienić na ciąg, więc porównanie .Name z ciągiem kończy się niepowodzeniem z zapytaniem osoby zadającej pytanie. XName składa się z prefiksu i nazwy lokalnej, a nazwa lokalna jest tym, o co chcesz zapytać, jeśli ignorujesz przestrzenie nazw.
— Adam Sills,

to było w komentarzu, który zamieściłem w odpowiedzi Somerockstar. Mogę to dodać dla jasności, masz rację
— Stéphane

Wielkie dzięki za szybką pomoc. Mam nadzieję, że pomoże to komuś innemu.
— Mike Barlow - BarDev

Mam nadzieję, że utknąłem na tym samym problemie po raz pierwszy używając XLinq :)
— Stéphane

1
@ MikeBarlow-BarDev tak się stało ;-)
— Simon_Weaver

88

Możesz wziąć przestrzeń nazw z elementu głównego:

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

Teraz możesz łatwo uzyskać wszystkie pożądane elementy za pomocą operatora plus:

root.Elements(ns + "CreditCardNumber")

Wydaje się, że jest to lepsza odpowiedź, ponieważ nadal umożliwia korzystanie z większości LINQoperacji.
— Ehtesh Choudhury

6
Ta odpowiedź jest akceptowalna tylko wtedy, gdy żaden z elementów nie znajduje się w innej przestrzeni nazw niż dokument główny. Tak, łatwo jest poznać przestrzeń nazw, jeśli po prostu zapytasz o nią dokument główny, ale trudniej jest zapytać o elementy o danej nazwie, niezależnie od przestrzeni nazw, w której sam element się znajduje. Dlatego uważam, że odpowiedź polega na użyciu XElement. Name.LocalName (zwykle przez linq) są bardziej uogólnione.
— Caleb Holt

Ta odpowiedź nie jest wystarczająco ogólna.
— ceztko

14

Myślę, że znalazłem to, czego szukałem. Możesz zobaczyć w poniższym kodzie, że wykonuję ocenę Element.Name.LocalName == "CreditCardNumber". To wydawało się działać w moich testach. Nie jestem pewien, czy to najlepsza praktyka, ale zamierzam z niej skorzystać.

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

Teraz mam elementy, w których mogę zaszyfrować wartości.

Jeśli ktoś ma lepsze rozwiązanie, podaj je. Dzięki.


To idealne rozwiązanie, jeśli nie znasz przestrzeni nazw ani nie przejmujesz się nią. Dziękuję Ci!
— SeriousM

2

Jeśli twoje dokumenty XML zawsze definiują przestrzeń nazw w tym samym węźle ( Requestwęźle w dwóch podanych przykładach), możesz to określić, wykonując zapytanie i sprawdzając, jaką przestrzeń nazw ma wynik:

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

Istnieje kilka odpowiedzi z metodami rozszerzającymi, które zostały usunięte. Nie pewny dlaczego. Oto moja wersja, która działa na moje potrzeby.

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

Służy IsEmptydo filtrowania węzłów za pomocąx:nil="true"

Mogą występować dodatkowe subtelności - więc używaj ich ostrożnie.


Śliczny! Dzięki Simon. Chciałbym niemal powiedzieć, że to powinna być tylko poprawna odpowiedź .... jeśli robisz to raz potem będziesz robił to 100 razy i wszystkie inne odpowiedzi są dość niezgrabne w porównaniu do: el.ElementByLocalName ( „foo”) .
— Tim Cooper

-7

Po prostu użyj metody Descendents:

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

3
to nie zadziała, ponieważ parametr descendant pyta o XName, a XName jest w tym miejscu poprzedzony przestrzenią nazw.
— Stéphane
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.