Главная » Java, Web, XML » Анализ документа XML

0

На первом этапе разбора проводится лексический анализ (lexical parsing) документа XML. Документ разбивается на отдельные неделимые элементы (tokens), которыми являются теги, служебные слова, разделители, текстовые константы. Проводится проверка полученных элементов и их связей между собой. Лексический анализ выполняют специальные программы — сканеры (scanners). Простейшие сканеры — это классы

и java.io.streamTokenizer из стандартной поставки Java 2 SDK Standard Edition.

Затем выполняется грамматический анализ (grammar parsing). При этом анализируется логическая структура документа, составляются выражения, выражения объединяются в блоки, блоки — в модули, которыми могут являться абзацы, параграфы, пункты, главы. Грамматический анализ проводят программы-анализаторы, так называемые парсеры (parsers).

Создание сканеров и парсеров — любимое развлечение программистов. За недолгую историю XML написаны десятки, если не сотни XML-парсеров. Многие из них написаны на языке Java. Все парсеры можно разделить на две группы.

В первую группу входят парсеры, проводящие анализ, основываясь на структуре дерева, отражающего вложенность элементов документа (tree- based parsing). Такие парсеры проще реализовать, но создание дерева требует большого объема оперативной памяти, ведь размер документов XML не ограничен. Необходимость частого просмотра дерева замедляет работу парсера.

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

Такие парсеры сложнее в реализации, зато они не строят дерево в оперативной памяти и могут анализировать не весь документ, а его отдельные элементы вместе с вложенными в них элементами. Фактическим стандартом здесь стал свободно распространяемый набор классов и интерфейсов SAX (Simple API for XML Parsing, простой API для анализа XML), созданный Давидом Меггинсоном (David Megginson). Основной сайт этого проекта http://www.saxproject.org/. Сейчас применяется второе поколение этого набора, называемое SAX2. Набор SAX2 входит во многие парсеры, например, Xerces2.

В стандартную поставку Java 2 Standard Edition и Enterprise Edition входит JAXP — набор интерфейсов и классов для создания парсеров и преобразования документов XML. С помощью одной из частей этого набора, называемой DOM API (Document Object Model API, API объектной модели документов), можно создавать парсеры первого типа, создающие дерево объектов, а с помощью второй части набора JAXP, называемой SAX API, можно создавать SAX-парсеры. Интерфейсы и классы SAX2 собраны В пакеты org.xml.sax, org.xml.sax.ext, org.xml.sax.helpers, javax. xml. parsers. Рассмотрим их подробнее.

Анализ документов XML с помощью SAX2API

Основу SAX2 составляет интерфейс org.xml.sax.ContentHandler, СПИЫ- вающий методы обработки событий: начало документа, появление открывающего тега, появление тела элемента, появление закрывающего тега, окончание документа. При возникновении такого события SAX2 обращается к методу-обработчику события, передавая ему аргументы, содержащие информацию о событии. Дело разработчика — реализовать эти методы, обеспечив правильный анализ документа.

В начале обработки документа вызывается метод

public void startDocument () ;

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

При появлении символа начинающего открывающий тег, вызывается метод

public void startElement (String uri, String name, String qname, Attributes attrs) ;

В метод передается три имени, два из которых связаны с пространством имен: идентификатор пространства имен uri, имя тега без префикса name и расширенное имя с префиксом qname, а также атрибуты открывающего тега элемента attrs, если они есть. Если пространство имен не определено, то значения первого и второго аргумента null. Если нет атрибутов, то передается ссылка на пустой объект attrs.

При появлении символов "</", начинающих закрывающий тег, вызывается метод

public void endElement (String uri. String name, String qname) ;

При появлении строки символов вызывается метод public void characters (char [ ] ch, int start, int length);

В него передается массив символов ch, индекс начала строки символов start в этом массиве и количество символов length.

При появлении в тексте документа инструкции по обработке вызывается метод

public void processinglnstruction(String target, String data);

В метод передается имя программы-обработчика target и дополнительные сведения data.

При появлении пробельных символов, которые должны быть пропущены, вызывается метод

public void ignorableWhitespace (char [] ch, int start, int length);

В него передается массив ch идущих подряд пробельных символов, индекс начала символов в массиве start и количество символов length.

Интерфейс org.xml.sax.ContentHandler реализован классом org.xml.sax. helpers. DefaultHandler. В нем сделана пустая реализация всех методов. Разработчику остается реализовать только те методы, которые ему нужны.

