Programming Taskbook


E-mail:

Пароль:

Регистрация пользователя   Восстановление пароля

 

ЮФУ SMBU

Электронный задачник по программированию

©  М. Э. Абрамян (Южный федеральный университет, Университет МГУ-ППИ в Шэньчжэне), 1998–2024

 

PT for LINQ | Примеры выполнения заданий | Обработка XML-документов

Prev


Создание XML-документа: LinqXml10

Задания группы LinqXml посвящены технологии LINQ to XML — интерфейсу LINQ, предназначенному для обработки документов XML.

Интерфейс LINQ to XML включает, помимо дополнительных методов расширения, набор классов, связанных с различными компонентами XML. Этот набор образует объектную модель документа XML (XML Document Object Model — XML DOM), которую мы в дальнейшем для краткости будем обозначать X-DOM. Основные классы, входящие в X-DOM, а также понятия, используемые при работе с XML-документами, кратко описаны в преамбуле к группе LinqXml. Многие классы, входящие в X-DOM, имеют свойства, возвращающие различные последовательности; некоторые методы классов (в частности, их конструкторы) могут принимать последовательности в качестве своих параметров. Во всех ситуациях связанных с обработкой последовательностей, можно использовать базовые запросы LINQ, входящие в интерфейс LINQ to Objects и изученные нами при выполнении заданий из групп LinqBegin и LinqObj.

Знакомство с возможностями LINQ to XML естественно начать с создания XML-документов. Этой теме посвящена первая подгруппа группы LinqXml. Рассмотрим последнее из заданий, входящих в эту подгруппу.

LinqXml10°. Даны имена существующего текстового файла и создаваемого XML-документа. Создать XML-документ с корневым элементом root, элементами первого уровня line и инструкциями обработки (инструкции обработки являются дочерними узлами корневого элемента). Если строка текстового файла начинается с текста «data:», то она (без текста «data:») добавляется в XML-документ в качестве данных к очередной инструкции обработки с именем instr, в противном случае строка добавляется в качестве дочернего текстового узла в очередной элемент line.

После создания с помощью программного модуля PT4Load проекта-заготовки для данного задания, автоматического запуска среды Visual Studio и загрузки в нее созданного проекта на экране будет отображен файл LinqXml10.cs. Приведем содержимое этого файла:

// File: "LinqXml10"
using PT4;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;

namespace PT4Tasks
{
    public class MyTask : PT
    {
        // When solving tasks of the LinqXml group, the following
        // additional methods defined in the taskbook are available:
        // (*) Show() and Show(cmt) (extension methods) - debug output
        //       of a sequence, cmt - string comment;
        // (*) Show(e => r) and Show(cmt, e => r) (extension methods) -
        //       debug output of r values, obtained from elements e
        //       of a sequence, cmt - string comment.

        public static void Solve()
        {
            Task("LinqXml10");

        }
    }
}

Подобно ранее рассмотренным заготовкам, создаваемым для заданий групп LinqBegin и LinqObj, этот файл содержит набор директив using и описание класса MyTask с функцией Solve, в которую требуется ввести решение задачи. В комментарии, предшествующем функции Solve, кратко описываются варианты метода Show, предназначенного для отладочной печати последовательностей.

Заметим, что список директив using содержит директиву подключения пространства имен System.Xml.Linq, с которым связаны все классы модели X-DOM.

В созданной заготовке отсутствуют дополнительные методы для ввода-вывода последовательностей (подобные методам GetEnumerableInt и GetEnumerableString и методу расширения Put, используемым при выполнении заданий группы LinqBegin). Это связано с тем, что в большинстве заданий требуется обработать XML-документ, хранящийся во внешнем файле, и записать в этот же файл результат обработки. Для операций файлового чтения-записи XML-документов предусмотрены специальные методы класса XDocument, которые и следует использовать при выполнении заданий. Исключение составляют задания на создание XML-документов, исходные данные для которых содержатся в «обычных» текстовых файлах. При выполнении подобных заданий, как и заданий группы LinqObj, следует использовать метод File.ReadLines, обеспечивающий чтение всех строк исходного текстового файла в строковую последовательность IEnumerable<string>. Для ввода имен файлов и дополнительных строковых данных следует, как обычно, использовать функцию задачника GetString; для вывода данных базовых типов (подобный вывод требуется только в заданиях подгруппы, связанной с анализом содержимого XML-документа) следует использовать функцию Put, также определенную в задачнике.

После запуска созданной программы-заготовки мы увидим на экране окно задачника, содержащее формулировку задания, а также пример исходных данных и правильных результатов:

Хотя файловые данные на приведенном рисунке отображаются в сокращенном виде, указанные фрагменты демонстрируют все особенности этих данных. В исходном текстовом файле содержатся строки, представляющие собой наборы слов, причем некоторые строки начинаются с текста «data:» (на рисунке этот текст содержит первая строка); результирующий файл содержит XML-документ в кодировке «us-ascii», включающий корневой элемент root, дочерними узлами которого (т. е. узлами первого уровня, поскольку корневой элемент считается узлом нулевого уровня) являются элементы line и инструкции обработки instr.

