Глава 8.2. Определение типа документа

8.2.1. Общие сведения

В предыдущей главе мы неоднократно подчеркивали, что интерес к XML обусловлен, в первую очередь, возможностью создания на его основе специализированных языков, имеющих собственные наборы тегов. Однако, описанная там структура XML-документа и разбор его XML-процессором позволяют произвести только простую проверку того, что документ является правильно оформленным. Для создания на этой основе специализированных языков необходимы дополнительные средства описания этих языков. XML поддерживает два механизма подобных описаний: определения типа документа (document type definition, DTD) и XML-схемы (XML schema). Схемы будут описаны в гл. 8.6, здесь же мы приводим подробное описание правил создания DTD.

Синтаксис DTD соответствует правилам языка SGML, поэтому DTD в XML по сути своей те же, что DTD в HTML. Однако, описывая HTML, мы ограничились простой констатацией их существования, поскольку для практической работы с HTML-документами формальное описание DTD не нужно. Совершенно по-другому обстоят дела в XML. Здесь DTD служит той основой, на которой базируется синтаксический анализ XML-документа. Именно DTD позволяет XML-процессору проверить не только, правильно ли оформлен документ, но и соответствует ли он описанию конкретного языка. По отношению к DTD все XML-процессоры делятся на верифицирующие (validating) и неверифицирующие (non-validating). Неверифицирующий процессор проверяет документ только на правильность оформления; верифицирующий, кроме того, проверяет его на соответствие заданной в декларации типа документа DTD. Документ, прошедший такую проверку, называется правильным (valid).

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

Эти декларации могут содержаться (все или частично) в параметрических разделах.

8.2.2. Декларации типов элементов

Синтаксис: <!ELEMENT имя спецификация>

Декларация типа элемента определяет, какое содержимое может иметь элемент с данным именем. Возможное содержимое элемента задается спецификацией, которая может быть:

  • ключевым словом EMPTY, означающим, что элемент пуст, т. е. не может иметь содержимого;
  • ключевым словом ANY, означающим, что элемент может иметь любое содержимое;
  • списком элементом-детей;
  • смешанным описателем.

Список детей — это заключенный в круглые скобки список имен элементов или ссылок на параметрические разделы, которые могут или должны содержаться в данном элементе. Список детей может также содержать подсписки, в свою очередь заключенные в круглые скобки. Элементы списков разделяются запятыми. Каждое имя элемента или подсписок может сопровождаться специальными символами:

  • плюс (+) означает, что данный элемент может встречаться один или более раз;
  • звездочка (*) означает, что данный элемент может встречаться нуль или более раз;
  • вопросительный знак (?) означает, что данный элемент может встречаться нуль или один раз;
  • отсутствие специального символа означает, что данный элемент должен присутствовать ровно один раз.

Например, для элементов book и bookstore из предыдущей главы мы можем написать такие декларации типов:

<!ELEMENT book (title, author+, price, present?)>
<!ELEMENT bookstore (book*)>

Здесь указано, что элемент book состоит из одного элемента title, одного или нескольких элементов author, элемента price и необязательного элемента present, а элемент bookstore состоит из любого количества элементов book.

Элемент author имеет более сложное строение: он может состоять либо из двух элементов first-name и last-name, либо из единственного элемента name. Подобные варианты включаются в списки элементов с помощью разделителя вертикальная черта (|), например, в данном случае:

<!ELEMENT author ((first-name, last-name) | name)>

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

