КаталогИндекс раздела


©IBM deweloperWork
© А.С.Деревянко (перевод)

XML-программирование в технологии Java. Часть 2

Doug Tidwell

09 июля 2004

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

Содержание

  1. Введение
  2. Получение и установка свойств парсера
  3. Обзор пространств имен
  4. Разбор с пространствами имен
  5. Проверка правильности XML-документов
  6. Резюме
  7. Ресурсы
  8. Об авторе

Раздел 1. Введение

Об этом учебнике

В предыдущем учебнике ("XML-программирование в технологии Java. Часть 1"), я показал вам основы разбора XML в языке Java. Я рассмотрел главные API (DOM, SAX и JDOM) и провел вас через несколько примеров, которые демонстрировали основные задачи, общие для большинства XML-приложений. Этот учебник рассмотрит более трудные вещи, которые не рассматривались раньше, такие как:

Как и во вводном учебнике, рассматриваемыми API являются:

Я также рассмотрю несколько подходов к проверке правильности, включая схему XML W3C, RELAX NG и Schematron.

О примерах

Большинство примеров здесь будут работать с сонетом Шекспира, который появился в предыдущем учебнике. Структура сонета такая:

 
<sonnet>
 <author>
  <lastName>
  <firstName>
  <nationality>
  <yearOfBirth>
  <yearOfDeath>
 </author>
 <lines>
   [14 элементов <line>]
 </lines>
</sonnet>

В различных программных примерах некоторые версии этого документа будут иметь пространства имен, а некоторые будут использовать DTD, схемы XML W3C или других языков схемы Schemas для проверки правильности. Полные примеры см. в следующих файлах:

Как альтернатива, вы можете выгрузить x-java2_code_files.zip и посмотреть эти файлы в текстовом редакторе.

Установка вашей машины

