Обход XML документа

171
25 октября 2021, 07:00

Подскажите пожалуйста каким образом в XML документе найти узел и его дочерние элементы и при этом вернуть объект класса соответствующий этим данным. Вот пример: XML нужно вывести все дочерние элементы INSTANCE CLASSNAME="ORG"

<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE CIM SYSTEM "CIM_DTD_V20.dtd"
[<!ENTITY lt      "&#38;#60;">
 <!ENTITY gt      "&#62;">
 <!ENTITY amp     "&#38;#38;">
 <!ENTITY apos    "&#39;"> 
 <!ENTITY quot    "&#34;"> ]>
<CIM CIMVERSION="2.0" DTDVERSION="2.2">
<DECLARATION>
<DECLGROUP>
<VALUE.OBJECT>
<INSTANCE CLASSNAME="Header">
 <PROPERTY NAME="Date" TYPE="string">
  <VALUE>01/11/2019</VALUE>
 </PROPERTY>
 <PROPERTY NAME="Application" TYPE="string">
  <VALUE>*</VALUE>
 </PROPERTY>
</INSTANCE>
</VALUE.OBJECT>
<VALUE.OBJECT>
<INSTANCE CLASSNAME="ORG">
 <PROPERTY NAME="ID" TYPE="string">
  <VALUE>1</VALUE>
 </PROPERTY>
 <PROPERTY NAME="ORG_NAME" TYPE="string">
  <VALUE>Клиент у которого обслуживаем технику</VALUE>
 </PROPERTY>
 <PROPERTY NAME="ORG_SEARCHCODE" TYPE="string">
  <VALUE>КЛИЕНТ-1</VALUE>
 </PROPERTY>
 <PROPERTY NAME="ORG_OID" TYPE="string">
  <VALUE>282</VALUE>
 </PROPERTY>
 <PROPERTY NAME="RCT_NAME" TYPE="string">
  <VALUE>Заказчик</VALUE>
 </PROPERTY>
</INSTANCE>
</VALUE.OBJECT>

<VALUE.OBJECT>
<INSTANCE CLASSNAME="CI">
 <PROPERTY NAME="ID" TYPE="string">
  <VALUE>3</VALUE>
 </PROPERTY>
 <PROPERTY NAME="NAME" TYPE="string">
  <VALUE>NULL</VALUE>
 </PROPERTY>
 <PROPERTY NAME="TOWN" TYPE="string">
  <VALUE>NULL</VALUE>
 </PROPERTY>
 <PROPERTY NAME="ADR" TYPE="string">
  <VALUE>NULL</VALUE>
 </PROPERTY>
 <PROPERTY NAME="MODEL" TYPE="string">
  <VALUE>HP LaserJet</VALUE>
 </PROPERTY>
 <PROPERTY NAME="TID" TYPE="string">
  <VALUE>NULL</VALUE>
 </PROPERTY>
 <PROPERTY NAME="SERNUM" TYPE="string">
  <VALUE>NULL</VALUE>
 </PROPERTY>
 <PROPERTY NAME="SC" TYPE="string">
  <VALUE>MFU-STANDART-1</VALUE>
 </PROPERTY>
 <PROPERTY NAME="CAT" TYPE="string">
  <VALUE>МФУ</VALUE>
 </PROPERTY>
 <PROPERTY NAME="ORG_OID" TYPE="string">
  <VALUE>282</VALUE>
 </PROPERTY>
 <PROPERTY NAME="BLOCKED" TYPE="string">
  <VALUE>0</VALUE>
 </PROPERTY>
</INSTANCE>
</VALUE.OBJECT>

Вот код запроса:

private static void ReadXML(string patch)
        {
            XDocument xdoc = XDocument.Load(patch);
            var items = from xe in xdoc.Element("CIM").Element("DECLARATION").Element("DECLGROUP").Element("VALUE.OBJECT").Elements("INSTANCE")
                        where xe.Attribute("CLASSNAME").Value == "ORG"
                        select new Organization
                        {
                            Id = xe.Attribute("ID").Value,
                            Org_name = xe.Attribute("ORG_NAME").Value,
                            Org_searchcode = xe.Attribute("ORG_SEARCHCODE").Value,
                            Org_oid = xe.Attribute("ORG_OID").Value,
                            Rct_name = xe.Attribute("RCT_NAME").Value
                        };

            foreach (var item in items)
                Console.WriteLine("${item.Id} - {item.Org_name} - {item.Org_oid} -" +
                    "{item.Org_searchcode} - {item.Rct_name}");
        }
Answer 1

Во-первых, элементов "VALUE.OBJECT" много, а не один. Поэтому нужно писать .Elements("VALUE.OBJECT") - обратите внимание на множественное число (Elements).

Во-вторых, вы получили запросом коллекцию элементов "INSTANCE", а нужно продвинуться ещё на шаг глубже - к элементам "PROPERTY".

В-третьих, "ID", "ORG_NAME" и т. д. - это не названия атрибутов, а значения атрибута NAME.

Итого:

var items =
    from xe in xdoc
        .Element("CIM").Element("DECLARATION").Element("DECLGROUP")
        .Elements("VALUE.OBJECT").Elements("INSTANCE")
    where xe.Attribute("CLASSNAME").Value == "ORG"
    let ps = xe.Elements("PROPERTY")
    select new Organization
    {
        Id = ps.First(p => p.Attribute("NAME").Value == "ID").Value,
        Org_name = ps.First(p => p.Attribute("NAME").Value == "ORG_NAME").Value,
        Org_searchcode = ps.First(p => p.Attribute("NAME").Value == "ORG_SEARCHCODE").Value,
        Org_oid = ps.First(p => p.Attribute("NAME").Value == "ORG_OID").Value,
        Rct_name = ps.First(p => p.Attribute("NAME").Value == "RCT_NAME").Value
    };

Можно сократить код, используя метод Descendants. Но его применение ухудшает производительность, поэтому на больших документах лучше оставить как есть.

Ещё можно написать метод расширения:

public static string GetValue(this IEnumerable<XElement> enumerable, string name)
{
    return enumerable.First(p => p.Attribute("NAME").Value == name).Value;
}

С ним код становится намного короче:

var items =
    from xe in xdoc.Descendants("INSTANCE")
    where xe.Attribute("CLASSNAME").Value == "ORG"
    let ps = xe.Elements("PROPERTY")
    select new Organization
    {
        Id = ps.GetValue("ID"),
        Org_name = ps.GetValue("ORG_NAME"),
        Org_searchcode = ps.GetValue("ORG_SEARCHCODE"),
        Org_oid = ps.GetValue("ORG_OID"),
        Rct_name = ps.GetValue("RCT_NAME")
    };
READ ALSO
Связь между таблицами, привязка к DataGridView

Связь между таблицами, привязка к DataGridView

Есть метод для связи между таблицами и биндинг к гриду

161
Как вернуть запись из текстового столбца как массив

Как вернуть запись из текстового столбца как массив

В бд есть поле типа longtext, в него записан массив такого типа

254
Ошибка в MySQL Workbench с RowCount

Ошибка в MySQL Workbench с RowCount

Значение 0 недопустимо для RowCountRowCount должен быть больше или равен 1

256
Php не видит расширение

Php не видит расширение

Пытаюсь подключиться к базе Postgres, у меня стоит Apache 24, Php 5

357