<!ELEMENT title (#PCDATA)>

Ключевое слово #PCDATA происходит от словосочетания "parsed character data", т. е. произвольные символьные данные, понимаемые XML-процессором. В некоторых случаях элемент может иметь символьное содержимое вперемешку с элементами. Например, описывая упрощенный диалект языка HTML, мы могли бы определить элемент P (абзац), как текст, фрагменты которого могут быть выделены курсивным или полужирным шрифтом (элементы I и B соответственно). Тогда его декларация имела бы следующий вид:

<!ELEMENT P (#PCDATA|B|I)*>

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

Примеры использования ссылок на параметрические разделы в списках элементов приведены ниже.

8.2.3. Декларации списков атрибутов

Синтаксис: <!ATTLIST имя спецификация_атрибута*>
           спецификация_атрибута: имя тип параметры

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

8.2.3.1. Типы атрибутов

Атрибуты в XML бывают трех типов: строковые, именные (tokenized) и списочные.

  • Строковые атрибуты могут принимать любые текстовые значения и обозначаются ключевым словом CDATA.
  • На именные типы наложены определенные лексические и семантические ограничения. Эти типы задаются следующими ключевыми словами:
    • ID — уникальный идентификатор элемента. Элемент может иметь не более одного атрибута этого типа; параметры для него должны иметь значение #IMPLIED или #REQUIRED.
    • IDREF — значение атрибута типа ID другого элемента.
    • IDREFS — список значений типа IDREF, разделенных пробелами.
    • ENTITY — имя неанализируемого раздела, объявленного в DTD.
    • ENTITIES — список значений типа ENTITY, разделенных пробелами.
    • NMTOKEN — любое имя.
    • NMTOKENS — список значений типа NMTOKEN, разделенных пробелами.
  • Списочные атрибуты могут принимать значения только из заданного списка. Они подразделяются на:
    • Перечисление: список имен, разделенных пробелами, заключенный в круглые скобки; возможно задание вариантов с помощью разделителя вертикальная черта (|).
    • Список нотаций: ключевое слово NOTATION, за которым следует перечисление имен нотаций. Элемент может иметь не более одного атрибута этого типа; значениями атрибута могут быть только имена нотаций, объявленных в DTD.

8.2.3.2. Дополнительные параметры атрибутов

Параметры атрибутов могут принимать следующие значения:

  • #REQUIRED — означает, что атрибут является обязательным;
  • #IMPLIED — означает, что атрибут не имеет значения по умолчанию;
  • значение — задает значения атрибута по умолчанию;
  • #FIXED значение — означает, что атрибут всегда имеет значение по умолчанию.

8.2.3.3. Примеры описания атрибутов

В качестве примера рассмотрим список атрибутов элемента button в языке XHTML.

<!ATTLIST button
  id          ID             #IMPLIED
  class       CDATA          #IMPLIED
  style       CDATA          #IMPLIED
  title       CDATA          #IMPLIED
  lang        NMTOKEN        #IMPLIED
  xml:lang    NMTOKEN        #IMPLIED
  dir         (ltr|rtl)      #IMPLIED
  name        CDATA          #IMPLIED
  value       CDATA          #IMPLIED
  type        (button|submit|reset) "submit"
  disabled    (disabled)     #IMPLIED
  tabindex    CDATA          #IMPLIED
  accesskey   CDATA          #IMPLIED
  >

В этом списке атрибут id является уникальным идентификатором элемента, lang и xml:lang задаются кодом языка, dir может принимать значения ltr или rtl, а type — значения button, submit и reset, причем его значением по умолчанию является submit. Все остальные атрибуты являются строковыми и не имеют значений по умолчанию.

В нашем примере с книжным магазином элемент book имеет один обязательный атрибут genre. Поэтому его декларация атрибутов будет иметь вид:

<!ATTLIST book genre CDATA #REQUIRED>

8.2.4. Декларации нотаций

Нотации — это имена, идентифицирующие формат неанализируемых разделов, формат элементов, имеющих атрибут нотации, или прикладную программу, которой адресована директива. Декларация нотации имеет две формы:

<!NOTATION имя SYSTEM URI>
<!NOTATION имя PUBLIC строка URI?>

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

Пример использования нотации:

<!NOTATION gif SYSTEM "gifview.exe">
...
<!ENTITY photo SYSTEM "photo.gif" NDATA gif>

В этом примере указано, что для обработки разделов в формате gif следует вызывать программу отображения GIF-образов gifview.exe.

8.2.5. Пример DTD

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

<!DOCTYPE bookstore [
  <!ELEMENT first-name (#PCDATA)>
  <!ELEMENT last-name (#PCDATA)>
  <!ELEMENT name (#PCDATA)>
  <!ELEMENT title (#PCDATA)>
  <!ELEMENT price (#PCDATA)>
  <!ELEMENT present EMPTY>
  <!ELEMENT author ((first-name, last-name) | name)>
  <!ELEMENT book (title, author+, price, present?)>
  <!ATTLIST book genre CDATA #REQUIRED>
  <!ELEMENT bookstore (book*)>
  <!ENTITY po "поэзия">
  <!ENTITY pr "проза">
  <!ENTITY dr "драматургия">
]>

8.2.6. Параметрические разделы

Параметрические разделы (parameter entities) — это макроопределения, обеспечивающие более сложные текстовые подстановки, чем обычные анализируемые разделы. Параметризованный раздел декларируется следующим образом:

<!ENTITY % имя значение>

Таким образом, отличие его декларации от обычного раздела состоит только в наличии символа процента (%) перед именем раздела. Для ссылок на параметрические разделы используется конструкция %имя; (а не &имя;, как в обычных ссылках). Параметрические разделы применяются исключительно в DTD, причем в двух целях: во-первых, они позволяют включать в состав DTD другие DTD-файлы, во-вторых, они используются как сокращения для повторяющихся деклараций. Пусть, например, DTD книжного магазина хранится в файле bookstore.dtd. Тогда для ее включения в другую DTD мы могли бы использовать следующий синтаксис:

<!ENTITY % bookstore SYSTEM "bookstore.dtd">
%bookstore;

Для уменьшения объема DTD мы могли бы создать параметрический раздел text вида

<!ENTITY % text "(#PCDATA)">

и затем изменить приведенные выше декларации так:

<!ELEMENT first-name %text;>
<!ELEMENT last-name %text;>

8.2.7. Условные секции

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

<![IGNORE[ декларации ]]>
<![INCLUDE[ декларации ]]>

Первая из них указывает XML-процессору, что ее содержимое должно им игнорироваться, а вторая — что ее содержимое должно обрабатываться обычным образом. Содержимым этих секций могут быть любые синтаксически законченные декларации, допустимые в DTD. Поясним использование условных секций на примере. Допустим, что в процессе отладки bookstore.dtd мы решили создать два варианта этой DTD: отладочный (draft) и окончательный (final). Тогда для отладки мы можем написать следующее:

<!ENTITY % draft 'INCLUDE'>
<!ENTITY % final 'IGNORE'>

<![%draft;[
<!ELEMENT book (comments*, title, author+, price, present?)>
]]>
<![%final;[
<!ELEMENT book (title, author+, price, present?)>
]]>

Теперь процессор будет включать в DTD первую из приведенных деклараций элемента book и игнорировать вторую. После завершения отладки нам достаточно изменить только декларации разделов draft и final:

<!ENTITY % draft 'IGNORE'>
<!ENTITY % final 'INCLUDE'>