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

时间:2010-04-09 21:09:48

标签: c# linq-to-xml

有没有办法在不知道命名空间的情况下搜索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>      
        ...

7 个答案:

答案 0 :(得分:82)

当Adam在注释中进行精确处理时,XName可以转换为字符串,但是当有一个字符串时,该字符串需要命名空间。这就是为什么.Name与字符串的比较失败,或者为什么你不能将“Person”作为参数传递给XLinq方法来过滤他们的名字。
XName由前缀(Namespace)和LocalName组成。如果忽略名称空间,则需要查询本地名称 谢谢Adam:)

您不能将节点的名称作为.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);
}

这对我有用......

答案 1 :(得分:80)

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

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

现在,您可以使用plus-operator轻松获取所有需要的元素:

root.Elements(ns + "CreditCardNumber")

答案 2 :(得分:12)

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

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

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

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

答案 3 :(得分: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;
}

答案 4 :(得分:0)

关于扩展方法的一些答案已被删除。不知道为什么。这是适合我需要的版本。

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"过滤掉节点

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

答案 5 :(得分:0)

我饱受“我知道那是解决方案,但我很失望那个是解决方案”的痛苦……我最近写了一个类似下面的查询(其中我很快就会更换,但它具有教育价值):

var result = xdoc.Descendants("{urn:schemas-microsoft-com:rowset}data")
.FirstOrDefault()?
.Descendants("{#RowsetSchema}row");

如果我从 XML 中删除命名空间,我可以像这样编写相同的查询:

var result = xdoc.Descendants("data")
.FirstOrDefault()?
.Descendants("row");

我打算编写自己的扩展方法,这样我就可以不用命名空间并搜索如下节点:

var result = xdoc.Descendants("rs:data") 
.FirstOrDefault()?
.Descendants("z:row"); 
//'rs:' {refers to urn:schemas-microsoft-com:rowset}
//'z:' {refers to xmlns:z=#RowsetSchema}

我在代码下方的评论指出我希望如何在扩展方法库中隐藏解决方案的丑陋之处。同样,我知道之前发布的解决方案 - 但我希望 API 本身能够更流畅地处理这个问题。 (看看我在那里做了什么?)

答案 6 :(得分:-6)

只需使用后代方法:

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