Получить дочерний узел на основе атрибута родительского узла

#c# #xml #linq #crystal-reports #linq-to-xml

#c# #xml #linq #crystal-отчеты #linq-to-xml

Вопрос:

У меня есть отчет CrystalReport в формате XML (извините за многословие, я вырезал большинство примеров данных)

 <?xml version="1.0" encoding="UTF-8" ?>
<FormattedReport xmlns = 'urn:crystal-reports:schemas' xmlns:xsi = 'http://www.w3.org/2000/10/XMLSchema-instance'>
<FormattedAreaPair Level="0" Type="Report">
<FormattedAreaPair Level="1" Type="Details">
<FormattedArea Type="Details">
<FormattedSections>
<FormattedSection SectionNumber="0">
<FormattedReportObjects>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:string" FieldName="{AIRCRAFT.Tail Number}"><ObjectName>Field2</ObjectName>
<FormattedValue>C-FBCS</FormattedValue>
<Value>C-FBCS</Value>
</FormattedReportObject>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:string" FieldName="{AIRCRAFT.Type ID}"><ObjectName>Field8</ObjectName>
<FormattedValue>DHC8</FormattedValue>
<Value>DHC8</Value>
</FormattedReportObject>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:unsignedLong" FieldName="{TRIP LEGS.Trip Number}"><ObjectName>Field9</ObjectName>
<FormattedValue>68344</FormattedValue>
<Value>68344.00</Value>
</FormattedReportObject>
</FormattedReportObjects>
</FormattedSection>
</FormattedSections>
</FormattedArea>
</FormattedAreaPair>
<FormattedAreaPair Level="1" Type="Details">
<FormattedArea Type="Details">
<FormattedSections>
<FormattedSection SectionNumber="0">
<FormattedReportObjects>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:string" FieldName="{AIRCRAFT.Tail Number}"><ObjectName>Field2</ObjectName>
<FormattedValue>C-FBCS</FormattedValue>
<Value>C-FBCS</Value>
</FormattedReportObject>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:string" FieldName="{AIRCRAFT.Type ID}"><ObjectName>Field8</ObjectName>
<FormattedValue>DHC8</FormattedValue>
<Value>DHC8</Value>
</FormattedReportObject>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:unsignedLong" FieldName="{TRIP LEGS.Trip Number}"><ObjectName>Field9</ObjectName>
<FormattedValue>68344</FormattedValue>
<Value>68344.00</Value>
</FormattedReportObject>
</FormattedReportObjects>
</FormattedSection>
</FormattedSections>
</FormattedArea>
</FormattedAreaPair>
...
</FormattedAreaPair>
</FormattedReport>
  

Я пытаюсь использовать запрос LINQ to XML для извлечения
Значение узла на основе атрибута FieldName родительского узла и поместить их в объект. Для Value или родительских узлов FormattedReportObject нет уникального атрибута. Пока что вот мой код для этого

 from fs in xDoc.Descendants("FormattedSection")
select new FlightSchedule
{
  AircraftType = from fos in fs.Descendants("FormattedReportObjects")
                 from fo in fs.Descendants("FormattedReportObject")
                 where fo.Attribute("FieldName").Value.Equals("{AIRCRAFT.Type ID}")
                 from e in fo.Element("Value")
                 select e.Value),
  ....
};
  

Я продолжаю получать ошибки:

Выражение типа ‘System.Xml.Linq.XElement’ не допускается в последующем предложении from в выражении запроса с исходным типом ‘System.Коллекции.Общий.IEnumerable’. Ошибка вывода типа при вызове ‘SelectMany’)

или, если я не получаю ошибку, я в конечном итоге ничего не получаю. Я был бы очень признателен за любые предложения по улучшению моего запроса.

Ответ №1:

В вашем коде есть несколько проблем. Во-первых, на что жалуется компилятор, так это, как упоминал @MizardX, на то, что вы используете fo.Element("Value") так, как если бы это была последовательность. Что вы, вероятно, хотите, так это написать let e = fo.Element("Value") (или полностью пропустить эту часть и писать напрямую select fo.Element("Value").Value ).

Другая проблема заключается в том, что ваш XML использует пространство имен, а вы нет. Это означает, что вы должны создать XNamespace объект и использовать его везде, где у вас есть имена элементов.

Кроме того, способ написания вашего кода AircraftType представляет собой последовательность строк. Я предполагаю, что это не то, чего вы хотели.

И, видя, что вы хотите сделать одно и то же для разных значений FieldName , вы, вероятно, захотите превратить это в метод.

После устранения всех проблем, упомянутых выше, код должен выглядеть примерно так:

 static readonly XNamespace ns = XNamespace.Get("urn:crystal-reports:schemas");

string GetFieldValue(XElement fs, string fieldName)
{
    return (from fo in fs.Descendants(ns   "FormattedReportObject")
            where fo.Attribute("FieldName").Value == fieldName
            let e = fo.Element(ns   "Value")
            select e.Value).Single();
}
…
var flts = (from fs in xDoc.Descendants(ns   "FormattedSection")
            select new FlightSchedule
            {
                AircraftType = GetFieldValue(fs, "{AIRCRAFT.Type ID}"),
                …
            }).ToList();
  

Ответ №2:

fo.Element("Value") возвращает XElement -объект. Вероятно, вы хотите fo.Elements("Value") (обратите внимание на множественное число «s»).

В сообщении об ошибке содержалась жалоба на то, что он не знал, как выполнить итерацию по XElement объекту.

Причина, по которой вы не получаете никаких результатов, заключается в том, что XML-файл использует пространства имен. Чтобы найти элементы за пределами пространства имен по умолчанию, вам нужно добавить префикс к пространству имен перед именем узла.

Я также заметил, что вы не используете fos переменную, так что этот цикл не нужен. fs.Decendants() уже дает вам правильный результат.

 List<FlightSchedule> flts =
    (from fs in xDoc.Descendants("{urn:crystal-reports:schemas}FormattedSection")
     select new FlightSchedule
     {
         AircraftType =
             (from fo in fs.Descendants("{urn:crystal-reports:schemas}FormattedReportObject")
              where fo.Attribute("FieldName").Value == "{AIRCRAFT.Type ID}"
              from e in fo.Elements("{urn:crystal-reports:schemas}Value")
              select e.Value),
                          ....
     }).ToList();