Содержание


API для валидации XML-документов в Java

Проверяйте свои документы на соответствие схемам

Comments

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

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

При работе с расширяемым языком разметки (XML) валидация, как правило, сводится к написанию подробной спецификации структуры документов на одном из языков схем, каковыми являются XML Schema (XSD, стандарт консорциума WWW), RELAX NG , DTD (Document Type Definitions) и Schematron. Валидация может выполняться как непосредственно в момент разбора документа, так и позже, но обычно – перед любой обработкой его содержимого (не стоит воспринимать это утверждение как строгое правило – у него есть исключения).

До недавнего времени API (прикладной интерфейс), через который приложения выполняли валидацию, определялся языком описания схем и используемым парсером. Работа с DTD и XSD осуществлялась через опции интерфейсов SAX (Simple API for XML), DOM (Document Object Model) и JAXP (Java API for XML Processing). Для использования RELAX NG была необходима специальная библиотека и соответствующий API, а для Schematron – например, TrAX (Transformations API for XML). Другие языки описания схем также требовали от разработчиков изучения специальных API, хотя все они выполняли аналогичные действия.

В Java 5 появился пакет javax.xml.validation, представляющий собой независящий от языка схем интерфейс использования механизма валидации. Этот пакет также был доступен начиная с Java 1.3, но для этого необходимо было отдельно установить JAXP 1.3. Его реализация является одним из компонентов библиотеки Xerces 2.8.

Валидация

В пакете javax.xml.validation для валидации документов используются три класса: SchemaFactory, Schema и Validator. Кроме того, этот пакет активно использует интерфейс javax.xml.transform.Source из TrAX для представления документов XML. Если не вдаваться в детали, то назначение этих классов таково: SchemaFactory читает описание схемы, которая часто представляет собой документ XML, создавая на его основе экземпляр типа Schema, который в свою очередь создает валидатор (объект типа Validator). Валидатор выполняет проверку документа XML, представленного в виде экземпляра Source.

В листинге 1 приведен код простой программы, которая проверяет документ с URL, полученным в виде параметра командной строки, на соответствие схеме в файле docbook.xsd.

Листинг 1. Валидация документа на расширяемом языке разметки гипертекста (XHTML)
import java.io.*;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
import org.xml.sax.SAXException;

public class DocbookXSDCheck {

    public static void main(String[] args) throws SAXException, IOException {

        // 1. Поиск и создание экземпляра фабрики для языка XML Schema
        SchemaFactory factory = 
            SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
        
        // 2. Компиляция схемы
        // Схема загружается в объект типа java.io.File, но вы также можете использовать 
        // классы java.net.URL и javax.xml.transform.Source
        File schemaLocation = new File("/opt/xml/docbook/xsd/docbook.xsd");
        Schema schema = factory.newSchema(schemaLocation);
    
        // 3. Создание валидатора для схемы
        Validator validator = schema.newValidator();
        
        // 4. Разбор проверяемого документа
        Source source = new StreamSource(args[0]);
        
        // 5. Валидация документа
        try {
            validator.validate(source);
            System.out.println(args[0] + " is valid.");
        }
        catch (SAXException ex) {
            System.out.println(args[0] + " is not valid because ");
            System.out.println(ex.getMessage());
        }  
        
    }

}

Ниже показан типичный пример результата валидации некорректного документа при помощи Xerces и JDK (Java 2 Software Development Kit) 5.0.

file:///Users/elharo/CS905/Course_Notes.xml is not valid because cvc-complex-type.2.3: Element 'legalnotice' cannot have character [children], because the type's content type is element-only.

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

  1. Загрузка фабрики для выбранного языка описания схем.
  2. Компиляция схемы по ее описанию.
  3. Создание валидатора на основе скомпилированной схемы.
  4. Создание объекта типа Source для представления валидируемого документа. Как правило, в качестве реализации такого объекта проще всего использовать класс StreamSource.
  5. Валидация документа. Если документ оказывается некорректным, метод validate() выбрасывает исключение типа SAXException, а в противном случае он просто завершает выполнение.

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

