Управление XML-данными через интегрированные классы чтения и записи в .NET Framework

Классы XmlTextReader и XmlTextWriter в .NET Framework поддерживают операции чтения и записи XML-данных. В статье рассматривается архитектура классов чтения (readers) и их связь с синтаксическими анализаторами (parsers) XMLDOM и SAX. Автор также объясняет, как использовать классы чтения для анализа и проверки XML-документов, как применять классы записи для создания синтаксически корректных документов и как оптимизировать обработку больших XML-документов с помощью функций чтения и записи текста, закодированного в форматах Base64 и BinHex. Кроме того, вы узнаете, как реализовать работающий с потоками анализатор в виде одного класса, выполняющего и чтение, и запись.

Статьи на смежную тематику:

Базовая информация, необходимая для понимания статьи:

Примерно три года назад я уехал с конференции по программному обеспечению в уверенности, что в будущем программирование без хорошего знания XML-технологий станет невозможным. С тех пор XML-технологии прошли большой путь и проникли в самые глубокие закоулки распространенных инфраструктур программирования. В этой статье я рассмотрю роль и внутреннее устройство Microsoft .NET Framework API, работающего с XML-документами, а затем мы обсудим несколько вопросов, которые пока остаются открытыми.

От MSXML к XML в .NET

До наступления эры .NET Framework вы писали Windows-приложения с поддержкой XML, обычно используя COM-библиотеку MSXML. В отличие от классов .NET Framework библиотека MSXML была в большей мере вещью в себе, нежели модулем кода, полностью интегрированным с операционной системой. Конечно, MSXML взаимодействовала с другими частями вашего приложения, но не была по-настоящему интегрирована с окружающей средой.

Библиотеку MSXML можно импортировать в Win32- и даже в CLR-код (common language runtime — общеязыковая исполняющая среда), но она остается «черным ящиком», работающим как серверный компонент. С другой стороны, приложения на основе .NET Framework позволяют использовать основные классы поддержки XML совместно с другими пространствами имен .NET Framework, поэтому конечный код оказывается хорошо интегрированным и удобным для чтения.

Как самодостаточный компонент, синтаксический анализатор MSXML поддерживает весьма продвинутые возможности, например асинхронный анализ. В классах .NET Framework, работающих с XML, эта возможность, конечно, отсутствует. Однако с помощью интеграции классов работы с XML и других классов .NET Framework можно легко реализовать те же функции и даже получить больший контроль над процессом синтаксического анализа.

XML-библиотека должна, как минимум, предоставлять набор базовых функций, в том числе для поддержки анализа, обработки запросов и преобразований. В .NET Framework имеются классы поддержки XPath-запросов и XSLT-преобразований, а также классы чтения и записи XML-документов. Кроме того, в .NET Framework реализованы классы, решающие при работе с XML вспомогательные задачи, такие как сериализация объектов (классы XmlSerializer и SoapFormatter), работа с файлом конфигурации (класс AppSettingsReader) и хранение данных (DataSet). В этой статье я сосредоточусь на классах, выполняющих базовые операции ввода-вывода.

Модель синтаксического анализа XML

XML — это язык разметки, поэтому для эффективного использования информации, хранящейся в документе, необходимо средство, способное анализировать и разбирать лексический синтаксис. Таким средством является XML-анализатор — своего рода «черный ящик», читающий размеченный текст и возвращающий объекты, специфичные для платформы.

Все доступные программистам XML-анализаторы независимо от используемой платформы относятся к одной из двух основных категорий: работающие с деревом и управляемые событиями. Основными представителями этих двух категорий являются две наиболее популярные реализации: XMLDOM (Microsoft XML Document Object Model) и SAX (Simple API for XML). Анализатор XMLDOM — универсальный API, который работает с деревом и представляет XML-документы в виде структуры данных в памяти, а анализатор SAX — событийно-управляемый API, который позволяет обрабатывать каждый значащий элемент потока XML-данных. Вообще говоря, реализацию DOM (Document Object Model) можно загрузить из SAX-потока, так что эти два типа процессоров не исключают друг друга.

Если говорить о концепциях, то анализатор SAX диаметрально противоположен анализатору XMLDOM. Между этими моделями действительно имеются значительные отличия. Набор функций, предоставляемых XMLDOM, вполне четко определен, и вряд ли можно ожидать, что эта модель претерпит значительную эволюцию. При всем богатстве возможностей у анализатора XMLDOM, конечно, есть и недостатки: потребление значительного объема памяти и высокие требования к полосе пропускания при обработке больших документов.

SAX-анализаторы позволяют клиентским приложениям передавать динамические экземпляры специфичных для платформы объектов, обрабатывающих события анализатора (parser events). Анализатор полностью управляет процессом и отправляет данные приложению, которое может принять эти данные или проигнорировать по своему усмотрению. Это весьма «тонкая» модель, требующая очень малого объема памяти.

.NET Framework полностью поддерживает модель XMLDOM, но к SAX это не относится. Тому есть веская причина. В .NET Framework используются две модели анализаторов: анализаторы XMLDOM и классы чтения XML. Отсутствие анализаторов SAX не означает, что придется отказаться от предоставляемых ими возможностей. Все функции анализатора SAX можно просто и эффективно реализовать на основе классов чтения XML. В отличие от анализатора SAX класс чтения .NET Framework работает под полным контролем клиентского приложения. Таким образом, приложение само может считать данные, которые действительно ему нужны, и пропустить остальные данные XML-потока. При использовании SAX анализатор передает всю доступную информацию клиентскому приложению, которое затем или использует, или отвергает информацию.

Классы чтения работают с потоками .NET Framework и устроены во многом аналогично курсорам базы данных. Интересно, что классы, реализующие аналогичную курсорам модель анализа, лежат и в основе реализации анализатора XMLDOM. Два абстрактных класса, XmlReader и XmlWriter, являются основой всех классов .NET Framework, работающих с XML: классов XMLDOM, классов работы с ADO.NET и классов конфигурации. Существует два подхода к обработке XML-данных в .NET Framework. Можно использовать либо классы, напрямую наследуемые от XmlReader или XmlWriter, либо классы, предоставляющие доступ к информации через общепринятую объектную модель XMLDOM. Более обобщенный обзор классов чтения документов, используемых в .NET Framework, можно прочитать в моей рубрике «На переднем крае» за август 2002 г. (EN)

Класс XmlReader

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