Вам следует установить несколько вещей на вашей машине прежде, чем вы сможете выполнять примеры. (Предполагается, что вы знаете, как компилировать и запускать Java-программы, и что вы знаете, как установить переменную CLASSPATH.)

  1. Прежде всего, зайдите на домашнюю страницу XML-парсера Xerces в проекте Apache XML (http://xml.apache.org/xerces2-j/). Вы можете сразу пойти на страницу выгрузки (http://xml.apache.org/xerces2-j/download.cgi).
  2. Распакуйте файл, который вы выгрузите с Apache. При этом создастся каталог с именем xerces-2_5_0 или как-то в этом роде, в зависимости от релиза парсера. JAR-файлы, которые вам нужны (xercesImpl.jar и xml-apis.jar) должны быть в корневом каталоге Xerces.
  3. Зайдите на Web-сайт проекта JDOM и выгрузите последнюю версию JDOM (http://jdom.org/).
  4. Распакуйте файл, который вы выгрузите с JDOM. При этом создастся каталог с именем jdom-b9 или как-то в этом роде. JAR-файл, который вам нужен (jdom.jar) должен быть в каталоге build.
  5. Наконец, выгрузите архив с примерами для этого учебника, x-java2_code_files.zip, и распакуйте файл.
  6. Добавьте текущий каталог (.), xercesImpl.jar, xml-apis.jar и jdom.jar в вашу переменную CLASSPATH.

 

Раздел 2. Получение и установка свойств парсера

Свойства парсера

По мере того, как XML становился более изощренным, парсеры должны были тоже становиться более изощренными. И DOM, и SAX, и JDOM определяют наборы свойств парсера. Некоторые свойства обязательны, некоторые - не обязательны. Каждый из трех API предоставляет похожие методы и исключения для получения и установки свойств парсера. Например, посмотрим на SAX. API SAX сам определяет методы получения и установки свойств парсера в интерфейсе XMLReader. JAXP обеспечивает те же самые методы в классах SAXParserFactory и SAXParser. Вот как работает этот код:

public static void checkFeatures()
{
  try
  {
    SAXParserFactory spf = SAXParserFactory.newInstance();
    spf.setFeature
     ("http://xml.org/sax/features/namespace-prefixes",
      true);
    if (!spf.getFeature
     ("http://xml.org/sax/features/validation"))
    spf.setFeature
     ("http://xml.org/sax/features/validation", true);
    . . .
}

В дополнение к методам getFeature() и setFeature() JAXP определяет методы для работы с часто используемыми свойствами пространства имен и проверки правильности.

Класс SAXParserFactory определяет методы setNamespaceAware() и setValidating() для установки этих свойств любого создаваемого SAXParser. Кроме того, и SAXParserFactory, и SAXParser обеспечивают методы isNamespaceAware() и isValidating().

Установка свойств парсера SAX

Я опустил одну деталь о методах установки свойств SAX getFeature() и setFeature(): обработку исключений. Когда вы устанавливаете свойства парсера SAX, вы должны всегда вылавливать два исключения: SAXNotSupportedException и SAXNotRecognizedException.

catch (SAXNotSupportedException snse)
{
  System.out.println
   ("The feature you requested is not supported.");
}
catch (SAXNotRecognizedException snre)
{
  System.out.println
   ("The feature you requested is not recognized.");
}

SAXNotSupportedException означает, что парсер распознает свойство, которое вы запрашиваете, но не поддерживает его, тогда как SAXNotRecognizedException означает, что парсер никогда не слыхал о свойстве, которое вы запрашиваете.

Свойства парсера SAX

Стандарт SAX 2.0 требует, чтобы парсер распознавал такие два свойства.

http://xml.org/sax/features/namespaces
Парсер распознает пространства имен: когда это свойство - true, для всех элементов и атрибутов возможны URI пространства имен и неквалифицированные локальные имена. Любой совместимый с SAX 2.0 парсер должен поддерживать значение этого свойства по умолчанию - true.

http://xml.org/sax/features/namespace-prefixes
Парсер обеспечивает поддержку префиксов пространств имен. Если это свойство - true, для элементов и атрибутов возможны префиксы пространств имен, включая xmlns: атрибуты. Любой совместимый с SAX 2.0 парсер должен поддерживать значение этого свойства по умолчанию - false.

Еще одно замечание по поводу этих двух обязательных свойств: от парсера требуется обеспечивать для них методы get, но не обязательно обеспечивать методы set.

Чтобы помочь вам избежать SAXNotRecognizedException, вот список общеподдерживаемых свойств. Они не определены в стандарте SAX 2.0 (этот стандарт определяет только два обязательных свойства). SAX-парсер не обязан поддерживать или распознавать какие-либо из этих свойств, и любой парсер может на свой выбор обладать любыми свойствами из этого списка.

http://xml.org/sax/features/external-general-entities
Определяет, обрабатывает ли парсер общие внешние сущности. Это свойство не имеет значения по умолчанию, хотя, если включена проверка правильности, это свойство будет true.

http://xml.org/sax/features/external-parameter-entities
Определяет, обрабатывает ли парсер внешние параметрические сущности. Это свойство не имеет значения по умолчанию, хотя, если включена проверка правильности, это свойство будет true.

http://xml.org/sax/features/is-standalone
Это свойство определяет, содержит ли XML-декларация standalone="yes". Оно не может быть изменено, оно может быть только запрошено, и оно может быть запрошено только после события startDocument.

http://xml.org/sax/features/lexical-handler/parameter-entities
LexicalHandler парсера будет докладывать о начале и конце параметрических сущностей.

http://xml.org/sax/features/resolve-dtd-uris
Значение true показывает, что ID SYSTEM, используемые для определения деклараций, будут представлены относительно базового URI документа. false показывает, что ID будут представлены как они найдены в документе, программа может использовать метод Locator.getSystemId(), чтобы получить базовый URI документа.

http://xml.org/sax/features/string-interning
Если это свойство - true, все URI имен XML и пространства имен обрабатываются с использованием java.lang.String.intern(). Использование intern() делает сравнение строк более быстрым, чем вызов java.lang.String.equals().

http://xml.org/sax/features/use-attributes2
http://xml.org/sax/features/use-locator2
http://xml.org/sax/features/use-entity-resolver2

Значение true показывает, что парсер использует измененные интерфейсы org.xml.sax.ext.Attributes2, org.xml.sax.ext.Locator2 или org.xml.sax.ext.EntityResolver2, соответственно.

http://xml.org/sax/features/validation
Задает, проверяет ли парсер правильность документа. Если это свойство - true, external-general-entities и external-parameter-entities автоматически устанавливаются в true.

http://xml.org/sax/features/xmlns-uris
Если установлено свойство namespace-prefixes, это свойство управляет тем, находятся ли декларации пространств имен в самих пространствах имен. Спецификация
Пространства имен в XML устанавливает, что декларации пространств имен не должны быть в любом пространстве имен, тогда как спецификация DOM Уровня 1 помещает декларации пространств имен в пространство имен http://www.w3.org/2000/xmlns/. Если это свойство - true, декларации пространств имен находятся в пространстве имен; если это свойство - false, нет.

Вы можете найти список URIвсех определенных свойств на saxproject.org/apidoc/org/xml/sax/package-summary.html.

Кратко о сущностях

Многие из свойств парсера имеют дело с сущностями и с тем, как они обрабатываются парсером. Я включаю сюда краткий обзор сущностей на случай, если вы не знакомы с ними. В общем случае сущность определяют строку, которая заменяется чем-то еще, когда XML-документ обрабатывается. Здесь перечислены различные типы сущностей.

Общие и параметрические сущности. Общая сущность определяет подстановку, которая используется внутри XML-документа. Параметрическая сущность, с другой стороны, появляется только в DTD и определяет подстановку, которая может использоваться только внутри DTD. (Подробнее о DTD позже.) Общая сущность выглядит так:

<!ENTITY corp "International Business Machines Corporation">

При использовании этой сущности строка &corp; заменяется строкой International Business Machines Corporation, где бы она не появилась. Параметрическая сущность выглядит так:

<!DOCTYPE article [
<!ENTITY % basic "a|b|code|dl|i|ol|ul|#PCDATA">
<!ELEMENT body (%basic;)*>

В этом примере параметрическая сущность basic связана с определенной строкой. Предположим, есть элементы HTML, где вы используете параметрическую сущность %basic;, это означает, что элемент может содержать текст (#PCDATA сокращение для "разбираемые символьные данные ") или элементы <a>, <b>, <code>, <dl>, <i>, <ol> и <ul>. Использование этой параметрической сущности в DTD может сократить объем печатанья.

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

<!ENTITY auth "Doug Tidwell">
<!ENTITY BoD SYSTEM "http://www.ibm.com/board_of_directors.html">

При использовании этого примера строка &auth; будет заменена текстом Doug Tidwell, тогда как строка &BoD; будет заменена содержимым файла board_of_directors.html. Первая сущность здесь является внутренней общей сущностью, в вторая - внешней общей сущностью.

Предопределенные сущности. Стандарт XML определяет пять сущностей, которые доступны всегда. Это сущности для знака меньше-чем (&lt;), больше-чем (&gt;), амперсанда (&amp;), апострофа или одинарной кавычки (&apos;) и двойной кавычки (&quot;).

Установка свойств парсера DOM

DocumentBuilderFactory в JAXP имеет меньший набор свойств, чем SAXParserFactory. Наиболее важные методы такие.

setValidating(boolean)
Установка свойства фабрики проверки правильности.

isValidating()
Возвращает true, если фабрика создает проверяющие парсеры, иначе - false.

setNamespaceAware(boolean)
Установка свойства фабрики учета пространств имен.

isNamespaceAware()
Возвращает true, если фабрика создает парсеры, учитывающие пространство имен, иначе - false.

setIgnoringElementContentWhitespace(boolean)
Установка свойства пропусков фабрики. Если оно true, парсеры, создаваемые фабрикой не будут создавать узлы для игнорируемых пропусков в документе.

isIgnoringElementContentWhitespace()
Возвращает true, если фабрика создает парсеры, игнорирующие пропуски, иначе - false.

Кроме приведенных выше свойств, часто используются следующие свойства:

setCoalescing(boolean) и isCoalescing()
Фабрика создает парсеры, которые конвертируют секции CDATA в текстовые узлы. (CDATA - сокращение для "символьные данные", и обозначает здесь неразбираемый текст.)

setExpandEntityReferences(boolean) и isExpandEntityReferences()
Фабрика создает парсеры, которые расширяют узлы ссылок на сущности.

setIgnoringComments(boolean) и isIgnoringComments()
Фабрика создает парсеры, которые игнорируют комментарии.

 

Раздел 3. Обзор пространств имен

Введение в пространства имен

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

свои данные в соответствие ограниченным структурам HTML. Когда начальная эйфория угасла, стало ясно, что не все так просто, рано или поздно две или более группы определят один и тот же тег. Например, я запускаю онлайновый книжный магазин, я использую тег <title> для названия книги. Вы в своем бизнесе можете хранить адреса ваших покупателей в XMLиспользуя тег <title> для титула покупателя.

Чтобы создать заказ на книгу в XML, для меня совершенно разумным будет использовать мои теги для описания заказываемых книг, и совершенно разумным для вас будет использовать ваши теги для описания адреса доставки заказа. Очевидная проблема: как мы различим эти два тега <title>? Ответом являются пространства имен.

Концептуально пространство имен работает как оператор Java package. Два класса могут иметь одно и то же имя, пока они в двух разных пакетах. Например, и JDOM, и DOM определяют класс Document. Чтобы было ясным, какой класс я хочу использовать, я комбинирую имя пакета с именем класса, как в org.jdom.Document и org.w3c.dom.Document.

Пространство имен имеет две части: префикс и уникальную строку. Вот фрагмент документа, ктороый использует пространства имен:

<bookOrder xmlns:lit="http://www.literarysociety.org/books"
      xmlns:addr=http://www.usps.com/addresses >
  . . .
  <lit:title>My Life in the Bush of Ghosts</lit:title>
  . . .
  <shipTo>
   <addr:title>Ms.</addr:title>
   <addr:firstName>Linda</addr:firstName>
   <addr:lastName>Lovely</addr:lastName>
   . . .

Этот документ определяет два пространства имен. Префикс lit связан со строкой http://www.literarysociety.org/books, а префикс addr связан со строкой http://www.usps.com/addresses. Когда вы используете элемент <lit:title> или <addr:title>, ясно, какой из элементов <title> вы используете.

Еще подробности о пространствах имен

Когда вы определяете пространство имен для данного элемента, это пространство имен может быть использовано этим элементом и любым элементом внутри него. В предыдущем примере я определил все пространства имен, которые я использовал, в корне документа. Я мог определить пространство имен addr в элементе <shipTo>:

<shipTo xmlns:addr="http://www.usps.com/addresses">
 <addr:title>Ms.</addr:title>
 <addr:firstName>Linda</addr:firstName>
 <addr:lastName>Lovely</addr:lastName>
 . . .

Если бы я хотел печатать как можно больше, я мог бы переопределить пространство имен в каждом отдельном элементе, который использует ее:

<shipTo>
 <addr:title xmlns:addr="http://www.usps.com/addresses">
  Ms.
 </addr:title>
 <addr:firstName xmlns:addr="http://www.usps.com/addresses">
  Linda
 </addr:firstName>
 <addr:lastName xmlns:addr="http://www.usps.com/addresses">
  Lovely
 </addr:lastName>
 . . .

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

Последним приемом является использование атрибута xmlns без определения префикса вообще. Это определяет пространство имен по умолчанию для текущего элемента и всех элементов-потомков, которые не имеют префикса пространства имен. Я мог бы закодировать элемент <title> так:

<title xmlns="http://www.literarysociety.org/books">
 My Life in the Bush of Ghosts
</title>

Через минуту я покажу вам версию XML-сонета, квалифицированную пространствами имен. Если вы хотите, вы можете использовать пространство имен по умолчанию для элемента <author>:

<author
  xmlns="http://www.literarysociety.org/authors">
 <lastName>Shakespeare</lastName>
 <firstName>William</firstName>
 <nationality>British</nationality>
 <yearOfBirth>1564</yearOfBirth>
 <yearOfDeath>1616</yearOfDeath>
</author> 

Поскольку никакой из этих элементов не имеет префикса пространства имен, учитывающий пространство имен парсер будет рассматривать все эти элементы как относящиеся к пространству имен http://www.literarysociety.org/authors. Вне элемента <author> пространство имен по умолчанию уже не определено.

сравнение двух пространств имен

Иногда вы хотите проверить значение пространства имен. Например в таблицах стилей XSLT все элементы таблиц стилей должны быть из пространства имен http://www.w3.org/1999/XSL/Transform. Обычно эта строка пространства имен связывается с префиксом xsl, но это не обязательно. Когда вы убеждаетесь, что пространство имен для данного элемента корректно, вам нужно проверять строку пространства имен, а не префикс пространства имен. Другими словами, такой элемент XSLT является правильным:

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

тогда как такой элемент XSLT - нет:

<xsl:stylesheet xmlns:xsl="http://i-love-stylesheets.com">

Во втором примере префикс пространства имен тот, какой вы ожидаете, но строка пространства имен неправильная. Последний пример, этот элемент XSLT является правильным:

<xqdkera:stylesheet
xmlns:xqdkera="http://www.w3.org/1999/XSL/Transform">

Здесь префикс не традиционный xsl, но это неважно. При сравнении двух пространств имен, префикс не имеет значения.

Общее ошибочное представление о пространствах имен

Наиболее сбивает с толку в пространствах имен то, уникальная строка не используется как URL (да, она так выглядит, но это не так). Общей практикой для групп является использование имен их доменов, чтобы гарантировать уникальность строки. Вы можете подумать (как я сначала думал), что соединенный с Internet XML-парсер будет выгружать DTD или схему с этого URL, но он этого не делает.

Хотя стандарт пространств имен в XML определяет уникальную строку как URI (Universal Resource Identifier - см. Ресурсы), это на самом деле только строка. xmlns:addr="cranberries" является правильным, пока cranberries является уникальным среди всех определений пространств имен в данном документе. Все подробности о правильных URI см в стандарте URI (RFC2396) в Ресурсы.

Одно последнее (потенциально сбивающее с толку) замечание

Если у вас есть трудности в понимании того, что уникальная строка не является URL, и что XML-парсер никогда не будет использовать эту строку как URL, приготовьтесь. Если вы имеете какие-либо сомнения или если вы все еще сбиты с толку, см. Получение информации пространства имен от парсера.

Даже хотя строка пространства имен не является URL, иногда, если вы введете эту строку в вашем браузере, вы найдете полезную информацию о наборе тегов или типе документа. Это совершенно не обязательно и предназначено для человеческого восприятия, а не для машин. Например, пространство имен http://www.w3.org/2003/05/soap-envelope/role/next определено в спецификации SOAP 1.2. Если вы введете в ваш браузер http://www.w3.org/2003/05/soap-envelope/role/next, вы увидите страницу HTML, которая указывает вам на дополнительную информацию о спецификации SOAP 1.2.

Если другие люди захотят использовать элементы, которые определены вами, хорошей идеей будет обеспечить такую информацию на случай, если кто-то захочет загрузить URI пространства имен.

 

Раздел 4. Разбор с пространствами имен

Получение информации пространства имен от парсера

Теперь, когда вы увидели основы пространств имен, я рассмотрю DOM, SAX и JDOM, чтобы показать, как они учитывают информацию пространства имен. Все эти примеры используют следующую модифицированную версию сонета:

<sonnet pt:type="Shakespearean"
    xmlns:pt="http://www.literarysociety.org/poemtypes" >
 <auth:author
     xmlns:auth="http://www.literarysociety.org/authors">
  <auth:lastName>Shakespeare</auth:lastName>
  <auth:firstName>William</auth:firstName>
  <auth:nationality>British</auth:nationality>
  <auth:yearOfBirth>1564</auth:yearOfBirth>
  <auth:yearOfDeath>1616</auth:yearOfDeath>
 </auth:author> 
 <title>Sonnet 130</title>
 <lines>
  . . .
 </lines>
</sonnet>

При работе с пространствами имен вам нужно знать несколько порций информации для каждого элемента, квалифицированного пространством имен. Как пример, рассмотрим ниже элемент <auth:lastName>.

Локальное (неквалифицированное) имя элемента
lastName - имя элемента без какого-либо префикса пространства имен

Префикс пространства имен
auth

Квалифицированное имя элемента
auth:lastName - имя элемента, включая любой префикс пространства имен

URI пространства имен
http://www.literarysociety.org/authors

Теперь я покажу каждый API, выставляющий эту информацию.

DOM и пространства имен

Поддержка пространств имен была добавлена в Объектную Модель Документа в DOM Уровня 2.

Вот список составляющих информации вместе с методами DOM, которые дают вам эту информацию.

Локальное (неквалифицированное) имя элемента
Node.getLocalName()

Префикс пространства имен
Node.getPrefix()

Квалифицированное имя элемента
Node.getNodeName()

URI пространства имен
Node.getNamespaceURI()

Заметьте, что все эти методы являются частью интерфейса Node. Знайте, что использование этих методов имеет некоторые сложности:

Создание парсера DOM, учитывающего пространство имен

Теперь давайте взглянем на DomNS, Java-программу, аналогичную по структуре программе DomOne из вводного учебника по XML-программированию на Java. Вы увидите здесь два основных отличия: вам необходимо создать учитывающий пространство имен DOM-парсер и добавить код для обработки информации пространства имен в дереве DOM. Вместо того, чтобы просто выводить разобранный документ на консоль, вы печатаете информацию о любых элементах в дереве DOM, квалифицированных пространством имен.

Первым вашим шагом является создание учитывающего пространство имен парсера. Для большинства парсеров учет пространства имен по умолчанию выключен. Поскольку вы используете JAXP, вам нужно установить свойство вашей DocumentBuilderFactory перед созданием вашего DOM-парсера:

DocumentBuilderFactory dbf =
  DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(uri);
if (doc != null)
printNamespaceInfo(doc.getDocumentElement());

Используйте метод JAXP setNamespaceAware() вашего класса фабрики для включения учета пространства имен; с этого момента любой DOM-парсер, создаваемый фабрикой, будет учитывать пространство имен. После установке парсера вызывайте рекурсивно метод printNamespaceInfo(), чтобы найти все элементы и атрибуты в вашем документе, квалифицированные пространством имен.

Нахождение пространств имен в дереве DOM

Метод printNamespaceInfo() ищет любые элементы и атрибуты в дереве DOM и печатает подробности о тех узлах, где метод getPrefix() возвращает не-null строку. Вот этот код:

case Node.ELEMENT_NODE:
{
  if (node.getPrefix() != null)
  {
    System.out.println("\nElement " + node.getNodeName());
    System.out.println("\tLocal name = " +
      node.getLocalName());
    System.out.println("\tNamespace prefix = " +
      node.getPrefix());
    System.out.println("\tNamespace URI = " +
      node.getNamespaceURI());
  }      
  if (node.hasAttributes())
  {
    NamedNodeMap attrs = node.getAttributes();
    for (int i = 0; i < attrs.getLength(); i++)
      if ((attrs.item(i).getPrefix()) != null)
        printNamespaceInfo (attrs.item(i));
  }
  if (node.hasChildNodes())
  {
    NodeList children = node.getChildNodes();
    for (int i = 0; i < children.getLength(); i++)
      printNamespaceInfo (children.item(i));
  }
  break;
}
case Node.ATTRIBUTE_NODE:
{
  System.out.println("\nAttribute " +
    node.getNodeName() + "="
    + node.getNodeValue());
  System.out.println("\tLocal name = " +
    node.getLocalName());
  System.out.println("\tNamespace prefix = " +
    node.getPrefix());
  System.out.println("\tNamespace URI = " +
    node.getNamespaceURI());
  break;
}

Чтобы обрабатывать элементные узлы, следуйте этим трем шагам:

  1. Если элемент имеет префикс пространства имен, печатайте подробности об узле.
  2. Просмотрите все атрибуты элемента, если любые из них квалифицированы пространством имен, вызывайте для них printNamespaceInfo().
  3. Вызывайте printNamespaceInfo() для всех дочерних элементов.

Для обработки узла атрибута просто печатайте подробности о нем. Заметьте, что printNamespaceInfo() предполагает, что он вызывается только для узлов, квалифицированных пространствами имен.

Когда вы запустите DomNS на файле sonnetNamespaces.xml, вы получите такой результат:

C:\adv-xml-prog>java DomNS sonnetnamespaces.xml

Attribute pt:type=Shakespearean
    Local name = type
    Namespace prefix = pt
    Namespace URI = http://www.literarysociety.org/poemtypes

Attribute xmlns:pt=http://www.literarysociety.org/poemtypes
    Local name = pt
    Namespace prefix = xmlns
    Namespace URI = http://www.w3.org/2000/xmlns/

Element auth:author
    Local name = author
    Namespace prefix = auth
    Namespace URI = http://www.literarysociety.org/authors

Attribute xmlns:auth=http://www.literarysociety.org/authors
    Local name = auth
    Namespace prefix = xmlns
    Namespace URI = http://www.w3.org/2000/xmlns/

Element auth:lastName
    Local name = lastName
    Namespace prefix = auth
    Namespace URI = http://www.literarysociety.org/authors
. . .

Результат показывает подробности о любых элементах и атрибутах в исходном документе, квалифицированных пространством имен. В части вывода, показанной здесь перечислены атрибут pt:type, элемент <auth:author> и элемент <auth:lastName>.

xmlns:pt и xmlns:auth отображаются на пространство имен http://www.w3.org/2000/xmlns/. Это пространство имен используется DOM-парсерами для пространства имен по умолчанию. Раздел 4 спецификации пространства имен в XML устанавливает, "Префикс xmlns используеться только для связывания пространства имен и сам не привязан ни к какому имени пространства имен." Однако, когда реализовывался DOM Уровня 2, Раздел 1.1.8 спецификации DOM Уровня 2 изменил это.

Примечание: В DOM, все атрибуты объявления пространства имен по определению привязаны к пространству имен URI: http://www.w3.org/2000/xmlns/. Это те атрибуты, в которых префикс пространства имен или квалифицированное имя - "xmlns". Хотя во время написания, это не являлось частью спецификации пространства имен в XML, планировалось включить это в будущие исправления.

Другими словами, любой атрибут, который определяет пространство имен (xmlns:abc="...") или пространство имен по умолчанию (xmlns="...") будет сам отображаться на пространство имен http://www.w3.org/2000/xmlns/. Когда вы разбираете тот же документ с пространством имен SAX, определения не рассматриваются как атрибуты, так что никакое пространство имен не определено для самих определений.

Полный исходный код см. в DomNS.java.

Методы DOM, учитывающие пространство имен

Чтобы завершить обсуждение DOM и пространств имен, приведем список всех методов DOM, учитывающих пространство имен. За именем каждого метода следует его краткое описание; все подробности о методах и их работе ищите в документации DOM, которую вы получите вместе со своим парсером.

Document.createAttributeNS(...)
Создает атрибут с заданным пространством имен и квалифицированным именем.

Document.createElementNS(...)
Создает элемент с заданным пространством имен и квалифицированным именем.

Document.getElementsByTagNameNS(...)
Возвращает NodeList со всеми узлами-потомками, которые соответствуют данному пространству имен и локальному имени.

Element.getAttributeNodeNS(...)
Возвращает узел Attribute с данным пространством имен и локальным именем атрибута.

Element.getAttributeNS(...)
Возвращает значение атрибута данным пространством имен и локальным именем атрибута.

Element.getElementsByTagNameNS(...)
Возвращает NodeList со всеми узлами-потомками, которые соответствуют данному пространству имен и локальному имени.

Element.hasAttributeNS(...)
Возвращает true, если этот элемент имеет атрибут с данным пространством имен и локальным именем, иначе - false.

Element.removeAttributeNS(...)
Для данного пространства имен и локального имени удаляет из элемента все атрибуты с этим пространством имен и локальным именем.

Element.setAttributeNodeNS(...)
Добавляет данный квалифицированный пространством имен объект Attr в данный элемент.

Element.setAttributeNS(...)
Для данного пространства имен и локального имени атрибута добавляет в данный элемент квалифицированный пространством имен атрибут с заданным значением.

Node.getLocalName()
Возвращает локальное (неквалифицированное) имя данного узла.

Node.getNamespaceURI()
Возвращает строку пространства имен, связанную с данным узлом, или null, если элемент или атрибут не связан с пространством имен.

Node.getNodeName()
Возвращает имя данного узла. Если узел квалифицирован пространством имен, этот метод возвращает префикс и имя элемента; иначе он возвращает только имя элемента.

Node.getPrefix()
Возвращает префикс пространства имен для данного узла или null, если элемент или атрибут в исходном XML не имеет префикса.

Node.setPrefix(...)
Устанавливает префикс пространства имен для данного узла.

Примечание: Все методы, определенные в интерфейсе Node возвращают полезную информацию только для Element и Attribute. Вызов этих методов на других типах узлов возвращает null.

SAX и пространства имен

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

Локальное (неквалифицированное) имя элемента
Параметр localName (второй параметр) событий startElement и endElement.

Префикс пространства имен
Недоступен непосредственно. Префиксом пространства имен является все до двоеточия в параметре qName (третий параметр) событий startElement и endElement.

Квалифицированное имя элемента
Параметр qName (третий параметр) событий startElement и endElement.

URI пространства имен
Параметр uri (первый параметр) событий startElement и endElement.

Как и следовало ожидать, информация пространства имен обеспечивается через различные события, особенно события startElement и endElement.

Создание парсера SAX, учитывающего пространства имен

Теперь посмотрим на SaxNS, Java-программу, похожую на DomNS. Как и в случае DomNS, вашим первым шагом является создание учитывающего пространство имен парсера. Установите свойство объекта SAXParserFactory, создающего SAXParser, и вы готовы начать разбор. Вот как создается такой SAXParser, какой вам нужен:

SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
SAXParser sp = spf.newSAXParser();
sp.parse(uri, this);

Используйте метод JAXP setNamespaceAware() для вашего класса фабрики для включения учета пространства имен; с этого момента любой создаваемый фабрикой SAXParser будет учитывать пространство имен. Если вы имеете установленный парсер, ваши обработчики событий будут получать события от него.

Нахождение пространств имен в событиях SAX

В SaxNS вы хотите выводить на консоль подробную информацию о любых элементах, квалифицированных пространством имен. Вы можете найти всю информацию, которая вам нужна в событии startElement. Вот код для обработчика события startElement:

public void startElement(String namespaceURI, String localName,
String qName, Attributes attrs)
{
  if (namespaceURI.length() > 0)
  {
    System.out.println("\nElement " + qName);
    System.out.println("\tLocal name = " + localName);
    if (qName.indexOf(':') > 0)
      System.out.println("\tNamespace prefix = " +
        qName.substring(0, qName.indexOf(':')));
    else
      System.out.println("\tNamespace prefix =");
    System.out.println("\tNamespace URI = " + namespaceURI);
  }
  if (attrs != null)
  {
    int len = attrs.getLength();
    for (int i = 0; i < len; i++)
      if (attrs.getURI(i).length() > 0)
      {
        System.out.println("\nAttribute " +
          attrs.getQName(i) + "=" +
          attrs.getValue(i));
        System.out.println("\tLocal name = " +
          attrs.getLocalName(i));
        if (qName.indexOf(':') > 0)
          System.out.println("\tNamespace prefix = " +
            attrs.getQName(i).
            substring(0,
              attrs.getQName(i).
              indexOf(':')));
        else
        System.out.println("\tNamespace prefix = ");
        System.out.println("\tNamespace URI = " +
          attrs.getURI(i));
      }
  }
}

Как и следовало ожидать, этот код очень похож не код DomNS. Однако, есть несколько отличий:

Когда вы запустите SaxNS на файле sonnetNamespaces.xml, вы получите такой результат:

C:\adv-xml-prog>java SaxNS sonnetnamespaces.xml

Attribute pt:type=Shakespearean
    Local name = type
    Namespace prefix = pt
    Namespace URI = http://www.literarysociety.org/poemtypes

Element auth:author
    Local name = author
    Namespace prefix = auth
    Namespace URI = http://www.literarysociety.org/authors

Element auth:lastName
    Local name = lastName
    Namespace prefix = auth
    Namespace URI = http://www.literarysociety.org/authors

Element auth:firstName
    Local name = firstName
    Namespace prefix = auth
    Namespace URI = http://www.literarysociety.org/authors

Element auth:nationality
    Local name = nationality
    Namespace prefix = auth
    Namespace URI = http://www.literarysociety.org/authors

По большей части результат SaxNS такой же, как и DomNS. Единственное отличие состоит в том, что SAXParser не сообщает о самих определениях пространств имен (таких, как xmlns:pt="http://www.lit..." ), как атрибутах. Через минуту я покажу вам события SAX, которые обрабатывают определения пространств имен.

Полный исходный код см. в SaxNS.java.

События SAX, связанные с пространством имен

В обсуждении нахождения пространств имен при помощи парсера DOM (Нахождение пространств имен в дереве DOM) я указывал, что все определения пространств имен обрабатываются как атрибуты, принадлежащие пространству имен http://www.w3.org/2000/xmlns/.

Вы должны были заметить, что в выводе SaxNS определения пространств имен не показывались в выводе вообще. Другими словами, атрибуты для элемента <sonnet> включают атрибут pt:type, но не включают определение самого пространства имен pt.

Если программе необходима информация об определениях пространств имен, вы можете получить ее через отдельные события. События startPrefixMapping и endPrefixMapping сообщают вам, когда определенное пространство имен определяется и когда пространство имен выходит из сферы видимости. Далее я рассмотрю новое приложение, SaxNSTwo, которое использует эти новые события для обработки пространств имен.

Одной из причин для обработки startPrefixMapping и endPrefixMapping является отслеживание различных префиксов и URI пространств имен, на которые они отображаются. В SaxNSTwo я показываю как создать HashMap для отслеживания событий пространства имен по мере их поступление. Вот код, который реализует HashMap и обработчики событий:

private HashMap prefixes = new HashMap();
. . .
public void startPrefixMapping(String prefix, String uri)
{
  System.out.println("\nNew namespace:");
  System.out.println("\tNamespace prefix = " + prefix);
  System.out.println("\tNamespace URI = " + uri);
  prefixes.put(uri, prefix);
}
public void endPrefixMapping(String prefix)
{
  System.out.println("\nPrefix " + prefix +
    " is no longer in scope.");
}

Логика здесь очень простая; когда вы получаете событие startPrefixMapping, вы добавляете комбинацию префикса и URI в HashMap. Когда вы получаете событие endPrefixMapping, вы удаляете префикс и URI. Последнее усовершенствование, сделанное в SaxNSTwo состоит в добавлении закрытого метода для получения отображения префикса для определенного URI:

private String getPrefix(String url)
{
  if (prefixes.containsKey(url))
    return prefixes.get(url).toString();
  else
    return "";
}

Когда вы выводите на консоль информацию пространства имен, вы можете использовать метод getPrefix() для выборки префикс, связанного с данным URI:

if (namespaceURI.length() > 0)
{
  System.out.println("\nElement " + qName);
  System.out.println("\tLocal name = " + localName);
  System.out.println("\tNamespace prefix = " +
    getPrefix(namespaceURI) ); 
  System.out.println("\tNamespace URI = " + namespaceURI);
}

Полный исходный код см. в SaxNSTwo.java.

Последний подход к обработке пространств имен

Подход, использованный в SaxNSTwo, очень прямолинейный: когда вы получаете событие startPrefixMapping, вы помещаете элемент в вашу HashMap, а затем выбираете значение, когда оно вам нужно. Если данный URI отображен на два префикса, и эти определения вложены друг в друга, SaxNSTwo не выполнит свою работу. Это не общий случай, но он возможен. SaxNTwo не обрабатывает правильно случай, когда данный префикс отображается на два URI, но проверка ошибок парсера поймает его прежде, чем ваш код сделает что-либо неправильное.

Чтобы обработать эту проблему (предполагая, что вы рассматриваете ее как проблему), вам понадобиться реализовать стек для каждого URI, проталкивая значение в стек URI по событию startPrefixMapping, и извлекая значение из стека этого URI по событию endPrefixMapping.

Если этот случай имеет для вас значение, пакет org.xml.sax.helpers обеспечивает специальный класс, NamespaceSupport, для управления пространствами имен по мере того, как они входят в сферу видимости и выходят из нее. Я рассмотрю последний класс, SaxNSThree, который использует объект NamespaceSupport, имеющий дело с пространствами имен.

Вот как обрабатывать пространства имен, используя объект NamespaceSupport:

Теперь взглянем на SaxNSThree. Прежде всего, вот объявление объекта NamespaceSupport и двух обработчиков событий:

private NamespaceSupport ns = new NamespaceSupport();
. . .
public void startPrefixMapping(String prefix, String uri)
{
  ns.pushContext();
  ns.declarePrefix(prefix, uri);
}
public void endPrefixMapping(String prefix)
{
  ns.popContext();
}

Чтобы проиллюстрировать обработку пространств имен, вы обработаете все элементы и атрибуты, как и прежде, плюс вы перечислите пространства имен, определенные при обработке каждого элемента. Результат для нашего сонета выглядит так:

C:\adv-xml-prog>java SaxNSThree sonnetnamespaces.xml

<sonnet> : 2 prefixes defined - (xml, pt)

Attribute pt:type=Shakespearean
    Local name = type
    Namespace prefix = pt
    Namespace URI = http://www.literarysociety.org/poemtypes

<auth:author> : 3 prefixes defined - (xml, auth, pt)
    Local name = author
    Namespace prefix = auth
    Namespace URI = http://www.literarysociety.org/authors

<auth:lastName> : 3 prefixes defined - (xml, auth, pt)
    Local name = lastName
    Namespace prefix = auth
    Namespace URI = http://www.literarysociety.org/authors
. . .
<title> : 2 prefixes defined - (xml, pt)

<lines> : 2 prefixes defined - (xml, pt)
. . .

Заметьте, что префикс XML всегда определен, он отображается на строку http://www.w3.org/XML/1998/namespace. Объект NamespaceSupport отслеживает каждое определение пространства имен, находящееся в текущий момент в сфере видимости.

Одним из недостатков NamespaceSupport является то, что он возвращает Enumeration для метода getPrefixes(). (NamespaceSupport использует Enumeration также и в других местах.) Чтобы получить число пространств имен в сфере видимости, вы должны написать некоторый некрасивый код:

private int getPrefixCount()
{
  Enumeration e = ns.getPrefixes();
  int count = 0;
  while (e.hasMoreElements())
  {
    count++;
    e.nextElement();
  }
  return count;
}

Если метод getPrefixes() возвращает объект какого-то рода коллекции Java, вы можете использовать метод size(), чтобы получить число префиксов, определенных на текущий момент.

Полный исходный код см. в SaxNSThree.java.

Объекты SAX, учитывающие пространство имен

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

org.xml.sax.Attributes
За исключением getLength(), который возвращает число атрибутов, все методы этого класса учитывают пространство имен.

org.xml.sax.ContentHandler
События startElement, endElement, startPrefixMapping и endPrefixMapping учитывают пространство имен.

org.xml.sax.helpers.AttributesImpl
Следующие методы учитывают пространство имен: addAttribute(), getIndex(), getLocalName(), getQName(), getType(), getURI(), getValue(), setAttribute(), setLocalName(), setQName() и setURI().

org.xml.sax.helpers.DefaultHandler
События startElement, endElement, startPrefixMapping и endPrefixMapping учитывают пространство имен. (Эти события определены в интерфейсе ContentHandler, который реализован в DefaultHandler.)

org.xml.sax.helpers.NamespaceSupport
Этот класс существует для управления пространствами имен по мере того, как они входят в сферу видимости и выходят из нее.

org.xml.sax.helpers.XMLFilterImpl
Этот класс реализует интерфейс ContentHandler, так что он включает в себя события, учитывающие пространство имен, startElement, endElement, startPrefixMapping и endPrefixMapping.

JDOM и пространства имен

В начале этого обсуждения взглянем на API JDOM для получения четырех основных порций информации для элемента и атрибута:

Element.getName()
Локальное (неквалифицированное) имя элемента

Element.getNamespacePrefix()
Префикс пространства имен

Element.getQualifiedName()
Квалифицированное имя элемента

Element.getNamespaceURI()
URI пространства имен

Имена этих методов прямолинейны. Чтобы проиллюстрировать, как JDOM работает с пространствами имен, я покажу вам JdomNS, приложение, которое разбирает XML-файл и выводит информацию пространства имен на консоль.

Когда я показывал вам, как обрабатывать пространства имен при помощи DOM и SAX, я отмечал, что вы должны создать учитывающий пространство имен парсер. В JDOM пространства имен включены по умолчанию в обеспечивающем SAX-парсере, инкапсулированном в объекте org.jdom.input.SAXBuilder. Если вам нужно управлять свойствами SAXBuilder, вы можете использовать методы setFeature() или setProperty(). Знайте, что JDOM нуждается в том, чтобы SAX-парсер был конфигурирован определенным образом, так что документация JDOM рекомендует вам использовать эти методы осторожно.

Обработка информации пространства имен при помощи JDOM

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

SAXBuilder sb = new SAXBuilder();
Document doc = sb.build(new File(argv[0]));
if (doc != null)
  printNamespaceInfo(doc.getRootElement() );

Заметьте, что аргументом printNamespaceInfo() является Element JDOM. В DOM, вы имели единственный тип данных (Node), и каждый тип узла был его подклассом. В JDOM вы будете работать только с Element.

Поговорим о printNamespaceInfo(), я покажу вам далее этот метод. Он имеет четыре задачи:

  1. Если данный элемент квалифицирован пространством имен, выводит информацию пространства имен на консоль.
  2. Если данный элемент имеет какое-либо объявление пространства имен, выводит его на консоль.
  3. Если данный элемент имеет какие-либо квалифицированные пространством имен атрибуты, выводит информацию пространства имен для этих атрибутов на консоль.
  4. Если данный элемент имеет каких-либо потомков, вызывает printNamespaceInfo() для каждого потомка.

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

if (el.getNamespaceURI().length() > 0 )
{
  System.out.println("\nElement " + el.getQualifiedName());
  System.out.println("\tLocal name = " + el.getName());
  System.out.println("\tNamespace prefix = " +
    el.getNamespacePrefix());
  System.out.println("\tNamespace URI = " +
    el.getNamespaceURI());
}

Далее посмотрите, определены ли в этом элементе дополнительные пространства имен. Заметьте, что JDOM обрабатывает объявления пространств имен не так, как DOM. В DOM-приложении объявление пространства имен (xmlns:tp="http://..." , например) учитывается как другой атрибут. В JDOM объявление пространства имен не рассматривается как атрибут, так что вы должны использовать метод getAdditionalNamespaces().

Последнее замечание: если текущий элемент содержит собственное объявление пространства имен (такое, как элемент <auth:author xmlns:auth="..."> в документе-примере), это объявление пространства имен доступно только через текущий элемент, не через getAdditionalNamespaces(). Вот следующий сегмент кода:

Iterator nsIter = el.getAdditionalNamespaces().listIterator();
while (nsIter.hasNext())
{
  Namespace ns = (Namespace) nsIter.next();
  System.out.println("\nNamespace declaration:");
  System.out.println("\tNamespace prefix = " + ns.getPrefix());
  System.out.println("\tNamespace URI = " + ns.getURI());
}

Во всех методах JDOM, которые возвращают наборы чего-то (getAttributes(), getAdditionalNamespaces(), getChildren() и т.д.), этот набор чего-то возвращается вам, как List, часть API Java Collections. Вы должны использовать ListIterator для движения через List. Последнее замечание о коллекциях узлов: поскольку ListIterator возвращает Object, вы должны преобразовывать тип различных элементов списка в Namespace, Attribute и т.д.

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

Iterator attrIter = el.getAttributes().listIterator();
while (attrIter.hasNext())
{
  Attribute attr = (Attribute)attrIter.next();
  if (attr.getNamespaceURI().length() > 0 )
  {
    System.out.println("\nAttribute " +
      attr.getQualifiedName() + "=" +
      attr.getValue());
. . .

Последняя задача - получить потомков этого Element и вызвать printNamespaceInfo для каждого из них. Вот как выглядит этот код:

Iterator childIter = el.getChildren().listIterator();
while (childIter.hasNext())
  printNamespaceInfo((Element)childIter.next());

Полный исходный код см. в JdomNS.java.

 

Раздел 5. Проверка правильности XML-документов

Обзор проверки правильности

Когда XML впервые был введен, схемой его проверки правильности было Определение Типа Документа или DTD. DTD пришли из мира SGML и обычно рассматривали только структуру документа. Они имели ограниченную нотацию для типов данных, но ничего, подобного тому, что вы ожидаете от современного языка программирования.

Например, я могу использовать DTD, чтобы определить, что элемент <postcode> обязательно требуется для элемента <address>, и проверяющий парсер будет отрабатывать это. Любой XML-документ, который содержит <address> без <postcode>, будет помечен нак неправильный. К сожалению, в парсере, использующем DTD для проверки правильности то же случится и с <postcode>B9C 4F8</postcode>, а также и с <postcode>Mad dogs and Englishmen</postcode>. Ясно, что мир XML нуждался в более надежном языке для проверки правильности.

Чтобы восполнить недостачу, Консорциум World Wide Web Consortium (W3C) создал язык схемы XML, как попытку удовлетворить нужды сообщества XML. Схема XML (я буду называть ее так) делится на две части: типы данных и структура документа. Спецификация типов данных определяет несколько базовых типов, а также правила создания новых типов, тогда как спецификация структуры документа определяет набор тегов XML для задания того, какие элементы может содержать документ.

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

Одно последнее замечание по поводу DTD: они имеют отличный (и совершенно несовместимый) синтаксис от XML-документов. Это другое наследство от мира SGML. Хотя некоторые люди считают, что разные синтаксисы - это хорошо, большинство пользователей предпочитают язык проверки правильности, определенный как XML. Схема XML, RELAX NG и Schematron - все базируются на XML. Помимо прочего, это означает, что вы можете писать таблицы стилей XSLT, которые конвертируют схему в читабельный для человека документ, который объясняет правила схемы.

Определение документа при помощи DTD

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

<!ELEMENT sonnet (author,title?,lines)>
<!ATTLIST sonnet type (Shakespearean | Petrarchan)
    "Shakespearean">
<!ELEMENT author (lastName,firstName,nationality,
    yearOfBirth?,yearOfDeath?)>
. . .
<!ELEMENT lines (line,line,line,line,
    line,line,line,line,
    line,line,line,line,
    line,line)>
<!ELEMENT line (#PCDATA)>

DTD определяет все ваши элементы и атрибуты. Синтаксис выше определяет type атрибутов элемента <sonnet>. Он также определяет допустимые значения (Shakespearean и Petrarchan), а также значение по умолчанию (Shakespearean). Другие синтаксические нотации:

Определение документа при помощи схемы XML

Далее посмотрим на схему XML, которая определяет тип документа сонета. Значащие части схемы такие:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

 <xsd:element name="sonnet">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element ref="author"/>
    <xsd:element ref="title" minOccurs="0"/>
    <xsd:element ref="lines"/>
   </xsd:sequence>
  </xsd:complexType>
  <xsd:attribute name="type" type="sonnetType"
   default="Shakespearean"/>
 </xsd:element>

 <xsd:simpleType name="sonnetType">
  <xsd:restriction base="xsd:string">
   <xsd:enumeration value="Petrarchan"/>
   <xsd:enumeration value="Shakespearean"/>
  </xsd:restriction>
 </xsd:simpleType>
. . .
 <xsd:element name="title" type="xsd:string"/>

 <xsd:element name="lines">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element ref="line" minOccurs="14" maxOccurs="14"/>
   </xsd:sequence>
  </xsd:complexType>
</xsd:element>

<xsd:element name="line" type="xsd:string"/>

Здесь определены несколько составляющих. Во-первых, это элемент <sonnet>: его содержимым является элемент <author>, элемент <title> и элемент <lines>. Некоторые замечания по синтаксису:

Определение документа при помощи RELAX NG

Когда заходит речь об определении структуры документа, схема XML не является единственным игроком. Другим популярным методом является RELAX NG, проект, возглавляемым признанным специалистом по XML James Clark. RELAX NG в настоящее время разрабатывается техническим сообществом OASIS (см. Ресурсы). Цитата с Web-сайта сообщества: "Целью этого TC является создание спецификации языка схемы, который является простым, легким в изучении и использует синтаксис XML".

В соответствии с целями проекта, синтаксис RELAX NG очень прост. Чтобы определить элемент, вы используете элемент <element>. Чтобы определить, что элемент или атрибут не является обязательным, вы используете элемент <optional>. Вот фрагмент определения RELAX NG для примера сонета:

<grammar>
. . .
 <start>
  <element name="sonnet">
   <ref name="typeAttribute"/>
   <ref name="author"/>
   <ref name="title"/>
   <ref name="lines"/>
  </element>
 </start>
</grammar>

Из этого простого листинга видно, элемент <sonnet> содержит четыре вещи, определенные где-то в файле RELAX NG, как typeAttribute, author, title и lines. typeAttribute - это атрибут с именем type, который содержит одно из двух значений, Shakespearean или Petrarchan. Вот определение:

<define name="typeAttribute">
 <attribute name="type">
  <choice>
   <value>Shakespearean</value>
   <value>Petrarchan</value>
  </choice>
 </attribute>
</define>

Элемент RELAX NG <define> работает как функция замещения; везде, где вы ссылаетесь на определение (как в <ref name="typeAttribute">), ссылка заменяется содержимым элемента <define>.

Примечание: Сообщество RELAX NG определило способ задания атрибута по умолчанию, дополнительную информацию см. в Ресурсы. Один аспект в RELAX NG, который не так хорош, как в схеме XML, это то, что здесь нет механизма для определения кардинального числа, количества появлений элемента в определенной части вашего XML-документа. В схеме XML вы используете minOccurs="14" и maxOccurs="14", чтобы определить, что элемент <lines> содержит 14 элементов <line>. В RELAX NG вы должны их перечислить:

<define name="lines">
 <element name="lines">
  <ref name="line"/>
  <ref name="line"/>
  <ref name="line"/>
. . .
  <ref name="line"/>
 </element>
</define>

Полный исходный код см. в sonnet.rng.

Определение документа при помощи Schematron

Документы Schematron используют выражения Xpath для определения содержимого правильного XML-документа. В приведенном здесь примере элемент Schematron <assert> используется для определения правил для сонета. Каждый оператор <assert> имеет атрибут test; если test не true, то текст элемента <assert> выводится как сообщение об ошибке.

Этот пример показывает правило Schematron для элемента <lines>:

<rule context="lines">
 <assert test="count(line) = 14">
  A sonnet must have 14 <line>s.
 </assert>
 <assert test="count(line) = count(*)">
  The <lines> element can only contain
  <line> elements.
 </assert>
</rule>

Первым ограничением здесь является то, что элемент <lines> должен содержать 14 элементов <line>. Второе ограничение значительно более сложное, оно говорит, что является то, что элемент <lines> не может содержать никаких других элементов. Способом задания этого является указание, что общее число элементов <line> должно равняться общему числу всех элементов.

Однако, Schematron не так удобен при определении последовательностей элементов. Это - отражение синтаксиса XPath; выражения XPath обычно определяют местоположение элемента или группы элементов. Использование XPath для определения последовательности не является приятным. Например, элемент <sonnet> должен содержать элемент <author> элемент, за которым следует не обязательный элемент <title>, за которым следует элемент <lines>. Вот как вы выражаете это в Schematron:

<assert test="count(*) &lt; 4">
 The &lt;sonnet&gt; element contains an &lt;author&gt;
 element, an optional &lt;title&gt; element, and a
 &lt;lines&lt; element.
</assert>
<assert test="*[1] = author">
 The first child of the &lt;sonnet&gt; element must be
 an &lt;author&gt; element.
</assert>
<assert test="(count(*) = 2 and *[2] = lines) or
                 (count(*) = 3 and
                 *[2] = title and *[3] = lines)">
 If you use the optional &lt;title&gt; element, the
 &lt;sonnet&gt; element must contain the &lt;author&gt;,
 &lt;title&gt;, and &lt;lines&gt; elements in that order.
</assert>

Первое правило то, что элемент <sonnet> не может содержать более трех элементов. Второе правило устанавливает, что первым потомком элемента <sonnet> должен быть элемент <author>. Наконец, третье правило говорит, что после элемента <author> (требуемого вторым правилом) <sonnet> может содержать либо элемент <lines>, либо элемент <title>, за которым следует элемент <lines>.

Здесь вы фактически перечисляете все комбинации элементов, появление которых допустимо в элементе <sonnet>. В данном случае это не слишком плохо, но очевидно, что это может выходить из-под контроля для более сложных документов. Попытки комбинировать подходы RELAX NG и Schematron находятся в процессе разработки, подробности см. в превосходной статье Eddie Robertsson's в XML.com в Ресурсы.

Полный исходный код см. в sonnetSchematron.xml.

Проверка правильности документа

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

Проверка правильности при помощи SAX

Для проверки правильности XML-документа при помощи using SAX вам нужно определить несколько свойств и для SAXParserFactory, и для SAXParser, который им создается. Это отличается от предыдущих примеров; до сих пор вы всегда устанавливали свойства объекта-фабрики а не создаваемого им объекта-парсера. Вот код, который вам нужен:

SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
spf.setValidating(true);
SAXParser sp = spf.newSAXParser();
sp.setProperty
  ("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
  "http://www.w3.org/2001/XMLSchema");
sp.parse(uri, this);

Первые два свойства включают пространства имен и проверку правильности для всех SAXParser, создаваемых SAXParserFactory. Свойство schemaLanguage property определяет, какой язык схемы вы будете использовать для проверки правильности документа. Используемое здесь значение (http://www.w3.org/2001/XMLSchema) определено JAXP. Возможно, что данный парсер может определять другое значение, чтобы показать поддержку RELAX NG, Schematron или какого-то другого языка, хотя никакие популярные парсеры этого не делают по состоянию на июль 2004 г.

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

Теперь, когда вы установили три свойства ваших объектов фабрики и парсера, вы готовы проверять правильность документа. Отредактируйте sonnetSchema.xml и добавьте элемент <nickname> внутрь элемента <author>:

. . .
<author>
 <lastName>Shakespeare</lastName>
 <firstName>William</firstName>
 <nickname>Shakin' Billy</nickname> 
 <nationality>British</nationality>
. . .

Когда вы проверите этот документ вашим проверяющим парсером, результат будет:

C:\adv-xml-prog>java SaxValidator sonnetSchema.xml
[Error] sonnetschema.xml:9:15: cvc-complex-type.2.4.a: Invalid
content was found starting with element 'nickname'. One of '{"
":nationality}' is expected.

Your документ is not valid.

Полный исходный код см. в SaxValidator.java.

Проверка правильности при помощи DOM-парсера

Процесс проверка правильности XML-документа при помощи DOM-парсера похож на проверку при помощи SAX-парсера. Я покажу вам, как создать DocumentBuilderFactory, установить некоторые его свойства, а затем создать парсер (DocumentBuilder). Прежде, чем разбирать и проверять правильность XML-документа, вам нужно определить обработчик ошибок для DOM-парсера. Удивительно, но обработчик ошибок является обработчиком ошибок SAX; это отражает тот факт, что DOM-парсеры обычно строятся над SAX-парсерами.

Вот секция кода, которая устанавливает фабрику парсеров и создает парсер:

DocumentBuilderFactory dbf =
  DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setValidating(true);

Теперь, когда вы установили вашу фабрику парсеров, вы должны сделать еще одну вещь прежде, чем вы создадите ваш парсер: определить язык схемы, который будет использовать парсер. Если If XML-документ использует DTD, вы не устанавливаете никакие специальные свойства для фабрики парсеров. С другой стороны, если документ использует схему XML, вам нужно определить свойство schemaLanguage:

if (useSchema)
  dbf.setAttribute
    ("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
    "http://www.w3.org/2001/XMLSchema");

Используйте аргумент командной строки, чтобы определить, собираетесь вы использовать схему XML или DTD.

Теперь вы готовы создавать ваш объект-парсер. Когда парсер создан, вам нужно установить обработчик ошибок:

DocumentBuilder db = dbf.newDocumentBuilder();
db.setErrorHandler(this);
doc = db.parse(uri);

Простоты ради вы реализуете интерфейс обработчика ошибок в коде DomValidator. (Технически вы реализуете интерфейс SAX DefaultHandler, который включает в себя ErrorHandler.) Для вас это означает, что вы должны реализовать методы warning(), error() и fatalError().

Полный исходный код см. в DomValidator.java.

Проверка правильности при помощи Jing

Проверка правильности при помощи Jing относительно очевидна. Поскольку сам Jing является инструментом проверки правильности, вы можете проверять правильность XML-документа по схеме RELAX NG без написания какого-либо кода. Синтаксис командной строки таков:

c:>java -jar c:/jing-20030619/bin/jing.jar sonnet.rng sonnet.xml

Первым параметром для исполняемого jar-файла является схема RELAX NG, а вторым - файл XML-документа, который вы хотите проверять. Если ваш документ правильный, вы не получите никаких сообщений, иначе вы получите сообщение, которое скажет вам, где произошла ошибка. Например, если вы удалите элемент if you delete the <firstName>, вы получите такое сообщение:

c:>java -jar c:/jing-20030619/bin/jing.jar sonnet.rng sonnet.xml
 sonnet.xml:6:18: error: required elements missing
c:>

Хотя это и не самое подробное сообщение на свете, оно дает вам строку и столбец, в которых документ неправильный.

Если вы хотите встроить Jing в ваш собственный код, наилучшим подходом будет использовать код из класса com.thaiopensource.relaxng.util.Driver (следуя, конечно, лицензионному соглашению, распространяемому с Jing).

Проверка правильности при помощи Schematron

Schematron применяет уникальный подход к определению содержимого документов: он использует таблицы стилей XSLT. Чтобы проверить правильность XML-документа при помощи Schematron, используйте такие три шага:

  1. Создайте XML-документ (sonnetSchematron.xml), который соответствует правилам Schematron для документ.
  2. Используйте таблицу стилей Schematron для преобразования ваших правил документа в новую таблицу стилей (sonnetRules.xsl), которая настроена для проверки правильности вашего типа документа.
  3. Преобразуйте ваш XML-документ при помощи вашей прикладной таблицы стилей.

    Если документ правильный, преобразование не генерирует сообщений об ошибках; иначе таблица стилей выработает отчет об ошибках.

Эти три шага показаны на иллюстрации этого процесса:

Вы уже выполнили первый шаг, так что вы готовы преобразовать ваши правила документа Schematron в прикладную таблицу стилей. Чтобы сделать это вам нужно иметь файлы schematron-basic.xsl и skeleton1-5.xsl, доступные на домашней странице проекта Schematron (см. Ресурсы).

Предположим, вы используете движок XSLT Xalan, применяйте такую команду для генерации вашей прикладной таблицы стилей:

java org.apache.xalan.xslt.Process -in sonnetSchematron.xml
 -xsl schematron-basic.xsl -out sonnetRules.xsl

Это выработает файл sonnetRules.xsl, таблицу стилей, которая проверяет правильность документа-сонета. Чтобы использовать sonnetRules.xsl, запустите опять движок таблиц стилей:

java org.apache.xalan.xslt.Process -in sonnet.xml
 xsl sonnetRules.xsl

Если документ sonnet.xml правильный, вы не увидите сообщений об ошибках. Если что-то неправильно, вы увидите вывод одного из элементов <assert>, которые вы закодировали раньше. Например, если вы уберете из сонета один из 14 элементов <line>, вы увидите такое сообщение об ошибке:

In pattern count(line) = 14:
  A sonnet must have 14 <line>s.

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

 

Раздел 6. Резюме

Резюме

Я обсудил в этом учебнике различные приемы и API. Все они в основе фокусируются на проверке правильности; вам нужно установить свойства парсера на использование проверки правильности, и парсеры должны учитывать пространства имен для проверки правильности XML-документов. Как и в моем предыдущем учебнике, я рассмотрел несколько разных стандартов, API и подходов, так что вы можете выбирать инструменты, которые лучше работают для вас. В последнем учебнике этой серии я рассмотрю создание структур DOM и SAX с самого начала, преобразование структур данных из одного API в другой и некоторые продвинутые свойства API DOM и SAX.

 

Ресурсы

Обучение

Получение продуктов и технологий

 

Об авторе

Doug Tidwell

Одна из 13 изначальных колоний, Doug "Штат садов" Tidwell является домом для более 7 миллионов людей. Неугомонный и динамичный, он имеет необычное разнообразие от буколических холмов Трентона до урбанистических районов, относящихся к Нью-Йорку и Филадельфии. Гордый своим естественным наследием, Doug многие годы гордится таким разнообразием живой природы, как knobbed whelk (раковина штата) и восточный щегол (птица штата).

Doug также издавна известен поддержкой современного транспорта. Делавер и Хадсон были двумя исходными национальными супермагистралями, а сегодня его Turnpike является самой совершенной магистралью в стране. Его приверженность современным технологиям общеизвестна, вместе с тем он был первым штатом, который дал возможность своим жителям платить за на парковку и покупать рыболовные лицензии он-лайн. Вы можете связаться с ним по адресу dtidwell@us.ibm.com или посетить его Web-сайт, www.state.nj.us.


КаталогИндекс раздела