Проверка на соответствие схеме, указанной в документе

Некоторые документы самостоятельно указывают, какой схеме они должны соответствовать. Как правило, для этого используются атрибуты xsi:noNamespaceSchemaLocation или xsi:schemaLocation, как показано ниже.

<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://www.example.com/document.xsd">
  ...

Если вы создадите экземпляр схемы без указания URL, файла или объекта-источника, как показано на примере ниже, то он попытается найти ссылку на схему в самом проверяемом документе.

SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();

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

Абстрактные фабрики

Класс SchemaFactory не является абстрактной фабрикой. Принцип абстрактной фабрики позволяет работать через один API со множеством языков схем и моделей данных. Каждая реализация, как правило, поддерживает лишь ограниченный набор языков и моделей. Однако, разобравшись с API для валидации документов DOM по схемам, скажем, RELAX NG, вы сможете использовать его же для проверки документов JDOM по схемам W3C. 

В листинге 2 приведен пример программы, которая проверяет документы DocBook на соответствие схеме RELAX NG. Как видите, код практически идентичен показанному в листинге 1. Все отличия заключаются только в местоположении схемы и URL, который идентифицирует используемый язык схем.

Листинг 2. Валидация документа DocBook при помощи RELAX NG
import java.io.*;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
import org.xml.sax.SAXException;

public class DocbookRELAXNGCheck {

    public static void main(String[] args) throws SAXException, IOException {

        // 1. Создание фабрики схем для языка RELAX NG
        SchemaFactory factory 
         = SchemaFactory.newInstance("http://relaxng.org/ns/structure/1.0");
        
        // 2. Загрузка требуемой схемы
        // Схема загружается в объект типа java.io.File, но вы также можете использовать 
        // классы java.net.URL и javax.xml.transform.Source
        File schemaLocation = new File("/opt/xml/docbook/rng/docbook.rng");
        
        // 3. Компиляция схемы
        Schema schema = factory.newSchema(schemaLocation);
    
        // 4. Создание валидатора для схемы
        Validator validator = schema.newValidator();
        
        // 5. Разбор проверяемого документа
        String input 
         = "file:///Users/elharo/Projects/workspace/CS905/build/Java_Course_Notes.xml";
        
        // 6. Валидация документа
        try {
            validator.validate(source);
            System.out.println(input + " is valid.");
        }
        catch (SAXException ex) {
            System.out.println(input + " is not valid because ");
            System.out.println(ex.getMessage());
        }  
        
    }

}

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

Exception in thread "main" java.lang.IllegalArgumentException: 
http://relaxng.org/ns/structure/1.0
	at javax.xml.validation.SchemaFactory.newInstance(SchemaFactory.java:186)
	at DocbookRELAXNGCheck.main(DocbookRELAXNGCheck.java:14)

Дело в том, что в состав стандартного JDK не входит валидатор RELAX NG, а если не удается распознать язык схемы, метод SchemaFactory.newInstance() выбрасывает исключение типа IllegalArgumentException. Однако если вы установите библиотеку RELAX NG, например Jing, и адаптер JAXP 1.3, то результат должен быть аналогичен результату валидации по схеме W3C.

Определение языка схемы

Класс javax.xml.constants содержит следующие константы, служащие для определения языка схемы:

  • XMLConstants.W3C_XML_SCHEMA_NS_URI: http://www.w3.org/2001/XMLSchema
  • XMLConstants.RELAXNG_NS_URI: http://relaxng.org/ns/structure/1.0
  • XMLConstants.XML_DTD_NS_URI: http://www.w3.org/TR/REC-xml

Это отнюдь не законченный перечень, и разработчики библиотек могут добавлять собственные URL, идентифицирующие другие языки схем. Как правило, URL представляет собой URI (Uniform Resource Identifier) пространства имен языка. Например, для Schematron таковым URL является http://www.ascc.net/xml/schematron.

