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


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

Проверка правильности XML

Nicholas Chase

27 августа 2003

Содержание

  1. Введение
  2. Основы проверки правильности
  3. Проверка правильности документа
  4. Определения Типа Документа (DTD)
  5. Схема XML
  6. Сводка проверки правильности

 

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

Нужен ли мне этот учебник?

Этот учебник исследует проверку правильности XML-документов при помощи либо Описаний Типа Документов (Document Type Definition - DTD), либо схем XML. Он предназначен для разработчиков, которым нужно управлять типами и содержимым данных в их XML-документах, и предполагает, что вы знакомы с базовыми концепциями XML. (Вы можете получить базовое представление о самом XML в учебнике Введение в XML.) Он также предполагает базовое знакомство с пространствами имен XML. (Вы можете найти базовые сведения о пространствах имен в учебнике Понимание DOM.)

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

Что такое проверка правильности XML?

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

DTD были исходно определены в XML 1.0 Recommendation и происходят из Standard Generalized Markup Language (SGML), предшественника HTML. Их синтаксис немного отличается от XML, что является одной из помех для их использования. Они также имеют ограничения в применении, что заставило разработчиков искать им альтернативу в схемах XML. Однако DTD все еще используются в значительном количестве сред, так что понимание их является важным.

Главной альтернативой DTD является рекомендация XML Schema, поддерживаемая консорциумом World Wide Web (W3C). (Во всем этом учебнике, "схема XML" является синонимом "схема XML W3C".) Схемы, которые также являются XML-документами, обеспечивают более знакомую и более мощную среду, в которой ограничения на данные могут существовать как XML-документ.

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

Инструменты

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

Если у вас инсталлирован другой набор инструментов, вы можете использовать его. Только проверьте в документации инструкции по настройке проверки правильности. Вы можете выгрузить реализации Xerces для C++ и Perl в проекте Apache на http://xml.apache.org.

Об авторе

Nicholas Chase, автор Studio B, участвовал в разработке Web-сайтов для таких компаний, как Lucent Technologies, Sun Microsystems, Oracle и Tampa Bay Buccaneers. Nick был преподавателем физики в высшей школе, менеджером низшего звена по использованию радиоактивных отходов, редактором онлайнового журнала научной фантастики, инженером по мультимедиа и инструктором по Oracle. В последнее время он - руководитель технического отдела фирмы Site Dynamics Interactive Communications в Clearwater, Florida, USA и автор четырех книг по Web-разработке, включая XML Primer Plus (Sams). Он любит слышать отзывы читателей, с ним можно связаться по адресу nicholas@nicholaschase.com.

 

Раздел 2. Основы проверки правильности

Что такое проверка правильности?

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

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

Документ, который был проверен таким способом по DTD или схеме, рассматривается, как правильный документ.

Правильные и правильно форматированные

Поскольку слово правильный (valid) имеет другие значения в английском языке, оно иногда конфликтует со специфическим термином XML правильно форматированный (well formed).

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

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

Правильный документ всегда правильно форматирован, правильно форматированный документ может быть неправильным.

Определения Типа Документа (DTD)

Когда XML изначально создавался, он был одним из приложений Стандартного Обобщенного Языка Разметки (Standard Generalized Markup Language - SGML). SGML дает возможность различным системам сообщаться друг с другом, позволяя авторам создавать DTD. Пока данные следуют DTD, каждая система может прочитать и интерпретировать их.

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

Сравним этот простой документ с его DTD:

<?xml version="1.0"?>
<!DOCTYPE memories SYSTEM "memory.dtd">
<memories>
 <memory tapeid="23412">
  <subdate>5/23/2001</subdate>
  <donor>John Baker</donor>
  <subject>Fishing off Pier 60</subject>
 </memory>
 <memory tapeid="23692">
  <subdate>8/01/2001</subdate>
  <donor>Elizabeth Davison</donor>
  <subject>Beach volleyball</subject>
 </memory>
</memories>

DTD