Применим методы SAX2 для обработки нашей адресной книжки. Запись документа на языке XML удобна для выявления структуры документа, но неудобна для работы с документом в объектно-ориентированной среде. Поэтому чаще всего содержимое документа XML представляется в виде одного или нескольких объектов, называемых объектами данных JDO (Java Data Objects). Эта операция называется связыванием данных (data binding) с объектами

Свяжем содержимое нашей адресной книжки с объектами Java. Для этого сначала опишем классы Java, которые представят содержимое адресной книги.

Листинг 1.7. Класс, описывающим адрес

public class Address)

private String street, city, zip, type = "город"

public Address () {}

public String getStreet(){ return street; }

public void setStreet(String street){ this.street = street; }

public String getCity(){ return city; }

public void setCity(String city){ this.city = city; }

public String getZip(){ return zip; }

public void setZip(String zip){ this.zip = zip; }

public String getType(){ return type; }

public void setType (String type) { this. type = type; }

public String toString(){

return "Address: " + street + " " + city + " " + zip;

}

}

t Листинг 1.8. Класс, списывающий запись адресной кн                  i

public class Person{

private String firstName, s^eccncNare, surnames, birthday; private Vector address; private Vector workPhone ; private Vector home Phone;

public Person () {}

public Person (string firstNane, aring secondNane, axing surname) {

this. firstName = firstNane; this. secondName = secondNane; this, surname = surname;

}

public String getFirstName (){ return firstName; } public void setFirstNaroe(String firstName){

this.firstName = firstName;

}

public String getSecondName () { return secondName; } public void setSecondName (String secondName){ this.secondName = secondName;

}

public String getSurname(){ return surname; } public void setSurname(String surname){

= surname;

}

public String getBirthday(){ return birthday; } public void setBirthdaytString birthday)) this.birthday = birthday;

}

public void addAddress (Address addr) {

if (address == null) address = new Vector () address.add(addr);

}

public Vector getAddress (){ return address; }

public void removeAddress(Address addr){ if (address != null) address.remove(addr);

}

public void addWorkPhone(String phone){

if (workPhone == null) workPhone = new Vector () ; workPhone.add(new Integer(phone));

}

public Vector getWorkPhone(){ return workPhone; }

public void removeWorkPhone(String phone)( if (workPhone != null)

workPhone.remove(new Integer(phone));

}

public void addHomePhone(String phone)(

if (homePhone = null) homePhone = new Vector (); homePhone.add(new Integer(phone));

}

public Vector getHomePhone(){ return homePhone; }

public void removeHomePhone(String phone){

if (homePhone != null)

homePhone.remove(new Integer(phone));

}

public String toString() { return "Person: " + surname;

}

}

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

В листинге 1.9 приведен пример класса-обработчика NotebookHandler для адресной книжки, описанной в листинге 1.2. Методы класса NotebookHandler анализируют содержимое адресной книжки и помещают его в вектор, составленный из объектов класса Person, описанного в листинге 1.8.

‘ Листинг 1.9. Класс-обработчик документа XML

import org.xml.sax.*; import org.xml.sax.helpers.*; import j avax. xml. parsers . * ; import java.util.*; import j ava.io.*;