JDK 5 от Sun поддерживает только язык XSD. При этом валидация по DTD, хотя и возможна, но не через API javax.xml.validation. Если вы хотите использовать DTD, вам следует использовать стандартный SAX-класс XMLReader. При этом вы можете устанавливать дополнительные библиотеки для поддержки других языков схем.

Поиск фабрик схем

В Java есть возможность работы с несколькими фабриками схем. Получив на вход URI, позволяющий определить язык описания схем, метод SchemaFactory.newInstance() пытается загрузить фабрику, пробуя следующие варианты в указанном порядке.

  1. Класс, указанный в системном свойстве "javax.xml.validation.SchemaFactory:URLсхемы".
  2. Класс, указанный в свойстве "javax.xml.validation.SchemaFactory:URLсхемы", находящемся в файле $java.home/lib/jaxp.properties.
  3. Провайдеры javax.xml.validation.SchemaFactory, находящиеся в каталогах META-INF/services всех доступных JAR-файлов.
  4. Класс, по умолчанию реализующий SchemaFactory на данной платформе (в JDK 5 таковым является com.sun.org.apache.xerces.internal.jaxp.validation.xs.SchemaFactoryImpl).

Для того чтобы добавить поддержку вашего собственного языка описания схем и соответствующего валидатора, вам необходимо создать классы, реализующие SchemaFactory, Schema и Validator и умеющие работать с вашим языком. Затем следует убедиться, что расположение ваших классов соответствует одному из четырех вариантов, приведенных выше. Собственный язык схем полезен при необходимости описывать ограничения, которые легче проверять на полном по Тьюрингу языке, таком как Java, чем на декларативном языке, подобном XML Schema. В этом случае вы можете создать собственный мини-язык, быстро реализовать нужный валидатор и фабрику, а затем установить свою библиотеку в инфраструктуру валидации Java.

Обработчики ошибок

По умолчанию валидатор выбрасывает исключение типа SAXException  в случае некорректного содержимого документа, а в противном случае он просто тихо завершает работу. Однако существует возможность получения более детальной информации о проблемах в документе через классы, реализующие ErrorHandler в SAX. Например, допустим, что вы хотите заносить в журнал все ошибки валидации, но при этом очередная ошибка не должна останавливать процесс проверки. Для этого можно использовать обработчик ошибок, как показано в листинге 3.

Листинг 3. Обработчик ошибок, регистрирующий нефатальные ошибки валидации в журнале приложения
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class ForgivingErrorHandler implements ErrorHandler {

    public void warning(SAXParseException ex) {
        System.err.println(ex.getMessage());
    }

    public void error(SAXParseException ex) {
        System.err.println(ex.getMessage());
    }

    public void fatalError(SAXParseException ex) throws SAXException {
        throw ex;
    }

}

Установка обработчика ошибок заключается в создании его экземпляра и его последующей передаче в метод setErrorHandler() валидатора.

  ErrorHandler lenient = new ForgivingErrorHandler();
  validator.setErrorHandler(lenient);

Изменение документов при помощи схем

Возможности схем не обязательно ограничиваются валидацией документов. Некоторые из них могут не только проверять корректность содержимого документа, но также добавлять в него дополнительную информацию, например, указывать значения по умолчанию для атрибутов элементов. Кроме того, они могут указывать типы данных, например int или gYear, для атрибутов и элементов. При этом валидаторы записывают подобные изменения в объект типа javax.xml.transform.Result, который в этих случаях должен передаваться вторым параметром в метод validate. В листинге 4 показан пример одновременной валидации документа и создания его расширенной DOM-версии на основе схемы.

Листинг 4. Добавление дополнительной информации в документ в процессе валидации по схеме
import java.io.*;
import javax.xml.transform.dom.*;
import javax.xml.validation.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

public class DocbookXSDAugmenter {

