Создание 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.
 
    В приведенном фрагменте не обрабатываются особым образом строки
исходной последовательности, начинающиеся с текста «data:» (которые
надо преобразовывать не в элементы, а в инструкции обработки). Этот
недочет мы исправим позже.
 
    Осталось сохранить полученный XML-документ под требуемым
именем. Для этого достаточно использовать метод Save класса XDocument,
указав имя в качестве параметра:
 
d.Save(GetString());
 
    При вызове этого метода не требуется дополнительно указывать
используемую кодировку, поскольку она была ранее указана в объявлении
XML-документа. Заметим также, что метод Save по умолчанию форматирует
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
    Вместо типа 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());
}
    После пяти тестовых испытаний программы мы получим сообщение о
том, что задание выполнено.
 
  
 
   |