Durchsuchen Sie XDocument mit LINQ, ohne den Namespace zu kennen

79

Gibt es eine Möglichkeit, ein XDocument zu durchsuchen, ohne den Namespace zu kennen? Ich habe einen Prozess, der alle SOAP-Anforderungen protokolliert und die vertraulichen Daten verschlüsselt. Ich möchte alle Elemente finden, die auf dem Namen basieren. Geben Sie mir alle Elemente, bei denen der Name CreditCard lautet. Es ist mir egal, was der Namespace ist.

Mein Problem scheint mit LINQ zu sein und einen XML-Namespace zu erfordern.

Ich habe andere Prozesse, die Werte aus XML abrufen, aber ich kenne den Namespace für diese anderen Prozesse.

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

Ich möchte wirklich die Möglichkeit haben, XML zu durchsuchen, ohne etwas über Namespaces zu wissen.

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

Dies funktioniert nicht, da ich den Namespace zur Kompilierungszeit nicht vorher kenne.

Wie kann das gemacht werden?

<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>      
        ...
Mike Barlow - BarDev
quelle
Überprüfen Sie heraus diese Antwort von einer anderen Frage: stackoverflow.com/questions/934486/…
MonkeyWrench

Antworten:

89

Wie Adam im Kommentar präzisiert, kann XName in eine Zeichenfolge konvertiert werden, für diese Zeichenfolge ist jedoch der Namespace erforderlich, wenn es einen gibt. Aus diesem Grund schlägt der Vergleich von .Name mit einer Zeichenfolge fehl oder Sie können "Person" nicht als Parameter an die XLinq-Methode übergeben, um nach ihrem Namen zu filtern.
XName besteht aus einem Präfix (dem Namespace) und einem LocalName. Der lokale Name ist das, wonach Sie abfragen möchten, wenn Sie Namespaces ignorieren.
Danke Adam :)

Sie können den Namen des Knotens nicht als Parameter der .Descendants () -Methode angeben, aber Sie können auf folgende Weise abfragen:

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

EDIT: schlechte Kopie / Vergangenheit von meinem Test :)

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

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

Das ist für mich in Ordnung...

Stéphane
quelle
5
Könnte helfen, eine Erklärung dafür zu geben, warum Ihre Antwort so ist, wie sie ist: Name ist ein XName, und XName kann zufällig in eine Zeichenfolge konvertiert werden, sodass der Vergleich von .Name mit einer Zeichenfolge mit der Abfrage des Fragestellers fehlschlägt. XName besteht aus einem Präfix und einem lokalen Namen. Der lokale Name ist das, wonach Sie abfragen möchten, wenn Sie Namespaces ignorieren.
Adam Sills
das war in dem kommentar, den ich in somerockstar antwort gegeben habe. Ich kann dies aus Gründen der Klarheit hinzufügen, Sie haben Recht
Stéphane
Vielen Dank für die schnelle Hilfe. Hoffentlich hilft das jemand anderem.
Mike Barlow - BarDev
Ich hoffe, ich habe das erste Mal mit XLinq das gleiche Problem festgestellt :)
Stéphane
1
@ MikeBarlow-BarDev hat es getan ;-)
Simon_Weaver
87

Sie können den Namespace aus dem Root-Element übernehmen:

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

Jetzt können Sie alle gewünschten Elemente einfach mit dem Plus-Operator abrufen:

root.Elements(ns + "CreditCardNumber")
Sascha
quelle
Dies scheint die bessere Antwort zu sein, da Sie damit immer noch die meisten LINQOperationen ausführen können.
Ehtesh Choudhury
6
Diese Antwort ist nur akzeptabel, wenn sich keines der Elemente in einem anderen Namespace als das Stammdokument befindet. Ja, es ist einfach, den Namespace zu kennen, wenn Sie nur das Stammdokument danach fragen, aber es ist schwieriger, nach Elementen eines bestimmten Namens zu fragen, unabhängig davon, in welchem ​​Namespace sich das Element selbst befindet. Deshalb erwäge ich, dass Antworten XElement verwenden. Name.LocalName (normalerweise über linq) sind allgemeiner.
Caleb Holt
Diese Antwort ist nicht allgemein genug.
Ceztko
14

Ich glaube, ich habe gefunden, wonach ich gesucht habe. Sie können im folgenden Code sehen, dass ich die Auswertung mache Element.Name.LocalName == "CreditCardNumber". Dies schien in meinen Tests zu funktionieren. Ich bin mir nicht sicher, ob es eine bewährte Methode ist, aber ich werde sie verwenden.

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

Jetzt habe ich Elemente, mit denen ich die Werte verschlüsseln kann.

Wenn jemand eine bessere Lösung hat, geben Sie diese bitte an. Vielen Dank.

Mike Barlow - BarDev
quelle
Das ist eine perfekte Lösung, wenn Sie den Namespace nicht kennen oder sich nicht darum kümmern. Vielen Dank!
SeriousM
2

Wenn Ihre XML-Dokumente immer den Namespace im selben Knoten definieren ( RequestKnoten in den beiden angegebenen Beispielen), können Sie ihn ermitteln, indem Sie eine Abfrage durchführen und sehen, welchen Namespace das Ergebnis hat:

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;
}
Clivest
quelle
2

Es gibt einige Antworten mit Erweiterungsmethoden, die gelöscht wurden. Nicht sicher warum. Hier ist meine Version, die für meine Bedürfnisse funktioniert.

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

Das IsEmptyist, um Knoten mit herauszufilternx:nil="true"

Es kann zusätzliche Feinheiten geben - verwenden Sie diese daher mit Vorsicht.

Simon_Weaver
quelle
Schön! Danke Simon. Ich würde fast sagen, dass dies die einzig richtige Antwort sein sollte. Wenn Sie dies einmal tun, werden Sie es 100 Mal tun und alle anderen Antworten sind ziemlich ungeschickt im Vergleich zu: el.ElementByLocalName ("foo") .
Tim Cooper
-7

Verwenden Sie einfach die Descendents-Methode:

XDocument doc = XDocument.Load(filename);
String[] creditCards = (from creditCardNode in doc.Root.Descendents("CreditCardNumber")
                        select creditCardNode.Value).ToArray<string>();
Evan Chiu
quelle
3
Dies funktioniert nicht, da der untergeordnete Parameter nach einem XName fragt und dem XName hier ein Namespace vorangestellt ist.
Stéphane