    public static void main(String[] args) 
      throws SAXException, IOException, ParserConfigurationException {

        SchemaFactory factory 
         = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
        File schemaLocation = new File("/opt/xml/docbook/xsd/docbook.xsd");
        Schema schema = factory.newSchema(schemaLocation);
        Validator validator = schema.newValidator();
        
        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        domFactory.setNamespaceAware(true); // never forget this
        DocumentBuilder builder = domFactory.newDocumentBuilder();
        Document doc = builder.parse(new File(args[0]));
        
        DOMSource source = new DOMSource(doc);
        DOMResult result = new DOMResult();
        
        try {
            validator.validate(source, result);
            Document augmented = (Document) result.getNode();
            // здесь вы можете делать все, что необходимо с измененным документом
        }
        catch (SAXException ex) {
            System.out.println(args[0] + " is not valid because ");
            System.out.println(ex.getMessage());
        }  
        
    }

}

Эта возможность, однако, не позволяет преобразовывать любые документы в произвольную форму. Она также работает не для всех типов источников данных и результирующих объектов. В частности, документы, полученные из SAX-источников, могут быть расширены и сохранены в объектах SAX, а документы DOM – в объектах DOM, но SAX-документы не могут быть преобразованы в DOM-представление, и наоборот. Если вам это необходимо, то следует сначала внести необходимые изменения, сохранив их в том же представлении, а затем использовать преобразование на основе TrAX для конвертации из DOM в SAX или обратно.

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

Информация о типах

Понятие типа является одним из центральных в языке XML Schema. Элементы и атрибуты имеют свои типы, например, int, double, date, duration, person, PhoneNumber или какие-либо еще. API валидации в Java предоставляет возможности определения типов, хотя эта функциональность, как ни странно, не зависит от остальных компонентов инфраструктуры.

Типы представляются в виде экземпляров org.w3c.dom.TypeInfo. Этот интерфейс, показанный в листинге 5, позволяет определить локальное имя типа, URI его пространства имен, а также то, является ли он расширением некоего базового типа. Все остальные манипуляции с типами остаются на усмотрение вашего приложения. API не предоставляет никакой информации о семантике типов или об их преобразованиях в Java-типы, например, double или java.util.Date.

Листинг 5. Интерфейс TypeInfo в DOM
package org.w3c.dom;

public interface TypeInfo {

  public static final int DERIVATION_RESTRICTION;
  public static final int DERIVATION_EXTENSION;
  public static final int DERIVATION_UNION;

  public String  getTypeName();
  public String  getTypeNamespace()
  public boolean isDerivedFrom(String namespace, String name, int derivationMethod);

}

Для получения экземпляров TypeInfo следует запросить у экземпляра Schema специальный объект-обработчик ValidatorHandler, реализующий SAX-интерфейс ContentHandler, а не Validator. Его следует передавать SAX-парсеру при разборе документа.

Вы можете зарегистрировать собственный класс ContentHandler в объекте ValidatorHandler (а не в парсере). В этом случае ValidatorHandler будет делегировать события вашему объекту.

Класс ValidatorHandler предоставляет доступ к объекту TypeInfoProvider, который может использоваться вашим обработчиком ContentHandler для определения типа текущего элемента или его атрибутов. При помощи этого класса можно также определить, является ли атрибут идентификатором, был он явно указан в документе или появился в процессе валидации по схеме. Методы этого класса приведены в листинге 6.

Листинг 6. Класс TypeInfoProvider
package javax.xml.validation;

public abstract class TypeInfoProvider {

  public abstract TypeInfo getElementTypeInfo();
  public abstract TypeInfo getAttributeTypeInfo(int index);
  public abstract boolean  isIdAttribute(int index);
  public abstract boolean  isSpecified(int index);

}

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

Листинг 7. Вывод типов всех элементов
import java.io.*;
import javax.xml.validation.*;

import org.xml.sax.*;
import org.xml.sax.helpers.*;

public class TypeLister extends DefaultHandler {

    private TypeInfoProvider provider;
    
    public TypeLister(TypeInfoProvider provider) {
        this.provider = provider;
    }