С точки зрения классов чтения, XML-документ — это не размеченный текстовый файл, а сериализованный набор узлов. Такая курсорная модель специфична для .NET Framework; вы не встретите аналогичный API где-либо еще.

Между классами чтения и анализаторами XMLDOM есть несколько коренных отличий. Классы чтения XML позволяют перемещаться только вперед, им ничего не известно об узлах, окружающих текущий узел (элементах того же уровня, родителях, предках, потомках), и они умеют лишь считывать данные. В .NET Framework функции чтения и записи XML-документов полностью отделены друг от друга и для работы с ними используются два разных, не связанных друг с другом класса: XmlReader и XmlWriter. Для редактирования содержимого XML-документа можно либо использовать анализатор XMLDOM (построенный на основе класса XmlDocument), либо разработать собственный класс, объединяющий два разных класса — чтения и записи — под одной логической крышей. Начнем с анализа возможностей программирования, предоставляемых классами чтения.

XmlReader — это абстрактный класс, применяемый для разработки более конкретной функциональности. Пользовательские приложения обычно опираются на один из трех производных классов: XmlTextReader, XmlValidatingReader или XmlNodeReader. Все эти классы используют общий набор свойств (таблица 1) и методов (таблица 2). Обратите внимание, что иногда значение этих свойств зависит от того, какой именно класс чтения используется вашим кодом. Следовательно, описание каждого свойства в таблице 1 относится к изначальному для этого свойства предназначению, но может не отражать его роль в производных классах чтения. Так, CanResolveEntity возвращает True только для класса XmlValidatingReader; для всех других типов классов чтения оно имеет значение False. Аналогичным образом поведение и возвращаемое значение некоторых методов, перечисленных в таблице 2, зависит от типа узла, с которым работает класс чтения. Например, все методы, связанные с обработкой атрибутов, возвращают пустое значение, если узел не является узлом элемента.

Таблица 1. Свойства классов, производных от XmlReader

Свойство Описание
AttributeCount Сообщает число атрибутов для текущего узла
BaseURI Сообщает базовый URL текущего узла
CanResolveEntity Сообщает значение, указывающее, может ли класс чтения разрешать сущности
Depth Сообщает глубину текущего узла в XML-документе
EOF Указывает, достиг ли класс чтения конца потока
HasAttributes Указывает, имеются ли у текущего узла атрибуты
HasValue Указывает, может ли быть у текущего узла значение
IsDefault Указывает, является ли текущий узел атрибутом, сгенерированным на основе значения по умолчанию, определенному в DTD или схеме
IsEmptyElement Указывает, является ли текущий элемент пустым — без атрибутов и значения
Item Индексное свойство, возвращающее значение заданного атрибута
LocalName Сообщает имя текущего узла, из которого удалены префиксы
Name Сообщает полностью определенное имя текущего узла
NamespaceURI Сообщает URI пространства имен текущего узла; имеет смысл только для узлов Element и Attribute
NameTable Сообщает имя объекта таблицы имен, связанного с классом чтения
NodeType Сообщает тип текущего узла
Prefix Сообщает префикс пространства имен, связанный с текущим узлом
QuoteChar Сообщает, какой символ обрамляет значения атрибутов
ReadState Сообщает состояние класса чтения; имеет перечислимый тип ReadState
Value Сообщает текстовое значение текущего узла
XmlLang Указывает область xml:lang, к которой относится текущий узел
XmlSpace Указывает текущую область xml:space; имеет перечислимый тип XmlSpace (Default, None или Preserve)

Таблица 2. Методы классов, производных от XmlReader

Метод Описание
Close Закрывает класс чтения и присваивает переменной, описывающей внутреннее состояние, значение Closed
GetAttribute Получает значение заданного атрибута; позволяет обращаться к атрибуту по индексу, локальному имени или полному (с путем) имени
IsStartElement Указывает, является ли текущий узел контента начальным тэгом
LookupNamespace Возвращает URI пространства имен, которому сопоставляется данный префикс
MoveToAttribute Перемещает указатель на заданный атрибут; атрибут можно указывать с помощью индекса, локального имени или полного (с путем) имени
MoveToContent Перемещает указатель на следующий узел контента или в конец файла; метод сразу же возвращает управление, если текущий узел уже является узлом контента, например текстом, CDATA, Element, EndElement, EntityReference или EndEntity
MoveToElement Перемещает указатель обратно на узел элемента, к которому относится текущий узел атрибута; имеет смысл, только когда текущим узлом является атрибут
MoveToFirstAttribute Перемещает указатель на первый атрибут текущего узла Element
MoveToNextAttribute Перемещает указатель на следующий атрибут текущего узла Element
Read Читает текущий узел и перемещает указатель вперед
ReadAttributeValue Анализирует значение атрибута и заносит результаты в один или несколько узлов Text или EntityReference
ReadElementString Читает и возвращает текст из чисто текстового элемента
ReadEndElement Проверяет, является ли текущий узел контента замыкающим тэгом, и перемещает указатель на следующий узел; генерирует исключение, если узел не является замыкающим тэгом
ReadInnerXml Читает и возвращает содержимое (контент) текущего узла (без самого узла), в том числе разметку
ReadOuterXml Читает и возвращает все содержимое текущего узла (вместе с самим узлом), в том числе разметку
ReadStartElement Проверяет, является ли текущий узел элементом, и перемещает указатель на следующий узел; генерирует исключение, если узел не является начальным тэгом
ReadString Читает содержимое элемента или текстового узла как строку; метод объединяет весь текст, встречающийся до ближайшего элемента разметки; в случае узлов атрибутов этот метод считывает значение атрибута
ResolveEntity Раскрывает и разрешает текущий узел ссылки на сущность
Skip Пропускает дочерние элементы текущего узла

Класс XmlTextReader обеспечивает быстрый доступ к потокам XML-данных только для чтения и в направлении только вперед. Класс чтения проверяет, являются ли получаемые XML-данные синтаксически корректными, и, если нет, генерирует исключение. Кроме того, класс чтения выполняет быструю проверку, существуют ли DTD (Document Type Definition), на которые есть ссылки. Но XmlTextReader ни в коем случае не проверяет документ на соответствие схеме или DTD. Класс XmlTextReader идеально подходит для быстрой обработки корректных XML-данных, доступных при обращении к файлам, URL или открытым потокам. Если требуется проверять данные на допустимость, следует воспользоваться классом XmlValidatingReader.

