在不知道名称空间的情况下使用LINQ搜索XDocument


81

有没有不知道名称空间的搜索XDocument的方法?我有一个记录所有SOAP请求并加密敏感数据的过程。我想根据名称查找任何元素。像这样,给我所有名称为CreditCard的元素。我不在乎命名空间是什么。

我的问题似乎与LINQ有关,并且需要一个xml名称空间。

我还有其他从XML检索值的进程,但是我知道这些其他进程的名称空间。

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

我确实希望能够在不了解名称空间的情况下搜索xml,如下所示:

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

这是行不通的,因为在编译时我不知道名称空间。

如何才能做到这一点?

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

从另一个问题,看看这个答案:stackoverflow.com/questions/934486/...
MonkeyWrench

Answers:


90

正如Adam在评论中指出的那样,XName可转换为字符串,但是如果存在,则该字符串需要命名空间。这就是为什么.Name与字符串的比较失败的原因,或者为什么您不能将“ Person”作为参数传递给XLinq方法来过滤其名称的原因。
XName由前缀(命名空间)和LocalName组成。如果忽略名称空间,则要查询的是本地名称。
谢谢亚当:)

您不能将节点的名称作为.Descendants()方法的参数,但可以通过以下方式查询:

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

编辑: 从我的测试中有错误的复制/粘贴:)

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

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

这对我行得通...


5
可能有助于您解释为什么答案是这样:Name是一个XName,而XName恰好可以转换为字符串,因此将.Name与字符串进行比较失败,并出现问题询问者查询。XName由前缀和本地名称组成,如果忽略名称空间,则要查询的名称是本地名称。
亚当·希尔斯

这就是我在rockrockstar答案中发表的评论。我可以添加此为清楚起见,你是对的
斯特凡

非常感谢您的快速帮助。希望这会帮助其他人。
Mike Barlow-BarDev 2010年

希望如此,我同样的问题卡住了使用XLINQ第一次:)
斯特凡

1
@ MikeBarlow-BarDev它做到了;-)
Simon_Weaver

88

您可以从根元素中获取名称空间:

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

现在,您可以使用plus运算符轻松获得所有所需元素:

root.Elements(ns + "CreditCardNumber")

这似乎是一个更好的答案,因为它仍然可以让您使用大多数LINQ操作。
Ehtesh Choudhury

6
仅当没有一个元素与根文档位于不同的名称空间中时,此答案才可接受。是的,如果只问根文档就很容易知道名称空间,但是无论给定元素本身可能位于哪个名称空间中,都很难询问给定名称的任何元素。这就是为什么我认为答案使用XElement的原因。 Name.LocalName(通常通过linq)更为通用。
卡勒布·霍尔特

这个答案不够笼统。
ceztko

14

我想我找到了想要的东西。您可以在下面的代码中看到我进行的评估Element.Name.LocalName == "CreditCardNumber"。这似乎在我的测试中有效。我不确定这是否是最佳做法,但我将使用它。

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

现在,我有了可以加密值的元素。

如果有人有更好的解决方案,请提供。谢谢。


如果您不知道名称空间也不关心它,那将是一个完美的解决方案。谢谢!
认真的M

2

如果您的XML文档始终在同一节点(Request给定的两个示例中为节点)中定义名称空间,则可以通过查询并查看结果具有的名称空间来确定它:

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

对于已删除的扩展方法,有几个答案。不知道为什么。这是适合我需要的版本。

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

使用IsEmpty过滤掉节点x:nil="true"

可能还有其他细微之处-请谨慎使用。


可爱!谢谢西蒙。我几乎会说这应该是唯一正确的答案。...如果您执行一次,那么您将执行100次,而其他所有答案则与el.ElementByLocalName(“ foo”)相比非常笨拙。
蒂姆·库珀

-7

只需使用Descendents方法:

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

3
因为后裔参数要求输入XName,并且XName在此处以命名空间作为前缀,所以该方法将不起作用。
斯特凡2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.