Шукайте XDocument за допомогою LINQ, не знаючи простору імен


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

Відповіді:


90

Як уточнює Адам у коментарі, 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>");

EDIT: погана копія / минуле з мого тесту :)

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

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

Це працює для мене ...


5
Може допомогти пояснити, чому ваша відповідь така: Ім'я - це ім’я XName, а XName просто перетворюється на рядок, тому порівняння .Name із рядком не вдається із запитом запитувача. XName складається з префікса та локального імені, а локальне ім’я - це те, до чого ви хочете зробити запит, якщо ігноруєте простори імен.
Адам Сіллс

це було в коментарі, який я дав у відповіді rockstar. Я можу додати це для наочності, ви маєте рацію
Стефан

Велике спасибі за швидку допомогу. Сподіваємось, це допоможе комусь іншому.
Mike Barlow - BarDev

Сподіваюся, я застряг у тій же проблемі вперше, використовуючи XLinq :)
Стефан,

1
@ MikeBarlow-BarDev це зробило ;-)
Simon_Weaver

88

Ви можете взяти простір імен з кореневого елемента:

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

Тепер ви можете легко отримати всі бажані елементи, використовуючи оператор плюс:

root.Elements(ns + "CreditCardNumber")

Це здається кращою відповіддю, оскільки все одно дозволяє використовувати більшість LINQоперацій.
Ехтеш Чоудхурі,

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

Тепер у мене є елементи, де я можу зашифрувати значення.

Якщо хтось має краще рішення, будь ласка, надайте його. Дякую.


Це ідеальне рішення, якщо ви не знаєте простору імен і не піклуєтесь про нього. Дякую!
SeriousM

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 тут має префікс простору імен.
Стефан,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.