Экземпляр класса XmlTextReader можно создать несколькими способами и использовать различные источники, в частности дисковые файлы, URL, потоки и классы чтения:

XmlTextReader reader = new XmlTextReader(file);

Обратите внимание, что во всех открытых конструкторах нужно указывать источник данных — поток, файл или что-либо еще. Конструктор класса XmlTextReader по умолчанию помечен как protected, следовательно, его нельзя вызвать напрямую. Как и в случае любого другого класса чтения .NET Framework, после создания и инициализации объекта класса чтения для обращения к данным вызывается метод Read. Перейти к следующему узлу можно методом Read или другим специализированным методом: Skip, MoveToContent и ReadInnerXml. Чтобы обработать все содержимое источника XML-данных, обычно выполняется цикл, в котором проверяется значение, возвращаемое методом Read: False означает, что еще остались непрочитанные данные, True — в ином случае1.

В листинге 1 приведена простая функция, выводящая структуру узлов заданного XML-документа. Она открывает документ и выполняет цикл по всему его содержимому. При каждом вызове метода Read внутренний указатель класса чтения смещается на один узел вперед. Хотя для обработки узлов элементов вы скорее всего будете использовать метод Read, имейте в виду, что при перемещении от одного узла к другому эти два узла не обязательно будут одного и того же типа. Например, этот метод не позволяет пропустить узлы атрибутов. Метод класса чтения MoveToContent проходит все узлы заголовков и перемещает указатель на самый первый узел контента. Таким образом, этот метод пропускает узлы типов ProcessingInstruction, DocumentType, Comment, Whitespace и SignificantWhitespace.

Листинг 1. Вывод структуры узлов XML-документа

string GetXmlFileNodeLayout(string file)
{
    // Открываем поток
    XmlTextReader reader = new XmlTextReader(file);

    // Выполняем цикл по узлам и накапливаем текст в строке
    StringWriter writer = new StringWriter();
    string tabPrefix = "";

    while (reader.Read())
    {
        // Записываем начальный тэг
        if (reader.NodeType == XmlNodeType.Element)
        {
            tabPrefix = new string('\t', reader.Depth);
            writer.WriteLine("{0}<{1}>", tabPrefix, reader.Name);
        }
        else
        {
            // Записываем конечный тэг
            if (reader.NodeType == XmlNodeType.EndElement)
            {
                tabPrefix = new string('\t', reader.Depth);
                writer.WriteLine("{0}</{1}>", tabPrefix, reader.Name);
            }
        }
    }

    // Записываем текст в окно вывода
    string buf = writer.ToString();
    writer.Close();

    // Закрываем поток
    reader.Close();

    return buf;
}

Тип каждого узла задается свойством NodeType. В коде, приведенном в листинге 1, имеют значение узлы лишь двух типов: Element и EndElement. Код выводит документ, дублирующий структуру исходного документа, но отбрасывает атрибуты и текст узлов, оставляя только элементы. Таким образом, воспроизводится структура узла. Предположим, что на входе имеется следующий XML-фрагмент:

<mags>
   <mag name="MSDN Magazine">
   MSDN Magazine
   </mag>
   <mag name="MSDN Voices">
   MSDN Voices
   </mag>
</mags>

Тогда вывод выглядит так:

<mags>
   <mag>
   </mag>
   <mag>
   </mag>
</mags>

Отступы для дочерних узлов вычисляются с помощью свойства Depth класса чтения, которое возвращает целое значение — уровень вложения текущего узла. Весь текст накапливается в объекте StringWriter — весьма удобной оболочке класса StringBuilder, использующей потоки.

Как я уже говорил, при вызове метода Read класс чтения не переходит на узлы атрибутов автоматически. Чтобы перейти на атрибуты текущего узла элемента, напишите аналогичный цикл, управляемый методом MoveToNextAttribute. В следующем фрагменте кода читаются все атрибуты текущего узла (выбранного методом Read) и формируется строка, где их имена и значения перечисляются через запятую:

if (reader.HasAttributes)
while(reader.MoveToNextAttribute())
   buf += reader.Name + "=\"" + reader.Value + "\",";
reader.MoveToElement();

С атрибутами узлов мы разобрались. Теперь рассмотрим метод MoveToElement объекта класса чтения. Метод MoveToElement «перемещает» внутренний указатель обратно на элемент, содержащий атрибуты. Если точнее, то на самом деле метод не перемещает указатель, поскольку при перемещении по атрибутам указатель никогда не уходит с узла текущего элемента. MoveToElement просто актуализирует некоторые внутренние члены класса и заносит в них значения узла элемента, а не последнего прочитанного атрибута. Так, свойство Name до вызова MoveToElement содержит имя последнего прочитанного атрибута, а после — имя родительского узла. Если вы больше не собираетесь работать с узлом и хотите, закончив обработку его атрибутов, перейти к следующему элементу, то вызывать MoveToElement нет необходимости.

Анализ содержимого атрибутов

В большинстве случае содержимое атрибута — просто текстовая строка. Но это не значит, что атрибут обязательно относится к строковому типу. Иногда значение атрибута является строковым представлением более конкретного типа, например Date или Boolean, а затем преобразовывается в «родной» тип методами статических классов XmlConvert или System.Convert. Эти два класса выполняют почти идентичные функции, но класс XmlConvert работает в соответствии со спецификацией типов данных XSD (XML Schema Definition) и игнорирует текущие региональные стандарты.

Допустим, у вас есть XML-фрагмент вида:

<person birthday="2-8-2001" />

Предположим также, что в соответствии с текущими региональными стандартами атрибут birthday имеет значение February 8, 2001. Если преобразовать строку в конкретный тип .NET Framework (тип DateTime) с помощью класса System.Convert, все будет работать должным образом и строка правильно трансформируется в объект даты. Но при преобразовании строки через XmlConvert возникнет ошибка анализа строки, так как класс XmlConvert не воспринимает эту строку как содержащую корректную дату. Причина в том, что в языке XML у даты должен быть формат YYYY-MM-DD. Класс XmlConvert служит транслятором между CLR- и XSD-типами. При таком преобразовании результат не зависит от региональных стандартов.

В некоторых случаях значением атрибута является сочетание простого текста и сущностей. Среди всех классов чтения только XmlValidatingReader действительно способен разрешать сущности. Класс XmlTextReader, хотя и не умеет разрешать ссылки на сущности, может отделять текст от сущностей, когда значение атрибута содержит и то и другое. Для этого нужно анализировать содержимое атрибута методом ReadAttributeValue, а не просто считать содержимое из свойства Value.