Элемент XML-документа обязательно имеет имя; он может быть представлен в виде парных тегов вида <имя>...</имя> (где на месте многоточия могут располагаться дочерние узлы этого элемента) или в виде комбинированного тега <имя />. Элемент может также содержать атрибуты.

Инструкция обработки является особым компонентом XML-документа, который заключается в скобки вида <? ?> (заметим, что объявление XML-документа, с которого он начинается, также оформляется подобным образом). Первое слово в этих скобках считается именем инструкции обработки (точнее, именем ее программы-получателя), а последующий текст — данными, связанными с инструкцией. Инструкции обработки называются так потому, что они определяют не содержимое XML-документа, а дополнительные данные, предназначенные для программ, обрабатывающих этот документ. Разумеется, в образцах XML-документов, генерируемых задачником, используются инструкции обработки, не связанные ни с какими программами и имеющие лишь формальные признаки «настоящих» инструкций.

Данные для инструкций instr должны извлекаться из строк исходного файла, начинающихся с текста «data:»; прочие строки файла должны использоваться в качестве содержимого элементов line. Таким образом, при выполнении задания нам потребуется сформировать на основе строковой последовательности, полученной из исходного файла, последовательность дочерних узлов корневого элемента root для созданного XML-документа.

Как уже было отмечено, исходную строковую последовательность проще всего получить с помощью метода File.ReadLines. Начиная с версии 1.3 задачника PT for LINQ, все текстовые файлы, связанные с заданиями, содержат только символы из набора ASCII, поэтому при вызове метода ReadLine не нужно указывать дополнительный параметр, определяющий кодировку символов (символы будут правильно обрабатываться с применением кодировки UTF-8, которая выбирается по умолчанию):

var a = File.ReadLines(GetString());

Для создания как самого XML-документа, так и его компонентов, следует использовать конструкторы соответствующих классов. При выполнении данного задания нам потребуются следующие классы, входящие в X-DOM: XDocument (XML-документ), XDeclaration (объявление документа), XElement (элемент) и XProcessingInstruction (инструкция обработки).

Важной особенностью конструкторов классов XDocument и XElement является то, что они позволяют сразу указывать содержимое создаваемого документа (соответственно, элемента) в виде списка параметров, среди которых могут быть и параметры-последовательности (в случае параметров-последовательностей в создаваемый документ или элемент добавляются все элементы этих последовательностей). Указанной особенностью обладают и некоторые другие методы классов X-DOM. Благодаря этой особенности документ XML вместе с его содержимым может быть построен в одном выражении, причем при формировании содержимого можно использовать последовательности, получаемые с помощью методов LINQ. Подобный способ формирования XML-документа называется функциональным конструированием. Его достоинствами является краткость и наглядность получаемого кода, по виду которого можно легко определить структуру определяемого XML-документа или любого его фрагмента.

В качестве примера функционального конструирования XML-документа приведем фрагмент, который по последовательности строк a (полученной ранее из исходного текстового файла) формирует документ с корневым элементом root и элементами первого уровня line с текстовыми значениями, взятыми из последовательности a в том же порядке:

XDocument d = new XDocument(
  new XDeclaration(null, "us-ascii", null),
  new XElement("root",
    a.Select(e => new XElement("line", e))));

В этом фрагменте вначале вызывается конструктор класса XDocument; в списке его параметров указывается конструктор, создающий объявление документа (в нем достаточно явным образом определить второй параметр, определяющий кодировку документа), и конструктор, создающий элемент нулевого уровня root. В конструкторе элемента root вначале указывается его имя, а затем, в последующих параметрах, — его содержимое. В данном случае содержимое представляет собой последовательность элементов (т. е. объектов типа XElement), полученную из исходной строковой последовательности с помощью метода проецирования Select. Лямбда-выражение метода Select содержит вызов конструктора для объекта XElement; в нем в качестве имени указывается строковая константа «line», а в качестве содержимого — строка e.

Примечание. При определении содержимого в конструкторе класса XElement (и в других методах X-DOM, обеспечивающих функциональное конструирование) строки преобразуются в простейший вид узла XML-документа — текстовый узел, которому в модели X-DOM соответствует класс XText. Подчеркнем, что строки при функциональном конструировании не интерпретируются как символьные последовательности (в отличие от любых других параметров-последовательностей), а рассматриваются как единые объекты.

В приведенном фрагменте не обрабатываются особым образом строки исходной последовательности, начинающиеся с текста «data:» (которые надо преобразовывать не в элементы, а в инструкции обработки). Этот недочет мы исправим позже.

Осталось сохранить полученный XML-документ под требуемым именем. Для этого достаточно использовать метод Save класса XDocument, указав имя в качестве параметра:

