|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Глава 13XML XML играет очень большую роль в платформе .NET. Платформа не только позволяет использовать XML в приложениях, но сама применяет XML для таких вещей, как конфигурационные файлы и документация исходного кода. Для XML платформа .NET содержит пространство имен System.Xml. Это пространство имен загружается вместе с классами, задействованными при обработке XML. В этой главе говорится о том, как использовать реализацию DOM, и что предлагает .NET в качестве замены SAX. Будет показана совместная работа XML и ADO.NET и их преобразования. Мы узнаем так же, как можно сериализовать объекты в документ XML и создать объект из документа XML (десериализовать). Кроме того увидим, как включать XML в приложения C#. Следующие классы будут рассмотрены более подробно: □ XmlReaderи XmlTextReader □ XmlWriterи XmlTextWriter □ XmlDocumentи DOM □ XPathи XslTransform □ ADO.NETи XmlDataDocument □ XmlSerialization Начнем эту главу с текущего состояния стандартов XML. Стандарты W3CКонсорциум WWW (W3C) разработал множество стандартов, придающих XML мощь и потенциал. Без этих стандартов XML не смог бы оказать такого большого влияния на мир разработки. В этой книге не будут подробно рассматриваться тонкости XML. Для этого необходимо использовать другие источники. Среди книг Wrox, переведенных издательством "Лори", можно порекомендовать "Введение в XML" (2001 г., 656 стр.), "XML для профессионалов" (2001 г., 896 стр.) и "The XML Handbook" (ISBN 0-13-055068). Конечно, web-сайт W3C является ценным источником информации о XML (www.w3.org). В мае 2001 г. платформа .NET поддерживала следующие стандарты: □ XML 1.0 — www.w3.org/TR/1998/REC-XML-19980210 — включая поддержку DTD ( XmlTextReader). □ Пространства имен XML — www.w3.org/TR/REC-xml-names — уровень потока и DOM. □ Схемы XML — www.w3.org/TR/xmlschema-1 — поддерживается отображение схем и сериализация, но пока еще не поддерживается проверка. □ Выражения XPath — www.w3.org/TR/xpath □ Преобразования XSL/T — www.w3.org/TR/xslt □ Ядро DOM Level 2 — www.w3.org/TR/DOM-Level-2 □ Soap 1.1 — msdn.microsoft.com/xml/general/soapspec.asp Уровень поддержки стандартов будет меняться по мере развития платформы и по мере того, как W3C продолжает обновлять рекомендованные стандарты. В связи с этим необходимо всегда оставаться на современном уровне стандартов и уровне поддержки, который обеспечивает Microsoft. Пространство имен System.XmlРассмотрим (без определенного порядка) некоторые классы пространства имен System.Xml.
XML является также частью пространства имен System.Dataв классе DataSet.
XML 3.0 (MSXML3.DLL) в C#Как быть, если имеется большой объем кода, разработанного с помощью синтаксического анализатора компании Microsoft (в настоящее время XML 3.0)? Придется ли его выбросить и начать все сначала? А что если вам удобно использовать объектную модель XML 3.0 DOM? Нужно ли немедленно переключаться на .NET? Ответом будет — нет. XML 3.0 может использоваться непосредственно в приложениях. Если добавить ссылку на msxml3.DLL в свое решение, то можно будет начать писать некоторый код. Следующие несколько примеров будут использовать файл books.xml в качестве источника данных. Его можно загрузить с web-сайта издательства Wrox, он также включен в несколько примеров .NET SDK. Файл books.xml является каталогом книг воображаемого книжного склада. Он содержит такую информацию, как жанр, имя автора, цена и номер ISBN. Все примеры кода в этой главе также доступны на web-сайте издательства Wrox: www.wrox.com. Чтобы выполнить эти примеры, файлы данных XML должны находиться в структуре путей, которая выглядит примерно следующим образом: /XMLChapter/Sample1 /XMLChapter/Sample2 /XMLChapter/Sample3 и т. д. Файлы XML должны находиться в подкаталоге XMLChapter, а код для примеров должен быть в подкаталогах Sample1, Sample2 и т.д. Можно называть каталоги как угодно, но их относительное положение важно. Можно также изменять примеры, чтобы указать желаемое направление. В коде примеров будут сделаны указания, какие строки изменить. Файл books.xml выглядит следующим образом: <?xml version='1.0'?> <!-- Этот файл представляет фрагмент базы данных учета запасов книжного склада --> <bookstore> <book genre="autobiography" publicationdate="1981" ISBN="1-861003-11-0"> <title>The Autobiography of Benjamin Franklin</title> <author> <first-name>Benjamin</first-name> <last-name>Franklin</last-name> </author> <price>8.99</price> </book> <book genre="novel" publicationdate="1967" ISBN="0-201-63361-2"> <title>The Confidence Man</title> <author> <first-name>Herman</first-name> <last-name>Melville</last-name> </author> <price>11.99</price> </book> <book genre="philosophy" publicationdate="1991" ISBN="1-861001-57-6"> <title>The Gorgias</title> <author> <name>Plato</name> </author> <price>9.99</price> </book> </bookstore> Рассмотрим пример кода, использующего MSXML 3.0 для загрузки окна списка с номерами ISBN из books.xml. Ниже записан код, который можно найти в папке SampleBase1архива, загружаемого с web-сайта издательства Wrox. Можно скопировать его в Visual Studio IDE или создать новую форму Windows Form с самого начала. Эта форма содержит элементы управления listboxи button. Оба элемента используют имена по умолчанию listBox1и button1: namespace SampleBase { using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; Затем включается пространство имен для ссылки на msxml3.dll. Помните, что ссылку на эту библиотеку необходимо включать в проект (ее можно найти на вкладке COM диалогового окна Add Reference). using MSXML2; /// <summary> /// Краткое описание Form1. /// </summary> public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.ListBox listBox1; private System.Windows.Forms.Button button1; /// <summary> /// Необходимая для Designer переменная. /// </summary> private System.ComponentModel.Container components; Затем объявляется документ DOM на уровне модуля: private DOMDocument30 doc; public Form1() { // // Требуется для поддержки Windows Form Designer // InitializeComponent(); // // TODO: Добавьте любой код конструктора после вызова // InitializeComponent // } /// <summary> /// Очистить все использованные ресурсы. /// </summary> public override void Disposed { base.Dispose(); if (components != null) components.Dispose(); } #region Windows Form Designer создает код /// <summary> /// Необходимый для поддержки Designer метод — не изменяйте /// содержимое этого метода редактором кода. /// </summary> private void InitializeComponent() { this.listBox1 = new System.Windows.Forms.ListBox(); this.button1 = new System.Windows.Forms.Button(); this.listBox1.Anchor = ((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right); this.listBox1.Size = new System.Drawing.Size(336, 238); this.listBox1.TabIndex = 0; this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged); this.button1.Anchor = System.Windows.Forms.AnchorStyles.Bottom; this.button1.Location = new System.Drawing.Point(136, 264); this.button1.TabIndex = 1; this.button1.Text = "button1"; this.button1.Click += new System.EventHandler(this.button1_Click); this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(339, 320); this.Controls.AddRange(new System.Windows.Forms.Control[]{this.button1, this.listBox1}); this.Text = "Form1"; } #endregion /// <summary> /// Главная точка входа для приложения. /// </summary> [STAThread] static void Main() { Application.Run(new Form1()); } Мы хотим взять номер ISBN из listboxи, используя простой поиск XPath, найти узел книги, который ему соответствует, и вывести текст узла (заглавие книги и цену) в MessageBox. Язык пути доступа XML (XPath) является нотацией XML, которая может использоваться для запроса и фильтрации текста в документе XML. Рассмотрим XPath в .NET позже в этой главе. Вот код обработчика событий для выбора записи в окне списка: protected void listBox1_SelectedIndexChanged (object sender, System.EventArgs e){ string srch=listBox1.SelectedItem.ToString(); IXMLDOMNode nd=doc.selectSingleNode("bookstore/book[@ISBN='" + srch + "']"); MessageBox.Show(nd.text); } Теперь мы имеем обработчик события нажатия кнопки. Сначала мы загружаем файл books.xml— обратите внимание, что если файл выполняется не в папке bin/debugили bin/release, необходимо исправить соответствующим образом путь доступа: protected void button1_Click(object sender, System.EventArgs e) { doc=new DOMDocument30(); doc.load("..\\..\\..\\books.xml") Следующие строки объявляют, что узлы являются nodeListузлов книг. В данном случае имеется три узла: IXMLDOMNodeList nodes; nodes = doc.selectNodes("bookstore/book"); IXMLDOMNode node=nodes.nextNode(); Мы просматриваем узлы в цикле и добавляем текстовое значение атрибута ISBN в listBox1: while(node!=null) { listBox1.Items.Add(node.attributes.getNamedItem("ISBN").text); node=nodes.nextNode(); } } } } Вот как выглядит пример во время выполнения: Это изображение появляется после того, как была нажата кнопка button1 и загрузился listBox1 с номерами ISBN книг. После выбора номера ISBN будет выведено следующее: System.XmlПространство имен System.Xmlявляется мощным и относительно простым для использования, но оно отличается от модели MSXML 3.0. Если вы знакомы с MSXML 3.0, то применяйте его, пока не освоитесь с пространством имен System.Xml. Пространство имен System.Xmlпредлагает большую гибкость и легче расширяется. Этот файл XML будет использоваться в примерах этой главы. Код, который только что был рассмотрен, лежит в основе нескольких примеров. В большинстве других будет показан только код, имеющий отношение к делу, и не будет повторяться то, что уже было показано. Чтение и запись XMLТеперь посмотрим, что позволяет делать платформа .NET. Если раньше вы работали с SAX, то XmlReaderи XmlWriterвам будут знакомы. Классы на основе XmlReaderпредоставляют быстрый курсор только для чтения вперед, который создает поток данных XML для обработки. Так как это потоковая модель, то требования к памяти не очень большие. Однако в ней отсутствует навигационная гибкость и средства чтения/записи, присущие модели DOM. Классы на основе XmlWriterбудут создавать документ XML, который соответствует рекомендациям по пространствам имен XML 1.0 консорциума W3C. XmlReaderи XmlWriterявляются абстрактными классами. Рисунок ниже показывает, какие классы являются производными от XmlReaderи XmlWriter: XmlTextReaderи XmlTextWriterработают либо с объектами на основе потока, либо с объектами на основе TextReaderили TextWriter. XmlNodeReaderиспользует XmlNodeвместо потока в качестве своего источника. XmlValidatingReaderдобавляет DTD и проверку схем и поэтому предлагает проверку данных. Мы рассмотрим это подробнее позже в этой главе. XmlTextReaderXmlTextReaderпохож на SAX. Одно из различий заключается в том, что SAX является моделью типа рассылки (push), т.е. посылает данные приложению и разработчик должен быть готов принять их, a XmlTextReaderприменяет модель запроса (pull), где данные посылаются приложению, которое их запрашивает. Это предоставляет более простую и интуитивно понятную модель для программирования. Другое преимущество состоит в том, что модель запроса может быть избирательной в отношении данных, посылаемых приложению. Если нужны не все данные, то их не нужно обрабатывать. В модели рассылки все данные XML должны быть обработаны приложением, нужны они ему или нет. Возьмем простой пример считывания данных XML, и затем более внимательно рассмотрим класс XmlTextReader. Код можно найти в папке XmlReaderSample1. Можно заменить метод button1_Clickв предыдущем примере на следующий код. Эту версию данного кода можно найти в папке SampleBase2загруженного архива кода. Не забудьте изменить: using MSXML2; на using System.Xml; Мы должны это сделать, поскольку используем теперь не MSXML 3.0, а пространство имен System.Xml. Нужно также удалить метод listBox1_SelectedIndexChanged, так как он включает в себя некоторые неподдерживаемые методы и строку: private DOMDocument30 doc; protected void button1_Click(object sender, System.EventArgs e) { // Измените этот путь доступа, чтобы найти books.xml string fileName = "..\\..\\..\\books.xml"; // Создать новый объект TextReader XmlTextReader tr = new XmlTextReader(fileName); // Прочитать узел за раз while(tr.Read()) { if (tr.NodeType == XmlNodeType.Text) listBox1.Items.Add(tr.Value); } } Это XmlTextReaderв простейшей форме. Сначала создается строковый объект fileNameс именем файла XML. Затем создается новый объект XmlTextReader, передавая в качестве параметра строку fileName.XmlTextReaderв настоящее время имеет 13 различных перегружаемых конструкторов, которые получают различные комбинации строк (имен файлов и URL), потоков и таблиц имен. После инициализации объекта XmlTextReaderни один узел не выбран. Это единственный момент, когда узел не является текущим. Когда мы начинаем цикл tr.Read, первая операция чтения Readпереместит нас в первый узел документа. Обычно это бывает узел Declaration XML. В этом примере при переходе к каждому узлу tr.NodeTypeсравнивается с перечислением XmlNodeType, и когда встречается текстовый узел, значение текста добавляется в listbox. Вот экран после того, как было загружено окно списка: Существует несколько способов перемещения по документу. Как мы только что видели, Readперемещает нас к следующему узлу. Затем можно проверить, имеет ли узел значение ( HasValue) или, как мы скоро увидим, имеет ли узел атрибуты ( HasAttributes). Существует метод ReadStartElement, который проверяет, является ли текущий узел начальным элементом, и затем перемешает текущую позицию к следующему узлу. Если текущая позиция не является начальным элементом, то порождается исключение XmlException. Этот метод совпадает с вызовом метода IsStartElement, за которым следует метод Read. Методы ReadStringи ReadChartsсчитывают текстовые данные из элемента. ReadStringвозвращает строковый объект, содержащий данные, в то время как ReadChartsсчитывает данные в заданный массив символов. Метод ReadElementStringаналогичен методу ReadString, за исключением того, что при желании можно передать в него имена элемента. Если следующий узел содержимого не является начальным тегом или, если параметр Nameне совпадает с именем ( Name) текущего узла, то порождается исключение. Вот пример того, как это может использоваться (код можно найти в папке XmlReaderSample2): protected void button1_Click(object sender, System.EventArgs e) { // Использовать файловый поток для получения данных FileStream fs = new FileStream("..\\..\\..\\books.xml", FileMode.Open); XmlTextReader tr = new XmlTextReader(fs); while(!tr.EOF) { // если встретился тип элемента, проверить и загрузить его в окно списка if (tr.MoveToContent()==XmlNodeType.Element && tr.Name=="title") { listBox1.Items.Add(tr.ReadElementString()); } else //иначе двигаться дальше tr.Read(); } } В цикле whileиспользуется метод MoveToContentдля поиска каждого узла типа XmlNodeType.Elementс именем title. Если это условие не выполняется, то предложение elseвызывает метод Readдля перехода к следующему узлу. Если будет найден узел, соответствующий критерию, то результат работы метода ReadElementStringдобавляется в listbox. Таким образом мы получим заглавия книг в listbox. Отметим, что после успешного применения ReadElementStringметод Readне вызывается. Это связано с тем, что метод ReadElementStringобрабатывает весь Elementи перемещается к следующему узлу. Если удалить && tr.Name=="title"из предложения if, то придется отслеживать исключение XmlException, когда оно будет порождаться. При просмотре файла данных можно заметить, что первым элементом, который найдет метод MoveToContent, является элемент <bookstore>. Как элемент он будет проходить проверку в операторе if. Но так как он не содержит простой текстовый тип, он вынуждает метод ReadElementStringпорождать исключение XmlException. Одним из способов обхода этой проблемы является размещение вызова ReadElementStringв своей собственной функции. Назовем ее LoadList. XmlTextReaderпередается в нее в качестве параметра. Теперь, если вызов ReadElementStringотказывает внутри этой функции, мы можем иметь дело с ошибкой и вернуться назад в вызывающую функцию. Вот как выглядит пример с этими изменениями (код можно найти в папке XmlReaderSample3): protected void button1_Click(object sender, System.EventArgs e) { // использовать файловый поток для получения данных FileStream fs = new FileStream("..\\..\\..\\books.xml", FileMode.Open); XmlTextReader tr = new XmlTextReader(fs); while(!tr.EOF) { // если встретился тип элемента, проверить и загрузить его в окно списка if (tr.MoveToContent() == XmlNodeType.Element) { LoadList(tr); } else // иначе двигаться дальше tr.Read(); } } private void LoadList(XmlReader reader) { try { listBox1.Items.Add(reader.ReadElementString()); } //если инициировано исключение XmlException, игнорировать его. catch(XmlException er){} } Вот что должно появиться, когда код будет выполнен: Это тот же результат, который был раньше. Мы видим, что существует более одного способа достичь одной и той же цели. При этом становится очевидной гибкость пространства имен System.Xml. По мере чтения узлов можно заметить отсутствие каких-либо атрибутов. Это связано с тем, что атрибуты не считаются частью структуры документа. При нахождении в узле элемента мы можем проверить наличие атрибутов и получить значения атрибутов. Метод HasAttributesвозвращает true, если существуют какие-либо атрибуты, иначе возвращается false. Свойство AttributeCountсообщит, сколько имеется атрибутов. Метод GetAttributeполучает атрибут по имени или по индексу. Если желательно просмотреть все атрибуты по очереди, можно использовать методы MoveToFirstAttribute(перейти к первому атрибуту) и MoveToNextAttribute(перейти к следующему атрибуту). Вот пример просмотра атрибутов из XmlReaderSample4: protected void button1_Click(object sender, System.EventArgs e) { // задаем путь доступа в соответствии со структурой путей доступа // к данным string fileName = "..\\..\\..\\books.xml"; // Создать новый объект TextReader XmlTextReader tr = new XmlTextReader(filename); // Прочитать узел за раз while (tr.Read()) { // проверить, что это элемент NodeType if (tr.NodeType = XmlNodeType.Element) { // если это — элемент, то посмотрим атрибуты for(int i=0; i<tr.AttributeCount; i++) { listBox1.Items.Add(tr.GetAttribute(i)); } } } } На этот раз мы ищем узлы элементов. Когда такой узел найден, в цикле просматриваются все атрибуты и с помощью метода GetAttributeзначение атрибута загружается в listbox. ПроверкаЕсли нужно проверить документ XML, используйте класс XmlValidatingReader. Он обладает всей функциональностью класса XmlTextReader(оба реализуют XmlReader, но XmlValidatingReaderдобавляет свойство ValidationType, свойство Schemesи свойство SchemaType). Свойство ValidationTypeзадается как тип проверки, которую желательно выполнить. Допустимые значения этого свойства следующие:
Если свойство задано, то должен быть назначен обработчик событий ValidationEventHandler. Событие инициируется, когда случается ошибка проверки. На ошибку можно отреагировать любым подходящим образом. Рассмотрим пример. Добавим пространство имен схемы XDR (XML Data Reduced — приведенные данные XML) к файлу books.xmlи назовем этот файл booksVal.xml. Теперь он выглядит так: <?xml version='1.0'?> <!-- Этот файл представляет фрагмент базы данных учета запасов книжного склада --> <bookstore xmlns="x-schema:books.xdr"> <book genre="autobiography" publicationdate="1981" ISBN="1-861003-11-0"> <title>The Autobiography of Benjamin Franklin</title> <author> <first-name>Benjamin</first-name> <last-name>Franklin</last-name> </author> <price>8.99</price> </book> <book genre="novel" publicationdate="1967" ISBN="0-201-63361-2"> <title>The Confidence Man</title> <author> <first-name>Herman</first-name> <last-name>Melville</last-name> </author> <price>11.99</price> </book> <book genre="philosophy" publicationdate="1991" ISBN="1-861001-57-6"> <title>The Gorgias</title> <author> <name>Plato</name> </author> <price>9.99</price> </book> </bookstore> Отметим, что элемент bookstoreимеет теперь атрибут xmlns="x-schema:books.xdr". Это будет указывать на следующую схему XDR: <?xml version="1.0"?> <Schema xmlns="urn:schemas-microsoft-com:xml-data" xmlns:dt="urn:schemas-microsoft-com:datatypes"> <ElementType name="first-name" content="textOnly"/> <ElementType name="last-name" content="textOnly"/> <ElementType name="name" content="textOnly"/> <ElementType name="price" content="textOnly" dt:type="fixed.14.4"/> <ElementType name="author" content="eltOnly" order="one"> <group order="seq"> <element type="name"/> </group> <group order="seq"> <element type="first-name"/> <element type="last-name"/> </group> </ElementType> <ElementType name="title" content="textOnlу" /> <AttributeType name="genre" dt:type="string"/> <ElementType name="book" content="eltOnly"> <attribute type="genre" required="yes"/> <element type="title"/> <element type="author"/> <element type="price"/> </ЕlementType> <ElementType name="bookstore" content="eltOnly"> <element type="book"/> </ElementType> </Schema> Отметим, что имеются два атрибута в файле XML, которые не определены в схеме. Если посмотреть внимательно, то можно увидеть что в схеме нет атрибутов publication-dateи ISBN из элемента book. Мы сделали это, чтобы показать, что проверка действительно выполняется. Можно использовать для подтверждения этого следующий код. Необходимо будет добавить в класс using System.Xml.Schema. Весь код доступен в XMLReaderSample5: protected void button1_Click (object sender, System.EventArgs e) { //измените это в соответствии с используемой структурой путей доступа. string filename = "..\\..\\..\\booksVal.xml"; XmlTextReader tr = new XmlTextReader(filename); XmlValidatingReader trv=new XmlValidatingReader(tr); // Задать тип проверки trv.ValidationType=ValidationType.xdr; // Добавить обработчик события проверки trv.ValidationEventHandler += new ValidationEventHandler(this.ValidationEvent); // Считываем узел за раз while(trv.Read()) { if (trv.NodeType == XmlNodeType.Text) listBox1.Items.Add(trv.Value); } } public void ValidationEvent(object sender, ValidationEventArgs args) { MessageBox.Show(args.Message); } Мы создаем XmlTextReaderдля передачи в XmlValidationReader. Когда XmlValidationReader trvсоздан, можно использовать его по большей части так же, как XmlTextReaderв предыдущих примерах. Различия состоят в том что в данном случае определен атрибут ValidationTypeи добавлен ValidationEventHandler. Каждый раз при возникновении ошибки проверки инициируется ValidationEvent. Затем можно будет обработать ошибку проверки любым приемлемым способом. В данном примере выводится MessageBoxс описанием ошибки. Вот как выглядит MessageBox, когда инициируется ValdationEvent. В отличие от некоторых синтаксических анализаторов XmlValidationReaderпосле возникновения ошибки продолжает считывание. Имеется возможность определить серьезность ошибки проверки. Если окажется, что это серьезная ошибка, то можно остановить чтение. Свойство Schemasкласса XmlValidationReaderсодержит коллекцию XmlSchemaCollection, которая находится в пространстве имен System.Xml.Schema. В этой коллекции находятся предварительно загруженные схемы XSD и XDR, что позволяет выполнить очень быструю проверку, (особенно, если нужно проверить несколько документов), так как схему не нужно каждый раз перезагружать. Для получения выигрыша в производительности и создается объект XmlSchemaCollection. Метод Addимеет четыре перегружаемые версии. Можно передать объект на основе XmlSchema, объект на основе XmlSchemaCollection, строку stringс пространством имен вместе со строкой stringс URL файла схемы и, наконец, строку stringс пространством имен и объектом на основе XmlReader, который содержит схему. Запись XMLКласс XmlTextWriterпозволяет записывать XML в поток, файл или объект TextWriter. Подобно XmlTextReaderон делает это только вперед, некэшируемым образом. XmlTextWriterможно конфигурировать различным образом, что позволяет определить такие вещи, как наличие или отсутствие отступов, величину отступа, какой использовать символ кавычки в значениях атрибутов, и поддерживаются ли пространства имен. Свойство DataTypeNamespaceопределяет, как строго значения типов преобразуются в текст XML. Для этого свойства допустимо значение urn:schemas-microsoft-com:datatypes, которое поддерживает типы данных XDR, и другое значение www.w3.org/1999/XMLSchema-data-types, которое является схемой W3C типов данных XSD. Чтобы использовать, например, тип данных TimeSpan, необходимо будет задать это свойство для типов данных XSD. Приведем простой пример, чтобы увидеть, как может использоваться класс TextWriter(пример находится в папке XMLWriterSample1): private void button1_Click(object sender, System.EventArgs e) { // измените в соответствии с используемой структурой путей доступа string fileName="..\\..\\..\\booknew.xml"; //создайте XmlTextWriter XmlTextWriter tw=new XmlTextWriter(fileName, null); // задайте форматирование с отступом tw.Formatting=Formatting.Indented; tw.WriteStartDocument(); //Начать создание элементов и атрибутов tw.WriteStartElement("book"); tw.WriteAttributeString("genre", "Mystery"); tw.WriteAttributeString("publicationdate", "2001"); tw.WriteAttributeString("ISBN", "123456789"); tw.WriteElementString("title", "Case of the Missing Cookie"); tw.WriteStartElement("author"); tw.WriteElementString("name", "Cookie Monster"); tw.WriteEndElement(); tw.WriteElementString("price", "9.99"); tw.WriteEndElement(); tw.WriteEndDocument(); // очистить tw.Flush(); tw.Close(); } Создадим новый файл booknew.xmlи добавим новую книгу. Объект XmlTextWriterзаменит существующий файл. Вставку нового элемента или узла в существующий документ рассмотрим позже. Экземпляр объекта XmlTextWriterсоздается с помощью объекта FileStreamв качестве параметра. Можно также передать строку с именем файла и путем доступа или объект на основе TextWriter. При задании свойства Indentingузлы-потомки будут автоматически делать отступ от предка. Метод WriteStartDocument()помещает объявление документа. Начинаем запись данных. Сначала идет элемент book. Затем добавляем атрибуты genre, publicationdateи ISBN. После чего записываем элементы title, author, и price. Отметим, что элемент authorимеет элемент-потомок name. После нажатия на кнопку будет создан следующий файл booknew.xml: <?xml version="1 .0"?> <book genre= "Mystery" publicationdate="2001" ISBN="123456789"> <title>Case of the Missing Cookie</title> <author> <name>Cookie Monster</name> </author> <price>9,99</price> </book> Так же как в документе XML, здесь имеются начальный метод и конечный метод ( WriteStartElementи WriteEndElement). Вложенность контролируется отслеживанием начала и окончания записи элементов и атрибутов. Это можно видеть при добавлении элемента потомка nameк элементу authors. Отметим, как организуются вызовы методов WriteStartElementи WriteEndElementи как это связывается с выведенным документом XML. В дополнение к WriteElementStringи WriteAtributeStringимеется несколько других специализированных методов записи. Метод WriteCDateбудет выводить раздел CDate (<!CDATE[...]]>), взяв текст для записи из параметра. WriteComment записывает комментарий в подходящем формате XML. WriteChars записывает содержимое символьного буфера. Это работает аналогично методу ReadChars, который был рассмотрен ранее. Оба они используют один и тот же тип параметров. Методу WriteCharнужен буфер (массив символов), начальная позиция для записи (целое значение) и число символов для записи (целое значение). Чтение и запись XML с помощью классов, основанных на XMLReaderи XMLWriter, осуществляются очень просто. Далее мы рассмотрим реализацию DOM пространства имен System.Xml. Это классы на основе XmlDocumentи XmlNode. Объектная модель документа в .NETРеализация объектной модели документа (DOM, Document Object Model) в .NET поддерживает спецификации W3C DOM Level 1 и Core DOM Level 2. DOM реализуется с помощью класса XmlNode. XmlNodeявляется абстрактным классом, который представляет узел документа XML. XmlNodeListявляется упорядоченным списком узлов. Это живой список узлов, и любые изменения в любом узле немедленно отражаются в списке. XmlNodeListподдерживает индексный доступ или итеративный доступ. Эти два класса составляют основу реализации DOM на платформе .NET. Вот список классов, которые основываются на XmlNode.
Как можно видеть .NET делает доступным класс, соответствующий почти любому типу XML. Мы не будем рассматривать каждый класс подробно, но разберем несколько примеров. Вот как выглядит диаграмма наследования: Первый пример будет создавать объект XmlDocument, загружать документ с диска и загружать окно списка с данными из элементов title. Это аналогично одному из примеров, которые были выполнены в разделе XmlReader. Отличие заключается в том, что осуществляется выбор, с какими узлами мы хотим работать, вместо того чтобы использовать весь документ. Вот код для выполнения этого в среде XmlNode. Посмотрите, как просто он выглядит при сравнении (файл можно найти в папке DOMSample1загруженного архива): private void button1_Click(object sender. System.EventArgs e) { // doc объявлен на уровне модуля // изменить путь доступа в соответствии со структурой путей доступа doc.Load("..\\..\\..\\books.xml") // получить только те узлы, которые нужны XmlNodeList nodeLst=doc.GetElementsByTagName("title"); // итерации по списку XmlNodeList foreach(XmlNode node in nodeLst) listBox1.Items.Add(node, InnerText); } Обратите внимание, что мы добавили следующее объявление на уровне модуля: private XmlDocument doc=new XmlDocument(); Если бы это было все, что нужно делать, то использование XmlReaderбыло бы значительно более эффективным способом загрузки окна списка. Причина в том, что мы прошли через документ один раз и затем закончили с ним работу. Однако, если желательно повторно посетить узел, то использование XmlDocumentявляется лучшим для этого способом. Слегка расширим пример (новая версия находится в DOMSample2): private void button1_Click(object sender, System.EventArgs e) { //doc объявлен на уровне модуля // измените путь доступа в соответствии со структурой путей доступа doc.Load("..\\..\\..\\books.xml"); // получить только те узлы, которые хотим XmlNodeList nodeLst=doc.GetElementsByTagName("title"); // итерации через список XmlNodeList foreach(XmlNode node in nodeLst) listBox1.Items.Add(node.InnerText); } private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) { // создать строку поиска XPath string srch="bookstore/book[title='" + listBox1.SelectedItem.ToString() + "']"; // поиск дополнительных данных XmlNode foundNode=doc.SelectSingleNode(srch); if (foundNode!=null) MessageBox.Show(foundNode.InnerText); else MessageBox.Show("Not found"); } В этом примере listboxс заголовками загружается из документа books.xml. Когда мы щелкаем на окне списка, вызывая порождение события SelectedIndexChange(не забудьте добавить код, присоединяющий обработчик событий в функцию InitializeComponent), мы берем текст выбранного пункта в listbox, в данном случае заголовок книги, создаем оператор XPath и передаем его в метод SelectSingleNodeобъекта doc. Он возвращает элемент book, частью которого является title (foundNode). Выведем для наглядности InnerText узла в окне сообщения. Мы можем продолжать щелкать на элементах в listboxсколько угодно раз, так как документ загружен и остается загруженным, пока мы его не освободим. Небольшой комментарий в отношении метода SelectSingleNode. Это реализация XPathв классе XmlDocument. Существуют методы SelectSingleNodeи SelectNodes. Оба они определены в XmlNode, на котором основывается XmlDocument. SelectSingleNodeвозвращает XmlNode, и SelectNodesвозвращает XmlNodeList. Пространство имен System.Xml.XPathсодержит более насыщенную реализацию XPath(см. ниже). Ранее рассматривался пример XmlTextWriter, который создает новый документ. Ограничение состояло в том, что он не вставлял узел в текущий документ. Это можно сделать с помощью класса XmlDocument. Если изменить button1_Clickиз предыдущего примера, то получим следующий код ( DOMSample3): private void button1_Click(object sender, System.EventArgs e) { // изменить путь доступа, как требуется существующей структурой doc.Load("..\\..\\..\\books.xml"); // создать новый элемент 'book' XmlElement newBook=doc.CreateElement("book"); // задать некоторые атрибуты newBook.SetAttribute("genre", "Mystery"); newBook.SetAttribute("publicationdate", "2001"); newBook.SetAttricute("ISBN", "123456789"); // создать новый элемент 'title' XmlElement newTitle=doc.CreateElement("title"); newTitle.InnerText="Case of the Missing cookie"; newBook.AppendChild(newTitle); // создать новый элемент author XmlElement newAuthor=doc.CreateElement("author"); newBook.AppendChild(newAuthor); // создать новый элемент name XmlElement newName=doc.CreateElement("name"); newName.InnerText="С. Monster"; newAuthor.AppendChild(newName); // создать новый элемент price XmlElement newPrice=doc.CreateElement("price"); newPrice.innerText="9.95"; newBook.AppendChild(newPrice); // добавить к текущему документу doc.DocumenElement.AppendChild(newBook); // записать doc на диск XmlTextWriter tr=new XmlTextWriter("..\\..\\..\\booksEdit.xml", null); tr.Formatting=Formatting.Indented; doc.WriteContentTo(tr); tr.Close(); // загрузить listBox1 со всеми заголовками, включая новый XmlNodeList nodeLst=doc.GetElementsByTagName("title"); foreach(XmlNode node in nodeLst) listBox1.Items.Add(node.InnerText); } private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) { string srch="bookstore/book[title='" + listBox1.SelectedItem.ToString() + "']"; XmlNode foundNode=doc.SelectSingleNode(srch); if (foundNode!=null) MessageBox.Show(foundNode.InnerText); else MessageBox.Show("Not found"); } При выполнении этого кода будет получена функциональность предыдущего примера, но в окне списка появилась одна дополнительная книга "The Case of Missing Cookie". Щелчок мыши на заголовке этой книги приведет к выводу такой же информации, как и для других книг. Анализируя код, можно увидеть, что это достаточно простой процесс. Прежде всего создается новый элемент book: XmlElement newBook = doc.CreateElement("book); Метод CreateElementимеет три перегружаемые версии, которые позволяют определить имя элемента, имя и пространство имен URI, и, наконец, prefix(префикс), lоcalname(локальное имя) и namespace(пространство имен). Когда элемент создан, необходимо добавить атрибуты newBook.setAttribute("genre", "Mystery"); newBook.SetAttribute("publicationdate", "2001"); newBook.SetAttribute("ISBN", "123456789"); Напомним, что класс XmlAttributeрасширяет класс XmlNode, поэтому нам доступны все свойства и методы XmlNode. Даже если имеется очень сложная структура, то при ее размещении никаких проблем возникать не должно. Теперь, когда атрибуты созданы и необходимо добавить другие элементы книги: XmlElement newTitle=doc.CreateElement("title"); newTitle.InnerText="Case of the Missing Cookie"; newBook.AppendChild(newTitle); Здесь снова создается новый объект на основе XmlElement (newTitle). Присваиваем свойству InnerTextзаголовок новой книги и добавляем потомок к элементу book. Затем это повторяется для остальных элементов book. Отметим, что элемент nameдобавлен как потомок элемента author. Это дает нам правильное отношение вложенности. Наконец, мы добавляем элемент newBookк узлу doc.DocumentElement. Это тот же уровень, что и у всех других элементов book. Мы заменили существующий документ новым, в отличие от XmlWriter, где можно было только создать новый документ. Последнее, что нужно сделать, это записать новый документ XML на диск. В этом примере мы создаем новый XmlTextWriterи передаем его в метод WriteContentTo. Не забудьте вызвать метод Closeна XmlTextWriter, чтобы сбросить содержимое внутренних буферов и закрыть файл. Методы WriteContentToи WriteToполучают XmlTextWriterв качестве параметра. WriteContentToсохраняет текущий узел и всех потомков в XmlTextWriter, в то время как WriteToсохраняет текущий узел. Так как docявляется объектом на основе XmlDocument, он представляет весь документ и поэтому будет сохранен. Можно было бы также использовать метод Save. Он всегда будет сохранять весь документ. Saveимеет четыре перегружаемые версии. Можно определить строку с именем файла и путем доступа, объект на основе класса Stream, объект на основе класса TextWriter, или объект на основе XmlWriter. Именно это было использовано при выполнении примера. Отметим новую запись в конце списка: Если нужно создать документ с самого начала, можно использовать класс XmlTextWriter. Можно также использовать XmlDocument. Какой из них выбрать? Если данные, которые желательно поместить в XML, доступны и готовы для записи, то самым подходящий будет класс XmlTextWriter. Однако, если необходимо создавать документ XML постепенно, вставляя узлы в различные места, то наиболее приемлемым будет применение XmlDocument. Вот тот же пример, который только что был рассмотрен, но вместо редактирования текущего документа мы создаем новый документ ( DOMSample4): private void button1_Click(object sender, System.EventArgs e) { // создать раздел объявлений XmlDeclaration newDoc=doc.CreateXmlDeclaration("1.0", null, null); doc.AppendChild(newDoc); // создать новый корневой элемент XmlElement newRoot=doc.CreateElement("newBookstore"); doc.AppendChild(newRoot); // создать новый элемент 'book' XmlElement newBook=doc.CreateElement("book"); // создать и задать атрибуты элемента "book" newBook.SetAttribute("genre","Mystery"); newBook.SetAttribute("publicationdate", "2001"); newBook.SetAttribute("ISBN", "123456789"); // создать элемент 'title' XmlElement newTitle=doc.CreateElement("title"); newTitle.InnerText="Case of the Missing Cookie"; newBook.AppendChild(newTitle); // создать элемент author XmlElement newAuthor=doc.CreateElement("author"); newBook.AppendChild(newAuthor); // создать элемент name XmlElement newName=doc.CreateElement("name"); newName.InnerText="C. Monster"; newAuthor.AppendChild(newName); // создать элемент price XmlElement newPrice=doc.CreateElement("price"); newPrice.InnerText="9.95"; newBook.AppendChild(newPrice); // добавить элемент 'book' к doc doc.DocumentElement.AppendChild(newBook); // записать на диск Note новое имя файла booksEdit.xml XmlTextWriter tr=new XmlTextWriter("..\\..\\..\\booksEdit.xml", null); tr.Formatting=Formatting.Indented; doc.WriteContentTo(tr); tr.Close(); // загрузить заголовок в окно списка XmlNodeList nodeLst=doc.GetElementsByTagName("title"); foreach(XmlNode node in nodeLst) listBox1.Items.Add(node.InnerText); } private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) { String srch="newBookstore/book[title='"+ listBox1.SelectedItem.ToString() + "']"; XmlNode foundNode=doc.SelectSingleNode(srch); if (foundNode!=null) MessageBox.Show(foundNode.InnerText); else MessageBox.Show("Not found"); } Заметим, что изменились только две начальные строки. Прежде чем сделать doc.Load, внесем новые элементы: XmlDeclaration newDoc=doc.CreateXmlDeclaration("1.0", null, null); doc.AppendChild(newDoc); XmlElement newRoot=doc.CreateElement("newBookstore"); doc.AppendChild(newRoot); Сначала создается новый объект XmlDeclaration. Параметрами являются версия (в настоящее время всегда "1.0"), кодировка ( nullподразумевает UTF-8) и, наконец, флаг standalone. Он может быть yesили no, но если вводится null или пустая строка, как в нашем случае, этот атрибут не будет добавляться при сохранении документа. Параметр кодировки должен задаваться строкой, которая является частью класса System.Text.Encoding, если не используется null. Следующим создаваемым элементом станет DocumentElement. В данном случае мы называем его newBookstore, чтобы можно было видеть различие. Остальная часть кода является такой же, как и в предыдущем примере, и работает точно так же. Вот файл booksEdit.xml, создаваемый этим кодом: <?xml version="1.0"?> <newBookstore> <book genre="Mystery" publicationdate="2001" ISBN="123456789"> <title>Case of the Missing Cookie</title> <author> <name>C. Monster</name> </author> <price>9.95</price> </book> </newBookstore> Мы не рассмотрели всех особенностей класса XmlDocumentили других классов, способствующих созданию модели DOM в .NET. Однако мы видели мощь и гибкость, которые предлагает реализация DOM в .NET. Класс XmlDocumentобычно используется, когда требуется случайный доступ к документу. Используйте классы на основе XmlReader, когда желательна модель потокового типа. Помните, что гибкость XmlDocumentна основе XmlNodeобеспечивается более высокими требованиями к памяти, поэтому подумайте тщательно о том, какой метод предпочтительнее в конкретной ситуации. XPath и XslTransformМы рассмотрим XPathи XslTransformвместе, хотя они являются отдельными пространствами имен на платформе. XPathсодержится в System.Xml.XPath, a XslTransformнаходится в System.Xml.Xsl. Причина совместного рассмотрения состоит в том, что XPath, в частности класс XPathNavigator, предоставляет ориентированный на производительность способ выполнения XSLTransformв .NET. Для начала рассмотрим XPath, а затем его использование в классах System.Xsl. XPath Пространство имен XPathсоздается для скорости. Оно позволяет только читать документы XML без возможностей редактирования. XPathсоздается для поверхностного выполнения быстрых итераций и выбора в документе XML. Функциональность XPathпредставляется классом XPathNavigator. Этот класс может использоваться вместо XmlDocument, XmlDataDocumentи XPathDocument. Если требуются средства редактирования, то следует выбрать XmlDocument; при работе с ADO.NET будет использоваться класс XmlDataDocument(мы увидим его позже в этой главе). Если имеет значение скорость, то применяйте в качестве хранилища XPathDocument. Можно расширить XPathNavigatorдля таких вещей, как файловая система или реестр в качестве хранилища. В следующей таблице перечислены классы XPathс кратким описанием назначения каждого класса:
XPathDocumentне предлагает никакой функциональности класса XmlDocument. Он имеет четыре перегружаемые версии, позволяющие открывать документ XML из файла или строки пути доступа, объекта TextReader, объекта XmlReaderили объекта на основе Stream. Загрузим документ books.xmlи поработаем с ним, чтобы можно было понять, как действует навигация. Чтобы использовать эти примеры, необходимо добавить ссылки на пространства имен System.Xml.Xslи System.Xml.XPathследующим образом: using System.Xml.XPath; using System.Xml.Xsl; Для данного примера воспользуемся файлом bookspath.xml. Он аналогичен books.xml, за исключением того, что добавлены дополнительные книги. Вот код формы, который находится в папке XPathXSLSample1: private void button1_Click(object sender, System.EventArgs e) { // изменить в соответствии с используемой структурой путей доступа XPathDocument doc=new XPathDocument("..\\..\\..\\booksxpath.xml"); // создать XPathNavigator XPathNavigator nav=((IXPathNavigable)doc).CreateNavigator(); // создать XPathNodeIterator узлов книг // который имеют значение атрибута genre, совпадающее с novel XPathNodeIterator iter=nav.Select("/bookstore/book[@genre='novel']"); while(iter.MoveNext()) { LoadBook(iter.Current); } } private void LoadBook(XPathNavigator lstNav) { // Нам передали XPathNavigator определенного узла book, // мы выберем всех прямых потомков и // загрузим окно списка с именами и значениями XPathNodeIterator iterBook=lstNav.SelectDescendants(XPathNodeType.Element, false); while(iterBook.MoveNext()) listBox1.Items.Add(iterBook.Current.Name + ": " + iterBook.Current.Value); } Здесь сначала создается XPathDocument, передавая строку файла и пути доступа документа, который будет открыт. В следующей строке кода создается XPathNavigator: XPathNavigator nav=((IXPathNavigable)doc).CreateNavigator(); Отметим, что здесь происходит преобразование типа интерфейса IXPathNavigableв только что созданный XPathNavigator, что вызывает метод CreateNavigator. После создания объекта XPathNavigatorможно начать навигацию в документе. Этот пример показывает, как применяются методы Selectдля получения множества узлов, которые имеют novelв качестве значения атрибута genre. Затем мы используем цикл MoveNext()для итераций по всем novelsв списке книг. Для загрузки данных в listboxиспользуется свойство XPathNodeIterator.Current. При этом создается новый объект XPathNavigatorна основе узла, на который указывает XPathNodeIterator. В данном случае создается XPathNavigatorдля одного узла book(книги) в документе. LoadBookсоздает другой XPathNodeIterator, вызывая иной тип метода выбора — метод SelectDescendants. Это даст нам XPathNodeIteratorвсех узлов-потомков и потомков узлов-потомков узла book(книга), переданного в метод LoadBook. Мы делаем другой цикл MoveNext()на этом XPathNodeIteratorи загружаем окно списка именами и значениями элементов. XPathNavigatorсодержит все методы для перемещения и выбора элементов, которые могут понадобиться. Приведем некоторые из методов перемещения:
Существует также несколько методов Selectвыбора подмножества узлов для работы. Все методы Selectвозвращают объект XPathNodeIterator. XPathNodeIteratorможно считать эквивалентом NodeListили NodeSetв XPath. Этот объект имеет три свойства и два метода: □ Clone— создает новую копию себя □ Count— число узлов в объекте XPathNodeIterator □ Current— возвращает XPathNavigator, указывающий на текущий узел □ CurrentPosition— возвращает целое число, соответствующее текущей позиции □ MoveNext— перемещает в следующий узел, соответствующий выражению Xpath, которое создало XPathNodeIterator Можно использовать также существующие методы SelectAncestorsи SelectChildren. Они возвращают XPathNodelterator. В то время, как Selectполучает выражение XPathв качестве параметра, другие методы выбора получают в качестве параметра XPathNodeType. В рассматриваемом примере мы выбираем все узлы XPathNodeType.Element. Вот как выглядит экран после выполнения кода. Обратите внимание, что все перечисленные книги являются романами (novel). Для добавления стоимости книг XPathNavigatorсодержит метод Evaluate. Evaluateимеет три перегружаемые версии. Первая из них содержит строку, которая является вызовом функции XPath. Вторая перегружаемая версия Evaluate использует в качестве параметра объект XPathExpression, третья — XPathExpressionи XPathNodeIterator. Сделаем следующие изменения в примере (эту версию кода можно найти в XPathXSLSample2): private void button1_Click(object sender, System.EventArgs e) { //изменить в соответствии со структурой путей доступа XPathDocument doc=new XPathDocument("..\\..\\..\\booksxpath.XML"); //создать XPathNavigator XPathNavigator nav=((IXPathNavigable)doc).CreateNavigator(); //создать XPathNodeIterator узлов book, // которые имеют novel значением атрибута genre XPathNodeIterator iter=nav.Select("/bookstore/book[@genre="novel']"); while(iter.MoveNext()) { LoadBook(iter.Current.Clone()); } // добавим разделительную линию и вычислим сумму listBox1.Items.Add("========================"); listBox1.Items.Add("Total Cost = " + nav.Evaluate("sum(/bookstore/book[@genre='novel']/price)")); } При этом вывод изменится следующим образом: XslTransformПространство имен System.Xml.Xslсодержит классы XSL, применяемые .NET. XslTransformможет использоваться с любым хранилищем, которое реализует интерфейс IXPathNavigable. В настоящее время на платформе .NET это: XmlDocument, XmlDataDocumentи XPathDocument. Так же как и в случае XPath, воспользуйтесь тем хранилищем, которое подходит лучшим образом. Если планируется создание заказного хранилища, такого как файловая система, и желательно иметь возможность выполнять преобразования, не забудьте реализовать в классе интерфейс IXPathNavigable. XslTransformосновывается на потоковой модели запросов. В связи с этим можно соединить несколько преобразования вместе. Можно даже применять, если нужно, между преобразованиями заказной объект чтения. Это предоставляет большую гибкость при проектировании. В первом примере, который мы рассмотрим, берется документ books.xmlи преобразуется в простой документ HTML для вывода. (Этот код можно найти в папке XPathXSLSample3.) Необходимо будет добавить следующие операторы using: using System.IO; using System.Xml.Xsl; using System.Xml.XPath; Вот код, выполняющий преобразование: private void button1_Click(object sender System.EventArgs e) { //создать новый XPathDocument XPathDocument doc=new XPathDocument("..\\..\\..\\booksxpath.XML"); // создать новый XslTransForm XslTransform transForm=new XslTransform(); transForm.Load("..\\..\\..\\books.xsl"); // этот FileStream будет нашим выводом FileStream fs=new FileStream("..\\..\\..\\booklist.html", FileMode.Create); // Создать Navigator XPathNavigator nav=((IXPathNavigable)doc).CreateNavigator(); // Выполнить преобразование. Файл вывода создается здесь. transForm.Transform(nav, null, fs); } Сделать это преобразование проще почти невозможно. Сначала создается объект на основе XPathDocumentи объект на основе XslTransform. Затем файл bookspath.xmlзагружается в doc, a books.xslв transForm. В этом примере для записи нового документа HTML на диск создается объект FileStream. Если бы это было приложение ASP.NET, мы использовали бы объект TextWriterи передавали бы его в объект HttpResponse. Если бы мы преобразовывали в другой документ XML, то применялся бы объект на основе XmlWriter. После того как объекты XPathDocumentи XslTransformбудут готовы, мы создаем XPathNavigatorна docи передаем navи этот streamв метод Transformобъекта transForm. XslTransformимеет несколько перегружаемых версий, получающих комбинации навигаторов, XsltArgumentList(подробнее об этом позже) и потоков ввода/вывода. Параметром навигатора может быть XPathNavigatorили любой объект, реализующий интерфейс IXPathNavigable. Потоки ввода/вывода могут быть TextWriter, Streamили объектом на основе XmlWriter. Документ books.xslявляется таблицей стилей. Документ выглядит следующим образом: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <html> <head> <title>Price List</title> </head> <body> <table> <xsl:apply-templates/> </table> </body> </html> </xsl:template> <xsl:template match="bookstore"> <xsl:apply-templates select= "book"/> </xsl:template> <xsl:template match="book"> <tr><td> <xsl:value-of select="title"/> </td><td> <xsl:value-of select="price"/> </td></tr> </xsl:template> </xsl:stylesheet> Ранее упоминался объект XsltArgumentList. Это способ, которым можно объект с методами связать с пространством имен. Когда это сделано, можно вызывать методы во время преобразования. Рассмотрим пример, чтобы понять, как это работает (находится в XPathXSLSample4): private void button1_Click(object sender, System.EventArgs e) { // новый XPathDocument XPathDocument doc=new XPathDocument("..\\..\\..\\booksxpath.xml"); // новый XslTransform XslTransform transForm=new XslTransform(); transForm.Load("..\\..\\..\\booksarg.xsl"); // новый XmlTextWriter, так как мы создаем новый документ xml XmlWriter xw=new XmlTextWriter(..\\..\\..\\argSample.xml", null); // создать XslArgumentList и новый объект BookUtils XsltArgumentList argBook=new XsltArgumentList(); BookUtils bu=new BookUtils(); // это сообщает список аргументов BookUtils argBook.AddExtensionObject("urn:ProCSharp", bu); // новый XPathNavigator XPathNavigator nav=((IXPathNavigable)doc).CreateNavigator(); // выполнить преобразование transForm.Transform(nav, argBook, xw); xw.Close(); } // простой тестовый класс public class BookUtils { public BookUtils() {} public string ShowText() { return "This came from the ShowText method!"; } } Вывод преобразования ( argSample.xml) выглядит так: <?xml version="1.0"?> <books> <discbook> <booktitle>The Autobiography of Benjamin Franklin</booktitle> <showtext>This came from the ShowText method!</showLext> </discbook> <discbook> <booktitle>The Confidence Man</booktitle> <showtext>This came from the ShowText method!</showtext> </discbook> <discbook> <booktitle>The Gorgias</booktitle> <showtext>This came from the ShowText method!</showtext> </discbook> <discbook> <booktitle>The Great Cookie Caper</booktitle> <showtext>This came from the ShowText method!</showtext> </discbook> <discbook> <booktitle>A Really Great Book</booktitle> <showtext>This came from the ShowText method!</showtext> </discbook> </books> Определим новый класс BookUtils. В этом классе мы имеем один практически бесполезный метод, который возвращает строку "This came from the ShowText method!". Для события button1_Clickсоздаются XPathDocumentи XslTransformтак же, как это делалось раньше, но с некоторыми исключениями. В этот раз мы собираемся создать документ XML, поэтому используем XMLWriterвместо FileStream. Вот эти изменения: XsltArgumentList argBook=new XsltArgumentList(); BookUtils bu=new BookUtils(); argBook.AddExtensionObject("urn:ProCSharp", bu); Именно здесь создается XsltArgumentList. Мы создаем экземпляр объекта BookUtils, и когда вызывается метод AddExtensionObject, ему передается пространство имен расширения и объект, из которого мы хотим вызывать методы. Когда делается вызов Transform, ему передаются XsltArgumentList( argBook) вместе с XPathNavigatorи созданный объект XmlWriter. Вот документ booksarg.xsl: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:bookutil="urn:ProCSharp"> <xsl:output method="xml" indent="yes"/> <xsl:template match="/"> <xsl:element name="books"> <xsl:apply-templates/> </xsl:element> </xsl:template> <xsl:template match="bookstore"> <xsl:apply-templates select="book"/> </xsl:template> <xsl:template match="book"> <xsl:element name="discbook"> <xsl:element name="booktitle"> <xsl:value-of select="title"/> </xsl:element> <xsl:element name="showtext"> <xsl:value-of select="bookUtil:ShowText()"/> </xsl:element> </xsl:element> </xsl:template> </xsl:stylesheet> Здесь имеются две важные строки. В начале добавляется пространство имен, которое создается при добавлении объекта к XsltArgumentList. Затем применяется стандартный синтаксис использования префикса перед пространством имен XSLT и вызывается метод. Иначе это можно было бы выполнить с помощью сценария XSLT. В таблицу стилей можно включить код C#, VB и JavaScript. Большим достоинством этого является то, что в отличие от текущих реализаций, сценарий компилируется при вызове Transform.Load; таким образом выполняются уже откомпилированные сценарии, в значительной степени так же, как работает ASP.NET. Давайте выполним предыдущий пример таким способом. Добавим сценарий к таблице стилей. Эти изменения можно увидеть в файле bookscript.xsl: <xsl:stylesheet version="1.0" xmlns:Xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="http://wrox.com"> <msxsl:script language="C#" implements-prefix="user"> string ShowText() { return "This came from the ShowText method!"; } </msxsl:script> <xsl:output method="xml" indent="yes"/> <xsl:template match="/"> <xsl:element name="books"> <xsl:apply-templates/> </xsl:element> </xsl:template> <xsl:template match="bookstore"> <xsl:apply-templates select="book"/> </xsl:template> <xsl:template match="book"> <xsl:element name="discbook"> <xsl:element name="booktitle"> <xsl:value-of select="title"/> </xsl:element> <xsl:element name="showtext"> <xsl:value-of select="user:ShowText()"/> </xsl:element> </xsl:element> </xsl:template> </xsl:stylesheet> Изменения включают задание пространства имен сценариев, добавление кода (который скопирован из VS.NET IDE) и выполнение вызова в таблице стилей. Вывод выглядит так же, как и в предыдущем примере. Ключевой момент, о котором необходимо помнить при выполнении преобразований, состоит в том, чтобы не забыть использовать подходящее хранилище; XPathDocument, если не требуется редактирование, XmlDataDocument, если данные получают из ADO.NET, и XmlDocument, если необходимо иметь возможность редактировать данные. Процесс будет таким же, несмотря ни на что. XML и ADO.NETXML является средством, которое связывает ADO.NET с остальным миром. ADO.NET был создан для работы внутри среды XML. XML используется для преобразования данных в и из хранилища данных в приложение или страницу Web. Так как ADO.NET использует XML в качестве транспорта, то данными можно обмениваться с приложениями и системами, которые даже не знают об ADO.NET. Пока обрабатывается XML, они могут совместно использовать данные. ADO.NET может читать документы XML, возвращаемые из этих же приложений. В связи с важностью XML для ADO.NET, существует ряд полезных свойств ADO.NET, которые позволяют чтение и запись документов XML. Пространство имен XML содержит также классы, которые могут потреблять или утилизировать реляционные данные ADO.NET. Данные ADO.NET в документе XMLПервый пример, который будет рассмотрен, использует потоки ADO.NET и XML для извлечения данных из базы данных Northwindв DataSet, загрузки объекта XmlDocument, содержащего XML, из DataSet, и загрузки XML в listbox аналогично тому, что делалось ранее. Чтобы выполнить несколько следующих примеров, необходимо добавить инструкции using: using System.Data; using System.Xml; using System.Data.SqlClient; using System.IO; Также для примеров ADO в формы добавлены DataGrid, что позволит нам увидеть данные в DataSetиз ADO.NET, так как они ограничены сеткой, а также данные из созданных документов XML, которые загружаются в listbox. Вот код первого примера, который можно найти в папке ADOSample1: private void button1_Click(object sender, System.EventArgs e) { // создать множество данных DataSet DataSet ds=new DataSet("XMLProducts"); // соединиться с базой данных northwind и //выбрать все строки из таблицы продуктов //убедитесь, что имя пользователя соответствует версии SqlServer SqlConnection conn= new SqlConnection(@"server=GLYNNJ_CS\NetSDK;uid=sa;pwd=;database=northwind"); SqlDataAdapter da=new SqDataAdapter("select * from products", conn); После создания SqlDataAdapter, daи DataSet, dsсоздаются экземпляры объекта MemoryStream, объекта StreamReaderи объекта StreamWriter. Объекты StreamReaderи StreamWriterбудут применять MemoryStreamдля перемещения XML: MemoryStream memStrm=new MemoryStream(); StreamReader strmRead=new StreamReader(memStrm); StreamWriter strmWrite=new StreamWriter(memStrm); Мы будем использовать MemoryStream, поэтому ничего на диск записываться не будет, однако мы сможем применять любые объекты на основе класса Stream, такие как FileStream. Затем мы заполним DataSetи свяжем его с DataGrid. Данные из DataSetбудут выводиться теперь в DataGrid: da.Fill(ds, "products"); // загрузка данных в DataGrid dataGrid1.DataSource=ds; dataGrid1.DataMember="products"; На следующем шаге генерируется XML. Вызывается метод WriteXmlиз класса DataSet. WriteXmlгенерирует документ XML. Существуют две перегружаемые версии WriteXml, одна получает строку с путем доступа и именем файла, а в другом методе добавлен параметр режима mode. Этот modeявляется перечислением XmlWriteMode. Возможными значениями являются DiffGram, IgnoreSchema, и WriteSchema. Обсудим DiffGramпозже в этом разделе. IgnoreSchemaиспользуется, если нежелательно, чтобы WriteXmlзаписывал подставляемую ( inline) схему в файл XML; используйте параметр WriteSchema, если это желательно. Чтобы получить именно схему, вызывается WriteXmlSchema. Этот метод имеет четыре перегружаемые версии. Одна получает строку, содержащую путь доступа и имя файла, куда записывается документ XML. Вторая версия использует объект, который основывается на классе XmlWriter. Третья версия использует объект, который основывается на классе TextWriter. Четвертая версия используется в примере, параметр в этом случае является производным от класса Stream: ds.WriteXml(strmWrite, XmlWriteMode.IgnoreSchema); memStrm.Seek(0, SeekOrigin, Begin); // читаем из потока в памяти в объект XmlDocument doc.load(strmRead); // получить все элементы продуктов XmlNodeList nodeLst=doc.GetElementsByTagName("ProductName"); // загрузить их в окно списка foreach(XmlNode nd in nodeLst) listBox1.Items.Add(nd.InnerText); } private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) { // при щелчке в окне списка // появляется окно сообщения с ценой изделия string srch= "XmlProducts/products[ProductName= " + '"' + listBox1.SelectedItem.ToString() + "]"; XmlNode foundNode=doc.SelectSingleNode(srch); if (foundNode!=null) MessageBox.Show(foundNode.SelectSingleNode("UnitPrice").InnerText); else MessageBox.Show("Not found"); } На следующем экране можно видеть данные в списке, а также в таблице данных: Если желательно сохранить документ XML на диске, то нужно сделать примерно следующее: string file = "с:\\test\\product.xml"; ds.WriteXml(file); Это даст нам правильно сформированный документ XML на диске, который можно прочитать посредством другого потока, с помощью DataSet, или может использоваться другим приложением или web-сайтом. Так как никакого параметра XmlModeне определено, этот документ XmlDocumentбудет содержать схему. В нашем примере в качестве параметра для метода XmlDocument.Loadиспользуется поток. Когда XmlDocumentподготовлен, мы загружаем listboxс помощью того же объекта XPath, который использовался раньше. Если посмотреть внимательно, то можно заметить, что слегка изменено событие listBox1_SelectedIndexChanged. Вместо вывода InnerTextэлемента, выполняется другой поиск XPathс помощью SelectSingleNode, чтобы получить элемент UnitPrice. Каждый раз при щелчке на продукте в listboxбудет появляться MessageBoxдля UnitPrise. Теперь у нас есть два представления данных, но более важно то, что имеется возможность манипулировать данными с помощью двух различных моделей. Можно использовать пространство имен Data для данных или пространство имен XML через данные. Такой подход ведет к очень гибким конструкциям в приложениях, так как теперь при программировании нет жесткой связи только с одной объектной моделью. Таким образом, мы имеем несколько представлений одних и тех же данных и несколько способов доступа к данным. Следующий пример будет упрощать процесс, удаляя три потока и используя некоторые возможности ADO, встроенные в пространство имен XML. Нам понадобится изменить строку кода на уровне модуля: private XmlDocument doc=new XmlDocument(); на: private XmlDataDocument doc; Это нужно сделать, так как мы не собираемся использовать XmlDataDocument. Вот код, который можно найти в папке ADOSample2: private void button1_Click(object sender, System.EventArgs e) { // создать множество данных (DataSet) DataSet ds=new DataSet("XMLProducts"); // соединиться с базой данных northwind и //выбрать все строки из таблицы products //выполнить изменения в строке подключения с учетом имени пользователя и имени сервера SqlConnection conn= new SqlConnection(@"server=GLYNNJ_CS\NetSDK;uid=sa;pwd=;database=northwind"); SqlDataAdapter da=new SqlDataAdapter("select * from products", conn); // заполнить множество данных da.Fill(ds, "products"); // загрузить данные в сетку dataGrid1.DataSource=ds; dataGrid1.DataMember="products"; doc=new XmlDataDocument(ds); // извлечь все элементы продуктов XmlNodeList nodeLst=doc.GetElementsByTagName("ProductName"); // загрузить их в окно списка // здесь используется цикл for for(int ctr=0; ctr<nodeLst.Count; ctr++) listBox1.Items.Add(nodeLst[ctr].InnerText); } Как можно видеть, код для загрузки DataSetв документ XML был упрощен. Вместо использования класса XmlDocument, используется класс XmlDataDocument. Этот класс был создан специально для использования данных с объектом DataSet. XmlDataDocumentбазируется на классе XmlDocument, поэтому он имеет всю функциональность класса XmlDocument. Одним из основных отличий является перегруженный конструктор для XmlDataDocument. Отметим строку кода, где создается экземпляр XmlDataDocument: XmlDataDocument doc=new XmlDataDocument(ds); Он передает в качестве параметра созданный объект DataSet, ds. Документ XML создается из множества данных, поэтому не требуется использование метода Load. Существует также свойство DataSet, которое может задаваться с помощью текущего свойства DataSet. Фактически, если создается новый объект XmlDataDocumentбез передачи DataSetв качестве параметра, то он содержит объект DataSetс именем NewDataSet, который не имеет DataTablesв коллекции таблиц. Существует также свойство DataSet, которое можно установить после создания объекта на основе XmlDataDocument. Если после вызова DataSet.Fillдобавляется следующая строка кода: ds.WriteXml("с:\\test\\sample.xml" , XmlWriteMode, WriteSchema); …создается следующий XML. Отметим, что мы включили в документ схему XSD. Если нежелательно, чтобы схема включалась в файл, то можно передать член перечисления XmlWriteMode.IgnoreSchema: <?xml version="1.0" standalone="yes"?> <XMLProducts> <xsd:schema id="XMLProducts" targetNamespace="" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:element name="XMLProducts" msdata:IsDataSet="true"> <xsd:complexType> <xsd:choice maxOccurs="unbounded"> <xsd:element name="products"> <xsd:complexType> <xsd:sequence> <xsd:element name="ProductID" type="xsd:int" minOccurs="0" /> <xsd:element name="ProductName" type="xsd:string" minOccurs="0" /> <xsd:element name="SupplierID" type="xsd:int" minOccurs ="0" /> <xsd:element name="CategoryID" type="xsd:int" minOccurs="0" /> <xsd:element name="QuantityPerUnit" type="xsd:string" minOccurs="0" /> <xsd:element name="UnitPrice" type="xsd:decimal" minOccurs="0" /> <xsd:element name="UnitsInStock" type="xsd:short" minOccurs="0" /> <xsd:element name="UnitsOnOrder" type="xsd:short" minOccurs="0" /> <xsd:element name="ReorderLevel" type="xsd:short" minOccurs="0" /> <xsd:element name="Discontinued" type="xsd:boolean" minOccurs="0" /> </xsd:sequence> </xsd:сomplexType> </xsd:element> </xsd:choice> </xsd:complexType> </xsd:element> </xsd:schema> <products> <ProductID>1</ProductID> <ProductName>Chai</ProductName> <SupplierID>1</SupplierID> <CategoryID>1</CategoryID> <QuantityPerUnit>10 boxes x 20 bags</QuantityPerUnit> <UnitPrice>18</UnitPrice> <UnitsInStock>39</UnitsInStock> <UnitsOnOrder>0</UnitsOnOrder> <ReorderLevel>10</ReorderLevel> <Discontinued>false</Discontinued> </products> <products> <ProductID>2</ProductID> <ProductName>Chang</ProductName> <SupplierID>1</SupplierID> <CategoryID>1</CategoryID> <QuantityPerUnit>24 - 12 oz bottles</QuantityPerUnit> <Unitprice>19</UnitPrice> <UnitsInStock>17</UnitsInStock> <UnitsOnOrder>40</UnitsOnOrder> <ReorderLevel>25</ReorderLevel> <Discontinued>false</Discontinued> </products> </XMLProducts> Показаны только два первых продукта. Реальный файл XML будет содержать все продукты из таблицы Productsбазы данных Northwind. Это выглядит достаточно просто для одной таблицы, но что будет для реляционных данных, таких как несколько DataTablesи Relationsв DataSet? Все по-прежнему работает таким же образом. Внесем следующие изменения в коде (эту версию можно найти в ADOSample3): private void button1_Click(object sender, System.EventArgs e) { //создать множество данных (DataSet) DataSet ds=new DataSet("XMLProducts"); // соединиться с базой данных northwind и //выбрать все строки из таблицы products и таблицы suppliers //проверьте, что строка соединения соответствует конфигурации сервера SqlConnection conn= new SqlConnection(@"server=GLYNNJ_CS\NetSDK;uid=sa;pwd=;database=northwind"); SqlDataAdapter daProd=new SqlDataAdapter("select * from products", conn); SqlDataAdapter daSup=new SqlDataAdapter("select * from suppliers", conn); //Заполнить DataSet из обоих SqlAdapters daProd.Fill(ds, "products"); daSup.Fill(ds, "suppliers"); //Добавить отношение ds.Relations.Add(ds.Tables["suppliers"].Columns["SupplierId"], ds.Tables["products"].Columns["SupplierId"]); //Записать Xml в файл, чтобы можно было просмотреть его позже ds.WriteXml("..\\..\\..\\SuppProd.xml", XmlWriteMode.WriteSchema); //загрузить данные в таблицу dataGrid1.DataSource=ds; dataGrid1.DataMember="suppliers"; //создать XmlDataDocument doc=new XmlDataDocument(ds); //Выбрать элементы productname и загрузить их в таблицу XmlNodeList nodeLst=doc.SelectNodes("//ProductName"); foreach(XmlNode nd in nodeLst) listBox1.Items.Add(nd.InnerXml); } В этом примере создаются два объекта DataTablesв DataSetиз XMLProducts: Productsи Suppliers. Отношение состоит в том, что Suppliers(Поставщики) поставляют Products(Продукты). Мы создаем новое отношение на столбце SupplierIdв обоих таблицах. Вот как выглядит DataSet: Делая такой же вызов метода WriteXml, как в предыдущем примере, мы получим следующий файл XML ( SuppProd.xml): <?xml version="1.0" standalone="yes"?> <XMLProducts> <xsd:schema id="XMLProducts" targetNamespace="" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:element name="XMLProducts" msdata:IsDataSet="true"> <xsd:complexType> <xsd:choice maxOccurs="unbounded"> <xsd:element name="products"> <xsd:complexType> <xsd:sequence> <xsd:element name="Product ID" type="xsd:int" minOccurs="0" /> <xsd:element name="ProductName" type="xsd:string" minOccurs="0" /> <xsd:element name="SupplierID" type="xsd:int" minOccurs="0" /> <xsd:element name="CategoryID" type="xsd:int" minOccurs="0" /> <xsd:element name="QuantityPerUnit" type="xsd:string" minOccurs="0" /> <xsd:element name="UnitPrice" type="xsd:decimal" minOccurs="0" /> <xsd:element name="UnitsInStock" type="xsd:short" minOccurs="0" /> <xsd:element name="UnitsOnOrder" type="xsd:short" minOccurs="0" /> <xsd:element name="ReorderLevel" type="xsd:short" minOccurs="0" /> <xsd:element name="Discontinued" type="xsd:boolean" minOccurs="0" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="suppliers"> <xsd:complexType> <xsd:sequence> <xsd:element name="SupplierID" type="xsd:int" minOccurs="0" /> <xsd:element name="CompanyName" type="xsd:string" minOccurs="0" /> <xsd:element name="ContactName" type="xsd:string" minOccurs="0" /> <xsd:element name="ContactTitle" type="xsd:string" minOccurs="0" /> <xsd:element name="Address" type="xsd:string" minOccurs="0" /> <xsd:element name="City" type="xsd:string" minOccurs="0" /> <xsd:element name="Region" type="xsd:string" minOccurs="0" /> <xsd:element name="PostalCode" type="xsd:string" minOccurs="0" /> <xsd:element name="Country" type="xsd:string" minOccurs="0" /> <xsd:element name="Phone" type="xsd:string" minOccurs="0" /> <xsd:element name="Fax" type="xsd:string" minOccurs="0" /> <xsd:element name="HomePage" type="xsd:string" minOccurs="0" /> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:complexType> <xsd:unique name="Constraint1"> <xsd:selector xpath=".//suppliers" /> <xsd:field xpath="SupplierID" /> </xsd:unique> <xsd:keyref name="Relation1" refer="Constraint1"> <xsd:selector xpath=".//products" /> <xsd:field xpath="SupplierID" /> </xsd:keyref> </xsd:elements> </xsd:schema> <products> <ProductID>1</ProductID> <ProductName>Chai</ProductName> <SupplierID>1</SupplierID> <CategoryID>1</CategoryID> <QuantityPerUnit>10 boxes x 20 bags</QuantityPerUnit> <UnitPrice>18</UnitPrice> <UnitsInStock>39</UnitsInStock> <UnitsOnOrder>0</UnitsOnOrder> <ReorderLevel>10</ReorderLevel> <Discontinued>false</Discontinued> </products> <products> <ProductID>2</ProductID> <ProductName>Chang</ProductName> <SupplierID>1</SupplierID> <CategoryID>1</CategoryID> <QuantityPerUnit>24 - 12 oz bottles</QuantityPerUnit> <UnitPrice>19</UnitPrice> <UnitsInStock>17</UnitsInStock> <UnitsOnOrder>40<UnitsOnOrder> <ReorderLevel>25</ReorderLevel> <Discontinued>false</Discontinued> </products> <suppliers> <SupplierID>1</SupplierID> <CompanyName>Exotiс Liquids</CompanyName> <ContactName>Charlotte Cooper</ContactName> <ContactTitle>Purchasing Manager</ContactTitle> <Address>49 Gilbert St.</Address> <City>London</City> <PostalCode>EC1 4SD</PostalCode> <Country>UK</Country> <Phone>(171) 555-2222</Phone> </suppliers> <suppliers> <Supplier ID>2</SupplierID> <CompanyName>New Orleans Cajun Delights</CompanyName> <ContactName>Shelley Burke</ContactName> <ContactTitle>Order Adminisirator</ContactTitle> <Address>P.O. Box 78934</Address> <City>New Orleans</City> <Region>LA</Region> <PostalCode>70117</PostalCode> <Country>USA</Country> <Phone>(100) 555-4822</Phone> <HomePage>#CAJUN.HTM#</HomePage> </suppliers> </XMLProducts> Эта схема включает в себя обе таблицы данных DataTables, которые находились в DataSet. Данные также содержат все данные из обеих таблиц. Несколько продуктов и поставщиков были удалены из окончательного файла, чтобы сэкономить пространство. Как и раньше, можно сохранить только схему или только данные, передавая соответствующий параметр XmlWriteMode. Преобразование документа XML в данные ADO.NETПредположим что имеется документ XML, который нужно поместить в DataSetADO.NET. И вы хотите сделать это так. чтобы можно было загрузить XML в базу данных, или, может быть, связать данные с управляющим элементом данных .NET, таким как DataGrid. Таким образом, можно будет на самом деле использовать документ XML в качестве хранилища данных, и можно будет полностью исключить накладные расходы, связанные с базой данных. Вот некоторый код для начала ( ADOSample4): private void button1_Click(object sender, System.EventArgs e) { // создать новое множество данных (DataSet) DataSet ds=new DataSet("XMLProducts"); //считать документ Xml в Dataset ds.ReadXml("..\\..\\..\\prod.xml"); //загрузить данные в таблицу detaGrid1.DataSource=ds; dataGrid1.DataMember="products"; //создать новый XmlDataDocument doc=new XmlDataDocument(ds); //загрузить имена продуктов в окно списка XmlNodeList nodeLst=doc.SelectNodes("//ProductName"); foreach(XmlNode nd in nodeLst) listBox1.Items.Add(nd.InnerXml); } Действительно, просто. Создается экземпляр нового объекта DataSet. Вызывается метод ReadXml, и XML оказывается в DataTableв DataSet. Как и методы WriteXml, ReadXmlимеет параметр XmlReadModeи пару дополнительных опций в XmlReadMode. Они приводятся в следующей таблице:
Существует также метод ReadSchema. Он будет считывать автономную схему и создавать таблицы, столбцы и отношения соответственно. Этот метод используется, если схема не поставляется вместе с данными. ReadSchemaимеет те же четыре перегружаемые версии, строку с именем файла и путем доступа, объект на основе Stream, объектна основе TextReaderи объект на основе XmlReader. Чтобы показать, что таблицы данных будут созданы правильно, загрузим документ XML, который содержит таблицы Productsи Suppliers, использовавшиеся в предыдущем примере. В этот раз, однако, загрузим в listboxимена DataTable, имена DataColumnи тип данных. Мы можем сравнить это с первоначальной базой данных Northwind, чтобы убедиться, что все по-прежнему хорошо. Вот код, который будет применяться и который можно найти в ADOSample5: private void button1_Click(object sender, System.EventArgs e) { // создать DataSet DataSet ds=new DataSet("XMLProducts"); // считать документ xml ds.ReadXml("..\\..\\..\\SuppProd.xml"); // загрузить данные в сетку dataGrid1.DataSource=ds; dataGrid1.DataMember="products"; // загрузить в listbox информацию о таблицах, столбцах и типах данных foreach(DataTable dt in ds.Tables) { listBox1.Items.Add(dt.TableName); foreach(DataColumn col in dt.Columns) { listBox1.Items.Add('\t' + col.ColumnName + " - " + col.DataType.FullName); } } } Отметим добавление двух циклов foreach(выделенных полужирным шрифтом). Первый цикл извлекает имя таблицы из каждой таблицы в коллекции таблиц DataSet. Внутри цикла foreachизвлекаются имя и тип данных каждого столбца в DataTable. Эти данные загружаются в listbox, чтобы их можно было вывести. Вот что мы должны увидеть на экране: Посмотрев на listbox, можно увидеть, что DataTablesбыли созданы с помощью столбцов, которые имеют правильные имена и типы данных. Кроме того, можно заметить, что последние два примера не преобразуют никаких данных в или из базы данных, таким образом, не определены SqlDataAdaptersили SqlConnections. Это дает начальное представление о реальной гибкости пространства имен System.Xmlи ADO.NET. Можно посмотреть на одни и те же данные в множестве форматов. Если понадобиться сделать преобразование и показать данные в формате HTML или необходимо связать их с сеткой, возьмите те же самые данные и с помощью вызова метода получите их в нужном формате. Запись и чтение DiffGramDiffGramявляется документом XML, который содержит данные до и после сеанса редактирования, включающего любую комбинацию изменений данных, добавлений и удалений. DiffGramможет использоваться как аудиторский журнал или для процесса подтверждения/отката. Большинство систем DBMS сегодня имеют такое встроенное средство. Но если придется работать с DBMS, которая не имеет таких свойств или если хранилищем данных является XML и отсутствует DBMS, то можно будет самостоятельно реализовать свойства подтверждения/отката. Далее представлен код, показывающий, как DiffGramсоздается и как DataSetможно создать из DiffGram:pwd(он находится в папке ADOSample6). Начальная часть этого кода должна быть уже знакома. Мы определяем и задаем новый объект DataSet, ds, новый объект SqlConnection, connи новый объект SqlDataAdapter, da. Мы соединяемся с базой данных, выбираем все строки из таблицы Products, создаем новый объект DataTableс именем productsи загружаем данные из базы данных в DataSet: private void button1_Click(object sender, System.EventArgs e) { // новый объект DataSet DataSet ds=new DataSet("XMLProducts"); // Сделать соединение и загрузить строки продуктов SqlConnection conn= new SqlConnection(@"server=GLYNNJ_CS\NetSDK;uid=sa;pwd=;database=northwind"); SqlDataAdapter da=new SqlDataAdapter("select * from products", conn); // заполнить DataSet da.Fill(ds, "products"); // редактируем первую строку ds.Tables["products"].Rows[0]["ProductName"] = "NewProdName"; В следующем разделе мы сделаем следующие преобразования. Во-первых, изменим столбец ProductNameв первой строке на NewProdName. Во-вторых, создадим новую строку в DataTable, задавая значения столбцов и добавляя в конце новую строку данных в DataTable. // добавить новую строку DataRow dr=ds.Tables["products"].NewRow(); dr["ProductId"]=100; dr["CategoryId"]=2; dr["Discontinued"]=false; dr["ProductName"]="This is the new product"; dr["QuantityPerUnit"]=12; dr["ReorderLevel"]=1; dr["SupplierId"]=12; dr["UnitPrice"]=23; dr["UnitsInStock"]=5; dr["UnitsOnOrder"]=0; Tables["products"].Rows.Add(dr); Это интересная часть кода. Прежде всего записывается схема с помощью WriteXmlSchema. Это важно, так как нельзя будет заново считать в DiffGramбез схемы. WriteXmlс переданным в него параметром XmlWriteMode.DiffGramсоздает в действительности DiffGram. Следующая строка принимает сделанные изменения. Важно то, что DiffGramсоздается до вызова AcceptChanges, иначе не будет никакого различия между состоянием до того и после. // записать схему ds.WriteXmlSchema("..\\..\\..\\diffgram.xsd"); // создать DiffGram ds.WriteXml("..\\..\\..\\diffgram.xml", XmlWriteMode.DiffGram); ds.AcceptChanges(); // загрузить данные в сетку dataGrid1.DataSource=ds; dataGrid1.DataMember="products"; // новый объект XmlDataDocument doc=new XmlDataDocument(ds); // загрузить имена продуктов в список XmlNodeList nodeLst=doc.SelectNodes("//ProductName"); foreach (XmlNode nd in nodeLst) listBox1.Items.Add(nd.InnerXml); Чтобы вернуть данные в множество DataSet, можно сделать следующее: DataSet dsNew = new DataSet(); dsNew.ReadXmlSchema("..\\..\\..\\diffgram.xsd"); dsNew.XmlRead("..\\..\\..\\diffgram.xml", XmlReadMode.DiffGram); В этом примере создается новый объект множества данных DataSet, dsNew. Вызов метода ReadXmlSchemaсоздает новый объект DataTableна основе информации схемы. В данном случае он будет клоном DataTableпродуктов. Теперь можно считать в DiffGram. DiffGramне содержит информации о схеме, поэтому важно, чтобы объект DataTableбыл создан и готов к использованию до вызова метода ReadXml. Вот образец того, как выглядит DiffGram( diffgram.xml): <?xml version="1.0" standalone="yes"?> <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> <XMLProducts> <products diffgr:id="products1" msdata:rowOrder="0" diffgr:hasChanged="modified"> <ProductID>1</ProduсtID> <ProductName>NewProdName</ProductName> <SupplierID>1</SupplierID> <CategoryID>1</CategoryID> <QuantityPerUnit>10 boxes x 20 bags</QuantityPerUnit> <UnitPrice>18</UnitPrice> <UnitsInStock>39</UnitsInStock> <UnitsOnOrder>0</UnitsOnOrder> <ReorderLevel>10</ReorderLevel> <Discontinued>false</Discontinued> </products> <products diffgr:id="products2" msdata:rowOrder="1"> <ProductID>2</ProductID> <ProduсtName>Chang</ProductName> <SupplierID>1</SupplierID> <CategoryID>1</CategoryID> <QuantityPerUnit>24 - 12 oz bottles</QuantityPerUnit> <UnitPrice>19</UnitPrice> <UnitsInStock>17</UnitsInStock> <UnitsOnOrder>40</UnitsOnOrder> <ReorderLevel>25</ReorderLevel> <Discontinued>false</Discontinued> </products> … <products diffgr:id="products78" msdata:rowOrder="77" diffgr:hasChanges="inserted"> <ProductID>100</ProductID> <ProductName>This is a new product</ProductName> <SupplierID>12</SupplierID> <CategoryID>2</CategoryID> <QuantityPerUnit>12</QuantityPerUnit> <UnitPrice>23</UnitPrice> <UnitsInStock>5</UnitsInStock> <UnitsOnOrder>0</UnitsOnOrder> <ReorderLevel>1</ReorderLevel> <Discontinued>false</Discontinued> </products> </XMLProducts> <diffgr:before> <products diffgr:id="products1" msdata:rowOrder="0"> <ProductID>1</ProductID> <ProductName>Chai </ProductName> <SupplierID>1</SupplierID> <CategoryID>1</CategoryID> <QuantityPerUnit>10 boxes x 20 bugs </QuantityPerUnit> <UnitPrice>18</UnitPrice> <UnitsInStock>39</UnitsInStock> <UnitsOnOrder>0</UnitsOnOrder> <ReorderLevel>10</ReorderLevel> <Discontinued>false</Discontinued> </products> </diffgr:before> </diffgr:diffgram> Заметим, каким образом повторяется каждая строка DataTable, и что существует атрибут diffgr:idдля каждого элемента <products>. diffgrявляется префиксом пространства имен для urn:schemas-microsoft-com:xml-diffgram-v1. Для модифицированной строки и для вставленной строки ADO.NET добавляет атрибут diffgr:hasChanges. Здесь есть также элемент <diffgr:before>после элемента <XMLProducts>, который содержит элемент <products>, указывающий на предыдущее содержание всех модифицированных строк. Для добавленной строки не существует "before", поэтому здесь отсутствует элемент <diffgr:before>, однако он присутствует для модифицированной строки. После того как DiffGramсчитан в DataTable, он оказывается в состоянии, в котором он был бы после выполнения изменений в данных перед вызовом AcceptChanges. В этом месте можно на самом деле откатить изменения, вызывая метод RejectChanges. Проверяя свойство DataRow.Itemи передавая либо DataRowVersion.Original, либо DataRowVersion.Current, можно увидеть значения в DataTableперед и после изменений. Сериализация объектов в XMLСериализация является процессом сохранения объекта на диске. Другая часть приложения или даже другое приложение могут десериализовать объект и он будет в том же состоянии, в каком он был до сериализации. Платформа .NET содержит два способа выполнения сериализации. Рассмотрим пространство имен System.Xml.Serialization. Как показывает имя, сериализация производится в формате XML. Это делается преобразованием открытых свойств объекта и открытых полей в элементы и/или атрибуты. Сериализатор XML не может преобразовать скрытые данные, а только открытые. Представляйте это как способ сохранения состояния объекта. Если необходимо сохранить скрытые данные, то используйте BinaryFormatterв пространстве имен System.Runtime.Serialization.Formatters.Binary. Можно также: □ Определить, должны ли данные быть атрибутом или элементом. □ Определить пространство имен. □ Изменить имя атрибута или элемента. Вместе с возможностью сериализовать только открытые данные, невозможно сериализовать графы объектов (объекты, которые достижимы из сериализуемого объекта). Это не является серьезным ограничением. При тщательном проектировании классов этого легко можно избежать. Если необходимо иметь возможность сериализовать открытые и скрытые данные, а также граф объектов, содержащий множество вложенных объектов, то можно будет воспользоваться пространством имен System.Runtime.Serialization.Formatters.Binary. Данные для сериализации могут быть примитивными типами данных, полями, массивами и XML, встроенным в форму объектов XmlElementи XmlAttribute. Связью между объектом и документом XML являются специальные атрибуты, которые аннотируют классы. Эти атрибуты используются для того, чтобы информировать сериализатор, как записать данные. На платформе .NET существует инструмент, помогающий создавать атрибуты,— это утилита xsd.exe, которая может делать следующее: □ Генерировать схему XML из файла схемы XDR □ Генерировать схему XML из файла XML □ Генерировать классы DataSetиз файла схемы XSD □ Генерировать классы времени выполнения, которые имеют специальные атрибуты для XmlSerilization □ Генерировать XSD из классов, которые уже были разработаны □ Ограничивать список элементов, которые создаются в коде □ Определять, на каком языке программирования должен быть представлен генерируемый код (C#, VB.NET, или JScript.NET) □ Создавать схемы из типов в компилированных сборках В документации платформы можно найти подробное описание параметров командной строки. Несмотря на предлагаемые возможности, вовсе не обязательно использовать xsd.exe, чтобы создать классы для сериализации. Рассмотрим простое приложение, которое сериализует класс, считывающий данные о продуктах, сохраненных ранее в этой главе (пример находится в папке SerialSample1). В начале идет очень простой код, который создает новый объект Product, pd, и записывает некоторые данные: private void button1_Click(object sender, System.EventArgs e) { // новый объект Products Products pd=new Products(); // задать некоторые свойства pd.ProductXD=200; pd.CategoryID=100; pd.Discontinued=false; pd.ProductName="Serialize Objects"; pd.QuantityPerUnit="6"; pd.ReorderLevel=1; pd.SupplierID=1; pd.UnitPrice=1000; pd.UnitsInStock=10; pd.UnitsOnOrder=0; Метод Serializeкласса XmlSerializerимеет шесть перегружаемых версий. Одним из требуемых параметров является поток для записи в него данных. Это может быть Stream, TextWriterили XmlWriter. В данном случае мы создали объект trна основе TextWriter. Затем необходимо создать объект srна основе XmlSerializer. XmlSerializerдолжен знать информацию о типе сериализуемого объекта, поэтому используется ключевое слово typeofс указанием типа, который должен быть сериализован. После создания объекта srвызывается метод Serialize, в который передается tr(объект на основе Stream) и объект, который необходимо сериализовать, в данном случае pd. Не забудьте закрыть поток, когда закончите с ним работу. //новый TextWriter и XmlSerializer TextWriter tr=new StreamWriter("..\\..\\..\\serialprod.xml"); XmlSerializer sr=new XmlSerializer(typeof(Products)); // сериализуем объект sr.Serialize(tr,pd); tr.Close(); } Здесь мы добавляем событие другой кнопки для создания нового объекта newPdна основе Products. В этот раз мы будем использовать объект FileStreamдля чтения XML: private void button2_Click(object sender, System.EventArgs e) { // создаем ссылку на тип Products Products newPd; // новый файловый поток для открытия сериализованного объекта FileStream f=new FileStream("..\\..\\..\\serialprod.xml", FileMode.Open); Здесь создается новый объект XmlSerializer, таким образом передается информация о типе Product. Затем можно вызвать метод Deserialize. Отметим, что нам по-прежнему необходимо делать явное преобразование типа, когда создается объект newPd. В этом месте newPd имеет такое же состояние, как и pd: // новый Serializer XmlSerializer newSr=new XmlSerializer(typeof(Products)); // десериализация объекта newPd=(Products)newSr.Deserialize(f); // загружаем его в окно списка. listBox1.Items.Add(newPd.ProductName); f.Closed(); } Теперь мы проверим класс Products. Единственное различие между ним и любым другим классом, который можно записать, состоит в добавленных атрибутах. Не путайте эти атрибуты с атрибутами в документе XML. Эти атрибуты расширяют класс SystemAttribute. Атрибут является некоторой декларативной информацией, которая может извлекаться во время выполнения с помощью CLR (см. в главе 6 более подробно). В данном случае добавляются атрибуты, которые описывают, как объект должен быть сериализован: //класс, который будет сериализован, //атрибуты определяют, как объект сериализуется. [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)] public class Products { [System.Xml.Serialization.XmlElementAttribute(IsNullable=false)] public int ProductID; [System.Xml.Serialization.XmlElementAttribute(IsNullable=false)] public string ProductName; [System.Xml.Serialization.XmlElementAttribute()] public int SupplierID; [System.Xml.Serialization.XmlElementAttribute()] public int CategoryID; [System.Xml.Serialization.XmlElementAttribute()] public string QuantityPerUnit; [System.Xml.Serialization.XmlElementAttribute()] public System.Decimal UnitPrice; [System.Xml.Serialization.XmlElementAttribute()] public short UnitsInStock; [System.Xml.Serialization.XmlElementAttribute()] public short UnitsOnOrder; [System.Xml.Serialization.XmlElementAttribute()] public short ReorderLevel; [System.Xml.Serialization.XmlElementAttribute()] public bool Discontinued; } Созданный документ XML выглядит, как любой другой документ XML, который мы могли бы создать. <?xml version="1.0" ?> <Products xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <ProductID>200</ProductID> <ProductName>Serialize Objects</ProductName> <SupplierID>1</SupplierID> <CategoryID>100</CategoryID> <QuantityPerUnit>6</QuantityPerUnit> <UnitPrice>1000</UnitPrice> <UnitsInStock>10</UnitsInStock> <UnitsOnOrder>0</UnitsOnOrder> <ReorderLevel>1</ReorderLevel> <Discontinued>false</Discontinued> </Products> Здесь нет ничего необычного. Мы могли бы выполнить преобразование документа XML и вывести его как HTML, загрузить его в DataSetс помощью ADO.NET, загрузить с его помощью XmlDocument, как в примере, десериализовать его и создать объект в том же состоянии, которое имел pdперед своей сериализацией (что соответствует событию второй кнопки). Рассмотренный только что пример является очень простым. Обычно имеется ряд методов получения (get) и задания (set) свойств для работы с данными в объекте. Но что, если объект состоит из двух других объектов, или выводится из базового класса, из которого следуют и другие классы? Такие ситуации обрабатываются с помощью класса XmlSerializer. Давайте усложним пример (находится в SerialSample2). Для большей реалистичности сделаем каждое свойство доступным через методы get и set: private void button1_Click(object sender, System.EventArgs e) { // новый объект products Products pd=new Products(); // задать некоторые свойства pd.ProductID=200; pd.CategoryID=100; pd.Discontinued=false; pd.ProductName="Serialize Objects"; pd.QuantityPerUnit="6"; pd.ReorderLevel=1; pd.SupplierID=1; pd.UnitPrice=1000; pd.UnitsInStock=10; pd.UnitsOnOrder= 0; pd.Discount=2; //новые TextWriter и XmlSerializer TextWriter tr=new StreamWriter("..\\..\\..\\serialprod1.xml"); XmlSerializer sr=new XmlSerializer(typeof(Products)); // сериализируем объект sr.Serialize(tr, pd); tr.Close(); } private void button2_Click(object sender, System.EventArgs e) { //создать ссылку на тип Products Products newPd; // новый файловый поток для открытия сериализуемого объекта FileStream f=new FileStream("..\\..\\..\\serialprod1.xml", FileMode.Open); // новый сериализатор XmlSerializer newSr=new XmlSerializer(typeof(Products)); //десериализуем объект newPd=(Products)newSr.Deserialize(f); //загрузить его в окно списка. listBox1.Items.Add(newPd.ProductName); f.Close(); } //класс, который будет сериализован. //атрибуты определяют, как объект сериализуется [System.Xml.Serialization.XmlRootAttribute()] public class Products { private int prodId; private string prodName; private int suppId; private int catId; private string qtyPerUnit; private Decimal unitPrice; private short unitsInStock; private short unitsOnOrder; private short reorderLvl; private bool discont; private int disc; // добавлен атрибут Discount [XmlAttributeAttribute(AttributeName="Discount")] public int Discount { get {return disc;} set {disc=value;} } [XmlElementAttribute()] public int ProductID { get {return prodId;} set {prodId=value;} } [XmlElementAttribute()] public string ProductName { get {return prodName;} set {prodName=value;} } [XmlElementAttribute()] public int SupplierID { get {return suppId;} set {suppId=value;} } [XmlElementAttribute()] public int CategoryID { get {return catId;} set {catId=value;} } [XmlElementAttribute()] public string QuantityPerUnit { get {return qtyPerUnit;} set {qtyPerUnit=value;} } [XmlElementAttribute()] public Decimal UnitPrice { get {return UnitPrice;} set {unitPrice=value;} } [XmlElementAttribute()] public short UnitsInStock { get {return unitsInStock;} set {unitsInStock=value;} } [XmlElementAttribute()] public short UnitsOnOrder { get {return unitsOrOrder;} set {unitsOnOrder=value;} } [XmlElementAttribute()] public short ReorderLevel { get {return reorderLvl;} set {reorderLvl=value;} } [XmlElementAttribute()] public pool Discontinued { get {return discont;} set {discont=value;} } } Выполнение этого кода вместо класса Productsв предыдущем примере даст те же самые результаты с одним исключением. Мы добавили атрибут Discount, тем самым показав, что атрибуты также могут быть сериализуемыми. Вывод выглядит следующим образом ( serialprod1.xml): <?xml version="1.0" encoding="utf-8"?> <Products xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Discount="2"> <ProductID>200</ProductID> <ProductName>Serialize Objects</ProductName> <SupplierID>1</SupplierID> <CategoryID>100</CategoryID> <QuantityPerUnit>6</QuantityPerUnit> <UnitPrice>1000</UnitPrice> <UnitsInStock>10</UnitsInStock> <UnitsOnOrder>0</UnitsOnOrder> <ReorderLevel>1</ReorderLevel> <Discontinued>false</Discontinued> </Products> Отметим атрибут Discountэлемента Products. Поэтому теперь, когда определены средства задания и получения свойств, можно добавить более сложную проверку кода в свойствах. Что можно сказать о ситуации, когда имеются производные классы и, возможно, свойства, которые возвращают массив? XmlSerializerтакже это охватывает. Давайте обсудим более сложную ситуацию. В событии button1_Clickсоздается новый объект на основе Productи новый объект на основе BookProduct( newProdи newBook). Мы добавляем данные в различные свойства каждого объекта и помещаем объекты в массив на основе Product. Затем создается новый объект на основе Inventory, которому в качестве параметра передается массив. Затем можно сериализовать объект Inventory, чтобы впоследствии его восстановить: private void button1_Click(object sender, System.EventArgs e) { // создать новые объекты книги и книжной продукции Product newProd=new Product(); BookProduct newBook=new BookProduct(); // задать некоторые свойства newProd.ProductID=100; newProd.ProductName="Product Thing"; newProd.SupplierID=10; newBook.ProductID=101; newBook.ProductName="How to Use Your New Product Thing"; newBook.SupplierID=10; newBook.ISBN="123456789"; //поместить элементы в массив Product[] addProd={newProd, newBook}; // новый объект Inventory с помощью массива addProd Inventory inv=new Inventory(); inv.InventoryItems=addProd; // сериализуем объект Inventory TextWriter tr=new StreamWriter("..\\..\\..\\order.xml"); XmlSerializer sr=new XmlSerializer(typeof(Inventory)); sr.Serialize(tr, inv); tr.Close(); } Отметим в событии button2_Click, что мы просматриваем массив во вновь созданном объекте newInv, чтобы показать, что это те же данные: private void button2_Click(object sender, System.EventArgs e) { Inventory newInv; FileStream f=new FileStream("..\\..\\..\\order.xml", FileMode.Open); XmlSerializer newSr=new XmlSerializer(typeof{Inventory)); newInv=(Inventory)newSr.Deserialize(f); foreach(Product prod in newInv.Inventory Items) listBox1.Items.Add(prod.ProductName); f.Close(); } public class inventory { private Product[] stuff; public Inventory() {} Мы имеем XmlArrayItemдля каждого типа, который может быть добавлен в массив. Первый параметр определяет имя элемента в создаваемом документе XML. Если опустить этот параметр ElementName, то элементы получат имя типа объекта (в данном случае Productи BookProduct). Существует также класс XmlArrayAttribute, который будет использоваться, если свойство возвращает массив объектов или примитивных типов. Так как мы возвращаем в массиве различные типы, то используется объект XmlArrayItemAttribute, который предоставляет более высокий уровень управления: // необходимо иметь запись атрибута для каждого типа данных [XmlArrayItem("Prod", typeof(Product)), XmlArrayItem("Book", typeof(BookProduct))] //public Inventory(Product [] InventoryItems) { // stuff=InventoryItems; //} public Product[] InventoryItems { get {return stuff;} set {stuff=value;} } } //класс Product public class Product { private int prodId; private string prodName; private int suppId; public Product() {} public int ProductID { get {return prodId;} set {prodId=value;} } public string ProductName { get {return prodName;} set {prodName=value;} } public int SupplierID { get {return suppId;} set {suppId=value;} } } // Класс Bookproduct public class BookProduct: Product { private string isbnNum; public BookProduct() {} public string ISBN { get {return isbnNum;} set {isbnNum=value;} } } В этот пример добавлено два новых класса. Класс Inventoryбудет отслеживать то, что добавляется на склад. Можно добавлять продукцию на основе класса Productили класса BookProduct, который расширяет Product. В классе Inventoryсодержится массив добавленных объектов и в нем могут находиться как BookProducts, так и Products. Вот как выглядит документ XML: <?xml version="1.0" ?> <Inventory xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <InventoryItems> <Prod> <ProductID>100</ProductID> <ProductName>Product Thing</ProductName> <SupplierID>10</SupplierID> </Prod> <Book> <ProductID>101</ProductID> <ProductName>How to Use Your New Product Thing</ProductName> <SupplierID>10</SupplierID> <ISBN>123456789</ISBN> </Book> </InventoryItems> </Inventory> Все это работает прекрасно, но как быть в ситуации, когда нет доступа к исходному коду типов, которые будут сериализироваться? Невозможно добавить атрибут, если отсутствует исходный код. Существует другой способ. Можно воспользоваться классами XmlAttributesи XmlAtrtributeOverrides. Вместе эти классы позволят выполнить в точности то, что только что было сделано, но без добавления атрибутов. Вот пример, находящийся в папке SerialSample4: private void button1_Click(object sender, System.EventArgs e) { // создать объект XmlAttributes XmlAttributes attrs=new XmlAttributes(); // добавить типы объектов, которые будут сериализированы attrs.XmlElements.Add(new XmlElementAttribute("Book", typeof(BookProduct))); attrs.XmlElements.Add(new XmlElementAttribute("Product", typeof(Product))); XmlAttributeOverrides attrOver=new XmlAttributeOverrides(); //добавить к коллекций атрибутов attrOver.Add(typeof(Inventory), "InventoryItems", attrs); // создать объекты Product и Book Product newProd=new Product(); BookProduct newBook=new BookProduct(); newProd.ProductID=100; newProd.ProductName="Product Thing"; newProd.SupplierID=10; newBook.ProductID=101; newBook.ProductName="How to Use Your New Product Thing"; newBook.SupplierID=10; newBook.ISBN="123456789"; Product[] addProd={newProd, newBook}; //Product[] addProd={newBook}; Inventory inv=new Inventory(); inv.InventoryItems=addProd; TextWriter tr=new StreamWriter("..\\..\\..\\inventory.xml"); XmlSerializer sr=new XmlSerializer(typeof(Inventory), attrOver); sr.Serialize(tr, inv); tr.Close(); } private void button2_Click(object sender, System.EventArgs e) { //необходимо выполнить тот же процесс для десериализации // создаем новую коллекцию XmlAttributes XmlAttributes attrs=new XmlAttributes(); // добавляем информацию о типе к коллекции элементов attrs.XmlElements.Add(new XmlElementAttribute("Book", typeof(BookProduct))); attrs.XmlElements.Add(new XmlElementAttribute("Product", typeof(Product))); XmlAttributeOverrides attrOver=new XmlAttributeOverrides(); //добавляем к коллекции Attributes (атрибутов) attrOver.Add(typeof(Inventory), "InventoryItems", attrs); //нужен новый объект Inventory для десериализаций в него Inventory newInv; // десериализуем и загружаем данные в окно списка из // десериализованного объекта FileStream f=new FileStream("..\\..\\..\\inventory.xml", FileMode.Open); XmlSerializer newSr=new XmlSerializer(typeof(Inventory).attrOver); newInv=(Inventory)newSr.Deserialize(f); if (newInv!=null) { foreach(Product prod in newInv.InventoryItems) listBox1.Items.Add(prod.ProductName); } f.Close(); } // это те же классы, что и в предыдущем примере // за исключением удаленных атрибутов // из свойства InventoryItems для Inventory public class Inventory { private Product[] stuff; public Inventory() {} public Product[] InventoryItems { get {return stuff;} set {stuff=value;} } } public class Product { private int prodId; private string prodName; private int suppId; public Product() {} public int ProductID { get {return prodId;} set {prodId=value;} } public string ProductName { get {return prodName;} set {prodName=value;} } public int SupplierID { get {return suppId;} set {suppId=value;} } } public class BookProduct:Product { private string isbnNum; public BookProduct() {} public string ISBN { get {return isbnNum;} set {isbnNum=value;} } } Это тот же пример, что и раньше, но первое, что необходимо заметить,— здесь нет добавленных в класс Inventoryатрибутов. Поэтому в данном случае представьте, что классы Inventory, Productи производный класс BookProductнаходятся в отдельной DLL, и у нас нет исходного кода. Первым шагом в процессе является создание объекта на основе XmlAttributes, и объекта XmlElementAttributeдля каждого типа данных, который будет переопределяться: XmlAttributes attrs=new XmlAttributes(); attrs.XmlElements.Add(new XmlElementAttribute("Book", typeof(BookProduct))); attrs.XmlElements.Add(new XmlElementAttribute("Product", typeof(Product))); Здесь мы добавляем новый XmlElementAttributeк коллекции XmlElementsкласса XmlAttributes. Класс XmlAttributesимеет свойства, соответствующие атрибутам, которые могут применяться; XmlArrayи XmlArrayItems, которые мы видели в предыдущем примере, являются только парой. Теперь мы имеем объект XmlAttributesс двумя объектами на основе XmlElementAttribute, добавленными к коллекции XmlElements. Далее создадим объект XmlAttributeOverrides: XmlAttributeOverrides attrOver = new XmlAttributeOverride(); attrOver.Add(typeof(Inventory) , "Inventory Items", attrs); Meтод Addимеет две перегружаемые версии. Первая получает информацию о типе переопределяемого объекта и объект XmlAttributes, который был создан ранее. Вторая версия та, что мы используем, получает также с строковое значение, которое является членом в переопределенном объекте. В нашем случае мы хотим переопределить член InventoryItemsв классе Inventory. Теперь создадим объект XmlSerializer, добавляя объект XmlAttributeOverridesв качестве параметра. XmlSerializerуже знает, какие типы мы хотим переопределить и что нам нужно вернуть для этих типов. Если выполнить метод Serialize, то получится следующий вывод XML: <?xml version="1.0"?> <Inventory xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Products> <ProductID>100</ProductID> <ProductName>Product Thing</ProductName> <SupplierID>10</SupplierID> </Product> <Book> <ProductID>101</ProductID> <ProductName>How to Use Your New Product Thing</ProductName> <SupplierID>10</SupplierID> <ISBN>123456789</ISBN> </Book> </Inventory> Мы получили тот же самый XML, что и в предыдущем примере. Чтобы десериализовать этот объект и воссоздать объект на основе Inventory, с которого мы начали, необходимо создать все те же объекты XmlAttributes, XmlElementAttributeи XmlAttributeOverrides, которые создаются при сериализации объекта. Когда это будет сделано, можно прочитать XML и воссоздать объект Inventory, как это делалось раньше. Вот код для десериализации объекта Inventory: private void button2_Click(object sender, System.EventArgs e) { XmlAttributes attrs=new XmlAttributes(); attrs.XmlElements.Add(new XmlElementAttribute("Book", typeof(BookProduct))); attrs.XmlElements.Add(new XmlElementAttribute("Product", typeof(Product))); XmlAttributeOverrides attrOver=new XmlAttributeOverrides(); attrOver.Add(typeof(Inventory), "InventoryItems", attrs); Inventory newInv; FileStream f=new FileStream("..\\..\\..\\inventory.xml", FileMode.Open); XmlSerializer newSr=new XmlSerializer(typeof(Inventory), attrOver); newInv=(Inventory)newSr.Deserialize(f); foreach(Product prod, in newInv.InventoryItems) listBox1.items.Add(prod.ProductName); f.Close(); } Отметим, что первые несколько строк кода идентичны коду, который использовался для сериализации объекта. Пространство имен XmlSerializeпредоставляет мощный набор инструментов. Сериализуя объекты в XML, а не в двоичный формат, мы получаем дополнительные возможности, что может действительно увеличить гибкость проектирования. ЗаключениеВ этой главе рассматривались широкие возможности пространства имен System.Xmlплатформы .NET. Было показано, как прочитать и записать документы XML с помощью классов на основе XMLReaderи XMLWriter, как в .NET реализована DOM и как использовать возможности DOM. Мы увидели, что XML и ADO.NET действительно очень тесно связаны. DataSetи документ XML являются двумя различными представлениями одной и той же базовой архитектуры. Мы сериализовали объекты в XML и смогли вернуть их обратно с помощью вызова пары методов. Комбинация Reflectionи XMLSerilizationприводит к некоторым уникальным конструкциям. И, конечно, были рассмотрены XPathи XslTransform. В течение ближайших нескольких лет XML станет, если уже не стал, важной частью разработки приложений. Платформа .NET сделала доступным мощный набор инструментов для работы с XML. |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|