Метод ReadAttributeValue анализирует значение атрибута и выделяет каждую из образующих это значение лексем (tokens) — будь то простой текст или сущность. ReadAttributeValue повторно вызывается в цикле, пока не будет достигнут конец строкового значения атрибута. Поскольку XmlTextReader не разрешает сущности, все, что можно сделать, — написать свой класс, разрешающий сущности, или распознавать сущности и пропускать их. В следующем фрагменте кода показывается, как вызывать нестандартный класс, разрешающий сущности:

while(reader.ReadAttributeValue())
{
   if (reader.NodeType == XmlNodeType.EntityReference)
     // Разрешаем ссылку "reader.Name" и добавляем
     // результат в буфер
     buf += YourResolverCode(reader.Name);
   else
     // Просто добавляем значение в буфер
     buf += reader.Value;
}

Окончательное значение атрибута со смешанным контентом формируется путем накопления текста в глобальном буфере. Когда значение атрибута полностью проанализировано, метод ReadAttributeValue возвращает False.

Работа с XML-текстом

При работе с текстом XML-разметки некоторые достоинства при неправильном применении могут быстро превратиться в недостатки. Одним из таких камней преткновения является преобразование символов, которое иногда необходимо, чтобы передать текст в формате, отличном от XML, в поток XML-данных. Не все символы, допустимые на какой-либо платформе, допустимы и в XML. Только символы, описанные в спецификации XML (EN), можно безопасно использовать в именах элементов и атрибутов.

Класс XmlConvert предоставляет ключевую функциональность, позволяющую с помощью XML туннелировать между серверами имена, не поддерживаемые XML. Для приведения имен, содержащих недопустимые в XML символы, в соответствие со схемой именования, принятой в XML, применяются методы EncodeName и DecodeName. Многие приложения, в частности SQL Server и Microsoft Office, допускают использование в документах Unicode-символов. Однако некоторые из этих символов нельзя указывать в XML-именах. Типичная ситуация, показывающая важность XmlConvert, — работа с именами полей, содержащими пробелы. SQL Server допускает такие имена полей, как Invoice Details, но в XML-потоках эти имена недопустимы. Пробел нужно заменить шестнадцатеричным кодом, в результате чего получится Invoice_0×0020_Details. Единственный допустимый вид шестнадцатеричных последовательностей — _0×HHHH_, где HHHH — четырехразрядное шестнадцатеричное значение. Последовательности символов, имеющие аналогичный, но все же другой формат, остаются неизменными, хотя можно было бы считать, что они логически эквивалентны. Ниже показано, как программно получить такую закодированную строку:

XmlConvert.EncodeName("Invoice Details");

Обратная операция выполняется методом DecodeName, который восстанавливает XML-имя в исходный вид, декодируя закодированные последовательности. Обратите внимание, что учитываются только полностью закодированные последовательности. Например, только _0×0020_ воспринимается как пробел, а _0×20_ не обрабатывается:

XmlConvert.DecodeName("Invoice_0x0020_Details");

В XML пробел, обнаруженный в теле XML-документа, может оказаться значащим или незначащим. Пробел считается значащим, если он содержится в тексте узла элемента или в области объявления пробела:

<MyNode xml:space="preserve">
<!-- Любой пробел здесь является значащим -->
...
</MyNode>

В XML «пробел» — обобщенное понятие, относящееся не только к самим пробелам (ASCII 0×20), но и к символам возврата каретки (ASCII 0×0D), перевода строки (ASCII 0×0A) и табуляции (ASCII 0×09).

Класс XmlTextReader через свойство WhiteSpaceHandling позволяет управлять обработкой пробелов. Это свойство принимает и возвращает значение, относящееся к перечислимому WhiteSpaceHandling. Можно выбрать один из трех вариантов. По умолчанию используется значение All, при выборе которого и значащие, и незначащие пробелы возвращаются в отдельных узлах — SignificantWhitespace и Whitespace соответственно. Вариант None означает, что пробелы вообще не возвращаются как узлы. Наконец, вариант Significant означает, что все незначащие пробелы игнорируются и возвращаются только узлы типа SignificantWhitespace. Заметьте, что WhiteSpaceHandling — одно из немногих свойств класса чтения, которые можно изменить в любое время, и что его значение влияет на следующую операцию Read. Другие свойства с таким же поведением — Normalization и XmlResolver.

Строки и фрагменты

Программисты, съевшие собаку на MSXML, конечно, заметили ключевое отличие этой COM-библиотеки от XML API в .NET Framework. В классах .NET Framework нет встроенного средства анализа XML-данных, хранящихся в строке. В отличие от объекта анализатора MSXML класс XmlTextReader не поддерживает какие-либо разновидности метода loadXML, позволяющего выполнить чтение из синтаксически корректной строки. Отсутствие метода, аналогичного loadXML, спорно. Но такой метод не обязателен, поскольку той же функциональности можно добиться за счет StringReader — класса чтения текста.

Один из конструкторов XmlTextReader принимает объект, производный от TextReader, и создает экземпляр класса чтения XML, который обращается к содержимому класса чтения текста. Класс чтения текста — это поток, оптимизированный для ввода символов. Класс StringReader наследует от класса TextReader и использует в качестве входного потока строку в памяти. Следующий фрагмент кода показывает, как инициализировать класс чтения XML корректной XML-строкой:

string xmlText = "...";
StringReader strReader = new StringReader(xmlText);
XmlTextReader reader = new XmlTextReader(strReader);

Аналогичным образом при использовании класса StringWriter вместо TextWriter можно создавать XML-документы, которые хранятся в строках, размещаемых в памяти.

Особая разновидность XML-строк — XML-фрагменты. Фрагмент — это XML-текст, к которому не применяется правило наличия корневого узла. Следовательно, XML-фрагмент отличается от обычного документа тем, что у фрагмента не обязательно есть корневой узел. Например, следующая XML-строка является допустимым XML-фрагментом, но недопустимым XML-документом, так как у XML-документов должен быть корневой узел:

<firstname>Dino</firstname>
<lastname>Esposito</lastname>

.NET Framework XML API позволяет программистам связывать XML-фрагменты с контекстом анализатора, содержащим такую информацию, как кодировка набора символов, DTD-документ, пространства имен, язык и обработка пробелов:

public XmlTextReader(
   string xmlFragment,
   XmlNodeType fragType,
   XmlParserContext context
);

