|
Использование Java XML-обработчиков
(Александр Печерский)
Использование Java XML-обработчиков
Internet Explorer, несмотря на мощную встроенную поддержку XML,
сегодня далеко не единственное средство, которое можно использовать для работы с новым
языком. Обработкой XML документа на стороне клиента или сервера может также заниматься
любой другой анализатор XML-документов, который конвертирует их в обычную HTML страницу
или извлекает из нее информацию для других приложений.
Что такое XML Parser?
Любой XML-процессор, являясь, по сути, транслятором языка разметки,
может быть разбит на несколько модулей, отвечающих за лексический, синтаксический и
семантический анализ содержимого документа. Понятно, что если бы мы были вынуждены каждый
раз писать все эти блоки самостоятельно, необходимость в XML как в таковом бы отпала -
основное его преимущество, как уже упоминалось ранее, заключается в стандартном способе
извлечения информации из документа. Синтаксически правильно составленный XML-документ
может быть разобран любым универсальным XML анализатором, и нашему XML-обработчику
остается лишь использовать полученные на его выходе "чистые" данные (прошедшие
синтаксический анализ) - интерпретировать содержимое документа, в соответствии с его
DTD-описанием или схемами данных.

Рис. 2 Иллюстрация механизма доступа к содержимому XML-документа
при помощи интерфейсов анализатора
Конечно, синтаксический анализатор может быть довольно легко
реализован и самостоятельно, например, в Perl, с его мощными возможностями обработки
регулярных выражений. Но в общем случае такой "ручной" способ является довольно
нетривиальной задачей, требующей некоторых усилий и является дополнительным источником
ошибок. Поэтому применение универсальных XML-анализаторов может существенно облегчить
жизнь разработчикам, тем более, что уже сегодня количество свободно доступных программ
такого рода довольно велико.
В функции современного XML-процессора обычно входит получение общих
сведений о документе, извлечение информации о его структуре и построения некоторой
абстрактной объектной модели данных, представляющей эту структуру. По способу проверки
разбираемых документов универсальные программы-анализаторы делятся на два типа:
верифицирующие, способные обнаружить DTD-описания грамматики языка и использовать их для
проверки документа на семантическую корректность; и неверифицирующие, не осуществляющие
такой проверки.
Описывая разобранный XML-документ, универсальная
программа-анализатор должна представить его структуру в виде упорядоченной модели данных,
для доступа к которой используется какая-то станадртная, описанная в соответствующей
спецификации библиотека классов - интерфейсов XML документа. На сегодняшний день
существует два подхода к их построению: собыйтийный - Simple API for XML, SAX и
объектно-ориентированный - DOM(Document Object Model). Рассмотрим их использование на
конкретных примерах.
Что такое SAX
Сегодня стандартным интерфейсом для большинства универсальных
XML-анализаторов является событийно-ориентированное API SAX - Simple API for
XML.
Термин событийно-ориентированный является ключевым в этом
определении и объясняет способ использования SAX. Каждый раз, когда при разборе XML
документа анализатор оказывается в каком-то новом состоянии - обнаруживает какую-либо
синтаксическую конструкцию XML-документа (элемент, символ, шаблон, и т.д.), фиксирует
начало, конец объявлений элементов документа, просматривает DTD-правила или находит
ошибку, он воспринимает его как произошедшее событие и вызывает внешнюю процедуру -
обработчик этого события. Информация о содержимом текущей конструкции документа
передается ему в качестве параметров функции. Обработчик события - это какой-то объект
приложения, который выполняет необходимые для обработки полученной из XML информации
действия и осуществляет таким образом непосредственный разбор содержимого. После
завершения этой функции управление опять передается XML-анализатору и процесс разбора
продолжается.
Реализацией этого механизма в Java SAX 1.0 является библиотека
классов org.xml.sax (их можно получить, например, с узла:
www.megginson.com, но обычно эти
классы включаются в состав XML -анализатора). Наследуя клссы SAX-совместимого
анализатора, мы получаем универсальный доступ к XML документу при помощи классов,
содержимое и механизм использование которых приведено в соответствующем
описании.
Последовательный разбор XML-документа SAX-обработчиком обычно
производится по следующей схеме (более подробное описание приведено ниже):
- загрузить документ, установить обработчики событий, начать просмотр его содержимого
(если есть DTD-описания, то - их разбор);
- найдено начало документа (его корневой, самый первый элемент) - вызвать виртуальную
функцию- обработчик события startDocument;
- каждый раз, когда при разборе будет найден открывающий тэг элемента вызывается
обработчик-функция startElement. В качестве параметров ей передаются
название элемента и список его атрибутов;
- найдено содержимое элемента - передать его соответствующему обработчику -
characters,
ignorableWhitespace,processingInstruction и т.д.;
- если внутри текущего элемента есть подэлементы, то эта процедура повторяется;
- найден закрывающий тэг элемента - обработать событие
endElement();
- найден закрывающий тэг корневого элемента -обработать событие
endDocument;
- если в процессе обработки были обнаружены ошибки, то анализатором вызываются
обработчики предупреждений (warning), ошибок (error) и критических ошибок обработчика
(fatalError).
Ссылка на объект класса обработчика событий может передаваться
объекту XML-анализатора при помощи следующих функций:
parser.setDocumentHandler(event_class); // -
обработчик событий документа
parser.setEntityResolver(event_class); // -
обработчик событий загрузки DTD-описаний
parser.setDTDHandler(event_class); // - обработчик
событий при анализе DTD-описаний
parser.setErrorHandler(event_class); // -
обработчик чрезвычайных ситуаций
Здесь event_class - объект созданного нами ранее класса.
Краткое описание некоторых из объектов-обработчиков событий
приведено в следующей таблице:
Объект DocumentHandler
|
startDocument() |
Начало документа |
|
endDocument() |
Конец документа |
|
startElement (String name, AttributeList atts) |
Начало элемента. Функции передается название
элемента(открывающий тэг) и список его атрибутов. |
|
endElement (String name) |
Конец элемента |
|
characters (char[] cbuf, int start, int len) |
Обработка массива текстовых символов |
|
ignorableWhitespace (char[] cbuf, int start, int len) |
Необрабатываемые символы |
|
processingInstruction (String target, String data) |
Обработка инструкций
XML-анализатора) |
Объект ErrorHandler
|
warning (SAXParseException e) |
Получение сообщения о "несерьезной" ошибке.
Пдробная информация содержится в передаваемом объекте класса
SAXParseException |
|
error (SAXParseException e) |
Сообщение об ошибке |
|
fatalError (SAXParseException e) |
Сообщение о критической
ошибке |
Для демонстрции использования этих методов рассмотрим небольшой
пример обработчика регистрационного XML-документа (его структура описана в примере 2
первого раздела статьи). Java-приложение выводит содержимое документа и информацию о его
структуре, путь к документу задается в командной строке. Для компилирования потребуется
JDK 1.1.4 и классы SAX, находящиеся либо в текущем пакете, либо вместе с другими классами
в classes.zip.
Пример 1.
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import com.ibm.xml.parsers.DOMParser;
import org.xml.sax.Parser;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.AttributeList;
import org.xml.sax.HandlerBase;
import org.xml.sax.helpers.ParserFactory;
class saxParser extends HandlerBase{
private PrintWriter out;
private int elements;
private int attributes;
private int characters;
private int ignorableWhitespace;
private String url;
public saxParser(String url_str) {
url = url_str;
try {
out = new PrintWriter(new OutputStreamWriter(System.out,
"koi8-r"));
}
catch (UnsupportedEncodingException e) {
}
}
//=======================================================
// Обработчики событий. Методы интерфейса DocumentHandler
//========================
// Начало документа
public void startDocument() {
// Статистика
elements = 0;
attributes = 0;
characters = 0;
ignorableWhitespace = 0;
// Процессорные инструкции
out.println("<?xml version=\"1.0\"
encoding=\"UTF-8\"?>");
}
// Конец документа
public void endDocument() {
out.flush();
}
// Встретился открывающий тэг элемента //
public void startElement(String name, AttributeList attrs) {
elements++;
if (attrs != null) {
attributes += attrs.getLength();
}
// Печать тэга элемента вместе со списком его атрибутов,
// например, <elem id="48">
out.print('<');
out.print(name);
if (attrs != null) {
int len = attrs.getLength();
for (int i = 0; i < len; i++) {
out.print(' ');
out.print(attrs.getName(i));
out.print("=\"");
out.print(attrs.getValue(i));
out.print('"');
}
}
out.println('>');
}
// Встретился закрывающий тэг элемента
public void endElement(String name) {
out.println("</"+name+">");
}
// Текстовые символы
public void characters(char ch[], int start, int length) {
characters += length;
out.println(new String(ch, start, length));
}
// Необрабатываемые символы(например, содержимое секции CDATA)
public void ignorableWhitespace(char ch[], int start, int length) {
characters(ch, start, length);
}
// Инструкции XML-процессору
public void processingInstruction (String target, String data) {
out.print("<?");
out.print(target);
if (data != null && data.length() > 0) {
out.print(' ');
out.print(data);
}
out.print("?>");
}
//===================================================
// Методы интерфейса ErrorHandler
//===============================
// Последнее предупреждение
public void warning(SAXParseException ex) {
System.err.println("Warning at "+
ex.getLineNumber()+" . "+
ex.getColumnNumber()+" - "+
ex.getMessage());
}
// Произошла ошибка
public void error(SAXParseException ex) {
System.err.println("Error at {"+
ex.getLineNumber()+" . "+
ex.getColumnNumber()+" - "+
ex.getMessage());
}
// Такие ошибки исправить уже нельзя
public void fatalError(SAXParseException ex) throws SAXException {
System.err.println("Fatal error at {"+
ex.getLineNumber()+" . "+
ex.getColumnNumber()+" - "+
ex.getMessage());
throw ex;
}
//=======================================================
// Вывести информацию о документе
//===============================
public void printInfo() {
System.out.println();
System.out.println("Документ "+url+"
был успешно обработан");
System.out.println("Элементов : "+elements);
System.out.println("Атрибутов : "+attributes);
System.out.println("Символов : "+characters);
}
}
//=======================================================
// Обработка XML документа
//========================
public class saxSample{
public static void main(String argv[]) {
try {
saxParser sample = new saxParser(argv[0]);
Parser parser = ParserFactory.makeParser
("com.ibm.xml.parsers.SAXParser");
parser.setDocumentHandler(sample);
parser.setErrorHandler(sample);
parser.parse(argv[0]);
sample.printInfo();
}
catch (Exception e) {
e.printStackTrace(System.err);
}
}
}
Комментарии
Первым шагом в процессе построения XML-обработчика является
создание объекта из класса анализатора (в нашем случае это классы из паекета
com.ibm.xml.parsers). Для этого можно использовать класс ParserFactory,
входящий в org.xml.sax.helpers:
import org.xml.sax.*;
...
Parser parser = ParseFactory.createParser();
...
Затем следует определить обработчики возникающих в процессе разбора
XML-документа событий. Приложению необязательно устанавливать все обработчики сразу - в
классе HandlerBase все события могут обрабатываться "по умолчанию". Более
подробную информацию по использованию SAX-анализаторов можно найти в примерах приложений
в пакетах анализатора или на сервере www.megginson.com. Комментарии, файлы приложений и
результатов их работы можно найти по адресу www.mrcpk.nstu.ru/xml/
DOM совместимые анализаторы
Другим способом представления внутренней структуры документа
являются DOM - интерфейсы. Как уже упоминалось, их реализацией занимаются разработчики
XML-анализатора, используя для этого возможности конкретного языка программирования.
Программисты на Java могут найти эти классы в библиотеке org.w3.dom. Наследуя виртуальные
методы DOM интерфейсов, классы анализатора предоставляют приложению стандартный способ
манипулирования структурой документа. В свою очередь, приложение, использующее
XML-анализатор, может не знать о способе реализации интерфейсов, ему доступна готовая
библиотека методов, при помощи которой он может производить поиск нужных фрагментов
документа, создавать, удалять и модифицировать его элементы.
Одним из доступных на сегодня DOM-совместимых наборов классов для
работы с документами является библиотека com.ibm.dom, входящая в состав XML анализатора
xml4j от IBM. Получить ее можно по адресу
www.alphaworks.ibm.com.
Принцип использования DOM интерфесов по сравнению с IE5 практически не изменился -
поменялись только названия объектов и методов. Их краткий обзор представлен в следующей
таблице.
|
Node |
Базовый интерфейс для остальных элементов объектной
модели XML, представляющий узел дерева структуры документа. |
|
Document |
Используется для получения информации о документе и
изменения его структуры. Это интерфейс представляет собой корневой элемент XML документа
и содержит методы доступа ко всему содержимому документа. При помощи методов объекта
Document в программе можно создавать дочерние объекты, представляющие различные
конструкции документа (например, createElement - создание элемента, createComment -
создание комментария, createTextNode - текстового фрагмента), удалять, перемещать,
добавлять объекты (removeChild, replaceChild, insertBefore, ...), перемещаться по дереву
элементов(getFirstChild, getLastChild, getNextSibling, getParentNode, getPreviousSibling,
...), получать элементы по их названию (getElementsByTagName,
:) и т.д. В объектной модели IE5 этот интерфейс доступен для
сценариев на JScript, VB через объект XMLDOMDocument |
|
Element |
Представляет элемент документа, определяя методы доступа
к его названию(getTagName, getElementsByTagName), атрибутам (getAttribute,
getAttributeNode, setAttribute, removeAttribute, : ) и дочерним элементам(appendChild,
getChildNodes, getFirstChild, ...). |
|
Attr |
Интерфейс, представляющий атрибут элемента. Имеет методы
для получения(getValue) и установления(setValue) значения атрибута. Хотя согласно
синтаксису XML атрибуты должны назначаться только элементам, в DOM возможно их создание
любым объектом, наследующим интерфейс Node. Поэтому можно создать атрибут для документа,
который будет находится в списке атрибутов, но не принадлежать ни одному из его
элементов. |
|
CharacterData |
Интерфейс, предоставляющий доступ к текстовым данным
документа. В XML документе к этому типу данных относятся комментарии, текстовое
содержимое элементов, секции CDATA. При помощи методов этого интерфейса можно добавлять,
удалять, редактировать данные(appendData, deleteData, replaceData, setData), получать
размер области текста (getLength) и извлекать текстовое содержимое(getData,
substringData, ...) |
|
Comments |
Интерфейс для доступа к тексту
комментариев |
|
Text |
Представляет текстовое содержимое
элемента |
|
CDATASection |
Интерфейс, представляющий секции CDATA - фрагментов
документа, заключенные в символы "[[" и "]]>", которые не
обрабатываются XML-анализатором и поэтому могут содержать символы,
"запрешенные" в спецификации XML. В эту область можно, к примеру, помещать
стилевые таблицы или JavaScript сценарии, используемые при отображении HTML
страницы. |
|
ProcessingInstruction |
Предоставляет доступ к т.н. области "инструкций
процессора", данные из которой используются XML-анализатором при разборе документа.
Доступ к этим данным возможен при помощи методо getData, setData и
getTarget |
|
Notation |
Определяет инструкцию DTD описания. Для получения ее
идентификаторов используются методы getPublicId и getSystemId . DOM Level 1 не
поддерживает прямого доступа к DTD декларациям по записи и сейчас они доступны лишь для
чтения (при помощи параметра nodeName интерфейса Node) |
В следующем примере демонстрируется использование DOM-объектов для
вывода содержимого XML документа в двух форматах - в виде дерева элементов и обычной HTML
страницы. Немного изменив пример, можно заставить программу сохранять выходной формат в
файле и мы получим таким образом обычный XML-HTML конвертор.
Пример 2.
/*
Пример использования DOM анализатора.
Демонстрируется возможность рекурсивного обхода дерева элементов,
создание новых элементов, фильтрация элементов (поиска по параметрам)
*/
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.*;
import org.w3c.dom.*;
import org.xml.sax.Parser;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.ParserFactory;
import com.ibm.xml.parsers.DOMParser;
public class logParser {
static String defaultParser = "com.ibm.xml.parsers.DOMParser";
static String urlLog;
static Document xmldoc = null;
static PrintWriter out;
/*
Конструктор нашего класса- обработчика.
В нем создается выходной поток для печати
*/
public logParser(String url){
urlLog = url;
try {
out = new PrintWriter(new OutputStreamWriter(System.out,
"koi8-r"));
}
catch (UnsupportedEncodingException e) {
System.err.println(e.toString());
}
}
public void parseDoc(){
parseDoc(defaultParser);
}
/*
Создание класса анализатора, обрабтка им XML-документа
и создание объектной модели документа
*/
public void parseDoc(String parserName){
try {
Parser parser = ParserFactory.makeParser(parserName);
parser.parse(urlLog);
// Получение указателя на корневой элемент документа
xmldoc = ((DOMParser)parser).getDocument();
}
catch (Exception e) {
System.err.println(e.toString());
}
}
//==================================================
// Вывод содержимого документа
// в виде форматированного списка XML- элементов
//========================
public void viewLogAsXML(){
try {
viewLogAsXML(xmldoc,"");
}
catch (Exception e) {
System.out.println(e.toString());
}
out.flush();
}
/*
Рекурсивный обход элементов документа, начиная с указанного
элемента node.
*/
public void viewLogAsXML(Node node,String offs){
if (node == null) {
return;
}
// Получение информации о типе текущего узла
int type = node.getNodeType();
switch (type) {
/* Если текщий узел - корневой элемент документа */
case Node.DOCUMENT_NODE: {
out.println("<?xml version=\"1.0\"
encoding=\"koi-8\"?>");
viewLogAsXML(((Document)node).getDocumentElement(),offs);
out.flush();
break;
}
/* Если текщий узел - элемент */
case Node.ELEMENT_NODE: {
out.print(offs+"<");
// Печать названия элемента
out.print(node.getNodeName());
// Получение списка атрибутов текущего элемента
NamedNodeMap attrs = node.getAttributes();
Node attr;
for (int i = 0; i < attrs.getLength(); i++) {
attr = attrs.item(i);
out.print(' ');
out.print(attr.getNodeName()+"=\""+
attr.getNodeValue()+"\"");
}
out.println('>');
// Получение списка дочерних элементов
NodeList children = node.getChildNodes();
// Если у текщего элемента есть дочерние, то выводим и их
if (children != null) {
int len = children.getLength();
for (int i = 0; i < len; i++) {
viewLogAsXML(children.item(i),offs+" ");
}
}
break;
}
/* Если текщий узел - текстовый */
case Node.TEXT_NODE: {
out.println(offs+node.getNodeValue());
break;
}
}
// Печать закрывающего тэга элемента
if (type == Node.ELEMENT_NODE) {
out.print(offs+"</");
out.print(node.getNodeName());
out.println('>');
}
}
//=======================================================
// Вывод в формате HTML
//=====================
/* Вызов рекурсивного обходчика */
public void viewLog(){
// Header
viewAsHTML("All log records:");
try {
// Вывод содержимого
viewLog(null);
}
catch (Exception e) {
System.out.println(e.toString());
}
// Header
viewAsHTML();
}
/* Печать только сообщений об ошибках */
public void viewErrors(){
// Header
viewAsHTML("Log errors:");
try {
// Вывод содержимого
viewLog("error");
}
catch (Exception e) {
System.out.println(e.toString());
}
// Footer
viewAsHTML();
}
/*
Рекурсивный обход элементов, у которых атрибут type равен заданному.
*/
public int viewLog(String type){
int i=0;
int elemNum=0;
int messageCount=0;
Element elem;
NodeList elements;
elements = xmldoc.getElementsByTagName("event");
if(elements==null) System.out.println
("Empty element collection");
elemNum = elements.getLength();
if (type == null) {
for (i = 0; i < elemNum; i++) {
if(elements.item(i)==null) System.out.println
("Empty element");
viewLogMessage((Element)elements.item(i));
}
messageCount=elemNum;
}
else {
for (i = 0; i < elemNum; i++) {
elem = (Element)elements.item(i);
if(elem.getAttribute("type")==type){
messageCount++;
viewLogMessage(elem);
}
}
}
return messageCount;
}
/* Печать заголовка таблицы */
public void viewAsHTML(String title){
out.println("<html>");
out.println("<head><title>Log parser sample</title>
</head>");
out.println("<body><br><b>"+title+"</b>
<hr>");
out.println("<table cellspacing=\"2\" cellpadding=\"2\"
border=\"1\" width=\"600\">");
out.println("<tr bgcolor=\"silver\"><th>IP</th>
<th>Date</th>
<th>Method</th><th>
Request</th><th>Response</th></tr>");
}
/* Печать комментариев к таблице */
public void viewAsHTML(){
Date d = new Date();
String date = new String(""+d.getHours()+":"+
d.getMinutes()+":"+d.getSeconds());
out.println("</table><hr>generated by logParser at
<i>"+date+"</i><br>
</body></html>");
out.flush();
}
/* Форматированный вывод содержимого элемента event */
public void viewLogMessage(Element elem){
/*
Получение текста внутри элемента - обращаемся к первому
дочернему узлу (им должен оказаться текст) и получаем его
значение, используя метод getNodeValue() интерфейса Node
*/
String str_from=(elem.getElementsByTagName("ip-from")).
item(0).getFirstChild().getNodeValue();
String str_method=(elem.getElementsByTagName("method")).
item(0).getFirstChild().getNodeValue();
String str_to=(elem.getElementsByTagName("url-to")).
item(0).getFirstChild().getNodeValue();
String str_result=(elem.getElementsByTagName("response")).
item(0).getFirstChild().getNodeValue();
out.println("<tr><td>"+str_from+"<
/td><td>"+elem.getAttribute("date")+"
</td><td>"+str_method+"
</td><td>"+str_to+"
</td><td>"+str_result+"</td></tr>");
}
//=======================================================
// Модификация дерева элементов
//=============================
public void logMessage(String result, String datetime,
String method, String ipfrom, String urlto, String response){
if(xmldoc==null) return;
Element root = xmldoc.getDocumentElement();
Element log_elem = xmldoc.createElement("event");
log_elem.setAttribute("result",result);
log_elem.setAttribute("date",datetime);
Element elem;
Text elem_value;
elem = xmldoc.createElement("method");
elem_value = xmldoc.createTextNode(method);
elem.appendChild(elem_value);
log_elem.appendChild(elem);
elem = xmldoc.createElement("ip-from");
elem_value = xmldoc.createTextNode(ipfrom);
elem.appendChild(elem_value);
log_elem.appendChild(elem);
elem = xmldoc.createElement("url-to");
elem_value = xmldoc.createTextNode(urlto);
elem.appendChild(elem_value);
log_elem.appendChild(elem);
elem = xmldoc.createElement("response");
elem_value = xmldoc.createTextNode(response);
elem.appendChild(elem_value);
log_elem.appendChild(elem);
root.appendChild(log_elem);
}
//=======================================================
// Пример использования методов класса logParser
//==============================================
public static void main(String argv[]) {
/*
Создание объекта анализатора. В качестве параметра ему
передается название документа
(можно и через командную строку, конечно...)
*/
logParser log_file = new logParser("log.xml");
// Анализ документа
log_file.parseDoc();
// Что с ним делать
if (argv.length == 0) {
log_file.viewLogAsXML();
System.exit(0);
}
for (int i = 0; i < argv.length; i++) {
String arg = argv[i];
if (arg.startsWith("-")) {
if (arg.equals("-vx")) {
log_file.viewLogAsXML();
break;
}
if (arg.equals("-va")) {
log_file.viewLog();
break;
}
if (arg.equals("-ve")) {
log_file.viewErrors();
break;
}
if (arg.equals("-h")) {
usage();
}
}
}
log_file.logMessage("success","12",
"GET","127.0.0.1",".
/index.html","200");
log_file.viewLogAsXML();
}
private static void usage() {
System.err.println("usage: java logParser (options)");
System.err.println();
System.err.println("options:");
System.err.println(" -vx View result as XML tree (default)");
System.err.println(" -va View all messages as HTML page");
System.err.println(" -ve View only errors as HTML page");
System.err.println(" -h View help ");
}
}
Комментарии
Более подробные комментарии, файлы приложений и результатов их
работы можно найти по адресу www.mrcpk.nstu.ru/xml/
|