Как перебирать XML с помощью XDocument в.Net

У меня есть большой файл XML, где я беру небольшой фрагмент с помощью ReadFrom() и тогда я получу xmlsnippet который содержит leaf, sas, kir метки в разных положениях (иногда лист вверху сравнивают с кир или наоборот).

Теперь дело в том, что я использую три foreach Цикл, чтобы получить эти значения, что является плохой логикой, и это займет много времени, когда этот фрагмент также большой.

В любом случае я могу использовать один foreach цикл, а затем три if loop внутри foreach, чтобы получить значения?

arr это обычай arraylist

var xdoc = new XDocument(xmlsnippet);
string xml = RemoveAllNamespaces(xdoc.ToString());
foreach (XElement element in XDocument.Parse(xml).Descendants("leaf"))
{
    arr.Add(new Test("leaf", element.Value, 2));
    break;
}
foreach (XElement element in XDocument.Parse(xml).Descendants("sas"))
{
    arr.Add(new Test("sas", element.Value, 2));
    break;
}

foreach (XElement element in XDocument.Parse(xml).Descendants("kir"))
{
    if (element.Value == "0")
        arr.Add(new Test("kir", "90", 2));
    break;
}

1 ответ

Вам нужно только один раз проанализировать этот xmlsnippet (при условии, что он помещается в памяти), а затем использовать XNamespace для определения правильного XElement. Не нужно звонить RemoveAllnamespaces что, я думаю, делает то, что подразумевает его название, и, вероятно, делает это ужасным образом.

Я использовал следующий фрагмент XML в качестве примера ввода, обратите внимание на пространства имен a, b и c:

var xmlsnippet = @"<root xmlns:a=""https://a.example.com"" 
    xmlns:b=""https://b.example.com"" 
    xmlns:c=""https://c.example.com"">
    <child>
    <a:leaf>42</a:leaf>
    <a:leaf>43</a:leaf>
    <a:leaf>44</a:leaf>
    <somenode>
    <b:sas>4242</b:sas>
    <b:sas>4343</b:sas>
    </somenode>
    <other>
    <c:kir>80292</c:kir>
    <c:kir>0</c:kir>
    </other>
    </child>
</root>";

А затем используйте Linq для возврата экземпляра, если ваш класс Test, или null, если элемент не может быть найден. Этот экземпляр класса Test затем добавляется в массив.

var arr = new ArrayList();

var xdoc = XDocument.Parse(xmlsnippet);

// add namespaces
var nsa = (XNamespace) "https://a.example.com";
var nsb = (XNamespace) "https://b.example.com";
var nsc = (XNamespace) "https://c.example.com";

var leaf = xdoc.Descendants(nsa + "leaf").
    Select(elem => new Test("leaf", elem.Value, 2)).FirstOrDefault();
if (leaf != null) {
    arr.Add(leaf);
}
var sas = xdoc.Descendants(nsb + "sas").
    Select(elem => new Test("sas", elem.Value, 2)).FirstOrDefault();
if (sas != null) {
    arr.Add(sas);
}
var kir = xdoc.
    Descendants(nsc + "kir").
    Where(ele => ele.Value == "0").
    Select(elem => new Test("kir", "90", 2)).
    FirstOrDefault();
if (kir != null) {
    arr.Add(kir);
}

Я ожидаю, что это будет наиболее эффективный способ найти эти узлы, если вы хотите использовать XDocument. Если xml действительно большой, вы можете рассмотреть возможность использования XMLReader, но это, вероятно, поможет, только если проблема с памятью.

Если вы хотите сделать это одним LINQ Query, вы можете сделать это:

 var q =  xdoc
    .Descendants()
    .Where(elem => elem.Name.LocalName == "leaf" ||
                   elem.Name.LocalName == "sas" ||
                   elem.Name.LocalName == "kir" && elem.Value == "0" )
    .GroupBy(k=> k.Name.LocalName)
    .Select(k=>
        new Test(
            k.Key, 
            k.Key != "kir"? k.FirstOrDefault().Value: "90",
            2)
    );
 arr.AddRange(q.ToList());

Этот запрос выполняет поиск всех элементов с именем leaf, sas или kir, группирует их по имени элемента, а затем получает первый элемент в каждой группе. Обратите внимание на дополнительную обработку в случае, если имя элемента - kir. Как пункт, где и проекция в Select нужно иметь дело с этим. Возможно, вы захотите протестировать производительность, так как я не уверен, насколько это будет эффективно.

Для полноты здесь приведена версия XmlReader:

var state = FoundElement.NONE; 
using(var xe = XmlReader.Create(new StringReader(xmlsnippet)))
while (xe.Read())
{ 
    // if we have not yet found an specific element
    if (((state & FoundElement.Leaf) != FoundElement.Leaf) && 
       xe.LocalName == "leaf") 
    {
       // add it ... do not change the order of those arguments
       arr.Add(new Test(xe.LocalName, xe.ReadElementContentAsString(), 2));
       // keep track what we already handled.
       state = state | FoundElement.Leaf;
    }
    if (((state & FoundElement.Sas) != FoundElement.Sas) && 
        xe.LocalName == "sas") 
    {
        arr.Add(new Test(xe.LocalName, xe.ReadElementContentAsString(), 2));
        state = state | FoundElement.Sas;
    }
    if (((state & FoundElement.Kir) != FoundElement.Kir) && 
        xe.LocalName == "kir") 
    {
        var localName = xe.LocalName; // we need this ...
        var cnt = xe.ReadElementContentAsString();  // ... because this moves the reader
        if (cnt == "0") {
            arr.Add(new Test(localName, "90", 2));
            state = state | FoundElement.Kir;
        }
    }
}

А вот и перечисление с разными состояниями.

[Flags]
enum FoundElement
{
   NONE =0,
   Leaf = 1,
   Sas = 2,
   Kir = 4
}
Другие вопросы по тегам