Параметр xmlFragment содержит анализируемую XML-строку. Аргумент fragType — это тип фрагмента, который задается типом корневого узла или узлов фрагмента. Только узлы элемента, атрибута и документа могут являться корневыми узлами фрагмента. Контекст анализатора задается аргументом типа XmlParserContext.

Классы чтения с проверкой на допустимость

Класс XmlValidatingReader — это реализация класса XmlReader, поддерживающая несколько видов проверки XML-данных на допустимость: DTD, XDR-схемы (XML-Data Reduced) и XSD. DTD и XSD являются официальными рекомендациями W3C, а XDR — Microsoft-реализация первоначального рабочего проекта XML Schema.

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

Класс XmlValidatingReader реализует лишь очень малое подмножество функциональности, которую должен предоставлять класс чтения XML. XmlValidatingReader всегда работает поверх существующего класса чтения XML и отражает многие методы и свойства этого класса. Зависимость класса чтения с проверкой на допустимость от существующего класса чтения текста становится особенно заметной, если взглянуть на конструкторы класса. Класс чтения XML с проверкой на допустимость нельзя инициализировать, напрямую указав файл или URL. Список доступных конструкторов содержит следующие перегруженные версии:

public XmlValidatingReader(XmlReader);
public XmlValidatingReader(Stream, XmlNodeType, XmlParserContext);
public XmlValidatingReader(string, XmlNodeType, XmlParserContext);

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

Список методов, которые по-настоящему реализованы в этом классе, очень короткий. Помимо метода Read к ним относятся методы Skip и ReadTypedValue. Метод Skip пропускает дочерние элементы текущего активного узла класса чтения. (Следует заметить, что синтаксически некорректный XML-текст пропустить не удастся.) Кроме того, метод Skip проверяет на допустимость пропущенный контент.

Метод ReadTypedValue возвращает значение узла, приведенное к CLR-типу. Если метод может отобразить XSD-тип на CLR-тип, возвращается этот CLR-тип. Если прямое преобразование невозможно, значение узла возвращается в виде строки.

Класс чтения с проверкой на допустимость точно соответствует своему названию: это класс чтения, который работает с узлами и проверяет структуру текущего узла на соответствие текущей схеме. Проверка выполняется пошагово; в классе нет метода, который возвращал бы значение типа Boolean, указывающее, допустим ли данный документ. Входной документ, как обычно, проходится с помощью метода Read. Класс чтения с проверкой на допустимость используется так же, как любой другой класс чтения .NET Framework, работающий с XML. На каждом этапе структура текущего узла проверяется на соответствие заданной схеме, и при обнаружении ошибки генерируется исключение. В листинге 2 приведено консольное приложение, которое получает из командной строки имя файла и выводит информацию о ходе проверки.

Листинг 2. Консольное приложение

using System;
using System.Xml;
using System.Xml.Schema;

class MyXmlValidApp
{
    public MyXmlValidApp(String fileName)
    {
        try {
            Validate(fileName);
        }
        catch (Exception e) {
            Console.WriteLine("Error:\t{0}", e.Message);
            Console.WriteLine("Exception raised: {0}",
                e.GetType().ToString());
        }
    }

    private void Validate(String fileName)
    {
        XmlTextReader xtr = new XmlTextReader(fileName);
        XmlValidatingReader vreader = new XmlValidatingReader(xtr);
        vreader.ValidationType = ValidationType.Auto;
        vreader.ValidationEventHandler += new
            ValidationEventHandler(this.ValidationEventHandle);

        vreader.Read();
        vreader.MoveToContent();

        while (vreader.Read()) {}

        xtr.Close();
        vreader.Close();
    }

    public void ValidationEventHandle(Object sender,
        ValidationEventArgs args)
    {
        Console.Write("Validation error: " + args.Message + "\r\n");
    }

    public static void Main(String[] args)
    {
        MyXmlValidApp o = new MyXmlValidApp(args[0]);
        return;
    }
}

Свойство ValidationType задает используемый тип проверки на допустимость — DTD, XSD, XDR (или отсутствие проверки). Если тип проверки на допустимость не указан (задано значение ValidationType.Auto), класс чтения автоматически выполняет проверку на допустимость, которую «считает» наиболее подходящей для документа. Вызывающее приложение уведомляется об ошибках через события ValidationEventHandler. Если не указать свой обработчик события, генерируется XML-исключение, которое приходится обрабатывать приложению. Определение метода ValidationEventHandler позволяет перехватывать любые XML-исключения, вызванные нарушениями целостности исходного документа. Обратите внимание, что механизмы, используемые при проверке документа на корректность синтаксиса и при проверке на соответствие схеме, различны. Если класс чтения с проверкой на допустимость сталкивается с некорректным XML-документом, возникает не событие, а XmlException.

Проверка на допустимость выполняется, когда пользователь перемещает указатель вперед с помощью метода Read. После анализа и чтения узел передается для дальнейшей обработки внутреннему объекту-верификатору (validator object). Верификатор действует в зависимости от типа узла и заданного типа проверки на допустимость и смотрит, есть ли в узле все атрибуты и дочерние элементы, которые он должен содержать.

Верификатор использует на внутреннем уровне два вида объектов: DTD-анализатор и формирователь схемы (schema builder). DTD-анализатор проверяет, соответствуют ли DTD содержимое текущего узла и его поддерево. Формирователь схемы создает для текущего узла объектную модель схемы (SOM) по исходному коду XDR-схемы или XSD-схемы. На самом деле класс формирователя схемы является базовым для более специализированных формирователей XDR-схем и XSD-схем. Это означает, что XDR-схемы и XSD-схемы интерпретируются во многом одинаково и что производительность при их применении одинакова.

Если узел имеет дочерние узлы, используется еще один временный класс чтения, собирающий информацию о дочерних узлах, что позволяет получить все данные, необходимые для проверки соответствия узла схеме (рис. 1).

Применение временного класса чтения для дочерних узлов
Рис. 1. Применение временного класса чтения для дочерних узлов

Заметьте: хотя в сигнатуре одного из конструкторов XmlValidatingReader указывается универсальный класс чтения XmlReader, на самом деле этот класс чтения может быть только экземпляром класса XmlTextReader или производного от него класса. Поэтому нельзя использовать любой класс, производный от XmlReader (например, собственный класс чтения XML). На внутреннем уровне XmlValidatingReader работает, полагаясь на то, что класс чтения — это объект XmlTextReader, и специально приводит входной класс чтения к типу XmlTextReader. Если вы укажете в конструкторе класс XmlNodeReader или собственный класс чтения, то компиляция пройдет без ошибок, но в период выполнения будет сгенерировано исключение.