<!ELEMENT memories (memory*) >
<!ELEMENT memory (subdate, donor, subject) >
<!ATTLIST memory tapeid CDATA #REQUIRED >
<!ELEMENT subdate (#PCDATA) >
<!ELEMENT donor (#PCDATA) >
<!ELEMENT subject (#PCDATA) >

DTD использует синтаксис, отличный от XML, но оно описывает различные элементы и атрибуты и, как они могут применяться. Более подробная информация об использовании и создании DTD будет дана в этом учебнике позже, пока же отметим, что DTD связывается с XML-документами через оператор DOCTYPE.

Схемы XML

DTD выполняют свою задачу, но существуют и серьезные ограничения.

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

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

XML-документ:

<?xml version="1.0"?>
<memories xmlns:xsi=
    "http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation=="'memory.xsd">
 <memory tapeid="23412">
  <subdate>5/23/2001</subdate>
  <donor>John Baker</donor>
  <subject>Fishing off Pier 60</subject>
 </memory>
 <memory tapeid="23692">
  <subdate>8/01/2001</subdate>
  <donor>Elizabeth Davison</donor>
  <subject>Beach volleyball</subject>
 </memory>
</memories>

Схема XML:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <xsd:element name="memories">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="memory" type="memoryType"/>
   </xsd:sequence>
  </xsd:complexType>
 </xsd:element>
 <xsd:complexType name="memoryType">
  <xsd:sequence>
   <xsd:element name="subdate" type="xsd:date"/>
   <xsd:element name="donor" type="xsd:string"/>
   <xsd:element name="subject" type="xsd:string"/>
   <xsd:attribute name="tapeid" type="idNumber" />
  </xsd:sequence>
 </xsd:complexType>
</xsd:schema>

Заметьте, что в этом случае синтаксис определения схемы отличается от синтаксиса DTD. Синтаксис определений схем предполагает, что их можно интерпретировать как XML-документы с определенной схемой, используя пространства имен XML вместо DOCTYPE.

Целевой документ

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

Каждый экспонат содержит данные и информацию о них, такую, как имя дарителя, место и тема.

<?xml version="1.0"?>
<memories>
 <memory tapeid="T1">
  <media mediaid="T1" status="vhs" />
  <subdate>2001-05-23</subdate>
  <donor>John Baker</donor>
  <subject>Fishing with the grandchildren on beautiful day.</subject>
  <location><description>Pier 60</description></location>
 </memory>
 <memory tapeid="T2">
  <media mediaid="T2" status="vhs"/>
  <subdate>2001-05-18</subdate>
  <donor>Elizabeth Davison</donor>
  <subject>Beach volleyball</subject>
  <location><place>Asbury Park, NJ</place></location>
 </memory>
</memories>

 

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

Как работает процесс проверки правильности

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

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

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

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

Создание обработчика ошибок

Класс обработчика ошибок обычно расширяет вспомогательный класс DefaultHandler, который реализует интерфейс ErrorHandler.

Обработчик ошибок служит одной и только одной цели: иметь дело с нарушениями, которые возникают в ходе разбора документа.

Три типа ситуаций, которые могут возникать:

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

Откомпилируйте этот класс, и он готов для того, чтобы к нему обращался парсер.

import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXParseException;
public class ErrorChecker extends DefaultHandler
{
  public ErrorChecker() {
  }
  public void error (SAXParseException e) {
    System.out.println("Parsing error: "+e.getMessage());
  }
  public void warning (SAXParseException e) {
    System.out.println("Parsing problem: "+e.getMessage());
  }
  public void fatalError (SAXParseException e) {
    System.out.println("Parsing error: "+e.getMessage());
    System.out.println("Cannot continue.");
    System.exit(1);
  }
}

Проверка правильности в JAXP

Как обсуждалось во введении этого учебника, нет необходимости кодировать и выполнять следующие примеры для понимания проверки правильности. Если же вы решите сделать это, использование код Java для разбора (и, разумеется, проверки правильности) документа включает в себя четыре шага. (В следующем подразделе обсуждается Проверка правильности в Xerces Java.):

  1. Создайте DocumentBuilderFactory. Поскольку DocumentBuilder, который реально разбирает документ, является интерфейсом, экземпляр его не может быть создан непосредственно. Вместо этого создается DocumentBuilderFactory. Эта фабрика имеет определенные свойства, такие, как свойство isValidating(), которые определяют свойства любого созданного при его помощи парсера. Чтобы создать проверяющий парсер, примените setValidating(true) .
  2. Создайте DocumentBuilder. Используйте DocumentBuilderFactory для создания объекта DocumentBuilder, который разбирает документ.
  3. Назначьте ErrorHandler. У парсера нет возможности проверять ваши проблемы, если он не знает, что с ними делать. Используйте метод setErrorHandler() класса DocumentBuilder, чтобы заставить парсер посылать ошибки новому объекту ErrorChecker, который был создан в подразделе Создание обработчика ошибок.
  4. Разберите документ. Если документ не является правильно форматированным, главный класс, StructureTest, будет генерировать исключение. Если документ является правильно форматированным, но имеет ошибки проверки правильности, парсер посылает их в объект ErrorChecker, который рапортует о них.

Таковы базовые принципы: перед проверкой правильности документа создать проверяющий парсер, определить приемник ошибок проверки правильности и разобрать документ. (Если вы используете старую версию языка Java с парсером Xerces-Java, некоторые детали могут отличаться от описанных в подразделе Проверка правильности в Xerces Java.)

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import org.w3c.dom.Document;
public class StructureTest {
  public static void main (String args[]) {
    File docFile = new File("memory.xml");
    try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      dbf.setValidating(true);
      DocumentBuilder db = dbf.newDocumentBuilder();
      ErrorChecker errors = new ErrorChecker();
      db.setErrorHandler(errors);
      Document doc = db.parse(docFile); 
    } catch (Exception e) {
      System.out.print("Parsing problem.");
    }
  }
}

Проверка правильности в Xerces Java

Использование Xerces для проверки правильности документа включает в себя те же базовые принципы, что и Проверка правильности в JAXP в предыдущем подразделе.

import org.apache.xerces.parsers.DOMParser;
import java.io.File;
import org.w3c.dom.Document;
public class SchemaTest {
  public static void main (String args[]) {
    File docFile = new File("memory.xml");
    try {
      DOMParser parser = new DOMParser();
      parser.setFeature("http://xml.org/sax/features/validation", true);
      parser.setProperty(
            "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation",
            "memory.xsd");
      ErrorChecker errors = new ErrorChecker();
      parser.setErrorHandler(errors);
      parser.parse("memory.xml");
    } catch (Exception e) {
      System.out.print("Problem parsing the file.");
    }
  }
}

Приложение непосредственно создает экземпляр DOMParser. Каждый парсер, созданный таким способом, имеет ряд свойств, одним из которых является возможность проверки правильности. Метод парсера setFeature() включает его.

Вы можете задать местоположение документа схемы внутри XML (как в Документ экземпляра схемы XML), или можете задать его в самом приложении, используя свойство парсера, как было показано выше.

Далее назначается обработчик ошибок. Заметьте, что это тот же класс, который вы использовали для обработки ошибок в JAXP. Ошибки те же, так что у вас нет необходимости кодировать отдельный класс.

Наконец, разберите документ. ErrorChecker докладывает о любых ошибках.

Таким способом все документы проверяются по DTD или схеме.

 

Раздел 4. Определения Типа Документа (DTD)

Внешние DTD

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

Когда идет речь о DTD, большинство людей лучше знакомо с внешним вариантом, в котором объявление DOCTYPE ссылается на файл, содержащий действующие определения.

Может быть использовано несколько способов для определения местонахождения DTD. Например, файл XHTML может определить DTD, которое определяет, следует ли он рекомендациям XHTML Strict, XHTML Transitional или XHTML Frameset, разработанным W3C. Чтобы определить XHTML Transitional, автор может задать:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

Объявление DOCTYPE состоит из нескольких частей:

Для прикладных DTD разработчики обычно используют идентификатор SYSTEM, такой, как:

<!DOCTYPE memories SYSTEM "http://www.nicholaschase.com/memories.dtd">

Части оператора соответствуют таковым для идентификатора PUBLIC, кроме объявления, показывающего местонахождение DTD.

Обычно, однако, объявление DOCTYPE также задает идентификатор SYSTEM при использовании идентификатора PUBLIC на случай, если процессор не распознает последний:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

Внешний файл DTD просто содержит определение, начинающиеся с описанного в подразделе Элементы. Для внутреннего DTD эти определения являются частью самого XML-файла.

Структура внутреннего DTD

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

<?xml version="1.0"?>
<!DOCTYPE memories [
<!ELEMENT memories (memory+) >
<!ELEMENT memory (#PCDATA) >
>
<memories>
<memory>TBD</memory>
<memory>TBD</memory>
</memories>

Игнорируя на минутку реальное содержимое, обратите внимание на структуру внутреннего DTD. Объявление DOCTYPE продолжает содержать информацию, но вместо ссылки на локальный или удаленный файл, действующее DTD включается между скобками.

Элементы

Независимо от того, имеем мы дело с внешними или внутренними DTD, элементы являются фундаментом XML-документов, так что они обычно определяются первыми.

Все определения элементов состоят из ключевого слова ELEMENT, имени элемента и содержимого, которое может в нем быть. Элемент может содержать текст, другие элементы или ничего вообще (в случае пустого элемента).

<!DOCTYPE memories [
<ELEMENT memories (memory) >
<ELEMENT memory (subdate, donor, subject, media) >
<ELEMENT subdate (#PCDATA) >
<ELEMENT donor (#PCDATA) >
<ELEMENT subject (#PCDATA) >
<ELEMENT media EMPTY >
]>

Элемент, который содержит текст, определяется с ключевым словом #PCDATA. Это сокращение для разбираемых символьных данных (parsed character data); оно относится к любому тексту внутри элемента и не может содержать разметку. Примерами являются элементы subdate, donor и subject.

Элементы memory и memories показывают синтаксис, используемый для задания элементов, которые содержат в себе только другие элементы.

Элемент может также быть определен как EMPTY, как элемент media. Пустые элементы обычно несут всю информацию в атрибутах. Например:

<media type="vhs" />

Переменное содержимое элемента

Иногда автор хочет допустить возможность выбора для содержимого элемента. Например, структура данных содержит элемент с именем location, который может содержать либо элемент place, либо элемент description. Это записывается следующим образом, с использованием символа вертикальной черты (|), разделяющего варианты:

<!ELEMENT location (place | description) > 
<!ELEMENT place (#PCDATA) >
<!ELEMENT description (#PCDATA) >

Модификаторы (*,+ и ?)

DTD, которое создавалось до сих пор, является весьма специфичным. Каждый элемент должен появляться один раз и точно в таком порядке. Единственным исключением из этого является location, где должен появляться либо place, либо description, но не оба вместе.

Модификаторы предлагают большую гибкость в проекте. Они таковы:

Код, приведенный ниже, показывает DTD, модифицированный таким образом, что количество элементов memory является неограниченным. Он также показывает, что требуется хотя бы один элемент subject, но допускается и более одного. Наконец, элемент donor не является обязательным, но если оно есть, оно может появляться только один раз. Немодифицированные элементы должны появляться один и только один раз.

<!DOCTYPE memories [
<!ELEMENT memories (memory)*  >
<!ELEMENT memory (media,
subdate,
donor?, 
subject+,
location) >
<!ELEMENT subdate (#PCDATA) >
<!ELEMENT donor (#PCDATA) >
<!ELEMENT subject (#PCDATA) >
<!ELEMENT location (place |
description) >
<!ELEMENT description (#PCDATA) >
<!ELEMENT place (#PCDATA) >
<!ELEMENT media EMPTY >
]>

Заметьте, что это ограничения на элемент. Элемент donor может появляться только один раз в элементе memory, но может появляться в каждом экземпляре memory, если нужно.

Порядок следования дочерних элементов

Порядок следования дочерних элементов может также быть определен по DTD. Парадоксально, но, хотя дочерние элементы должны всегда появляться в том порядке, в каком они появляются в DTD, DTD может быть написано так, чтобы дочерние элементы появлялись в любом порядке.

Коротко говоря, требуемый порядок не изменяется, но может выбираться. Например, это DTD задает, что элемент location может иметь либо place, либо description:

<!ELEMENT location (place|description) >

Если этот вариант может быть повторено, как в:

<!ELEMENT location (place|description)* >

то location может содержать place и description в любом порядке. То же самое может быть применено к элементу memory:

<!ELEMENT memory (media | subdate | donor?| subject+| location)* >

В этом случае элементы могут появляться в любом порядке, поскольку DTD разрешает неограниченное число вариантов. Сначала может быть выбран subdate, затем location, затем donor и т.д. Заметьте, однако, что если применен этот прием, то некоторые предыдущие ограничения становятся бесполезными. Поскольку варианты могут быть применены более одного раза, любой из заданных элементов может быть выбран любое число раз или вообще не выбран.

Это серьезное ограничение DTD, и оно преодолено при помощи схем XML, которые обеспечивают гораздо большие возможности управления. Схемы также полезны при определении смешанного содержимого.

Смешанное содержимое в DTD

Одной из вариаций, которая не обсуждалась, являются элементы, которые имеют смешанное содержимое. Смешанное содержимое содержит и текст, и другие элементы. Хорошим примером является текст, содержащий разметку HTML. Рассмотрим следующий потенциальный subject:

<subject>
A reading of Charles Dickens' <i>A Christmas Carol</i>.
Absolutely marvelous!
</subject>

Это то, что называют смешанным содержимым, потому что здесь есть и символьные данные, и элемент (<i></i>). Чтобы сделать его доступным для проверяющего парсера, должен быть определен элемент i, и элементу subject должно быть разрешено иметь любое число вариантов либо #PCDATA, либо i. Чтобы допустить общепринятую разметку, в DTD нужно иметь:

<!ELEMENT i (#PCDATA) >
<!ELEMENT b (#PCDATA) >
<!ELEMENT h1 (#PCDATA) >
<!ELEMENT br EMPTY >
<!ELEMENT p (#PCDATA) >
<!ELEMENT subject (#PCDATA|i|b|h1|br|p)* >

Заметьте, что хотя это и порождает некоторые проблемы, в DTD нет способа ограничить порядок. Это тоже проблема, решенная в схеме XML.

Определение атрибутов

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

Вы можете определить атрибуты несколькими способами. Первый - просто определить их как символьные данные или CDATA:

<!ATTLIST memory tapeid CDATA #REQUIRED >

В этом случае DTD назначает элементу memory атрибут tapeid. Атрибут tapeid состоит из символьных данных и является обязательным. Элемент может также быть определен как #IMPLIED или #FIXED, в этом случае должно быть также задано значение по умолчанию.

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

<!ATTLIST media type (8mm | vhsc | vhs | audio) '8mm' #IMPLIED >

В этом случае документ должен выбрать значение из списка, если же значение не обеспечено, парсер использует значение по умолчанию 8mm. Это случай для любого документа, для которого существует DTD, даже, если парсер непроверяющий. В одном определении ATTLIST может быть определено много атрибутов:

<!ATTLIST media type (8mm | vhsc | vhs | audio) '8mm' #IMPLIED length CDATA >

Второй способ определения атрибутов включает в себя ID и IDREF.

ID и IDREF

У вас может появиться необходимость связать данные при помощи использования идентификаторов, работа которых соответствует первичным и внешним ключам в реляционной базе данных. Например, может потребоваться, чтобы идентификатор memory соответствовал идентификатору media, носителя, на котором эта запись размещена. Типы данных ID и IDREF позволяют вам обеспечить такую целостность данных:

<!ATTLIST media mediaid ID #REQUIRED >
<!ATTLIST memory tapeid IDREF #REQUIRED >

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

Каждый элемент может иметь максимум один атрибут ID. Важно понимать, что все значения ID должны принадлежать к одному пулу (пространству значений). Если вы задаете более одного типа атрибутов ID, у вас нет способа заставить IDREF ссылаться на определенный атрибут ID.

Сущности

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

Параметрическая сущность может быть использована только в пределах DTD. Она обычно применяется для создания определения, которое используется снова и снова, хотя может применяться для создания DTD с условиями. (Дополнительную информацию о DTD с условиями ищите в Ресурсы.)

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

<!ENTITY % memorytype "media | subdate | donor?| subject+| location" > 
<!ELEMENT memory (%memorytype;)* >

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

<!ENTITY unknownLocation "<description>Unknown</description>" >

но она может использоваться только в самом документе:

...
 <memory tapeid="T1">
  <media mediaid="T1" status="vhs" />
  <subdate>2001-05-23</subdate>
  <donor>John Baker</donor>
  <subject>Fishing off Pier 60</subject>
  <location>&unknownLocation;</location>
 </memory>
...

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

Полное DTD и документ

DTD:

<!ELEMENT memories (memory)* >
<!ELEMENT memory (media | subdate | donor?| subject+| location)* >
<!ATTLIST memory tapeid IDREF #REQUIRED >
<!ELEMENT subdate (#PCDATA) >
<!ELEMENT donor (#PCDATA) >
<!ELEMENT subject (#PCDATA) >
<!ELEMENT location (place|description) >
<!ELEMENT description (#PCDATA) >
<!ELEMENT place (#PCDATA) >
<!ELEMENT media EMPTY >
<!ATTLIST media mediaid ID #REQUIRED
status CDATA #IMPLIED>

Документ:

<?xml version="1.0"?>
<!DOCTYPE memories SYSTEM "memory.dtd">
<memories>
 <memory tapeid="T1">
  <media mediaid="T1" status="vhs" />
  <subdate>2001-05-23</subdate>
  <donor>John Baker</donor>
  <subject>Fishing off Pier 60</subject>
  <location><description>Outside in the woods</description></location>
 </memory>
 <memory tapeid="T2">
  <media mediaid="T2" status="vhs"/>
  <subdate>2001-05-18</subdate>
  <donor>Elizabeth Davison</donor>
  <subject>Beach volleyball</subject>
  <location><place>Clearwater beach</place></location>
 </memory>
</memories>

Ограничения DTD

DTD чрезвычайно ограничены, если идет речь о реальных типах данных, которые могут в них включаться. Например, DTD не может ограничить данные только числовым типом или типом даты. Атрибуты могут быть ограничены уникальностью значений, как ID, но в DTD нет способа определить, какого типа данных они должны быть. (И, странно, ID не может быть числом, что противоречит общепринятой практике реляционных баз данных!)

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

Вы можете преодолеть эти трудности, используя схему XML.

 

Раздел 5. Схема XML

Документ экземпляра схемы XML

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

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

  1. Из самого документа: Документы используют объявление DOCTYPE для указания на внешнее DTD и используют атрибуты и пространства имен для указания на внешний документ схемы:
    <?xml version="1.0"?>
    <memories xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
          xsi:noNamespaceSchemaLocation='memory.xsd'>
     <memory tapeid="idnum">
    ...
    

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

  2. Через установку свойств внутри приложения: В Xerces установите свойства http://apache.org/xml/properties/schema/external-schemaLocation и http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation, чтобы определить местоположение документа схемы, как показано в Проверка правильности в Xerces Java.

Структура документа схемы

Документ схемы XML - это просто XML-документ с предопределенными элементами и атрибутами, описывающими структуру другого XML-документа.

Рассмотрим такой простой документ схемы:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <xsd:element name="memories">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="memory" maxOccurs="unbounded" type="memoryType"/>
   </xsd:sequence>
  </xsd:complexType>
 </xsd:element>
 <xsd:complexType name="memoryType">
  <xsd:sequence>
   <xsd:element name="media">
    <xsd:complexType>
     <xsd:attribute name="mediaid" type="xsd:string" />
     <xsd:attribute name="status" type="xsd:string" />
    </xsd:complexType>
   </xsd:element>
   <xsd:element name="subdate" type="xsd:string"/>
   <xsd:element name="donor" type="xsd:string"/>
   <xsd:element name="subject" type="xsd:string"/>
   <xsd:element name="location" type="locationType" />
  </xsd:sequence>
  <xsd:attribute name="tapeid" type="xsd:string" />
  <xsd:attribute name="status" type="xsd:string" />
 </xsd:complexType>
 <xsd:complexType name="locationType">
  <xsd:choice>
   <xsd:element name="description" type="xsd:string" />
   <xsd:element name="place" type="xsd:string" />
  </xsd:choice>
 </xsd:complexType>
</xsd:schema>

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

Элементы и встроенные типы

XML-документы строятся из элементов. Определение элемента в документе схемы XML состоит в придании ему имени и типа. Например:

  <xsd:element name="subdate" type="xsd:date"/>
  <xsd:element name="donor" type="xsd:string"/>
  <xsd:element name="subject" type="xsd:string"/>
  <xsd:element name="description" type="xsd:string" />
  <xsd:element name="place" type="xsd:string" />

Это простые элементы, которые содержат только текст. Элемент subdate, однако, должен быть ограничен датой в формате yyyy-mm-dd, как определено в рекомендации W3C XML Schema.

42 простых типа определены как часть этой рекомендации, включая string, int, date, decimal, boolean, timeDuration и uriReference.

Вы также можете создавать новые типы.

Простые типы: ограниченные значения

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

Для проекта-примера нужно два новых простых типа. Первый - idNumber:

  <xsd:simpleType name="idNumber">
   <xsd:restriction base="xsd:integer">
    <xsd:minInclusive value="1" />
    <xsd:maxInclusive value="100000" />
   </xsd:restriction> 
  </xsd:simpleType>

Этот тип, используемый для атрибута tapeid, просто ограничивает значение для целого числа между 1 и 100000.

Простые типы: перечисление

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

 <xsd:simpleType name="mediaType">
  <xsd:restriction base="xsd:string">
   <xsd:enumeration value="8mm" />
   <xsd:enumeration value="vhs" />
   <xsd:enumeration value="vhsc" />
   <xsd:enumeration value="digital" />
   <xsd:enumeration value="audio" />
  </xsd:restriction> 
 </xsd:simpleType>

Вы можете использовать производные простые типы так же, как и встроенные типы.

Сложные типы: атрибуты

Одно из ограничений для простого типа состоит в том, что такие элементы не могут содержать атрибутов. Чтобы добавить атрибуты в элемент, вы должны преобразовать его в complexType.

Одним из способов сделать это является использование анонимных сложных типов. Он включает в себя добавление элемента complexType как потомка элемента element.

<xsd:element name="media">
 <xsd:complexType>
  <xsd:attribute name="mediaid" type="xsd:integer" />
  <xsd:attribute name="status" type="mediaType" />
 </xsd:complexType> 
</xsd:element>

В данном примере элемент media имеет теперь два атрибута, включая атрибут перечисляемого типа mediaType.

Вы можете также создавать и именовать сложные типы.

Сложные типы: элементы

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

<xsd:element name="memories">
 <xsd:complexType>
  <xsd:sequence> 
   <xsd:element name="memory" type="memoryType"/>
  </xsd:sequence> 
 </xsd:complexType>
</xsd:element>

Этот пример показывает только один дочерний элемент, но вам все же нужен элемент sequence.

В типе memoryType комбинируются несколько приемов, увиденных вами недавно:

<xsd:complexType name="memoryType">
 <xsd:sequence>
  <xsd:element name="media">
   <xsd:complexType>
    <xsd:attribute name="mediaid" type="xsd:integer" />
    <xsd:attribute name="status" type="mediaType" />
   </xsd:complexType>
  </xsd:element>
  <xsd:element name="subdate" type="xsd:date"/>
  <xsd:element name="donor" type="xsd:string"/>
  <xsd:element name="subject" type="xsd:string"/>
  <xsd:element name="location" type="locationType" />
 </xsd:sequence>
 <xsd:attribute name="tapeid" type="idNumber" />
 <xsd:attribute name="status" type="xsd:string" />
</xsd:complexType>

Тип memoryType также включает ссылку на locationType, что позволяет пользователю выбирать между потенциальными потомками элемента.

Выбор элементов

Элемент sequence показывает все возможные дочерние элементы для данного элемента. В некоторых случаях, однако, вы хотите выбрать один элемент из списка альтернатив. Для этого вам нужен элемент choice:

<xsd:complexType name="locationType">
 <xsd:choice>
  <xsd:element name="description" type="xsd:string" />
  <xsd:element name="place" type="xsd:string" />
 </xsd:choice> 
</xsd:complexType>

Или элемент description, или place - но не оба вместе - могут появляться в качестве потомков любого элемента locationType. Этот прием также может применяться для определения выбора атрибутов.

Необязательные и повторяющиеся элементы

До сих пор все элементы и атрибуты, добавляемые в схему, должны были появляться ровно один раз. Очевидно, что это не всегда желательно. Используя minOccurs и maxOccurs, вы можете управлять тем, должен ли компонент появляться обязательно и может ли он повторяться. В данном примере схема требует, чтобы элемент subject присутствовал, и позволяет ему появляться до пяти раз в одном элементе:

...
<xsd:element name="subject" minOccurs="1" maxOccurs="5" type="xsd:string"/>
...

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

<xsd:element name="memories">
 <xsd:complexType>
  <xsd:sequence>
    <xsd:element name="memory"
      minOccurs="0" maxOccurs="unbounded"
      type="memoryType"/>
  </xsd:sequence>
 </xsd:complexType>
</xsd:element>

Эта возможность становится очень полезной, когда мы имеем дело со смешанным содержимым в схеме.

Смешанное содержимое в схемах

Иногда элемент содержит смешанное содержимое, а не только элементы или только текст. Например, элемент subject может содержать разметку HTML:

<subject>
 A reading of Charles Dickens' <i>A Christmas Carol</i>.
 Absolutely <b>marvelous</b>!
</subject>

Это содержимое не может быть описано как xsd:string, поскольку оно содержит элементы. Также и простое перечисление элементов i и b в последовательности не будет работать, поскольку они содержат текст. Вместо этого элемент subject должен быть определен как mixed:

<xsd:element name="subject">
 <xsd:complexType mixed="true">
  <xsd:sequence>
   <xsd:element name="i" minOccurs="0" maxOccurs="unbounded"
      type="xsd:string" />
  <xsd:element name="b" minOccurs="0" maxOccurs="unbounded"
      type="xsd:string" />
  </xsd:sequence>
 </xsd:complexType>
</xsd:element>

Этот метод создания смешанного содержимого является усовершенствованием по сравнению с DTD потому, что он позволяет лучше управлять количеством и порядком следования элементов. Конечно, в случае, если вы не хотите другого.

Неограничиваемый порядок

Иногда может ограничиваться содержимое элемента, но не порядок, в котором оно появляется. Это, в частности, так для смешанного содержимого в схемах. Чтобы создать элемент, который не ограничивает порядок его потомков, используйте элемент all вместо sequence.

<xsd:element name="subject">
 <xsd:complexType mixed="true">
  <xsd:all> 
   <xsd:element name="i" minOccurs="0" maxOccurs="1" type="xsd:string" />
   <xsd:element name="b" minOccurs="0" maxOccurs="1" type="xsd:string" />
  </xsd:all> 
 </xsd:complexType>
</xsd:element>

Заметьте, что maxOccurs установлен в 1, а не в unbounded. Это обязательно для применения элемента all. Атрибуты minOccurs и maxOccurs должны быть либо 0, либо 1. Комбинирование и вложение этих групп может создавать элементы, в которых ограничены типы элементов, но не их порядок или повторяемость.

Документы схемы также поддерживают полностью неограниченные элементы.

Полностью неограниченные элементы

Полностью неограниченным элементом является такой, в котором разрешается какое угодно содержимое. Вы можете достичь этого через встроенный тип anyType. Например, вы можете определить элемент location содержащим любое содержимое, в любом порядке и с любой повторяемостью. Это включает в себя не только текст, но и элементы:

<xsd:element name="description" type="xsd:anyType" />

Комбинация всех этих приемов приводит к полному документу.

Полные документы схемы и экземпляра

Документ схемы XML:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <xsd:element name="memories">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="memory" minOccurs = "0" maxOccurs="unbounded"
          type="memoryType"/>
   </xsd:sequence>
  </xsd:complexType>
 </xsd:element>
 <xsd:complexType name="memoryType">
  <xsd:sequence>
   <xsd:element name="media">
    <xsd:complexType>
     <xsd:attribute name="mediaid" type="xsd:integer" />
     <xsd:attribute name="status" type="mediaType" />
    </xsd:complexType>
   </xsd:element>
   <xsd:element name="subdate" type="xsd:date"/>
   <xsd:element name="donor" type="xsd:string"/>
   <xsd:element name="subject">
    <xsd:complexType mixed="true">
     <xsd:all>
      <xsd:element name="i" minOccurs="0" maxOccurs="1"
            type="xsd:string" />
      <xsd:element name="b" minOccurs="0" maxOccurs="1"
            type="xsd:string" />
     </xsd:all>
    </xsd:complexType>
   </xsd:element>
   <xsd:element name="location" type="locationType" />
  </xsd:sequence>
  <xsd:attribute name="tapeid" type="idNumber" />
  <xsd:attribute name="status" type="xsd:string" />
 </xsd:complexType>
 <xsd:complexType name="locationType">
  <xsd:choice>
   <xsd:element name="description" type="xsd:anyType" />
   <xsd:element name="place" type="xsd:string" />
  </xsd:choice>
 </xsd:complexType>
 <xsd:simpleType name="idNumber">
  <xsd:restriction base="xsd:integer">
   <xsd:minInclusive value="1" />
   <xsd:maxInclusive value="100000" />
  </xsd:restriction>
 </xsd:simpleType>
 <xsd:simpleType name="mediaType">
  <xsd:restriction base="xsd:string">
   <xsd:enumeration value="8mm" />
   <xsd:enumeration value="vhs" />
   <xsd:enumeration value="vhsc" />
   <xsd:enumeration value="digital" />
   <xsd:enumeration value="audio" />
  </xsd:restriction>
 </xsd:simpleType>
</xsd:schema>

Документ экземпляра:

<?xml version="1.0"?>
<memories xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
        xsi:noNamespaceSchemaLocation='memory.xsd'>
 <memory tapeid="1">
  <media mediaid="1" status="vhs" />
  <subdate>2001-05-23</subdate>
  <donor>John Baker</donor>
  <subject>Fishing off Pier 60</subject>
  <location>
   <description>Outside in the woods</description>
  </location>
 </memory>
 <memory tapeid="2">
  <media mediaid="2" status="vhs"/>
  <subdate>2001-05-18</subdate>
  <donor>Elizabeth Davison</donor>
  <subject>Beach volleyball</subject>
  <location>
   <place>Clearwater beach</place>
  </location>
 </memory>
</memories>

 

Раздел 6. Итоговая сводка проверки правильности

Резюме

Этот учебник показал вам, как создавать и DTD, и документы схемы XML для проверки по ним ваших XML-документов. Обсуждалось также преимущество схем над DTD.

В этом учебнике также обсуждалась проверка правильности с точки зрения двух разных API Java (JAXP и Xerces). Однако XML является платформенно-независимым средством для представления информации; проверяющие парсеры доступны и в C++, и в Perl, и в других языках, и концепции их те же самые.

Ресурсы

В Web есть много хорошей информации о проверке правильности XML-документов:

Обратная связь

Пожалуйста, присылайте ваши отзывы об этом учебнике. Мы заинтересованы в том, чтобы услышать ваше мнение!

Кроме того, вы можете напрямую связаться с автором, Nicholas Chase, nicholas@nicholaschase.com


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