Почему не работает цикл (не обходится второй узел)?

Подскажите пожалуйста где ошибка! Вот фрагмент XML:

<MessageInfo MessageType="StartOfExtrajudicialBankruptcy">
<StartOfExtrajudicialBankruptcy>
  <CreditorsNonFromEntrepreneurship>
    <MonetaryObligations>
      <MonetaryObligation>
        <CreditorName>ао тинькофф банк</CreditorName>
        <CreditorRegion>г. Москва</CreditorRegion>
        <CreditorLocation>123060 г. москва 1-й волоколамский проезд 10</CreditorLocation>
        <Content>кредитный договор</Content>
        <Basis>кредитный договор №0219164910 от 08.09.2016</Basis>
        <TotalSum>220449.4</TotalSum>
        <DebtSum>220449.4</DebtSum>
        <PenaltySum xsi:nil="true" />
      </MonetaryObligation>
      <MonetaryObligation>
        <CreditorName>поа сбербанк</CreditorName>
        <CreditorRegion>Рязанская область</CreditorRegion>
        <CreditorLocation>рязань ул. пожалостина, 19</CreditorLocation>
        <Content>кредитный договор</Content>
        <Basis>кредитный договор</Basis>
        <TotalSum>46590.94</TotalSum>
        <DebtSum>46590.94</DebtSum>
        <PenaltySum xsi:nil="true" />
      </MonetaryObligation>
    </MonetaryObligations>
    <ObligatoryPayments />
  </CreditorsNonFromEntrepreneurship>
  <IsIndividualEntrepreneur>false</IsIndividualEntrepreneur>
  <CreditorsFromEntrepreneurship>
    <MonetaryObligations />
    <ObligatoryPayments />
  </CreditorsFromEntrepreneurship>
  <Banks />
  <TaxOffices />
</StartOfExtrajudicialBankruptcy>

Вот код:

XmlNode MonetaryObligation = messageRoot.SelectSingleNode("//MessageInfo/StartOfExtrajudicialBankruptcy/CreditorsNonFromEntrepreneurship/MonetaryObligations/MonetaryObligation");
                     
foreach (XmlNode childnode in MonetaryObligation.ChildNodes) {
    if (childnode.Name == "CreditorName") Console.WriteLine($"Банк:  {childnode.InnerText}");
    if (childnode.Name == "TotalSum")     Console.WriteLine($"Сумма: {childnode.InnerText}");
}

Console.WriteLine(" ------end-----");

Вывод:

Банк:  ао тинькофф банк
Сумма: 220449.4
------end-----

Почему выводится информация только по первому банку?


Ответы (2 шт):

Автор решения: Aziz Umarov

Сделайте так

XmlNode MonetaryObligations = messageRoot.SelectSingleNode("//MessageInfo/StartOfExtrajudicialBankruptcy/CreditorsNonFromEntrepreneurship/MonetaryObligations");
                     
foreach (XmlNode MonetaryObligation in MonetaryObligations.ChildNodes) {
  foreach (XmlNode childnode in MonetaryObligation.ChildNodes) {
    if (childnode.Name == "CreditorName") Console.WriteLine($"Банк:  {childnode.InnerText}");
    if (childnode.Name == "TotalSum")     Console.WriteLine($"Сумма: {childnode.InnerText}");
  }
}

Console.WriteLine(" ------end-----");

Во избежание второго цикла попробуйте его переписать так

Console.WriteLine($"Банк:  {MonetaryObligation["CreditorName"].InnerText}");
 
Console.WriteLine($"Сумма: {MonetaryObligation["TotalSum"].InnerText}");
→ Ссылка
Автор решения: EvgeniyZ

У вас изначально неверная логика работы, ибо вы ищете первый попавшийся MonetaryObligation, а потом "пляшете" от него, взят все дочерние элементы, зачем эти лишние действия, если можно сразу взять то, что вы хотите. Давайте так и поступим:

  • Меняем .SelectSingleNode() на .SelectNodes(), ибо он берет все найденные элементы, соответствующие данному XPath. Получаем такое:

    var MonetaryObligation = messageRoot.SelectNodes("//MessageInfo/StartOfExtrajudicialBankruptcy/CreditorsNonFromEntrepreneurship/MonetaryObligations/MonetaryObligation");
    
  • Далее убираем .ChildNodes у foreach, ибо предыдущий шаг нам уже выдал коллекцию нужных нам объектов.

    foreach (XmlNode childnode in MonetaryObligation)
    
  • Теперь вывод, меняем его на примерно такой вид:

    Console.WriteLine($"Банк: {childnode["CreditorName"]?.InnerText}");
    Console.WriteLine($"Сумма: {childnode["TotalSum"]?.InnerText}");
    

    Думаю, вы уже поняли, что у нас на руках сейчас нода <MonetaryObligation>, из нее то мы и получаем необходимые значения, просто, по ключу, а знак ?(вопроса) не даст выполниться правой части кода, если такого значения нету и выведет просто пустоту.

Собственно весь код будет таким:

var MonetaryObligation = messageRoot.SelectNodes("//MessageInfo/StartOfExtrajudicialBankruptcy/CreditorsNonFromEntrepreneurship/MonetaryObligations/MonetaryObligation");

foreach (XmlNode childnode in MonetaryObligation)
{
    Console.WriteLine($"Банк: {childnode["CreditorName"]?.InnerText}");
    Console.WriteLine($"Сумма: {childnode["TotalSum"]?.InnerText}");
}

Console.WriteLine(" ------end-----");

Немного полезного.

  1. Если вы в XPath пишете //, то это означает "искать по всему документу и всем нодам", да, думаю вы уже догадались, что не обязательно писать весь путь и весь XPath сводится к простому "//MonetaryObligation".

  2. В C# есть как по мне, более продвинутая и удобная версия парсера XML, зовется XDocument (из System.Xml.Linq) и там вам вообще XPath не понадобиться, ибо если переписать код выше на него, то выйдет нечто такое:

    foreach (var obligation in xDoc.Descendants("MonetaryObligation"))
    {
        Console.WriteLine($"Банк: {obligation.Element("CreditorName")?.Value}");
        Console.WriteLine($"Сумма: {obligation.Element("TotalSum")?.Value}");
    }
    

    Как видите, ни единой строчки XPath, а лишь простые методы .Element(), .Elements(), .Descendants(), да и значение сразу .Value. Это конечно дело вкусов и на этом примере особо не показать что с ним можно, но как вариант, знайте.

    Ну а вообще, я вам настоятельно рекомендую при работе с подобными объектами, в первую очередь, искать способы их десериализации/сериализации, ибо когда у вас данные храниться в классе, это не только удобно, но и сберегает кучу нерв и сил в дальнейшем, ибо если вдруг в вашем XML измениться хоть одно название, то вы будете искать во всем проекте где вы его используете в виде простой строки, а имея класс, вам надо лишь поменять название нужного свойство и во всех других местах вы сразу получите ошибку о несущесвующем имени.

→ Ссылка