d.Save(GetString());

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

Примечание. При выполнении заданий возможность автоматического форматирования XML-документа оказывается очень полезной, так как она упрощает анализ полученного XML-документа и его сравнение с «правильным» образцом. В ситуации, когда программа генерирует XML-документ, не предназначенный для непосредственного просмотра человеком, можно отключить возможность форматирования, указав в методе Save необязательный второй параметр SaveOptions.DisableFormatting; это позволит уменьшить размер файла, содержащего документ.

Можно получить текстовое представление XML-документа, не сохраняя его в файле; для этого достаточно вызвать для объекта типа XDocument метод ToString, возвращающий строку (этот метод тоже может иметь необязательный параметр SaveOptions.DisableFormatting).

Метод Save предусмотрен и для объектов типа XElement; он позволяет сохранить в файле текстовое представление отдельного элемента XML. Метод ToString объекта XElement возвращает строку с текстовым представлением XML-элемента.

Объединяя указанные выше операторы, получаем первый (пока еще не вполне правильный) вариант решения:

public static void Solve()
{
  Task("LinqXml10");
  var a = File.ReadLines(GetString());
  XDocument d = new XDocument(
    new XDeclaration(null, "us-ascii", null),
    new XElement("root",
      a.Select(e => new XElement("line", e))));
  d.Save(GetString());
}

При запуске программы в окне будет выведено сообщение об ошибочном решении (для уменьшения размеров окна в нем скрыт раздел с формулировкой задания):

Для получения XML-документа, соответствующего условиям задачи, надо при создании документа d предусмотреть специальную обработку строк исходного файла, начинающихся с текста «data:», создавая для них вместо объектов XElement объекты XProcessingInstruction. Подобную обработку можно реализовать, включив в лямбда-выражение тернарную операцию:

e => e.StartsWith("data:") ?
  new XProcessingInstruction("instr", e.Substring(5)) :
  new XElement("line", e)

При создании инструкции обработки в качестве ее данных указывается подстрока строки e без начального текста «data:» (число 5 определяет индекс начального символа подстроки; напомним, что индексация символов начинается от 0).

Однако при попытке откомпилировать полученную программу будет выведено сообщение об ошибке компиляции. Ошибка заключается в том, что по приведенному лямбда-выражению нельзя определить тип элементов возвращаемой последовательности (часть элементов будет иметь тип XProcessingInstruction, а часть — тип XElement). Чтобы исправить данную ошибку, следует дополнить тернарное выражение операцией приведения к некоторому общему типу, который и будет считаться типом элементов возвращаемой последовательности. В качестве такого общего типа можно указать любой тип, являющийся предком как класса XProcessingInstruction, так и класса XElement. Ближайшим общим предком этих классов является класс XNode, являющийся абстрактным базовым классом для всех компонентов-узлов XML-документа (класс XDocument также является потомком класса XNode). Используя операцию приведения типа as, получаем следующий вариант лямбда-выражения (добавленный фрагмент выделен полужирным шрифтом):

e => e.StartsWith("data:") ?
  new XProcessingInstruction("instr", e.Substring(5)) :
  new XElement("line", e) as XNode

Примечание. Операция as как имеющая более высокий приоритет, чем тернарная операция, относится только к выражению, указанному после двоеточия. Однако этого достаточно для успешной компиляции программы, поскольку в ходе анализа типов возвращаемых значений (теперь это XProcessingInstruction и XNode) компилятор обнаружит, что тип XProcessingInstruction может быть приведен к типу XNode, и выполнит это приведение автоматически, получив в результате последовательность элементов типа XNode. Того же результата мы добились бы, указав текст as XNode перед двоеточием или заключив все тернарное выражение в круглые скобки.

Вместо типа XNode, мы могли бы использовать и другие общие классы-предки классов XProcessingInstruction и XElement, например, XObject (общий абстрактный базовый класс для узлов и атрибутов XML-документа) или object (общий предок всех классов .NET).

Подчеркнем, что приведение типа не изменяет фактический тип элементов последовательности, оно лишь обеспечивает единообразную интерпретацию всех этих элементов как XML-узлов, давая тем самым компилятору возможность определить тип получаемой последовательности (в нашем случае IEnumerable<XNode>).

Приведем окончательный вариант решения:

public static void Solve()
{
  Task("LinqXml10");
  var a = File.ReadLines(GetString());
  XDocument d = new XDocument(
    new XDeclaration(null, "us-ascii", null),
    new XElement("root",
      a.Select(e => e.StartsWith("data:") ?
        new XProcessingInstruction("instr", e.Substring(5)) :
        new XElement("line", e) as XNode)));
  d.Save(GetString());
}

После пяти тестовых испытаний программы мы получим сообщение о том, что задание выполнено.


Prev

 

Рейтинг@Mail.ru

Разработка сайта:
М. Э. Абрамян, В. Н. Брагилевский

Последнее обновление:
01.01.2024