public class NotebookHandler extends DefaultHandler{ static final String   =

"http: //j ava. sun. com/xml / j axp/properties / schemaLanguage" ;

static final String W3C_XML_SCHEMA =

"http: //www.w3 .org/2001/XMLSchena";

Person person;

Address address;

static Vector pers = new vector () ;

boolean inBirthday, inStreet, inCity, inZip, inWorkPhone, inHomePhone;

public void startElement (String uri, String name, String qname, Attributes attrs)

throws SAXExcept ion{ if (qname.equals("name"))

person = new Person(attrs.getValue("first") ,

attrs.getValue("second") , attrs.getValue("surname")); else if (qname.equals("birthday"))

inBirthday = true; else if (qname.equals("address") )

address = new Address () ; else if (qname.equals("street") )

inStreet = true; else if (qname.equals("city")) { inCity = true;

if (attrs != null) address.setType(attrs.getValue("type")); }else if (qname.equals ("zip") )

inZip = true; else if (qn^e.equals ("work") )

inWorkPhone = true; else if (qname.equals("home"))

= true;

}

public void characters (char [] buf, int offset, int len) throws SAXException{

String s = new String(buf, offset, len) ;

if (inBirthday){

person.setBirthday(s);

inBirthday = false;

}else if (inStreet){

address.setStreet(s);

inStreet = false;

}else if (inCity){

address.setCity(s);

inCity = false;

}else if (inZip){

address.setzip(s);

inZip = false; }else if (inWorkPhone) {

person.addWorkPhone(s);

inWorkPhone = false;

}else if (inHomePhone){ person.addHomePhone(s);

inHomePhone = false;

}

}

public void endElement (String uri, String name, String qname) throws SAXException{

if (qname.equals("address")){

person.addAddress(address);

address = null;

(else if (qname.equals("person")){

pers.add(person);

person = null;

}

}

public static void             args){

if (args.length< 1) {

System.err.println("Usage: java Notebook ntb.xml") ; System.exit(1);

}

try{

NctebcckHandler handler = new NotebookHandler ()

SAXParserFactory fact = SAXParserFactory.newInstance () ; fact.setNamespaceAware(true); fact.setValidating(true);

SAXParser saxParser =

saxParser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);

File f = new saxParser.parse(f, handler);

for (int к = 0; к < pers.size(); k++)

System.out.println(((Person)pers.get(k)).getSurname());

}catch(SAXNotRecognizedException x) {

System.err.println("Неизвестное свойство: " +

JAXP_SCHEMA_LANGUAGE); System.exit(1);

}catch(Exception ex) {

System.err.println(ex);

}

}

public void warning (SAXParseException ex) {

System.err.println("Warning: " + ex) ; System.err.println("line = " + ex.getLineNumber() + " col = " + ex.getColumnNumberO);

}

public void error (SAXParseException ex) { System.err.println("Error: " + ex) ;

System.err.println("line = " + ex.getLineNumber() + " col = " + ex.getColumnNumber ());

}

public void fatalError(SAXParseException ex) {

System.err.println("Fatal error: " + ex); System.err.println("line = " + ex.getLineNumber() + " col = " + ex.getColumnNumber());

}

}

После того как класс-обработчик написан, проанализировать документ очень легко. Стандартные действия приведены в методе main о программы листинга 1.9.

Поскольку реализация парсера сильно зависит от его программного окружения, SAX-napcep — объект класса SAXParser — создается не конструктором, а фабричным методом newSAXParser () .

Объект-фабрика, в свою очередь, создается методом newinstance (). После создания объекта-фабрики можно методом void setFeature(String name, boolean value) установить свойства парсеров, создаваемых этой фабрикой. Например, после

fact.setFeature("http://xml.org/sax/features/namespace-prefixes", true);

парсеры, создаваемые фабрикой fact, будут учитывать префиксы имен тегов и атрибутов.

Список таких свойств можно посмотреть на сайте проекта SAX

Следует учитывать, что не все парсеры выполняют эти свойства.

Если к объекту-фабрике применить метод void setValidating(true), как это сделано в листинге 1.9, то она будет производить парсеры, проверяющие структуру документа. Если применить метод void setNamespaceAware(true), то объект-фабрика будет производить парсеры, учитывающие пространства имен.

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

В классе javax.xml.parsers. SAXParser есть десяток методов parse (). Кроме метода    использованного в листинге 1.9, есть еще методы, позволяющие извлечь документ из входного потока класса объекта класса адреса URI или из специально созданного источника класса inputsource.

Методом setProperty о можно задать различные свойства парсера. В листинге 1.9 этот метод использован для того, чтобы парсер проверял правильность документа с помощью схемы XSD. Эта возможность включена в JAXP, начиная с версии JAXP 1.2.

Если парсер выполняет проверки, то есть, применен метод setvalidating(true), то имеет смысл сделать развернутые сообщения об ошибках. Это предусмотрено интерфейсом ErrorHandler, Он различает предупреждения, ошибки и фатальные ошибки и описывает три метода, которые автоматически выполняются при появлении ошибки соответствующего вида:

public void warning (SMParserException ex) ; public void error (SAXParserException ex) ; public void fatalError (SAXParserException ex) ;

Класс DefaultHandler делает пустую реализацию этого интерфейса. При расширении этого класса можно сделать реализацию одного или всех методов интерфейса ErrorHandler. Пример такой реализации приведен в листинге 1.9. Класс SAXParserException хранит номер строки и столбца проверяемого документа, в котором замечена ошибка. Их можно получить методами getLineNumber () И getColumnNumber (), как сделано В листинге 1.9.

Литература:

Хабибуллин И. Ш. Разработка Web-служб средствами Java. — СПб.: БХВ-Петербург, 2003. — 400 с: ил.

По теме:

  • Комментарии