Р’ развитии Web-технологии РѕРіСЂРѕРјРЅСѓСЋ роль сыграл язык HTML (HyperText Markup Language) — язык разметки гипертекста. Любой человек, совсем РЅРµ знакомый СЃ программированием, РјРѕРі Р·Р° полчаса понять принцип разметки текста Рё Р·Р° пару дней изучить теги HTML. Пользуясь простейшим текстовым редактором, РѕРЅ РјРѕРі написать СЃРІРѕСЋ страничку HTML, тут же посмотреть ее РІ своем браузере, испытать чувство глубокого удовлетворения Рё РіРѕСЂРґРѕ выставить РІ Рнтернете СЃРІРѕР№ шедевр.
Замечательно! Не надо месяцами изучать запутанные языки программирования, явно предназначенные только для яйцеголовых "ботаников", осваивать сложные алгоритмы, возиться с компиляторами и отладчиками, размножать свое творение на дисках. Очень скоро появились текстовые редакторы, размечающие обычный "плоский" текст тегами HTML. Разработчику оставалось только поправлять готовую страницу HTML, созданную таким редактором.
Простота языка HTML привела Рє взрывному росту числа сайтов, пользователей Рнтернета Рё авторов многочисленных Web-страничек. Обычные пользователи компьютеров ощутили себя творцами, получили возможность заявить Рѕ себе, высказать собственные мысли Рё чувства, найти РІ Рнтернете единомышленников.
Ограниченные возможности языка HTML быстро перестали удовлетворять поднаторевших разработчиков, почувствовавших себя "профи". Набор тегов языка HTML строго определен и должен одинаково пониматься всеми браузерами. Нельзя ввести дополнительные теги или указать браузеру, как следует отображать на экране содержимое того или иного тега. Введение таблиц стилей CSS (Cascading Style Sheet) и включений на стороне сервера SSI (Server Side Include) лишь ненадолго уменьшило недовольство разработчиков. Профессионалу всегда не хватает средств разработки, он постоянно испытывает потребность добавить к ним какое-то свое средство, позволяющее реализовать все его фантазии.
Такая возможность есть. Еще в 1986 году стал стандартом язык создания языков разметки SGML (Standard Generalized Markup Language), с помощью которого и был создан язык HTML. Основная особенность языка SGML заключается в том, что он позволяет сформировать новый язык разметки, определив его набор тегов. Каждый конкретный набор тегов, созданный по правилам SGML, снабжается описанием DTD (Document Type Definition) — определением типа документа, разъясняющим связь тегов между собой и правила их применения. Специальная программа — драйвер принтера или SGML-браузер — руководствуется этим описанием для печати или отображения документа на экране дисплея.
В это же время выявилась еще одна, самая важная область применения языков разметки — поиск и выборка информации. В настоящее время подавляющее большинство информации хранится в реляционных базах данных. Они удобны для хранения и поиска сведений, представимых в виде таблиц: анкет, ведомостей, списков и т. п., но неудобны для хранения различных документов, планов, отчетов, статей, книг, не представимых в виде таблицы. Тегами языка разметки можно задать структурную, а не визуальную разметку документа, разбить документ на главы, параграфы и абзацы или на какие-то другие элементы, выделить важные для поиска участки документа. Легко написать программу, анализирующую размеченный такими тегами документ и извлекающую из него нужную информацию.
Язык SGML оказался слишком сложным, требующим тщательного и объемистого описания элементов создаваемого с его помощью языка. Он применяется только в крупных проектах, например для создания единой системы документооборота крупной фирмы. Скажем, man-страницы Solaris Operational Environment написаны на специально сделанной реализации языка SGML.
Золотой серединой между языками SGML Рё HTML стал язык XML (eXtensible Markup Language) — расширяемый язык разметки. Рто подмножество языка SGML, избавленное РѕС‚ излишней сложности, РЅРѕ позволяющее разработчику Web-страниц создавать собственные теги. Язык XML достаточно широк, чтобы можно было создать РІСЃРµ нужные теги, Рё достаточно РїСЂРѕСЃС‚, чтобы можно было быстро РёС… описать.
Организуя описание документа на языке XML, надо прежде всего продумать структуру документа. Приведем пример. Пусть мы решили, наконец, упорядочить записную книжку с адресами и телефонами. В ней записаны фамилии, имена и отчества родственников, сослуживцев и знакомых, дни их рождения, адреса, состоящие из почтового индекса, города, улицы, дома и квартиры, и телефоны, если они есть: рабочие и домашние. Мы придумываем теги для выделения каждого из этих элементов, продумываем вложенность элементов и получаем структуру, показанную в листинге 28.1.
Листинг 28.1. Пример XML-документа
<СЃ^Сѓ>РЈСЂСЋРїРёРЅСЃРє
Документ XML начинается с необязательного пролога, состоящего из двух частей.
В первой части пролога — объявлении XML (XML declaration), — записанной в первой строке листинга 28.1, указывается версия языка XML, необязательная кодировка документа и отмечается, зависит ли этот документ от других документов XML (standalone="yes"/"no"). По умолчанию принимается кодировка UTF-8.
Все элементы документа XML обязательно должны содержаться в корневом элементе (root element), в листинге 28.1 это элемент
Очень часто описание DTD составляется сразу для нескольких документов XML. В таком случае его удобно записать отдельно от документа. Если описание DTD отделено от документа, то во второй части пролога вместо квадратных скобок указывается одно из слов: system или public. За словом system идет URI файла с описанием DTD, а за словом public, кроме того, можно записать дополнительную информацию.
Документ XML состоит РёР· элементов. Рлемент начинается открывающим тегом, далее идет необязательное тело элемента, потом — закрывающий тег:
<Открывающий тег>Тело элементаЗакрывающий тег>
Закрывающий тег содержит наклонную черту, после которой повторяется имя открывающего тега.
Язык XML, в отличие от языка HTML, требует обязательно записывать закрывающие теги. Если у элемента нет тела и закрывающего тега (empty — пустой элемент), то его открывающий тег должен заканчиваться символами "/>", например:
Внимание!
Сразу надо сказать, что язык XML, в отличие от HTML, различает регистры букв.
РР· листинга 28.1 РІРёРґРЅРѕ, что элементы документа XML РјРѕРіСѓС‚ быть вложены РґСЂСѓРі РІ РґСЂСѓРіР°. Надо следить Р·Р° тем, чтобы элементы РЅРµ пересекались, Р° полностью вкладывались РґСЂСѓРі РІ РґСЂСѓРіР°. Как уже говорилось ранее, РІСЃРµ элементы, составляющие документ, вложены РІ корневой элемент этого документа. Тем самым документ наделяется структурой дерева вложенных элементов. РќР° СЂРёСЃ. 28.1 показана структура адресной книжки, описанной РІ листинге 28.1.
Рис. 28.1. Дерево элементов документа XML |
У открывающих тегов XML могут быть атрибуты. Например, имя, отчество и фамилию можно записать как атрибуты first, second и surname тега
В отличие от языка HTML в языке XML значения атрибутов обязательно надо заключать в кавычки или в апострофы.
Атрибуты СѓРґРѕР±РЅС‹ для описания простых значений. РЈ каждого гражданина Р РѕСЃСЃРёРё, уважающего паспортный режим, обязательно есть РѕРґРЅРѕ РёРјСЏ, РѕРґРЅРѕ отчество Рё РѕРґРЅР° фамилия. РС… лучше записывать атрибутами. РќРѕ Сѓ гражданина Р РѕСЃСЃРёРё может быть несколько телефонов, поэтому РёС… номера удобнее оформить как элементы list>. Заметьте, что элемент <name> СЃ атрибутами пустой, Сѓ него нет тела, следовательно, РЅРµ нужен закрывающий тег. Поэтому тег <name> СЃ атрибутами завершается символами "/>". Р’ листинге 28.2 приведена измененная адресная книжка.
Листинг 28.2. Пример XML-документа с атрибутами в открывающем теге
<с^у>Жмеринка city>
Атрибуты открывающего тега удобны и для указания типа элемента. Например, мы не уточняем, в городе живет наш родственник, в поселке или в деревне. Можно ввести в тег <city> атрибут type, принимающий одно из значений: город, поселок, деревня. Например:
Для описания адресной книжки нам понадобились открывающие теги
соответствующие им закрывающие теги, помеченные наклонной чертой. Теперь необходимо дать их описание. В описании указываются только самые общие признаки логической взаимосвязи элементов и их тип.
в–Ў Рлемент
в–Ў Рлемент
в–Ў Рлемент
□ В открывающем теге
в–Ў Рлемент
содержит по одному элементу
в–Ў Рлементы
в–Ў Рлемент
□ У открывающего тега
□ Необязательный элемент
в–Ў Рлементы
Рто словесное описание, называемое схемой документа XML, формализуется несколькими способами. Наиболее распространены РґРІР° СЃРїРѕСЃРѕР±Р°: можно сделать описание DTD, пришедшее РІ XML РёР· SGML, или описать схему РЅР° языке XSD (XML Schema Definition Language).
Описание DTD
Описание DTD нашей адресной книжки записано в листинге 28.3.
Листинг 28.3. Описание DTD документа XML
first CDATA #IMPLIED second CDATA #IMPLIED surname CDATA #REQUIRED>
type (город | поселок | деревня) "город">
Как видите, описание DTD почти очевидно. Оно повторяет приведенное ранее словесное описание. Первое слово element означает, что элемент может содержать тело с вложенными элементами. Вложенные элементы перечисляются в круглых скобках. Порядок перечисления вложенных элементов в скобках должен соответствовать порядку их появления в документе. Слово empty в третьей строке листинга 28.3 означает пустой элемент.
Слово attlist начинает описание списка атрибутов элемента. Для каждого атрибута указывается имя, тип и обязательность присутствия атрибута. Типов атрибута всего девять, но чаще всего употребляется тип cdata (Character DATA), означающий произвольную строку символов Unicode, или перечисляются значения типа. Так сделано в описании атрибута type тега
Обязательность указания атрибута отмечается одним из трех слов:
□ #required — атрибут обязателен;
□ #implied — атрибут необязателен;
□ #fixed — значение атрибута фиксировано, оно задается в DTD.
Первым словом могут быть, кроме слов element или attlist, слова any, mixed или entity. Слова any и mixed означают, что элемент может содержать и простые данные и/или вложенные элементы. Слово entity служит для обозначения или адреса данных, приведенного в описании DTD, так называемой сущности.
После имени элемента в скобках записываются вложенные элементы или тип данных, содержащихся в теле элемента. Тип pcdata (Parsed Character DATA) означает строку символов Unicode, которую надо интерпретировать. Тип cdata — строку символов Unicode, которую не следует интерпретировать.
Звездочка, записанная после имени элемента, означает "нуль или более вхождений" элемента, после которого она стоит, а плюс — "одно или более вхождений". Вопросительный знак означает "нуль или один раз". Если эти символы относятся ко всем вложенным элементам, то их можно указать после круглой скобки, закрывающей список вложенных элементов.
Описание DTD можно занести в отдельный файл, например ntb.dtd, указав его имя во второй части пролога, как показано во второй строке листингов 28.1 и 28.2. Можно включить описание во вторую часть пролога XML-файла, заключив его в квадратные скобки:
После того как создано описание DTD нашей реализации XML и написан документ, размеченный тегами этой реализации, следует проверить правильность их написания. Для этого есть специальные программы — проверяющие анализаторы (validating parsers). Все фирмы, разрабатывающие средства для работы с XML, выпускают бесплатные или коммерческие проверяющие анализаторы.
Проверяющий анализатор корпорации Sun Microsystems содержится в пакете классов JAXP (Java API for XML Processing), входящем в состав Java SE. Кроме того, этот пакет можно загрузить отдельно с адреса .
Корпорация Microsoft поставляет проверяющий анализатор MSXML (Microsoft XML Parser), доступный по адресу .
Есть еще множество проверяющих анализаторов, но лидером среди них является, пожалуй, Apache Xerces2, входящий во многие средства обработки документов XML, выпускаемые другими фирмами. Он свободно доступен по адресу xerces2-j/.
Ограниченные средства DTD не позволяют полностью описать структуру документа XML. В частности, описание DTD не указывает точное количество повторений вложенных элементов, оно не задает точный тип тела элемента. Например, в листинге 28.3 из описания DTD не видно, что в элементе
Посмотрим, как создаются схемы XML, но сначала познакомимся еще с одним понятием XML — пространством имен.
Пространства имен XML
Поскольку в разных языках разметок — реализациях XML — могут встретиться одни и те же имена тегов и их атрибутов, имеющие совершенно разный смысл, а в документе XML их часто приходится смешивать, анализатору надо дать возможность их как-то различать. В языке Java мы имена полей класса уточняем именем класса, а имена классов уточняем указанием пакета, что позволяет назвать поле просто именем a, применяя при необходимости полное имя, что-нибудь вроде com.mydomain.myhost.mypack.MyClass.a. При этом рекомендуется пакет для уникальности называть доменным именем сайта разработчика, записывая его справа налево.
Р’ языке XML принята подобная схема: локальное РёРјСЏ уточняется идентификатором. Рдентификатор должен быть уникальным РІРѕ всем Рнтернете, поэтому РёРј должна быть строка символов, имеющая форму адреса URI. Адрес может соответствовать или РЅРµ соответствовать какому-то реальному сайту Рнтернета, это РЅРµ важно. Важно, чтобы РѕРЅ РЅРµ повторялся РІ Рнтернете, можно записать, например, строку 2008/ntbml. Р’СЃРµ имена СЃ РѕРґРЅРёРј Рё тем же идентификатором образуют РѕРґРЅРѕ пространство имен (namespace).
Поскольку идентификатор пространства имен получается весьма длинным, было бы очень неудобно всегда записывать его перед локальным именем. В языке Java для сокращения записи имен мы используем оператор import. В XML принят другой подход.
В каждом документе идентификатор пространства имен связывается с некоторым коротким префиксом, отделяемым от локального имени двоеточием. Префикс определяется атрибутом xmlns следующим образом:
Как видите, префикс ntb только что определен, но его уже можно использовать в имени
ntb:notebook.
После такого описания имена тегов и атрибутов, которые мы хотим отнести к пространству имен , снабжаются префиксом ntb, например:
РРјСЏ вместе СЃ префиксом, например ntb:city, называется расширенным или уточненным именем (Qualified Name, QName).
Хотя идентификатор пространства имен записывается РІ форме адреса URI, такого как , анализатор документа XML Рё РґСЂСѓРіРёРµ программы, использующие документ, РЅРµ Р±СѓРґСѓС‚ обращаться РїРѕ этому адресу. Там даже может РЅРµ быть никакой Web-странички вообще. Просто идентификатор пространства имен должен быть уникальным РІРѕ всем Рнтернете, Рё разработчики рекомендации РїРѕ применению пространства имен, которую можно посмотреть РїРѕ адресу , справедливо решили, что будет СѓРґРѕР±РЅРѕ использовать для него DNS-РёРјСЏ сайта, РЅР° котором размещено определение пространства имен. Смотрите РЅР° URI просто как РЅР° строку символов, идентифицирующую пространство имен. Обычно указывается URL фирмы, создавшей данную реализацию XML, или РёРјСЏ файла СЃ описанием схемы XML.
Поскольку идентификатор — это строка символов, то и сравниваются они как строки, с учетом регистра символов. Например, идентификатор будет считаться XML-анализатором, отличным от идентификатора 2008/ntbml, введенного нами ранее, и будет определять другое пространство имен.
По правилам SGML и XML двоеточие может применяться в именах как обычный символ, поэтому имя с префиксом — это просто фокус, анализатор рассматривает его как обычное имя. Отсюда следует, что в описании DTD нельзя опускать префиксы имен. Некоторым анализаторам надо специально указать необходимость учета пространства имен. Например, при работе с анализатором Xerces следует применить метод
setNamespaceAware(true).
Атрибут xmlns, определяющий префикс имен, может появиться в любом элементе XML, а не только в корневом элементе. Определенный им префикс можно применять в том элементе, в котором записан атрибут xmlns, и во всех вложенных в него элементах. Больше того, в одном элементе можно определить несколько пространств имен:
xmlns:bk = "">
Появление имени тега без префикса в документе, использующем пространство имен, означает, что имя принадлежит пространству имен по умолчанию (default namespace). Например, язык XHTML допускает применение тегов HTML и XML в одном документе. Допустим, мы определили тег с именем title. Чтобы анализатор не принял его за один из тегов HTML, поступаем следующим образом:
xmlns:ntb = "">
В этом примере пространством имен по умолчанию становится пространство имен XHTML, имеющее общеизвестный идентификатор , и теги, относящиеся к этому пространству имен, записываются без префикса.
Атрибуты никогда не входят в пространство имен по умолчанию. Если имя атрибута записано без префикса, то это означает, что атрибут не относится ни к одному пространству имен.
Префикс имени не относится к идентификатору пространства имен и может быть разным в разных документах. Например, в каком-нибудь другом документе мы можем написать корневой элемент
и записывать элементы с префиксом nb:
Теперь, после того как мы ввели понятие пространства имен, можно обратиться к схеме XML.
Схема XML
В мае 2001 года консорциум W3C рекомендовал описывать структуру документов XML на языке описания схем XSD (XML Schema Definition Language). На этом языке составляются схемы XML (XML Schema), описывающие элементы документов XML.
Схема XML сама записывается как документ XML. Его элементы называют компонентами (components), чтобы отличить их от элементов описываемого документа XML. Корневой компонент схемы носит имя
Язык XSD различает простые и сложные элементы XML. Простыми (simple) элементами описываемого документа XML считаются элементы, не содержащие атрибутов и
вложенных элементов. Соответственно, сложные (complex) элементы содержат атрибуты и/или вложенные элементы. Схема XML описывает простые типы — типы простых элементов, и сложные типы — типы сложных элементов.
Язык описания схем содержит много встроенных простых типов. Они перечислены в следующем разделе.
Встроенные простые типы XSD
Встроенные типы языка описания схем XSD позволяют записывать двоичные и десятичные целые числа, вещественные числа, дату и время, строки символов, логические значения, адреса URI. Рассмотрим их по порядку.
Вещественные числа
Вещественные числа в языке XSD разделены на три типа: decimal, float и double.
РўРёРї decimal составляют вещественные числа, записанные СЃ фиксированной точкой: 123.45, -0.1234567689345 Рё С‚. Рґ. Фактически хранятся РґРІР° целых числа: мантисса Рё РїРѕСЂСЏРґРѕРє. Спецификация языка XSD РЅРµ ограничивает количество цифр РІ мантиссе, РЅРѕ требует, чтобы можно было записать РЅРµ менее 18 цифр. Ртот тип легко реализуется классом java.math.BigDecimal, описанным РІ главе 4.
Типы float и double соответствуют стандарту IEEE754-85 и одноименным типам Java. Они записываются с фиксированной или с плавающей десятичной точкой.
Целые числа
РћСЃРЅРѕРІРЅРѕР№ целый тип integer понимается как подтип типа decimal, содержащий числа СЃ нулевым РїРѕСЂСЏРґРєРѕРј. Рто целые числа СЃ любым количеством десятичных цифр: -34567, 123456789012345 Рё С‚. Рґ. Данный тип легко реализуется классом java.math.BigInteger, описанным РІ главе 4.
Типы long, int, short и byte полностью соответствуют одноименным типам Java. Они понимаются как подтипы типа integer, типы более коротких чисел считаются подтипами более длинных чисел: тип byte — это подтип типа short, оба они подтипы типа int и т. д.
Типы nonPositiveInteger и negativeInteger — подтипы типа integer — составлены из неположительных и отрицательных чисел соответственно с любым количеством цифр.
Типы nonNegativeInteger и positiveInteger — подтипы типа integer — составлены из неотрицательных и положительных чисел соответственно с любым количеством цифр.
У типа nonNegativeInteger есть подтипы беззнаковых целых чисел unsignedLong, unsignedInt, unsignedShort и unsignedByte.
Строки символов
Основной символьный тип string описывает произвольную строку символов Unicode. Его можно реализовать классом java.lang.String.
Тип normalizedString — подтип типа string — это строки, не содержащие символы перевода строки '\n', возврата каретки '\r' и символы горизонтальной табуляции '\t'.
В строках типа token — подтипа типа normalizedString — нет, кроме того, начальных и завершающих пробелов и нет нескольких подряд идущих пробелов.
В типе token выделены три подтипа. Подтип language определен для записи названия языка согласно RFC 1766, например: ru, en, de, fr. Подтип nmtoken используется только в атрибутах для записи их перечисляемых значений. Подтип name составляют имена XML — последовательности букв, цифр, дефисов, точек, двоеточий, знаков подчеркивания, начинающиеся с буквы (кроме зарезервированной последовательности букв X, x, м, m, l, l в любом сочетании регистров) или знака подчеркивания. Двоеточие в значениях типа name используется для выделения префикса пространства имен.
РР· типа name выделен подтип NCName (Non-Colonized Name) имен, РЅРµ содержащих двоеточия, РІ котором, РІ СЃРІРѕСЋ очередь, определены три подтипа: id, entity, idref, описывающие идентификаторы XML, сущности Рё перекрестные ссылки.
Дата и время
Тип duration описывает промежуток времени, например запись P1Y2M3DT10H30M45S означает один год (1y), два месяца (2м), три дня (3d), десять часов (10h), тридцать минут (30м) и сорок пять секунд (45s). Запись может быть сокращенной, например P120M означает 120 месяцев, а T120M — 120 минут.
Тип dateTime содержит дату и время в формате CCYY-MM-DDThh:mm:ss, например 2008 — 04 — 25Т09:30: 05. Остальные типы выделяют какую-либо часть даты или времени.
Тип time содержит время в обычном формате hh:mm:ss.
Тип date содержит дату в формате ccyy-mm-dd.
Тип gYearMonth выделяет год и месяц в формате CCYY-MM.
Тип gMonthDay содержит месяц и день месяца в формате -mm-dd.
Тип gYear означает год в формате CCYY, тип gMonth- месяц в формате -mm-, тип gDay —
день месяца в формате -dd.
Двоичные типы
Двоичные целые числа записываются либо в шестнадцатеричной форме без всяких дополнительных символов: 0B2F, 356C0A и т. д., это тип hexBinary, либо в кодировке Base64,
это тип base64Binary.
Прочие встроенные простые типы
Еще три встроенных простых типа описывают значения, часто используемые в документах XML.
Адреса URI относятся к типу anyURI.
Расширенное имя тега или атрибута (qualified name), т. е. имя вместе с префиксом, отделенным от имени двоеточием,-это тип QName.
Рлемент notation описания DTD выделен как отдельный простой тип схемы XML. Его используют для записи математических, химических Рё РґСЂСѓРіРёС… символов, РЅРѕС‚, азбуки Брайля Рё прочих обозначений.
Определение простых типов
В схемах XML с помощью встроенных типов можно тремя способами определить новые типы простых элементов. Они вводятся как сужение (restriction) встроенного или ранее определенного простого типа, список (list) или объединение (union) простых типов.
Простой тип определяется компонентом схемы
Сужение
Сужение простого типа определяется компонентом
Можно дать другое определение простого типа zip как целого положительного числа, находящегося в диапазоне от 100 000 до 999 999:
Теги
в–Ў
в–Ў
в–Ў
в–Ў
в–Ў
в–Ў
в–Ў
в–Ў
в–Ў
в–Ў
в–Ў
в–Ў
• preserve — не убирать пробельные символы;
• replace — заменить пробельные символы пробелами;
• collapse — после замены пробельных символов пробелами убрать начальные и конечные пробелы, а из нескольких подряд идущих пробелов оставить только один.
В тегах-фасетках можно записывать следующие атрибуты, называемые базисными фасетками (fundamental facets):
□ ordered — задает упорядоченность определяемого типа, принимает одно из трех значений:
• false — тип неупорядочен;
• partial — тип частично упорядочен;
• total — тип полностью упорядочен;
□ bounded — задает ограниченность или неограниченность типа значениями true или
false;
□ cardinality — задает конечность или бесконечность типа значениями finite или
countably infinite;
□ numeric — показывает, числовой этот тип или нет, значениями true или false.
Как видно из приведенных ранее и далее примеров, в одном сужении может быть несколько ограничений-фасеток. При этом фасетки
РЎРїРёСЃРѕРє
Простой тип-список — это тип элементов, в теле которых записывается, через пробел, несколько значений одного и того же простого типа. Например, в документе XML может встретиться такой элемент, содержащий список целых чисел:
Список определяется компонентом , в котором атрибутом itemType указывается тип элементов определяемого списка. Тип элементов списка можно указать и в теле элемента
. Например, показанный ранее элемент документа XML
а использованный при его определении тип listOfInteger задать как список не более чем из пяти целых чисел следующим образом:
При определении списка можно применять фасетки
Объединение
Простой тип-объединение определяется компонентом
Другой способ — записать в теле компонента
После этого атрибут size можно использовать, например, так:
Простой TeKCT
Описание элементов и их атрибутов
Рлементы, которые Р±СѓРґСѓС‚ применяться РІ документе XML, описываются РІ схеме компонентом
minOccurs="наименьшее число появлений элемента в документе" maxOccurs="наибольшее число появлений" />
Значение РїРѕ умолчанию необязательных атрибутов minOccurs Рё maxOccurs равно 1. Рто означает, что если эти атрибуты отсутствуют, то элемент должен появиться РІ документе XML СЂРѕРІРЅРѕ РѕРґРёРЅ раз. Определение типа элемента можно вынести РІ тело элемента
Определение типа элемента
Описание атрибута элемента тоже несложно:
use="обязательность атрибута" default="значение по умолчанию" />
Необязательный атрибут use принимает три значения:
□ optional — описываемый атрибут необязателен (это значение по умолчанию);
□ required — описываемый атрибут обязателен;
в–Ў prohibited — описываемый атрибут неприменим. Рто значение полезно РїСЂРё определении подтипа, чтобы отменить некоторые атрибуты базового типа.
Если описываемый атрибут необязателен, то атрибутом default можно задать его значение по умолчанию.
Определение типа атрибута — а это должен быть простой тип — можно вынести в тело элемента
Тип атрибута
Определение сложных типов
Напомним, что тип элемента называется сложным, если в элемент вложены другие элементы и/или в открывающем теге элемента есть атрибуты.
Сложный тип определяется компонентом
Необязательный атрибут name задает имя типа, а в теле компонента
Определение сложного типа можно разделить на определение типа пустого элемента, элемента с простым телом и элемента, содержащего вложенные элементы. Рассмотрим эти определения подробнее.
Определение типа пустого элемента
Проще всего определяется тип пустого элемента — элемента, не имеющего тела, а содержащего только атрибуты в открывающем теге. Таков, например, элемент
После этого определения можно в схеме описать элемент
а в документе XML использовать это описание:
Определение типа элемента с простым телом
Немного сложнее описание элемента, содержащего тело простого типа Рё атрибуты РІ открывающем теге. Ртот тип отличается РѕС‚ простого типа только наличием атрибутов Рё определяется компонентом
В компоненте
type="xsd:nonNegativeInteger" />
Рту конструкцию можно описать словами так: "Определяется тип calcResultType элемента, тело которого содержит значения встроенного простого типа xsd:decimal. Простой тип расширяется тем, что Рє нему добавляются атрибуты unit Рё precision".
Если в схеме описать элемент
то в документе XML можно написать
В компоненте
type="xsd:nonNegativeInteger" />
Определение типа вложенных элементов
Если значениями определяемого сложного типа будут элементы, содержащие вложенные элементы, как, например, элементы
Компонент
Потом описываем элемент:
Рлементы
^^^>Золотой тeлeнок
Если же вместо компонента
Компонент
Как видно из приведенного примера, компонент
Модель группы
Определение типа со сложным телом
При определении сложного типа можно воспользоваться уже определенным, базовым, сложным типом, расширив его дополнительными элементами или, наоборот, удалив из него некоторые элементы. Для этого надо применить компонент
Расширим, например, определенный ранее тип bookType, добавив год издания — элемент
При сужении базового типа компонентом
Рто описание выглядит странно. Почему надо заново описывать РІСЃРµ элементы, остающиеся после сужения? РќРµ проще ли определить новый тип?
Дело в том, что в язык XSD внесены элементы объектно-ориентированного программирования, которых мы не будем касаться. Расширенный и суженный типы связаны со своим базовым типом отношением наследования, и к ним можно применить операцию подстановки. У всех типов языка XSD есть общий предок базовый тип anyType. От
него наследуются РІСЃРµ сложные типы. Рто РїРѕРґРѕР±РЅРѕ тому, как Сѓ всех классов Java есть общий предок — класс Object, Р° РІСЃРµ массивы наследуются РѕС‚ него. РћС‚ базового типа anyType наследуется Рё тип anySimpleType — общий предок всех простых типов.
Таким образом, сложные типы определяются как сужение типа anyType. Если строго подходить к определению сложного типа, то определение типа bookType, сделанное в начале предыдущего раздела, надо записать так:
Рекомендация языка XSD позволяет сократить эту запись, что РјС‹ Рё сделали РІ предыдущем разделе. Рто РїРѕРґРѕР±РЅРѕ тому, как РІ Java РјС‹ опускаем слова "extends Object" РІ заголовке описания класса.
Закончим на этом описание языка XSD и перейдем к примерам.
Пример: схема адресной книги
В листинге 28.4 записана схема документа, приведенного в листинге 28.2.
Листинг 28.4. Схема документа XML
xmlns:ntb="" targetNamespace="">
minOccurs="0" maxOccurs="unbounded" />
minOccurs="0" maxOccurs="unbounded" />
minOccurs="0" maxOccurs="unbounded" />
Листинг 28.4, как обычный документ XML, начинается СЃ пролога, показывающего версию XML Рё определяющего стандартное пространство имен схемы XML СЃ идентификатором . Ртому идентификатору дан префикс xsd. Конечно, префикс может быть РґСЂСѓРіРёРј, часто пишут префикс xs.
Еще РЅРµ встречавшийся нам атрибут targetNamespace определяет идентификатор пространства имен, РІ которое попадут определяемые РІ этом документе имена типов, элементов Рё атрибутов, так называемое целевое пространство имен (target namespace). Ртот идентификатор сразу же связывается СЃ префиксом ntb, который тут же используется для уточнения только что определенных имен РІ ссылках РЅР° РЅРёС….
Р’СЃРµ описание схемы нашей адресной книжки заключено РІ РѕРґРЅРѕР№ третьей строке, РІ которой указано, что адресная РєРЅРёРіР° состоит РёР· РѕРґРЅРѕРіРѕ элемента СЃ именем notebook, имеющего сложный тип notebookType. Ртот элемент должен появиться РІ документе СЂРѕРІРЅРѕ РѕРґРёРЅ раз. Остаток листинга 28.4 посвящен описанию типа этого элемента Рё РґСЂСѓРіРёС… типов.
Описание сложного типа notebookType несложно (простите за каламбур). Оно занимает три строки листинга, не считая открывающего и закрывающего тега, и просто говорит о том, что данный тип составляют несколько элементов person типа personType.
Описание типа personType немногим сложнее. Оно говорит, что этот тип составляют четыре элемента: name, birthday, address и phone-list. Для элемента name сразу же указаны необязательные атрибуты first и second простого типа string, определенного в пространстве имен xsd. Тип обязательного атрибута surname тоже string.
Далее в листинге 28.4 определяются оставшиеся типы: addressType, phone-listType и ruDate. Необходимость определения простого типа ruDate возникает потому, что встроенный в схему XML тип date предписывает задавать дату в виде 2004-10-22, а в России принят формат 22.10.2004. Тип ruDate определяется как сужение (restriction) типа string с помощью шаблона. Шаблон (pattern) для записи даты в виде дц.мм.гггг задается регулярным выражением.
Безымянные типы
Р’СЃРµ описанные РІ листинге 28.4 типы используются только РѕРґРёРЅ раз. Поэтому необязательно давать типу РёРјСЏ. Схема XML, как говорилось ранее, позволяет определять безымянные типы. Такое определение дается внутри описания элемента. Рменно так РІ листинге 28.4 описаны атрибуты элемента name. Р’ листинге 28.5 показано упрощенное описание схемы адресной РєРЅРёРіРё.
Листинг 28.5. Схема документа XML с безымянными типами
targetNamespace='>
Все перечисленные языки позволяют более или менее полно описывать схему документа. Возможно, они вытеснят язык XSD, возможно, будут существовать совместно.
Рнструкции РїРѕ обработке
Упомянем еще РѕРґРЅСѓ конструкцию языка XML — инструкции РїРѕ обработке (processing instructions). РћРЅР° позволяет передать анализатору или РґСЂСѓРіРѕР№ программе-обработчику документа дополнительные сведения для обработки. Рнструкция РїРѕ обработке выглядит так:
сведения для анализатора ?>
Первая часть пролога документа XML — первая строка XML-файла — это как раз инструкция по обработке. Она передает анализатору документа версию языка XML и кодировку символов, которыми записан документ.
Первая часть работы закончена. Документы XML и их схемы написаны. Теперь надо подумать о том, каким образом они будут отображаться на экране дисплея, на листе бумаги, на экране сотового телефона, т. е. нужно подумать о визуализации документа XML.
Прежде всего, документ следует разобрать, проанализировать (parse) его структуру.
Анализ документа XML
На первом этапе разбора проводится лексический анализ (lexical parsing) документа XML. Документ разбивается на отдельные неделимые элементы (tokens), которыми являются теги, служебные слова, разделители, текстовые константы. Проводится проверка полученных элементов и их связей между собой. Лексический анализ выполняют специальные программы — сканеры (scanners). Простейшие сканеры — это классы java.util.StringTokenizer и java.io.StreamTokenizer из стандартной поставки Java SE JDK, которые мы рассматривали в предыдущих главах.
Затем осуществляется грамматический анализ (grammar parsing). При этом анализируется логическая структура документа, составляются выражения, выражения объединяются в блоки, блоки — в модули, которыми могут являться абзацы, параграфы, пункты, главы. Грамматический анализ проводят программы-анализаторы, так называемые парсеры (parsers).
Создание сканеров и парсеров — любимое развлечение программистов. За недолгую историю XML написаны десятки, если не сотни XML-парсеров. Многие из них написаны на языке Java. Все парсеры можно разделить на три группы.
В первую группу входят парсеры, проводящие анализ, основываясь на структуре дерева, отражающего вложенность элементов документа (tree-based parsing). Дерево документа строится в оперативной памяти перед просмотром. Такие парсеры проще реализовать, но создание дерева требует большого объема оперативной памяти, ведь размер документов XML не ограничен. Необходимость частого просмотра узлов дерева замедляет работу парсера.
Р’Рѕ вторую РіСЂСѓРїРїСѓ РІС…РѕРґСЏС‚ парсеры, проводящие анализ, основываясь РЅР° событиях (event-based parsing). Рти парсеры просматривают документ РѕРґРёРЅ раз, отмечая события просмотра. Событием считается появление очередного элемента XML: открывающего или закрывающего тега, текста, содержащегося РІ теле элемента. РџСЂРё возникновении события вызывается соответствующий метод его обработки: startElement(), endElement (), characters () Рё С‚. Рґ. Такие парсеры сложнее РІ реализации, зато РѕРЅРё РЅРµ строят дерево РІ оперативной памяти Рё РјРѕРіСѓС‚ анализировать РЅРµ весь документ, Р° его отдельные элементы вместе СЃ вложенными РІ РЅРёС… элементами. Фактическим стандартом здесь стал СЃРІРѕР±РѕРґРЅРѕ распространяемый набор классов Рё интерфейсов SAX (Simple API for XML), созданный Давидом Меггинсоном (David Megginson). РћСЃРЅРѕРІРЅРѕР№ сайт данного проекта — . Сейчас применяется второе поколение этого набора, называемое SAX2. Набор SAX2 РІС…РѕРґРёС‚ РІРѕ РјРЅРѕРіРёРµ парсеры, например Xerces2.
Третью группу образуют потоковые парсеры (stream parsers), которые так же, как и парсеры второй группы, просматривают документ, переходя от элемента к элементу. При каждом переходе парсер предоставляет программе методы getName ( ), getText ( ),
getAttributeName (), getAttributeValue() и т. д., позволяющие обработать текущий элемент.
В стандартную поставку Java и Standard Edition и Enterprise Edition входит набор интерфейсов и классов для создания парсеров и преобразования документов XML, называемый JAXP (Java API for XML Processing). С помощью одной из частей этого набора, называемой DOM API (Document Object Model API), можно создавать парсеры первого типа, формирующие дерево объектов. С помощью второй части набора JAXP, называемой SAX API, можно создавать SAX-парсеры. Третья часть JAXP, предназначенная для создания потоковых парсеров, называется StAX (Streaming API for XML).
Анализ документов XML с помощью SAX2
Рнтерфейсы Рё классы SAX2 собраны РІ пакеты org.xml.sax, org.xml.sax.ext,
org.xml.sax.helpers, javax.xml.parsers. Рассмотрим их подробнее.
Основу SAX2 составляет интерфейс org.xml.sax.ContentHandler, описывающий методы обработки событий: начала документа, появления открывающего тега, появление тела элемента, появление закрывающего тега, окончание документа. При возникновении такого события SAX2 обращается к методу-обработчику события, передавая ему аргументы, содержащие информацию о событии. Дело разработчика — реализовать эти методы, обеспечив правильный анализ документа.
В начале обработки документа вызывается метод
public void startDocument();
В нем можно задать начальные действия по обработке документа.
При появлении символа "<", начинающего открывающий тег, вызывается метод
public void startElement(String uri, String name,
String qname, Attributes attrs);
В метод передаются три имени, два из которых связаны с пространством имен: идентификатор пространства имен uri, локальное имя тега без префикса name и расширенное имя с префиксом qname, а также атрибуты открывающего тега элемента attrs, если они есть. Если пространство имен не определено, то значения первого и второго аргументов равны null. Если нет атрибутов, то передается ссылка на пустой объект attrs.
При появлении символов "", начинающих закрывающий тег, вызывается метод
public void endElement(String uri, String name, String qname);
При появлении строки символов вызывается метод
public void characters(char[] ch, int start, int length);
В него передается массив символов ch, индекс начала строки символов start в этом массиве и количество символов length.
При появлении в тексте документа инструкции по обработке вызывается метод
public void processingInstruction(String target, String data);
В метод передается имя программы-обработчика target и дополнительные сведения
data.
При появлении пробельных символов, которые должны быть пропущены, вызывается метод
public void ignorableWhitespace(char[] ch, int start, int length);
В него передается массив ch идущих подряд пробельных символов, индекс начала символов в массиве start и количество символов length.
Рнтерфейс org.xml.sax.ContentHandler реализован классом org.xml.sax.helpers.DefaultHandler. Р’ нем сделана пустая реализация всех методов. Разработчику остается реализовать только те методы, которые ему нужны.
Применим методы SAX2 для обработки нашей адресной книжки. Запись документа РЅР° языке XML СѓРґРѕР±РЅР° для выявления структуры документа, РЅРѕ неудобна для работы СЃ документом РІ объектно-ориентированной среде. Поэтому чаще всего содержимое документа XML представляется РІ РІРёРґРµ РѕРґРЅРѕРіРѕ или нескольких объектов, называемых объектами данных JDO (Java Data Objects). Рта операция называется связыванием данных (data binding) СЃ объектами JDO.
Свяжем содержимое нашей адресной книжки с объектами Java. Для этого сначала опишем классы Java (листинги 28.7 и 28.8), которые представят содержимое адресной книги.
Листинг 28.7. Класс, описывающий адрес
public class Address{
private String street, city, zip, type = "РіРѕСЂРѕРґ"; public Address(){}
public String getStreet(){ return street; }
public void setStreet(String street){ this.street = street; }
public String getCity(){ return city; }
public void setCity(String city){ this.city = city; }
public String getZip(){ return zip; }
public void setZip(String zip){ this.zip = zip; }
public String getType(){ return type; }
public void setType(String type){ this.type = type; }
public String toString(){
return "Address: " + street + " " + city + " " + zip;
}
Листинг 28.8. Класс, описывающий запись адресной книжки
public class Person{
private String firstName, secondName, surname, birthday; private Vector
public Person(){}
public Person(String firstName, String secondName, String surname){ this.firstName = firstName; this.secondName = secondName; this.surname = surname;
}
public String getFirstName(){ return firstName; } public void setFirstName(String firstName){ this.firstName = firstName;
}
public String getSecondName(){ return secondName; } public void setSecondName(String secondName){ this.secondName = secondName;
}
public String getSurname(){ return surname; } public void setSurname(String surname){ this.surname = surname;
}
public String getBirthday(){ return birthday; } public void setBirthday(String birthday){ this.birthday = birthday;
}
public void addAddress(Address addr){
if (address == null) address = new Vector(); address.add(addr);
}
public Vector
public void removeAddress(Address addr){ if (address != null) address.remove(addr);
}
public void addWorkPhone(String phone){
if (workPhone == null) workPhone = new Vector(); workPhone.add(new Integer(phone));
}
public Vector
public void removeWorkPhone(String phone){
if (workPhone != null) workPhone.remove(new Integer(phone));
}
public void addHomePhone(String phone){
if (homePhone == null) homePhone = new Vector(); homePhone.add(new Integer(phone));
}
public Vector
public void removeHomePhone(String phone){
if (homePhone != null) homePhone.remove(new Integer(phone));
}
public String toString(){
return "Person: " + surname;
}
}
После определения классов Java, в экземпляры которых будет занесено содержимое адресной книжки, напишем программу, читающую адресную книжку и связывающую ее с объектами Java.
В листинге 28.9 приведен пример класса-обработчика NotebookHandler для адресной книжки, описанной в листинге 28.2. Методы класса NotebookHandler анализируют содержимое адресной книжки и помещают его в вектор, составленный из объектов класса Person, описанного в листинге 28.8.
Листинг 28.9. Кпасс-обработник документа XML средствами SAX2
import org.xml.sax.*; import org.xml.sax.helpers.*; import javax.xml.parsers.*; import java.util.*; import java.io.*;
public class NotebookHandler extends DefaultHandler{
static final String JAX P_SCHEMA_LAN GUAGE =
" ava.sun.com/xml/j axp/properties/schemaLanguage";
static final String W3C XML SCHEMA = "";
private Person person; private Address address;
private static Vector
public void startElement(String uri, String name,
String qname, Attributes attrs)
throws SAXException{ switch (qname){ case "name":
person = new Person(attrs.getValue("first"),
attrs.getValue("second"), attrs.getValue("surname"));
break;
case "birthday":
inBirthday = true; break; case "address":
address = new Address(); break; case "street":
inStreet = true; break; case "city":
inCity = true;
if (attrs != null) address.setType(attrs.getValue("type")); break; case "zip":
inZip = true; break; case "work-phone":
inWorkPhone = true; break; case "home-phone":
inHomePhone = true;
}
public void characters(char[] buf, int offset, int len) throws SAXException{
String s = new String(buf, offset, len);
if (inBirthday){
person.setBirthday(s); inBirthday = false;
}else if (inStreet){
address.setStreet(s); inStreet = false;
}else if (inCity){
address.setCity(s); inCity = false;
}else if (inZip){
address.setZip(s); inZip = false;
}else if (inWorkPhone){ person.addWorkPhone(s); inWorkPhone = false;
}else if (inHomePhone){ person.addHomePhone(s); inHomePhone = false;
}
}
public void endElement(String uri, String name, String qname) throws SAXException{
if (qname.equals("address")){ person.addAddress(address); address = null;
}else if (qname.equals("person")){ pers.add(person); person = null;
}
}
public static void main(String[] args){
if (args.length < 1){
System.err.println("Usage: java NotebookHandler ntb.xml");
System.exit(1);
}
try{
NotebookHandler handler = new NotebookHandler();
SAXParserFactory fact = SAXParserFactory.newInstance();
fact.setNamespaceAware(true); fact.setValidating(true);
SAXParser saxParser = fact.newSAXParser();
saxParser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
File f = new File(args[0]);
saxParser.parse(f, handler);
for (int k = 0; k < pers.size(); k++)
System.out.println((pers.get(k)).getSurname());
}catch(SAXNotRecognizedException x){
System.err.println("HeH3BecTHoe свойство: " +
JAXP_SCHEMA_LANGUAGE) ;
System.exit(1);
}catch(Exception ex){
System.err.println(ex);
}
}
public void warning(SAXParseException ex){ System.err.println("Warning: " + ex); System.err.println("line = " + ex.getLineNumber() +
" col = " + ex.getColumnNumber());
}
public void error(SAXParseException ex){ System.err.println("Error: " + ex);
System.err.println("line " col
+ ex.getLineNumber() +
+ ex.getColumnNumber());
}
public void fatalError(SAXParseException ex){ System.err.println("Fatal error: " + ex); System.err.println("line = " + ex.getLineNumber() +
" col = " + ex.getColumnNumber());
}
}
После того как класс-обработчик написан, проанализировать документ очень легко. Стандартные действия приведены в методе main () программы листинга 28.9.
Поскольку реализация парсера сильно зависит от его программного окружения, SAX-парсер — объект класса SAXParser — создается не конструктором, а фабричным методом newSAXParser().
Объект-фабрика, в свою очередь, формируется методом newinstance (). Далее можно методом
void setFeature(String name, boolean value);
установить свойства парсеров, создаваемых этой фабрикой. Например, после
fact.setFeature("", true);
парсеры, создаваемые фабрикой fact, будут учитывать префиксы имен тегов и атрибутов.
Список таких свойств можно посмотреть в документации Java API в описании пакета org.xml.sax или на сайте проекта SAX . Следует учитывать, что не все парсеры полностью выполняют эти свойства.
Если к объекту-фабрике применить метод
void setValidating(true);
как это сделано в листинге 28.9, то она будет производить парсеры, проверяющие структуру документа. Если применить метод
void setNamespaceAware(true);
то объект-фабрика будет производить парсеры, учитывающие пространства имен.
После того как объект-парсер создан, остается только применить метод parse (), передав ему имя анализируемого файла и экземпляр класса-обработчика событий.
В классе javax.xml.parsers.SAXParser есть десяток методов parse(). Кроме метода parse (File, DefaultHandler), использованного в листинге 28.9, существуют еще методы, позволяющие извлечь документ из входного потока класса InputStream, объекта класса InputSource, адреса URI или из специально созданного источника класса InputSource.
Методом setProperty() можно задать различные свойства парсера. Р’ листинге 28.9 этот метод использован для того, чтобы парсер проверял правильность документа СЃ помощью схемы XSD. Если парсер выполняет проверки, С‚. Рµ. применен метод setValidating(true), то имеет смысл сделать развернутые сообщения РѕР± ошибках. Рто предусмотрено интерфейсом ErrorHandler. РћРЅ различает предупреждения, ошибки Рё фатальные ошибки Рё описывает три метода, которые автоматически выполняются РїСЂРё появлении ошибки соответствующего РІРёРґР°:
public void warning(SAXParserException ex); public void error(SAXParserException ex); public void fatalError(SAXParserException ex);
Класс DefaultHandler делает пустую реализацию этого интерфейса. РџСЂРё расширении данного класса можно сделать реализацию РѕРґРЅРѕРіРѕ или всех методов интерфейса ErrorHandler. Пример такой реализации приведен РІ листинге 28.9. Класс SAXParserException хранит номер строки Рё столбца проверяемого документа, РІ котором замечена ошибка. РС… можно получить методами getLineNumber( ) Рё getColumnNumber ( ), как сделано РІ листинге 28.9.
Анализ документов XML с помощью StAX
Рнтерфейсы Рё классы StAX собраны РІ пакеты javax.xml.stream, javax.xml.stream.events, javax.xml. stream.util, РЅРѕ РЅР° практике достаточно применения только нескольких интерфейсов Рё классов.
Основные методы разбора документа описаны в интерфейсе XMLStreamReader. Разбор заключается в том, что парсер просматривает документ, переходя от элемента к элементу и от одной части элемента к другой методом next (). Метод next () возвращает одну из целочисленных констант, описанных в интерфейсе XMLStreamConstants, показывающих тип той части документа, в которой находится парсер: start_document, start_element, END_ELEMENT, CHARACTERS и т. д. В зависимости от этого типа интерфейс XMLStreamReader предоставляет те или иные методы обработки документа. Так, если парсер находится в открывающем теге элемента, start_element, то мы можем получить короткое имя элемента методом getLocalName ( ), число атрибутов методом getAttributeCount(), имя и значение k-го атрибута методами getAttributeName(k) и getAttributeValue(k). Если парсер находится в теле элемента, characters, можно получить содержимое тела методом
getText().
Программа следующего листинга 28.10 выполняет средствами StAX ту же работу, что и программа, записанная в листинге 28.9.
Листинг 28.10. Класс-обработчик документа XML средствами StAX
import javax.xml.stream.*; import java.util.*; import j ava.io.*;
public class NotebookHandlerStAX implements XMLStreamConstants{ private Person person; private Address address;
private static Vector
inWorkPhone, inHomePhone;
private void processElement(XMLStreamReader element) throws XMLStreamException{ switch (element.getLocalName()){ case "name": person = new Person(element.getAttributeValue(0), element.getAttributeValue(1), element.getAttributeValue(2)); break; case "birthday": inBirthday = true; break; case "address": address = new Address(); break; case "street": inStreet = true; break; case "city": inCity = true; break; case "zip": inZip = true; break; case "work-phone": inWorkPhone = true; break; case "home-phone": inHomePhone = true; break;
}
}
private void processText(String text){ if (inBirthday){
person.setBirthday(text); inBirthday = false;
}else if (inStreet){
address.setStreet(text) ; inStreet = false;
}else if (inCity) {
address.setCity(text); inCity = false;
}else if (inZip){
address.setZip(text); inZip = false;
}else if (inWorkPhone){ person.addWorkPhone(text); inWorkPhone = false;
}else if (inHomePhone){ person.addHomePhone(text); inHomePhone = false;
}
}
private void finishElement(String name){ switch (name){
case "address": person.addAddre s s(addre ss);
address = null; break; case "person": pers.add(person); person = null; break;
}
}
public static void main(String[] args){ if (args.length < 1){
System.err.println("Usage: java NotebookHandlerStAX ntb.xml");
System.exit(1);
}
NotebookHandlerStAX handler = new NotebookHandlerStAX(); try{
FileInputStream inStream = new FileInputStream(args[0]);
XMLStreamReader xmlReader =
XMLInputFactory.newInstance(). createXMLStreamReader(inStream) ; int event;
while (xmlReader.hasNext()) { event = xmlReader.next(); switch (event){
case START_ELEMENT: handler.processElement(xmlReader); break; case CHARACTERS: handler.processText(xmlReader.getText()); break; case END_ELEMENT: handler.finishElement(xmlReader.getLocalName());
}
}
xmlReader.close() ;
}catch(Exception ex){ ex.printStackTrace() ;
}
}
}
Связывание данных XML с объектами Java
В приведенном примере мы сами создали классы Address и Person, представляющие документ XML. Поскольку структура документа XML четко определена, можно разработать стандартные правила связывания данных XML с объектами Java и создать программные средства для их реализации.
Корпорация Sun Microsystems разработала пакет интерфейсов и классов JAXB (Java Architecture for XML Binding), облегчающих связывание данных. Он входит в стандартную поставку Java SE, а также может быть скопирован с сайта http:// jaxb.dev.java.net/. Для работы с пакетом JAXB анализируемый документ XML обязательно должен быть снабжен описанием на языке XSD.
В состав пакета JAXB входит компилятор xj c (XML-Java Compiler). Он просматривает схему XSD и строит по нему объекты Java в оперативной памяти, а также создает исходные файлы объектов Java. Например, после выполнения команды
$ xjc -roots notebook ntb.xsd -d sources
в которой ntb.xsd — файл листинга 28.4 — в каталоге sources (по умолчанию в текущем каталоге) будут созданы файлы Addressjava, Namejava, Notebookjava, Personjava, PhoneListjava с описаниями объектов Java.
Флаг -roots показывает один или несколько корневых элементов, разделенных запятыми.
Созданные компилятором xjc исходные файлы обычным образом, с помощью компилятора j avac, компилируются в классы Java.
Получив объекты данных, можно перенести РІ РЅРёС… содержимое документа XML методом unmarshal (), который создает дерево объектов, или, наоборот, записать объекты Java РІ документы XML методом marshal (). Рти методы уже записаны РІ созданный компилятором xj c класс корневого элемента, РІ примере это класс Notebook.
Объекты данных JDO
Задачу связывания данных естественно обобщить — связывать объекты Java не только с документами XML, но и с текстовыми файлами, реляционными или объектными базами данных, другими хранилищами данных.
Корпорация Sun Microsystems опубликовала спецификацию JDO, сейчас уже версии 3.0, и разработала интерфейсы для работы с JDO. Спецификация JDO рассматривает более широкую задачу связывания данных, полученных не только из документа XML, но и из любого источника данных, называемого информационной системой предприятия (Enterprise Information System, EIS). Спецификация описывает два набора классов и интерфейсов:
□ JDO SPI (JDO Service Provider Interface) — вспомогательные классы и интерфейсы, которые следует реализовать в сервере приложений для обращения к источнику данных, создания объектов, обеспечения их сохранности, выполнения транзакций, проверки прав доступа к объектам; эти классы и интерфейсы составляют пакет
javax.jdo.spi;
□ JDO API (JDO Application Programming Interface) — интерфейсы, предоставляемые пользователю для доступа к объектам, управления транзакциями, создания и удаления объектов; эти интерфейсы собраны в пакет javax.jdo.
Есть много коммерческих и свободно распространяемых реализаций интерфейсов JDO.
Сообщество Apache Software Foundition назвало СЃРІРѕСЋ реализацию Apache JDO. Рту разработку можно посмотреть РїРѕ адресу .
Фирма DataNucleus, , выпускает продукт Access Platform, бывший JPOX.
Компания SolarMetric, , выпускает свою реализацию спецификации JDO под названием Kodo JDO. Ее можно встроить в серверы приложений WebLogic, WebSphere, JBoss.
Некоторые фирмы разработали свои варианты JDO, более или менее соответствующие спецификации. Наиболее известна свободно распространяемая разработка, названная Castor. Ее можно посмотреть по адресу .
С помощью Castor можно предельно упростить связывание данных. Например, создание объекта Java из простого документа XML, если отвлечься от проверок и обработки исключительных ситуаций, выполняется одним действием:
Person person = (Person)Unmarshaller.unmarshal(
Person.class, new FileReader("person.xml"));
Обратно, сохранение объекта Java в виде документа XML в самом простом случае выглядит так:
Marshaller.marshall(person, new FileWriter("person.xml"));
В более сложных случаях надо написать файл XML, аналогичный схеме XSD, с указаниями по связыванию данных (mapping file).
Анализ документов XML с помощью DOM API
Как РІРёРґРЅРѕ РёР· предыдущих разделов, SAX-парсер читает документ только РѕРґРёРЅ раз, отмечая появляющиеся РїРѕ С…РѕРґСѓ чтения открывающие теги, содержимое элементов Рё закрывающие теги. Ртого достаточно для связывания данных, РЅРѕ неудобно для редактирования документа.
Консорциум W3C разработал спецификации Рё набор интерфейсов DOM (Document Object Model), которые можно посмотреть РЅР° сайте этого проекта DOM/. Методами этих интерфейсов документ XML можно загрузить РІ оперативную память РІ РІРёРґРµ дерева объектов. Рто позволяет РЅРµ только анализировать документ анализаторами, основанными РЅР° структуре дерева, РЅРѕ Рё менять дерево, добавляя или удаляя объекты РёР· дерева. РљСЂРѕРјРµ того, можно обращаться непосредственно Рє каждому объекту РІ дереве Рё РЅРµ только читать, РЅРѕ Рё изменять информацию, хранящуюся РІ нем. РќРѕ РІСЃРµ это требует большого объема оперативной памяти для загрузки большого дерева.
Корпорация Sun Microsystems реализовала интерфейсы DOM в пакетах javax.xml. parsers и org.w3c.dom, входящих в состав пакета JAXP. Воспользоваться этой реализацией очень легко:
DocumentBuilderFactory fact =
DocumentBuilderFactory.newInstance();
DocumentBuilder builder = fact.newDocumentBuilder();
Document doc = builder.parse("ntb.xml");
Метод parse () строит дерево объектов и возвращает ссылку на него в виде объекта типа Document. В классе DocumentBuilder есть несколько методов parse ( ), позволяющих загрузить файл с адреса URL, из входного потока, как объект класса File или из источника класса InputSource.
Рнтерфейс Document, расширяющий интерфейс Node, описывает дерево объектов документа РІ целом. Рнтерфейс Node — РѕСЃРЅРѕРІРЅРѕР№ интерфейс РІ описании структуры дерева — описывает узел дерева. РЈ него есть еще РѕРґРёРЅ наследник — интерфейс Element, описывающий лист дерева, соответствующий элементу документа XML. Как РІРёРґРЅРѕ РёР· структуры наследования этих интерфейсов, Рё само дерево, Рё его лист считаются узлами дерева. Каждый атрибут элемента дерева описывается интерфейсом Attr. Еще несколько интерфейсов — CDATASection, Comment, Text, Entity, EntityReference, ProcessingInstruction, Notation — описывают разные типы элементов XML.
На рис. 28.2 показано начало дерева объектов, построенного по документу, приведенному в листинге 28.2. Обратите внимание на то, что текст, находящийся в теле элемента, хранится в отдельном узле дерева — потомке узла элемента. Для каждого атрибута открывающего тега тоже создается отдельный узел.
Рнтерфейс Node
Рнтерфейс Node описывает тип узла РѕРґРЅРѕР№ РёР· следующих констант:
□ attribute_node — узел типа Attr, содержит атрибут элемента;
□ CDATA_SECTION_NODE узел типа CDADASection, содержит данные типа CDATA;
□ comment_node — узел типа Comment, содержит комментарий;
□ DOCUMENT_FRAGMENT_NODE в узле типа DocumentFragment находится фрагмент документа;
□ DOCUMENT_NODE корневой узел типа Document;
□ DOCUMENT_TYPE_NODE узел типа Document;
□ ELEMENT_NODE узел является листом дерева типа Element;
□ entity_node — в узле типа Entity хранится сущность entity;
□ ENTITY_REFERENCE_NODE в узле типа EntityReference хранится ссылка на сущность;
□ NOTATION_NODE-в узле хранится нотация типа Notation;
□ PROCESSING_INSTRUCTION_NODE- узел типа ProcessingInstruction, содержит инструкцию
по обработке;
□ TEXT_NODE в узле типа Text хранится текст.
Методы интерфейса Node описывают действия с узлом дерева:
□ public short getNodeType ( ) — возвращает тип узла;
□ public String getNodeName() — возвращает имя узла;
□ public string getNodeValue() — возвращает значение, хранящееся в узле;
□ public boolean hasAttributes ( ) - выполняет проверку существования атрибутов у
элемента XML, хранящегося в узле в виде объекта типа NamedNodeMap, если это узел типа Element;
□ public NamedNodeMap getAttributes() — возвращает атрибуты; метод возвращает null, если у элемента нет атрибутов;
□ public boolean hasChildNodes ( ) -проверяет, есть ли у данного узла узлы-потомки;
□ public NodeList getChildNodes() - возвращает список узлов-потомков в виде объекта
типа NodeList;
□ public Node getFirstchild() — возвращает первый узел в списке узлов-потомков;
□ public Node getLastchild() — возвращает последний узел в списке узлов-потомков;
□ public Node getParentNode() — возвращает родительский узел;
□ public Node getPreviousSibling( ) - возвращает предыдущий узел, имеющий того же
предка, что и данный узел;
□ public Node getNextsibling() — возвращает следующий узел, имеющий того же предка, что и данный узел;
□ public Document getOwnerDocument() — возвращает ссылку на весь документ. Следующие методы позволяют изменить дерево объектов:
□ public Node appendChild(Node newChild) — добавляет новый узел-потомок newChild;
□ public Node insertBefore(Node newChild, Node refChild) — вставляет новый узел-потомок newChild перед существующим потомком refChild;
□ public Node replaceChild(Node newChild, Node oldChild) — заменяет один узел-потомок oldChild новым узлом newChild;
□ public Node removeChild(Node child) — удаляет узел-потомок.
Рнтерфейс Document
Рнтерфейс Document добавляет Рє методам своего предка Node методы работы СЃ документом РІ целом:
□ public DocumentType getDocType() — возвращает общие сведения о документе в виде объекта типа DocumentType;
□ getName(), getEntitied(), getNotations() и другие методы интерфейса DocumentType возвращают конкретные сведения о документе;
□ public Element getDocumentElement() — возвращает корневой элемент дерева объектов;
в–Ў public NodeList getElementsByTagName(String name);
public NodeList getElementsByTagNameNS(String uri, String qname); public Element getElementById(String id)
возвращают все элементы с указанным именем tag без префикса или с префиксом, а также элемент, определяемый значением атрибута с именем ID.
Несколько методов позволяют изменить структуру и содержимое дерева объектов:
□ public Element createElement(String name) — создает новый пустой элемент по его имени;
□ public Element createElementNS(String uri, String name) — создает новый пустой элемент по имени с префиксом;
□ public CDATASection createCDATASection(String name) — создает узел типа CDATA SECTION
node;
□ public EntityReference createEntityReference(String name) — создает узел типа ENTITY
reference_node;
□ public ProcessingInstruction createProcessingInstruction(String name) — создает узел
типа processing_instruction_node;
□ public TextNode createTextNode(String name) — создает узел типа TEXT_NODE;
□ public Attr createAttribute (String name) — создает узел-атрибут с именем name;
□ public Attr createAttributeNS(String uri, String name) — аналогично;
□ public Comment createComment(String comment) — создает узел-комментарий;
□ public DocumentFragment createDocumentFragment() — создает пустой документ — фрагмент данного документа с целью его дальнейшего заполнения;
в–Ў public Node importNode(Node importedNode, boolean deep) — вставляет созданный узел, Р° значит, Рё РІСЃРµ его поддерево, РІ дерево документа. Ртим методом можно соединить РґРІР° дерева объектов. Если второй аргумент равен true, то рекурсивно вставляется РІСЃРµ поддерево.
Рнтерфейс Element
Рнтерфейс Element добавляет Рє методам своего предка Node методы работы СЃ атрибутами открывающего тега элемента XML Рё методы, позволяющие обратиться Рє вложенным элементам. Только РѕРґРёРЅ метод
public String getTagName();
дает сведения о самом элементе, а именно имя элемента.
Прежде чем получить значение атрибута с именем name, надо проверить его наличие методами
public boolean hasAttribute(String name);
public boolean hasAttributeNS(String uri, String name);
Второй из этих методов учитывает пространство имен с именем uri, записанным в виде строки URI; имя name должно быть полным, с префиксом.
Получить атрибут в виде объекта типа Attr или его значение в виде строки по имени name с учетом префикса или без него можно методами
public Attr getAttributeNode(String name);
public Attr getAttributeNodeNS(String uri, String name);
public String getAttribute(String name);
public String getAttributeNS(String uri, String name); Удалить атрибут можно методами
public Attr removeAttributeNode(Attr name);
public void removeAttribute(String name);
public void removeAttributeNS(String uri, String name); Установить значение атрибута можно методами
public void setAttribute(String name, String value);
public void setAttributeNS(String uri, String name, String value); Добавить атрибут в качестве потомка можно методами
public Attr setAttributeNode(String name);
public Attr setAttributeNodeNS(Attr name); Два метода позволяют получить список узлов-потомков:
public NodeList getElrmentsByTagName(String name);
public NodeList getElrmentsByTagNameNS(String uri, String name); Ртак, методы перечисленных интерфейсов позволяют перемещаться РїРѕ дереву, менять структуру дерева, просматривать информацию, хранящуюся РІ узлах Рё листьях дерева, Рё изменять ее. Приведем пример работы СЃ деревом объектов, построенным РїРѕ документу XML. Добавим РІ адресную книжку листинга 28.2 новый рабочий или домашний телефон РЎРёРґРѕСЂРѕРІРѕР№. Рто действие записано РІ листинге 28.11.
Листинг 28.11. Анализ адресной книжки с помощью DOM API
import org.w3c.dom.*; import javax.xml.parsers.*; import org.xml.sax.*;
class ErrHand implements ErrorHandler{
public void warning(SAXParseException ex){ System.err.println("Warning: " + ex); System.err.println("line = " + ex.getLineNumber() +
" col = " + ex.getColumnNumber());
}
public void error(SAXParseException ex){ System.err.println("Error: " + ex); System.err.println("line = " + ex.getLineNumber() +
" col = " + ex.getColumnNumber());
}
public void fatalError(SAXParseException ex){ System.err.println("Fatal error: " + ex); System.err.println("line = " + ex.getLineNumber() +
" col = " + ex.getColumnNumber());
}
public class TreeProcessDOM{
static final String JAXP_SCHEMA_MNGUAGE =
" ava.sun.com/xml/j axp/properties/schemaLanguage";
static final String W3C XML SCHEMA =
"";
public static void main(String[] args) throws Exception{ if (args.length != 3){
System.err.println("Usage: java TreeProcessDOM " + "
System.exit(-1);
}
DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance(); fact.setNamespaceAware(true); fact.setValidating(true) ;
try{
fact. setAttribute ( JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA) ; }catch(IllegalArgumentException x){
System.err.println("HeH3BecTHoe свойство: " +
JAXP_SCHEMA_LANGUAGE) ;
System.exit(-1);
}
DocumentBuilder builder = fact.newDocumentBuilder(); builder.setErrorHandler(new ErrHand());
Document doc = builder.parse(args[0]);
NodeList list = doc.getElementsByTagName("notebook"); int n = list.getLength(); if (n == 0){
System.err.println("Документ пуст.");
System.exit(-1);
}
Node thisNode = null;
for (int k = 0; k < n; k++){ thisNode = list.item(k);
String elemName = null;
if (thisNode.getFirstChild() instanceof Element){
elemName = (thisNode.getFirstChild()).getNodeName(); if (elemName.equals("name")){
if (!thisNode.hasAttributes()){
System.err.println("ATpH5yTbi отсутствуют " + elemName);
System.exit(1);
}
NamedNodeMap attrs = thisNode.getAttributes();
Node attr = attrs.getNamedItem("surname"); if (attr instanceof Attr)
if (((Attr)attr).getValue().equals("РЎРёРґРѕСЂРѕРІР°")) break;
}
}
}
NodeList topics = ((Element)thisNode)
.getElementsByTagName("phone-list");
Node newNode;
if (args[1].equals("work"))
newNode = doc.createElement("work-phone"); else newNode = doc.createElement("home-phone");
Text textNode = doc.createTextNode(args[2]);
newNode.appendChild(textNode);
thisNode.appendChild(newNode);
}
}
Дерево объектов можно вывести РЅР° экран дисплея, например, как объект класса JTree — РѕРґРЅРѕРіРѕ РёР· компонентов графической библиотеки Swing. Рменно так сделано РЅР° СЂРёСЃ. 28.2. Для вывода применена программа DomEcho РёР· электронного учебника "Web Services Tutorial". Рсходный текст программы слишком велик, чтобы приводить его здесь, РЅРѕ его можно посмотреть РїРѕ адресу tutorial.html. Р’ состав парсера Xerces РІ качестве примера анализа документа РІ раздел samples/ui/ РІС…РѕРґРёС‚ программа TreeView, которая тоже показывает дерево объектов РІ РІРёРґРµ дерева JTree библиотеки Swing.
Другие DOM-парсеры
Модель дерева объектов DOM была первоначально разработана РіСЂСѓРїРїРѕР№ OMG (Object Management Group) РІ рамках языка IDL (Interface Definition Language) без учета особенностей Java. Только потом РѕРЅР° была переведена РЅР° Java консорциумом W3C РІ РІРёРґРµ интерфейсов Рё классов, составивших пакет org.w3c.dom. Ртим объясняется, РІ частности, широкое применение РІ DOM API интерфейсов Рё фабричных методов вместо классов Рё конструкторов.
Данное неудобство привело к появлению других разработок.
Участники общественного проекта JDOM РЅРµ стали реализовать модель DOM, Р° разработали СЃРІРѕСЋ модель дерева объектов, получившую название JDOM. РћРЅРё выпускают одноименный СЃРІРѕР±РѕРґРЅРѕ распространяемый программный РїСЂРѕРґСѓРєС‚, СЃ которым можно ознакомиться РЅР° сайте проекта . Ртот РїСЂРѕРґСѓРєС‚ широко используется для обработки документов XML средствами Java.
Участники другого общественного проекта — dom4j — приняли модель W3C DOM, но упростили и упорядочили DOM API. С их одноименным продуктом dom4j можно ознакомиться на сайте .
Преобразование дерева объектов в XML
Ртак, дерево объектов DOM построено надлежащим образом. Теперь надо его преобразовать РІ документ XML, страничку HTML, документ PDF или объект РґСЂСѓРіРѕРіРѕ типа. Средства для выполнения такого преобразования составляют третью часть набора JAXP — пакеты javax.xml.transform, javax.xml.transform.dom, javax.xml.transform.sax, javax.xml. transform. stream, которые представляют СЃРѕР±РѕР№ реализацию языка описания таблиц стилей для преобразований XSLT (XML Stylesheet Language for Transformations) средствами Java.
Язык XSLT разработан консорциумом W3 как одна из трех частей, составляющих язык записи таблиц стилей XSL (XML Stylesheet Language). Все материалы по XSL можно посмотреть на сайте проекта по адресу .
Рнтерфейсы Рё классы, входящие РІ пакеты javax.xml.transform.*, управляют процессором XSLT, РІ качестве которого выбран процессор Xalan, разработанный РІ рамках проекта Apache Software Foundation, .
Рсходный объект преобразования должен иметь тип Source. Рнтерфейс Source определяет всего РґРІР° метода доступа Рє идентификатору объекта:
public String getSystemId(); public void setSystemId(String id);
У интерфейса Source есть три реализации. Класс DOMSource подготавливает к преобразованию дерево объектов DOM, класс SAXSource подготавливает SAX-объект, а класс StreamSource — простой поток данных. В конструкторы этих классов заносится ссылка на исходный объект — для конструктора класса DOMSource это узел дерева, для конструктора класса SAXSource-имя файла, для конструктора класса StreamSource — входной
поток. Методы перечисленных классов позволяют задать дополнительные свойства исходных объектов преобразования.
Результат преобразования описывается интерфейсом Result. Он тоже определяет точно такие же методы доступа к идентификатору объекта-результата, как и интерфейс Source. У него тоже есть три реализации- классы DOMResult, SAXResult и StreamResult. В конст
рукторы этих классов заносится ссылка на выходной объект. В первом случае это узел дерева, во втором — объект типа ContentHandler, в третьем — файл, в который будет занесен результат преобразования, или выходной поток.
Само преобразование выполняется объектом класса Transformer. Вот стандартная схема преобразования дерева объектов DOM в документ XML, записываемый в файл.
TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer transformer = transFactory.newTransformer();
DOMSource source = new DOMSource(document);
File newXMLFile = new File("ntb1.xml");
FileOutputStream fos = new FileOutputStream(newXMLFile);
StreamResult result = new StreamResult(fos); transformer.transform(source, result);
Вначале методом newInstance() создается экземпляр transFactory фабрики объектов-преобразователей. Методом
public void setAttrbute(String name, String value);
класса TransformerFactory можно установить некоторые атрибуты этого экземпляра. Рмена Рё значения атрибутов зависят РѕС‚ реализации фабрики.
С помощью фабрики преобразователей создается объект-преобразователь класса Transformer. При формировании этого объекта в него можно занести объект, содержащий правила преобразования, например таблицу стилей XSL.
В созданный объект класса Transformer методом
public void setParameter(String name, String value);
можно занести параметры преобразования, а методами
public void setOutputProperties(Properties out); public void setOutputProperty(String name, String value);
легко определить свойства преобразованного объекта. Рмена свойств name задаются константами, которые собраны РІ специально определенный класс OutputKeys, содержащий только эти константы. Р’РѕС‚ РёС… СЃРїРёСЃРѕРє:
□ cdata_sect I on_e lements — список имен секций cdata через пробел;
□ doctype_public — открытый идентификатор public преобразованного документа;
□ doctype_system — системный идентификатор system преобразованного документа;
□ encoding — кодировка символов преобразованного документа, значение атрибута encoding объявления XML;
□ indent — делать ли отступы в тексте преобразованного документа. Значения этого свойства "yes" или "no";
□ media_type — MIME-тип содержимого преобразованного документа;
□ METHOD — метод вывода, одно из значений: "xml", "html" или "text";
□ omit_xml_declaration — не включать объявление XML. Значения "yes" или "no";
□ STANDALONE- отдельный или вложенный документ, значение атрибута standalone
объявления XML. Значения "yes" или "no";
□ version — номер версии XML для атрибута version объявления XML.
Например, можно задать кодировку символов преобразованного документа следующим методом:
transformer.setOutputProperty(OutputKeys.ENCODING, "Windows-1251");
Затем в приведенном примере по дереву объектов document типа Node создается объект класса DOMSource — упаковка дерева объектов для последующего преобразования. Тип аргумента конструктора этого класса — Node, откуда видно, что можно преобразовать не все дерево, а какое-либо его поддерево, записав в конструкторе класса DOMSource корневой узел поддерева.
Наконец, определяется результирующий объект result, связанный с файлом newCourses.xml, и осуществляется преобразование методом transform().
Более сложные преобразования выполняются с помощью таблицы стилей XSL.
Таблицы стилей XSL
В документах HTML часто применяются таблицы стилей CSS (Cascading Style Sheet), задающие общие правила оформления документов HTML: цвет, шрифт, заголовки. Выполнение этих правил придает документам единый стиль оформления.
Для документов XML, в которых вообще не определяются правила визуализации, идея применить таблицы стилей оказалась весьма плодотворной. Таблицы стилей для документов XML записываются на специально сделанной реализации языка XML, названной XSL (XML Stylesheet Language). Все теги документов XSL относятся к пространству имен с идентификатором . Обычно они записываются с префиксом xsl. Если принят этот префикс, то корневой элемент таблицы стилей XSL будет называться
Простейшая таблица стилей выглядит так, как записано в листинге 28.12.
Листинг 28.12. Простейшая таблица стилей XSL
xmlns:xsl="">
Здесь только определяется префикс пространства имен xsl и правила вывода, а именно выводится "плоский" текст, на что показывает значение text (другие значения — html и xml), в кодировке CP866. Такая кодировка выбрана для вывода кириллицы на консоль MS Windows. Объект класса Transformer, руководствуясь таблицей стилей листинга 28.12, просто выводит тела элементов так, как они записаны в документе XML, т. е. просто удаляет теги вместе с атрибутами, оставляя их содержимое.
Рту таблицу стилей записываем РІ файл, например, simple.xsl. Ссылку РЅР° таблицу стилей можно поместить РІ документ XML как РѕРґРЅСѓ РёР· инструкций РїРѕ обработке:
После этого XML-парсер, если он, кроме того, является XSLT -процессором, выполнит преобразование, заданное в файле simple.xsl.
Другой путь — использовать таблицу стилей при создании объекта-преобразователя, например, так, как записано в листинге 28.13.
Листинг 28.13. Консольная программа преобразования документа XML
import java.io.*;
import javax.xml.transform.*;
import j avax.xml.transform.stream.*;
public class SimpleTransform{
public static void main(String[] args) throws TransformerException{ if (args.length != 2){
System.out.println("Usage: " +
"java SimpleTransform xmlFileName xsltFileName"); System.exit(1);
}
File xmlFile = new File(args[0]);
File xsltFile = new File(args[1]);
Source xmlSource = new StreamSource(xmlFile);
Source xsltSource = new StreamSource(xsltFile);
Result result = new StreamResult(System.out);
TransformerFactory transFact = TransformerFactory.newInstance();
Transformer trans = transFact.newTransformer(xsltSource); trans.transform(xmlSource, result);
}
} После компиляции набираем в командной строке
java SimpleTransform ntb.xml simple.xsl и получаем на экране дисплея содержимое тегов документа ntb.xml. В более сложных случаях просто изменяем таблицу стилей. Например, если мы хотим получить содержимое документа на консоли, то таблицу стилей для объекта-преобразователя класса Transformer надо записать так, как показано в листинге 28.14.
Листинг 28.14. Таблица стилей для показа адресной книжки
xmlns:xsl="">
Мы не будем в данной книге заниматься языком XSL — одно его описание будет толще всей этой книги. На русский язык переведена "библия" XSLT [20]. Ее автор Майкл Кэй (Michael H. Kay) создал и свободно распространяет популярный XSLT -процессор Saxon, .
Преобразование документа XML в HTML
С помощью языка XSL и методов класса Transformer можно легко преобразовать документ XML в документ HTML. Достаточно написать соответствующую таблицу стилей. В листинге 28.15 показано, как это можно сделать для адресной книжки листинга 28.2. Листинг 28.15. Таблица стилей для преобразования XML в HTML
xmlns:xsl="">
Рис. 28.2. Дерево объектов документа XML
Фамилии, адреса и телефоны!а2>
Рабочий:
Домашний:
Рту таблицу стилей можно записать РІ файл, например, ntb.xsl, Рё сослаться РЅР° него РІ документе XML, описывающем адресную книжку:
После этого любой браузер, "понимающий" XML и XSLT, например Mozilla Firefox или Internet Explorer, покажет содержимое адресной книжки, как предписано листингом 28.15.
Если набрать в командной строке
$ java SimpleTransform ntb.xml ntb.xsl > ntb.html
то в текущем каталоге будет записан преобразованный HTML-файл с именем ntb.html.
Вопросы для самопроверки
1. Зачем понадобился новый язык разметки XML?
2. Какой основной цели служат элементы XML?
3. Каким образом выявляется смысл элементов XML?
4. Обязателен ли корневой элемент в документе XML?
5. Почему документ XML должен быть снабжен описанием DTD, схемой XSD или описанием на каком-то другом языке?
6. Как выполняется синтаксический анализ документа XML?
7. В каких случаях удобнее проводить синтаксический анализ документа XML, основываясь на событиях, а в каких — построением дерева?
8. Каким образом можно преобразовать один документ XML в другой документ?
9. Почему в технологии XML предпочитают использовать таблицы стилей XSL, а не CSS?
10. Какими способами можно перебрать только узлы-элементы?
11. Преобразуются ли узлы, для которых не написаны правила преобразования?