Книга: C# для профессионалов. Том II
XmlTextReader
XmlTextReader
XmlTextReader
похож на 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
.