Поиск в 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>      
        ...
Майк Барлоу - BarDev
источник
Посмотрите этот ответ из другого вопроса: 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>");

РЕДАКТИРОВАТЬ: плохая копия / прошлое из моего теста :)

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 состоит из префикса и локального имени, а локальное имя - это то, что вы хотите запросить, если вы игнорируете пространства имен.
Адам Силлс,
это было в комментарии, который я вставил в ответ Somerockstar. Я могу добавить это для ясности, вы правы
Стефан
Большое спасибо за быструю помощь. Надеюсь, это поможет кому-то другому.
Майк Барлоу - 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");

Теперь у меня есть элементы, в которых я могу зашифровать значения.

Если у кого-то есть лучшее решение, предоставьте его. Благодарю.

Майк Барлоу - BarDev
источник
Это идеальное решение, если вы не знаете пространство имен и не заботитесь о нем. Спасибо!
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;
}
Clivest
источник
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"

Могут быть дополнительные тонкости - поэтому используйте с осторожностью.

Simon_Weaver
источник
Прекрасно! Спасибо, Саймон. Я почти сказал бы, что это должен быть единственный правильный ответ .... если вы сделаете это один раз, вы сделаете это 100 раз, и все остальные ответы довольно неуклюжи по сравнению с: el.ElementByLocalName ("foo") .
Тим Купер
-7

Просто используйте метод потомков:

XDocument doc = XDocument.Load(filename);
String[] creditCards = (from creditCardNode in doc.Root.Descendents("CreditCardNumber")
                        select creditCardNode.Value).ToArray<string>();
Эван Чиу
источник
3
это не сработает, поскольку параметр-потомок запрашивает XName, а перед XName здесь стоит префикс пространства имен.
Стефан