Tìm kiếm XDocument bằng LINQ mà không cần biết vùng tên


81

Có cách nào để tìm kiếm XDocument mà không cần biết không gian tên không? Tôi có một quy trình ghi lại tất cả các yêu cầu SOAP và mã hóa dữ liệu nhạy cảm. Tôi muốn tìm bất kỳ yếu tố nào dựa trên tên. Một cái gì đó như, cung cấp cho tôi tất cả các yếu tố có tên là CreditCard. Tôi không quan tâm không gian tên là gì.

Vấn đề của tôi dường như là với LINQ và yêu cầu một không gian tên xml.

Tôi có các quy trình khác truy xuất giá trị từ XML, nhưng tôi biết không gian tên cho quy trình khác này.

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

Tôi thực sự muốn có khả năng tìm kiếm xml mà không cần biết về không gian tên, đại loại như sau:

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

Điều này sẽ không hoạt động vì tôi không biết trước không gian tên tại thời điểm biên dịch.

Điều này có thể giải quyết như thế nào?

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

Kiểm tra câu trả lời này từ một câu hỏi khác: stackoverflow.com/questions/934486/…
MonkeyWrench

Câu trả lời:


90

Như Adam đã xác nhận trong nhận xét, XName có thể chuyển đổi thành một chuỗi, nhưng chuỗi đó yêu cầu không gian tên khi có một. Đó là lý do tại sao việc so sánh Tên với một chuỗi không thành công hoặc tại sao bạn không thể chuyển "Người" làm tham số cho Phương thức XLinq để lọc tên của họ.
XName bao gồm một tiền tố (Không gian tên) và một LocalName. Tên địa phương là những gì bạn muốn truy vấn nếu bạn đang bỏ qua không gian tên.
Cảm ơn Adam :)

Bạn không thể đặt Tên của nút làm tham số của phương thức .Descendants (), nhưng bạn có thể truy vấn theo cách đó:

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

CHỈNH SỬA: bản sao / quá khứ xấu từ bài kiểm tra của tôi :)

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

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

Nó ổn với tôi...


5
Có thể giúp giải thích lý do tại sao câu trả lời của bạn lại như vậy: Tên là một XName và XName chỉ xảy ra để chuyển đổi thành một chuỗi, vì vậy việc so sánh .Name với một chuỗi không thành công với truy vấn của người đặt câu hỏi. XName bao gồm một tiền tố và một tên cục bộ, và tên cục bộ là những gì bạn muốn truy vấn nếu bạn đang bỏ qua không gian tên.
Adam Sills

đó là trong nhận xét mà tôi đã đưa vào câu trả lời của một số sao mai. Tôi có thể thêm điều này cho rõ ràng, bạn nói đúng
Stéphane

Cảm ơn rất nhiều vì sự giúp đỡ nhanh chóng. Hy vọng rằng điều này sẽ giúp một người khác.
Mike Barlow - BarDev

Hy vọng vậy, tôi đã gặp vấn đề tương tự lần đầu tiên sử dụng XLinq :)
Stéphane

1
@ MikeBarlow-BarDev nó đã làm ;-)
Simon_Weaver

88

Bạn có thể lấy không gian tên từ phần tử gốc:

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

Giờ đây, bạn có thể dễ dàng nhận được tất cả các phần tử mong muốn bằng cách sử dụng toán tử dấu cộng:

root.Elements(ns + "CreditCardNumber")

Đây có vẻ là câu trả lời tốt hơn, vì nó vẫn cho phép bạn sử dụng phần lớn các LINQthao tác.
Ehtesh Choudhury

6
Câu trả lời này chỉ được chấp nhận nếu không có phần tử nào nằm trong không gian tên khác với tài liệu gốc. Có, thật dễ dàng để biết không gian tên nếu bạn chỉ hỏi tài liệu gốc về nó, nhưng khó hơn khi yêu cầu bất kỳ phần tử nào của một tên nhất định, bất kể không gian tên mà bản thân phần tử đó có thể nằm trong đó. Đó là lý do tại sao tôi cho rằng câu trả lời đang sử dụng XElement. Name.LocalName (thường thông qua linq) được khái quát hơn.
Caleb Holt

Câu trả lời này không đủ chung chung.
ceztko

14

Tôi nghĩ rằng tôi đã tìm thấy những gì tôi đang tìm kiếm. Bạn có thể xem trong đoạn mã sau đây tôi thực hiện đánh giá Element.Name.LocalName == "CreditCardNumber". Điều này dường như hoạt động trong các thử nghiệm của tôi. Tôi không chắc đó có phải là phương pháp hay nhất hay không, nhưng tôi sẽ sử dụng nó.

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

Bây giờ tôi có các phần tử mà tôi có thể mã hóa các giá trị.

Nếu ai có giải pháp tốt hơn, xin vui lòng cung cấp cho nó. Cảm ơn.


Đó là một giải pháp hoàn hảo nếu bạn không biết không gian tên cũng như không quan tâm đến nó. Cảm ơn bạn!
SeriousM

2

Nếu tài liệu XML của bạn luôn xác định không gian tên trong cùng một nút ( Requestnút trong hai ví dụ đã cho), bạn có thể xác định nó bằng cách thực hiện truy vấn và xem kết quả có không gian tên nào:

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

Có một số câu trả lời với các phương thức mở rộng đã bị xóa. Không chắc chắn lý do tại sao. Đây là phiên bản của tôi phù hợp với nhu cầu của tôi.

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

Các IsEmptylà để lọc ra các nút vớix:nil="true"

Có thể có thêm sự tinh tế - vì vậy hãy thận trọng khi sử dụng.


Đáng yêu! Cảm ơn Simon. Tôi gần như sẽ nói rằng đây phải là câu trả lời đúng duy nhất .... nếu bạn làm điều này một lần thì bạn sẽ làm nó 100 lần và tất cả các câu trả lời khác khá vụng về so với: el.ElementByLocalName ("foo") .
Tim Cooper

-7

Chỉ cần sử dụng phương pháp Descendents:

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

3
điều đó sẽ không hoạt động vì tham số con yêu cầu một XName và XName được đặt trước bởi một không gian tên ở đây.
Stéphane
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.