Классы чтения узлов

Классы чтения XML позволяют обрабатывать содержимое узлов поочередно, один за другим. Пока что я предполагал, что исходный документ — это поток, считываемый с диска, или строка. Но ничто не мешает использовать в качестве исходного документа объект XMLDOM. Правда, понадобится особый класс со специальным методом Read. Для этой цели .NET Framework предоставляет класс XmlNodeReader.

Аналогично тому, как XmlTextReader обходит все узлы заданного XML-потока, класс XmlNodeReader перебирает все узлы поддерева XMLDOM. Класс XMLDOM (XmlDocument в .NET Framework) поддерживает методы, использующие XPath, например SelectNodes и SelectSingleNode. В результате вызова этих методов в память загружаются узлы, соответствующие определенному критерию. Если требуется обработать все узлы поддерева, класс чтения узлов оказывается эффективнее, так как он работает аналогично курсорам:

// xmldomNode - это узел XML DOM
XmlNodeReader nodeReader = new XmlNodeReader(xmldomNode);
while (nodeReader.Read()) {
    // Что-то делаем здесь
}

Кроме того, совместное использование класса XmlNodeReader и классов XML DOM позволяет добиться выигрыша при обработке дерева XMLDOM, заполненного данными, которые считаны из файла конфигурации (например web.config).

Класс XmlTextWriter

Программное создание XML-документов никогда не было особо сложным делом. Разработчики годами занимались тем, что объединяли по несколько строк в буфере и, заполнив буфер, сбрасывали его содержимое в файл. Но так создавать XML-документы эффективно, только когда можно гарантировать, что в поток выводимого кода не попадут какие-то трудно уловимые ошибки. В .NET Framework предлагается более продуктивный и элегантный подход к созданию XML-документов — классы записи XML.

Класс записи XML (XML writer) выводит XML-данные в потоки или файлы с перемещением только вперед. Важнее то, что класс записи XML гарантирует, что все формируемые XML-данные соответствуют рекомендациям XML 1.0 and Namespaces консорциума W3C. Больше нет нужды беспокоиться о непарных угловых скобках или о том, что узел последнего элемента оставлен незакрытым. XmlWriter — абстрактный базовый класс для всех классов записи XML. В .NET Framework есть лишь один неабстрактный класс записи — XmlTextWriter.

Чтобы увидеть, чем использование классов записи XML отличается от записи в старом стиле, рассмотрим фрагмент кода, сохраняющий массив строк:

StringBuilder sb = new StringBuilder("");
sb.Append("<array>");
foreach(string s in theArray) {
   sb.Append("<element value=\"");
   sb.Append(s);
   sb.Append("\"/>");
}
sb.Append("</array>");

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

Класс записи XML содержит методы записи для каждого возможного типа XML-узлов и делает вывод XML-данных более логичным и менее зависимым от тонкостей языка разметки. В листинге 3 показана сериализация массива строк с помощью класса XmlTextWriter. При соизмеримом объеме код на основе класса записи XML понятнее и лучше структурирован.

Листинг 3. Сериализация массива строк

void CreateXmlFileUsingWriters(String[] theArray, string filename)
{
    // Открываем класс записи XML (набор символов по умолчанию)
    XmlTextWriter xmlw = new XmlTextWriter(filename, null);
    xmlw.Formatting = Formatting.Indented;

    xmlw.WriteStartDocument();
    xmlw.WriteStartElement("array");
    foreach(string s in theArray)
    {
        xmlw.WriteStartElement("element");
        xmlw.WriteAttributeString("value", s);
        xmlw.WriteEndElement();
    }
    xmlw.WriteEndDocument();

    // Закрываем класс чтения
    xmlw.Close();
}

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

Кроме того, при создании узла атрибута класс записи не проверяет существование у элемента атрибута с таким же именем. Наконец, класс XmlWriter не выполняет проверку на допустимость и не гарантирует, что выходной документ будет соответствовать какой-либо схеме или DTD. Сейчас в .NET Framework нет класса записи с проверкой на допустимость. Однако реализация такого класса моей разработки прилагается к моей книге Applied XML Programming for Microsoft .NET (Microsoft Press, 2002). Исходный код можно скачать по адресу www.microsoft.com/MSPress/books/6235.asp (EN).

В таблице 3 перечислены возможные состояния класса записи XML. Все они определяются значениями перечислимого WriteState. При создании нового класса чтения его состоянию присваивается значение Start, означающее, что объект конфигурируется и запись данных еще не началась. Следующее состояние — Prolog — устанавливается сразу после вызова метода WriteStartDocument, с которого начинается работа. Дальнейшие изменения состояния зависят от типа и содержимого записываемого документа. Пока добавляются узлы, не являющиеся элементами, такие как комментарии, инструкции обработки и тип документа, состояние Prolog сохраняется. После записи первого узла элемента (корневого узла документа) состояние меняется на Element. Состояние принимает значение Attribute при вызове метода WriteStartAttribute, но не при прямой записи атрибутов методом WriteAttributeString (в этом случае состояние сохраняет значение Element). После записи замыкающего тэга состояние меняется на Content. Когда запись завершена, вызывается метод WriteEndDocument и состояние снова принимает значение Start, которое и сохраняется до создания следующего документа или закрытия класса записи.

Таблица 3. Состояния класса чтения XML

Состояние Описание
Attribute Выполняется запись атрибута
Closed Вызван метод Close, и класс записи больше не позволяет выполнять операции записи
Content Записывается содержимое узла
Element Записывается начальный тэг элемента
Prolog Записывается пролог синтаксически корректного XML-документа
Start Класс записи находится в начальном состоянии и ждет, когда начнется запись

Класс записи хранит выводимый текст во внутреннем буфере. Обычно сброс буфера и запись XML-текста выполняются, только когда класс записи закрывается. В любой момент времени можно вызвать метод Flush и записать текущее содержимое в поток (для доступа к потоку используется свойство BaseStream). При этом часть рабочей памяти освобождается, класс записи остается открытым, и можно работать с ним дальше. Но учтите, что другие процессы не могут обращаться к частично записанному файлу, пока класс записи не закрыт.