    public static void main(String[] args) throws SAXException, IOException {

        SchemaFactory factory 
         = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
        File schemaLocation = new File("/opt/xml/docbook/xsd/docbook.xsd");
        Schema schema = factory.newSchema(schemaLocation);
    
        ValidatorHandler vHandler = schema.newValidatorHandler();
        TypeInfoProvider provider = vHandler.getTypeInfoProvider();
        ContentHandler   cHandler = new TypeLister(provider);
        vHandler.setContentHandler(cHandler);
        
        XMLReader parser = XMLReaderFactory.createXMLReader();
        parser.setContentHandler(vHandler);
        parser.parse(args[0]);
        
    }
    
    public void startElement(String namespace, String localName,
      String qualifiedName, Attributes atts) throws SAXException {
        String type = provider.getElementTypeInfo().getTypeName();
        System.out.println(qualifiedName + ": " + type);
    }

}

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

book: #AnonType_book
title: #AnonType_title
subtitle: #AnonType_subtitle
info: #AnonType_info
copyright: #AnonType_copyright
year: #AnonType_year
holder: #AnonType_holder
author: #AnonType_author
personname: #AnonType_personname
firstname: #AnonType_firstname
othername: #AnonType_othername
surname: #AnonType_surname
personblurb: #AnonType_personblurb
para: #AnonType_para
link: #AnonType_link

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

Заключение

В этом мире было бы гораздо скучнее жить, если бы все говорили на одном языке. Даже разработчики были бы недовольны, будучи ограниченными лишь одним языком программирования. Разные языки лучше приспособлены для решения различных задач, в том числе проверки XML-документов на соответствие схемам. В настоящее время вы можете выбирать из множества разных языков описания схем, при этом используя API javax.xml.validation в Java 5 в качестве универсального интерфейсного слоя.


Ресурсы для скачивания


Похожие темы

  • Оригинал статьи: The Java XML Validation API (Эллиотт Расти Гарольд, developerWorks, август 2006 г.). (EN)
  • Прочитайте статью RELAX NG: Ответный удар (Дэвид Мерц, David Merz, февраль 2003 г.), в которой приводится введение в язык схем RELAX NG. (EN)
  • Схема DocBook 5.0 описана на языке RELAX NG, но при необходимости ее можно транслировать в представление на языках DTD и W3C. (EN)
  • Загрузите JAXP 1.3 для Java 1.3 и 1.4 c сайта java.net. (EN)
  • Койсуке Кавагучи (Kohsuke Kawaguchi) создал адаптер для совместимости API JAXP, описанного в этой статье, с более ранним интерфейсом JARV, поддерживаемым некоторыми валидаторами RELAX NG, например JING. (EN)
  • Загрузите парсер Xerces, поддерживающий JAXP 1.3, в том числе интерфейсы валидации, описанные в этой статье. (EN)
  • В главе 20 Библии XML 1.1 (Эллиотт Расти Гарольд, Wiley, 2003 г.) содержится полное руководство по языку XML Schema, стандартизованному W3C. (EN)
  • В книге XML in a Nutshell (Эллиотт Расти Гарольд и У. Скотт Минз, W. Scott Means, O'Reilly, 2005 г.) приведен полный справочник, краткое руководство по языку XML Schema и технологиям DOM и TrAX. (EN)
  • Прочитайте книгу Processing XML with Java (Эллиотт Расти Гарольд, Addison-Wesley, 2002 г.), в которой объясняется использование таких API, как DOM, SAX, TrAX при обработке документов XML в Java. (EN)
  • Загрузите ознакомительные версии программного обеспечения IBM прямо с сайта developerWorks. (EN)
  • Сертификация по XML корпорации IBM: узнайте, как стать сертифицированным разработчиком IBM в области XML и связанных с ним технологий. (EN)

Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML, Технология Java
ArticleID=620336
ArticleTitle=API для валидации XML-документов в Java
publish-date=01312011