Is there a way to search an XDocument without knowing the namespace? I have a process that logs all SOAP requests and encrypts the sensitive data. I want to find any elements based on name. Something like, give me all elements where the name is CreditCard. I don't care what the namespace is.
My problem seems to be with LINQ and requiring a xml namespace.
I have other processes that retrieve values from XML, but I know the namespace for these other process.
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");
I really want to have the ability to search xml without knowing about namespaces, something like this:
XDocument xDocument = XDocument.Load(@"C:\temp\Packet.xml");
var elements = xDocument.Root
.DescendantsAndSelf()
.Elements()
.Where(d => d.Name == "CreditCardNumber")
This will not work because I don't know the namespace beforehand at compile time.
How can this be done?
<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/">
开发者_StackOverflow<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>
...
As Adam precises in the comment, XName are convertible to a string, but that string requires the namespace when there is one. That's why the comparison of .Name to a string fails, or why you can't pass "Person" as a parameter to the XLinq Method to filter on their name.
XName consists of a prefix (the Namespace) and a LocalName. The local name is what you want to query on if you are ignoring namespaces.
Thank you Adam :)
You can't put the Name of the node as a parameter of the .Descendants() method, but you can query that way :
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 : bad copy/past from my test :)
var persons = from p in doc.Descendants()
where p.Name.LocalName == "Person"
select p;
foreach (var p in persons)
{
Console.WriteLine(p);
}
That works for me...
You could take the namespace from the root-element:
XDocument xDocument = XDocument.Load(@"C:\temp\Packet.xml");
var ns = xDocument.Root.Name.Namespace;
Now you can get all desired elements easily using the plus-operator:
root.Elements(ns + "CreditCardNumber")
I think I found what I was looking for. You can see in the following code I do the evaluation Element.Name.LocalName == "CreditCardNumber"
. This seemed to work in my tests. I'm not sure if it's a best practice, but I'm going to use it.
XDocument xDocument = XDocument.Load(@"C:\temp\Packet.xml");
var elements = xDocument.Root.DescendantsAndSelf().Elements().Where(d => d.Name.LocalName == "CreditCardNumber");
Now I have elements where I can encrypt the values.
If anyone has a better solution, please provide it. Thanks.
There's a couple answers with extension methods that have been deleted. Not sure why. Here's my version that works for my needs.
public static class XElementExtensions
{
public static XElement ElementByLocalName(this XElement element, string localName)
{
return element.Descendants().FirstOrDefault(e => e.Name.LocalName == localName && !e.IsEmpty);
}
}
The IsEmpty
is to filter out nodes with x:nil="true"
There may be additional subtleties - so use with caution.
If your XML documents always defines the namespace in the same node (Request
node in the two examples given), you can determine it by making a query and seeing what namespace the result has:
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;
}
I a suffering from a major case of "I know that is the solution, but I am disappointed that that is the solution"... I recently wrote a query like the one below (which I will shortly replace, but it has educational value):
var result = xdoc.Descendants("{urn:schemas-microsoft-com:rowset}data")
.FirstOrDefault()?
.Descendants("{#RowsetSchema}row");
If I remove the namespaces from the XML, I can write the same query like this:
var result = xdoc.Descendants("data")
.FirstOrDefault()?
.Descendants("row");
I plan to write my own extension methods that should allow me to leave the namespaces alone and search for nodes like this:
var result = xdoc.Descendants("rs:data")
.FirstOrDefault()?
.Descendants("z:row");
//'rs:' {refers to urn:schemas-microsoft-com:rowset}
//'z:' {refers to xmlns:z=#RowsetSchema}
My comments just below the code point to how I would like to hide the ugliness of the solution in an Extension Methods library. Again, I'm aware of the solutions posted earlier - but I wish the API itself handled this more fluently. (See what I did there?)
Just use the Descendents method:
XDocument doc = XDocument.Load(filename);
String[] creditCards = (from creditCardNode in doc.Root.Descendents("CreditCardNumber")
select creditCardNode.Value).ToArray<string>();
精彩评论