Узлы атрибутов можно записывать двумя способами. Первый — использовать метод WriteStartAttribute для создания нового узла атрибута и соответствующего изменения состояния. Затем для задания значения атрибута вызывается метод WriteString. Запись узла атрибута завершается вызовом WriteEndElement. Второй — применить метод WriteAttributeString, который переводит класс в состояние Element и одновременно создает атрибут. Аналогичным образом метод WriteStartElement записывает открывающий тэг узла, после чего можно по своему усмотрению записывать атрибуты узла и текст. Обычно закрывающий тэг элемента записывается в компактной форме (/>). Если его нужно записать полностью, вызывается метод WriteFullEndElement.

В записываемый текст автоматически подставляются последовательности специальных знаков, если встречаются символы, которые могут конфликтовать с разметкой, например знак «меньше» (<). Кроме того, имеется метод WriteRaw, позволяющий без предварительного анализа записывать данные в поток. Так, первая из двух строк кода, показанных ниже, выведет &lt;, а вторая — сам символ <.

writer.WriteString("<");
writer.WriteRaw("<");

Чтение и запись потоков

Интересно, что классы чтения и записи даже содержат методы чтения и записи данных, закодированных в форматах Base64 или BinHex. Методы WriteBase64 и WriteBinHex по сигнатуре немного отличаются от других методов записи. Так как они предназначены для работы с потоками, параметрами этих методов являются массивы байтов, а не строки. Следующий код сначала транслирует строку в массив байтов, а затем записывает ее в поток с кодировкой Base64. Статический метод GetBytes класса Encoding выполняет преобразование:

writer.WriteBase64(
   Encoding.Unicode.GetBytes(buf),
   0, buf.Length*2);

В коде (листинг 4) массив строк сохраняется в XML-потоке с кодировкой Base64, а на рис. 2 показан результат работы кода, выведенный в окно Microsoft Internet Explorer.

Листинг 4. Сохранение массива строк в формате Base64

using System;
using System.Text;
using System.IO;
using System.Xml;

class MyBase64Array
{
    public static void Main(String[] args)
    {
        string outputFileName = "test64.xml";
        if (args.Length > 0)
            outputFileName = args[0];    // имя файла

        // Массив, который преобразовывается в XML
        String[] theArray = {"Rome", "New York", "Sydney", 
                             "Stockholm", "Paris"};

        CreateOutput(theArray, outputFileName);
        return;
    }

    private static void CreateOutput(string[] theArray,
        string filename)
    {
        // Открываем класс записи XML
        XmlTextWriter xmlw = new XmlTextWriter(filename, null);
        xmlw.Formatting = Formatting.Indented;

        xmlw.WriteStartDocument();
        xmlw.WriteComment("Array to Base64 XML");

        xmlw.WriteStartElement("array");
        xmlw.WriteAttributeString("xmlns", "x", null,
            "dinoe:msdn-mag");
            foreach(string s in theArray)
        {
            xmlw.WriteStartElement("x", "element", null);
            xmlw.WriteBase64(Encoding.Unicode.GetBytes(s), 0,
                             s.Length*2);    // Unicode
            xmlw.WriteEndElement();
        }
        xmlw.WriteEndDocument();

        // Закрываем класс чтения
        xmlw.Close();

        // Снова считываем данные!
        XmlTextReader reader = new XmlTextReader(filename);
        while(reader.Read())
        {
            if (reader.LocalName == "element")
            {
                byte[] bytes = new byte[1000];
                           int n = reader.ReadBase64(bytes, 0, 1000);
                string buf = Encoding.Unicode.GetString(bytes);

                Console.WriteLine(buf.Substring(0,n));
            }
        }
        reader.Close();
    }
}

Массив строк в Internet Explorer
Рис. 2. Массив строк в Internet Explorer

В классах чтения имеются методы, предназначенные для декодирования потоков Base64 и BinHex. В следующем фрагменте кода показывается, как декодировать ранее созданный файл с помощью метода ReadBase64 класса XmlTextReader:

XmlTextReader reader = new XmlTextReader(filename);
while(reader.Read()) {
  if (reader.LocalName == "element") {
    byte[] bytes = new byte[1000];
int n = reader.ReadBase64(bytes, 0, 1000);
string buf = Encoding.Unicode.GetString(bytes);
    Console.WriteLine(buf.Substring(0,n));
  }
}
reader.Close();

Преобразование массива байтов в строку выполняется методом GetString класса Encoding. Я привел код для Base64, но он годится и для BinHex, нужно лишь заменить имена методов. Этот способ подходит для любого вида двоичных данных, которые можно представить в виде массива байтов, в частности для изображений.

Придумываем класс XmlReadWriter

Как уже упоминалось, классы чтения и записи XML работают по отдельности: классы чтения только считывают, классы записи только записывают. Допустим, приложение обрабатывает большие XML-документы с изменяемыми данными. Классы чтения отлично подходят для чтения такого контента. С другой стороны, классы записи — очень удобны для создания подобного документа «с нуля». Но если вы хотите одновременно и считывать, и записывать документ, вам придется обратиться к более мощному средству — к XMLDOM.

А если документ особенно велик, у вас появится проблема. Как быть, если, например, требуется читать и записывать XML-документ, не загружая его в память целиком? Попробуем создать потоковый анализатор смешанного типа, который выступает в роли облегченного анализатора XMLDOM.

Если бы выполнялось только чтение, вы использовали бы обычный класс чтения XML, последовательно перебирающий узлы. В нашем случае отличие в том, что при чтении нужна возможность изменения значений атрибутов и содержимого узлов через класс записи XML, действующий поверх класса чтения. Последний используется для чтения каждого узла исходного документа, а класс записи создает скрытую копию документа. В копию можно добавить несколько новых узлов, какие-то другие узлы можно проигнорировать или поместить в измененном виде и, кроме того, можно изменить значения атрибутов. Когда все операции закончены, старый документ заменяется новым.

Эффективный способ копирования групп узлов (большого объема) из потока только для чтения в поток записи — применение двух методов класса XmlTextWriter: WriteAttributes и WriteNode. Метод WriteAttributes считывает все атрибуты узла, являющегося текущим узлом класса чтения. Затем метод копирует атрибуты как одну строку в текущий выходной поток. Аналогичным образом метод WriteNode выполняет такую же операцию для узлов любых типов, кроме узлов атрибутов. В листинге 5 показан фрагмент кода, где эти методы используются для создания копии исходного XML-файла, в которой некоторые узлы пропускаются. XML-дерево обходится как обычно (читаются все узлы), но записывается лишь каждый второй узел. Классы чтения и записи можно объединить в один новый класс и разработать новый программный интерфейс, который позволяет легко обращаться к узлам или атрибутам, работая с XML-данными как с потоками.

Листинг 5. Применение метода WriteNode

XmlTextReader reader = new XmlTextReader(inputFile);
XmlTextWriter writer = new XmlTextWriter(outputFile);

// Настраиваем классы чтения и записи
writer.Formatting = Formatting.Indented;
reader.MoveToContent();

// Записываем корневой элемент
writer.WriteStartElement(reader.LocalName);

// Читаем и выводим остальные узлы
int i=0;
while(reader.Read())
{
    if (i % 2)
        writer.WriteNode(reader, false);
    i++;
}

// Закрываем корневой элемент
writer.WriteEndElement();

// Закрываем классы чтения и записи
writer.Close();
reader.Close();

Мой класс XmlTextReadWriter не наследует от классов XmlReader или XmlWriter, но зато координирует работу экземпляров этих двух классов — один экземпляр работает с потоком, доступным только для чтения, другой — с потоком, доступным только для записи. Методы класса XmlTextReadWriter считывают данные класса чтения и записывают данные в класс записи, а в промежутке между этими двумя операциями изменяют данные в соответствии с запросом. К внутренним классам чтения и записи можно обращаться лишь через свойства только для чтения, которые я назвал соответственно Reader и Writer. Методы нового класса перечислены в таблице 4.

Таблица 4. Методы класса XmlTextReadWriter

Метод Описание
AddAttributeChange Кэширует всю информацию, необходимую для изменения узла атрибута; кэшированные атрибуты обрабатываются при последующем вызове WriteAttributes
Read Простая оболочка внутреннего метода Read класса чтения
WriteAttributes Специальная версия метода WriteAttributes класса записи, которая записывает все атрибуты заданного узла с учетом изменений, кэшированных методом AddAttributeChange
WriteEndDocument Завершает текущий документ в классе записи и закрывает классы чтения и записи
WriteStartDocument Готовит внутренний класс чтения к записи документа и добавляет текст комментария по умолчанию и стандартный XML-пролог

У класса имеется метод Read — простая оболочка метода Read класса чтения. Кроме того, есть пара методов WriteStartDocument и WriteEndDocument, которые инициализируют и закрывают внутренние классы чтения и записи, а также выполняют все необходимые операции ввода-вывода. Изменения узлов выполняются непосредственно клиентом в цикле чтения. Для большей производительности изменения атрибутов сначала регистрируются методом AddAttributeChange. Все изменения в атрибутах узла временно хранятся во внутреннем кэше и сбрасываются при вызове метода WriteAttributes.

В листинге 6 показан клиентский код, изменяющий через класс XmlTextReadWriter значения атрибутов в процессе чтения данных. Полный исходный код класса XmlTextReadWriter на C# и Visual Basic .NET можно скачать по адресу msdn.microsoft.com/msdnmag/code03.aspx (EN) в разделе за май.

Листинг 6. Изменение значений атрибутов

private void ApplyChanges(string nodeName, string attribName,
    string oldVal, string newVal)
{
    XmlTextReadWriter rw = new XmlTextReadWriter(InputFileName.Text,
                               OutputFileName.Text);
    rw.WriteStartDocument(true, CommentText.Text);

    // Изменяем корневой тэг вручную
    rw.Writer.WriteStartElement(rw.Reader.LocalName);

    // Готовим изменения атрибутов
    // (при необходимости можно изменить несколько атрибутов узла)
    rw.AddAttributeChange(nodeName, attribName, oldVal, newVal);

    // Проходим документ в цикле
    while(rw.Read())
    {
        switch(rw.NodeType)
        {
            case XmlNodeType.Element:
                rw.Writer.WriteStartElement(rw.Reader.LocalName);
                if (nodeName == rw.Reader.LocalName)
                    // Применяем изменения в атрибутах
                    rw.WriteAttributes(nodeName);
                else
                    // Копируем с учетом иерархии (deep copy)
                    rw.Writer.WriteAttributes(rw.Reader, false);

                if (rw.Reader.IsEmptyElement)
                    rw.Writer.WriteEndElement();
                break;
        }
    }

    // Закрываем корневой тэг
    rw.Writer.WriteEndElement();

    // Закрываем документ и все внутренние ресурсы
    rw.WriteEndDocument();
}

XmlTextReadWriter — это в большей степени класс чтения, чем записи. Идея в том, что класс используется для чтения XML-контента, но, если требуется, можно внести кое-какие базовые изменения. Под базовым я понимаю изменение значения одного или нескольких существующих атрибутов или содержимого узла либо добавление атрибутов или узлов. При более сложных изменениях не обойтись без анализаторов XMLDOM.

Подведем итог

Классы чтения и записи являются основой поддержки работы с XML-данными в .NET Framework. Они предоставляют базисный API для всех операций доступа к XML-данным. Классы чтения — это инновационный тип анализаторов, занимающий свое место где-то посередине между мощной XMLDOM и простой SAX. Классы записи — мир, параллельный миру классов чтения. Классы записи упрощают создание XML-документов. Хотя обычно классы чтения и записи рассматриваются как часть .NET Framework, на самом деле они являются полностью обособленным API. Мы обсудили, как с помощью классов чтения и записи решать ряд ключевых задач, а также познакомились с архитектурой классов чтения, поддерживающих проверку на допустимость. Классы чтения и записи можно объединить в одном универсальном классе, реализовав облегченную модель XMLDOM, аналогичную курсорам.


1 в документации утверждается обратное. — Прим. перев.


Дино Эспозито (EN) (Dino Esposito) — преподаватель и консультант из Рима. Автор книг «Building Web Solutions with ASP.NET» и «ADO.NET», выпущенных издательством Microsoft Press. В этой статье в сокращенном виде изложены концепции, представленные в его книге «Applied XML Programming for Microsoft .NET» (Microsoft Press, 2002).

Дино Эспозито
Уровень сложности 2
Эта статья предполагает знание XML и .NET Framework

Источник: http://www.microsoft.com/rus/msdn/magazine/archive/2003-05/xml_in_action_full.asp

Исходные коды:

Архив Zip XML на практике. Управление XML-данными через интегрированные классы чтения и записи в .NET Framework. Полная версия статьи 


Опубликовал admin
28 Июл, Четверг 2005г.



Программирование для чайников.