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


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

Введение в SAX

Nicholas Chase

29 июля 2003

Содержание

  1. Введение
  2. DOM, SAX и, когда каждый из них применяется
  3. Создание парсера SAX
  4. Обработчики событий и события SAX
  5. SAX и пространства имен
  6. Работа с потоком SAX
  7. Итоги по SAX
  8. Ресурсы

 

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

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

В этом учебнике исследуется Simple API for XML версии 2.0.x или SAX 2.0.x.

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

Предварительные замечания: SAX доступен во многих языках программирования, таких, как Java, Perl, C++ и Python. Этот учебник использует для демонстраций язык Java, но концепции в разных языках, по существу, одинаковы, и вы можете получить понимание SAX, даже не работая на самом деле с этими примерами.

Что такое SAX?

Стандартным средством для чтения и манипулирования XML-файлами является Объектная Модель Документа, Document Object Model (DOM). К сожалению, этот метод, который включает в себя чтение всего файла и сохранение его в структуре дерева, может быть малоэффективным, медленным и требовать много ресурсов.

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

SAX был разработан членами почтовой рассылки XML-DEV, а его Java-версия является сейчас проектом SourceForge (см. Ресурсы). Целью проекта было обеспечение более естественного средства для работы с XML - другими словами, такого, которое не включает в себя таких расходов и концептуальных сложностей, каких требует DOM.

Результатом был API, который является событийно-базированным. Парсер посылает события, такие, как начало и окончание элемента, в обработчик событий, который обрабатывает информацию. Затем с данными имеет дело само приложение. Исходный документ остается без изменений, но SAX обеспечивает средства для манипулирования данными, которые затем могут быть направлены в другой процесс или документ.

SAX не имеет держателя стандарта, он не поддерживается консорциумом World Wide Web (W3C) или каким-то другим официальным держателем, но де-факто является стандартом в сообществе XML.

Инструменты

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

 

Раздел 2. DOM, SAX и, когда каждый из них применяется

Как работает обработка в SAX

SAX анализирует поток XML и проходит через, как по телетайпной ленте. Рассмотрим следующий XML-код:

<?xml version="1.0"?>
<samples>
 <server>UNIX</server>
 <monitor>color</monitor>
</samples>

Процессор SAX, анализирующий этот код, будет генерировать, как правило, следующие события:

Начало документа
Начало элемента (samples)
Символы (пропуск)
Начало элемента (server)
Символы (UNIX)
Конец элемента (server)
Символы (пропуск)
Начало элемента (monitor)
Символы (color)
Конец элемента (monitor)
Символы (пропуск)
Конец элемента (samples)

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

Обработка в SAX включает в себя следующие шаги:

  1. Создание обработчика событий.
  2. Создание парсера SAX.
  3. Назначение обработчика событий для парсера.
  4. Разбор документа с посылкой каждого события в обработчик.

За и против событийно-базированной обработки

Преимущества этого вида обработки очень похожи на преимущества потоковых носителей. Анализ может начинаться немедленно, а не ожидать всех данных для обработки. Также поскольку работает с данными по мере их поступления, не требуется сохранять их в памяти. Это огромное преимущество, когда поступают большие документы. Фактически, приложение даже не должно разбирать весь документ; оно может остановиться, когда будут удовлетворены некоторые критерии. В общем случае SAX также значительно быстрее, чем его альтернатива, DOM.

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

DOM и обработка на базе дерева

DOM является традиционным способом обработки XML-данных. При применении DOM данные загружаются в память в древовидную структуру.

Например, тот же документ, который использовался как пример в Как работает обработка в SAX, может быть представлен в виде узлов, показанных ниже:

Прямоугольники представляют элементные узлы, а овалы - текстовые узлы.

DOM использует отношения предок-потомок. Например, в данном случае samples является корнем с пятью потомками: тремя текстовыми узлами (пропуски) и двумя элементными узлами, server и monitor.

Важно представлять себе, что узлы server и monitor на самом деле имеют значения null. Вместо этого они содержат текстовые узлы (UNIX и color) в качестве потомков.

За и против обработки на базе дерева

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

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

Как выбрать между SAX и DOM

Использовать вам DOM или SAX, зависит от нескольких факторов:

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

 

Раздел 3. Создание парсера SAX

Файл примера

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

Вот форма этой анкеты:

Ответы записаны в XML-файл:

<?xml version="1.0"?>
 <surveys>
  <response username="bob">
   <question subject="appearance">A</question>
   <question subject="communication">B</question>
   <question subject="ship">A</question>
   <question subject="inside">D</question>
  <question subject="implant">B</question>
  </response>
  <response username="sue">
   <question subject="appearance">C</question>
   <question subject="communication">A</question>
   <question subject="ship">A</question>
   <question subject="inside">D</question>
   <question subject="implant">A</question>
  </response>
  <response username="carol">
   <question subject="appearance">A</question>
   <question subject="communication">C</question>
   <question subject="ship">A</question>
   <question subject="inside">D</question>
   <question subject="implant">C</question>
  </response>
</surveys>

Создание обработчика событий

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

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

Когда создается новый объект, ищется конструктор класса для его выполнения. Например:

import org.xml.sax.helpers.DefaultHandler;
public class SurveyReader extends DefaultHandler
{
  public SurveyReader() {
    System.out.println("Object Created.");
  }
  public void showEvent(String name) {
    System.out.println("Hello, "+name+"!");
  }
  public static void main (String args[]) {
    SurveyReader reader = new SurveyReader();
    reader.showEvent("Nick");
  }
}

Когда выполняется метод main, он создает новый экземпляр класса SurveyReader. Это приводит к выполнению конструктора и выводу Object Created (вместе с приветствием ниже). Вы можете затем использовать этот объект, чтобы выполнять метод showEvent().

Непосредственное задание драйвера SAX

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

Если вы знаете имя класса драйвера SAX, вы можете вызвать драйвер непосредственно. Например, если это класс (на самом деле не существующий) com.nc.xml.SAXDriver, вы можете применить такой код:

  try {
    XMLReader xmlReader = new com.nc.xml.SAXDriver();
  } catch (Exception e) {
    System.out.println("Can't create the parser: " + e.getMessage());
  }

чтобы непосредственно создать XMLReader.

Вы можете также использовать системные свойства, чтобы сделать свое приложение более гибким. Например, вы можете задать имя класса как значение свойства org.xml.sax.driver из командной строки, когда вы запускаете приложение:

java -Dorg.xml.sax.driver=com.nc.xml.SAXDriver SurveyReader

(Заметьте, опция -D не допускает пробела после нее.)

Это делает информацию доступной для класса XMLReaderFactory, так что вы можете сказать:

  try {
    XMLReader xmlReader = XMLReaderFactory.createXMLReader();
  } catch (Exception e) {
    System.out.println("Can't create the parser: " + e.getMessage());
  }

Если вы знаете имя драйвера, вы можете также передать его непосредственно как аргумент для createXMLReader().

Создание парсера

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

Сначала объявите xmlReader типа XMLReader, а затем используйте SAXParserFactory для создания SAXParser. Именно SAXParser дает вам XMLReader.

import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.XMLReader; 
public class SurveyReader extends DefaultHandler
{
  public SurveyReader() {
  }
  public static void main (String args[]) {
    XMLReader xmlReader = null;
    try {
      SAXParserFactory spfactory = SAXParserFactory.newInstance();
      SAXParser saxParser = spfactory.newSAXParser();
      xmlReader = saxParser.getXMLReader();
    } catch (Exception e) {
      System.err.println(e);
      System.exit(1);
    }
  }
}

Проверяющий парсер против непроверяющего

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

Есть два вида парсеров: проверяющие и непроверяющие.

Документы, которые должны проверяться проверяющим парсером, называются правильными документами.

Установка опций проверки правильности

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

...
public static void main (String args[]) {
  XMLReader xmlReader = null;
  try {
    SAXParserFactory spfactory =
        SAXParserFactory.newInstance();
    spfactory.setValidating(false);
    SAXParser saxParser =
        spfactory.newSAXParser();
    xmlReader = saxParser.getXMLReader();
  } catch (Exception e) {
    System.err.println(e);
    System.exit(1);
  }
}
...

Установка обработчика содержимого

Парсер должен посылать свои события в ContentHandler. Для простоты SurveyReader является и главным приложением, и обработчиком событий, так что создайте его новый экземпляр и установите его как ContentHandler, используя метод setContentHandler() класса XMLReader:

...
    xmlReader = saxParser.getXMLReader();
    xmlReader.setContentHandler(new SurveyReader());
  } catch (Exception e) {
...

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

Разбор InputSource

Чтобы на самом деле разобрать файл (или что-то еще в этом роде), вам нужен InputSource. Этот класс SAX является оболочкой для любых данных, которые вы собираетесь обрабатывать, так что вам не надо беспокоиться (слишком сильно) о том, откуда они поступают.

Теперь вы готовы к тому, чтобы разобрать файл. Метод parse() принимает файл в оболочке InputSource и обрабатывает его, посылая каждое событие в ContentHander.

...
import org.xml.sax.InputSource; 
...
    xmlReader = saxParser.getXMLReader();
    xmlReader.setContentHandler(new SurveyReader());
    InputSource source = new InputSource("surveys.xml");
    xmlReader.parse(source); 
  } catch (Exception e) {
...

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

Установка ErrorHandler

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

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

Установите новый ErrorHandler точно так же, как вы установили ContentHandler:

...
  xmlReader.setContentHandler(new SurveyReader());
  xmlReader.setErrorHandler(new SurveyReader());
  InputSource source = new InputSource("surveys.xml");
...

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

 

Раздел 4. Обработчики событий и события SAX

startDocument()

Теперь, когда вы сделали все установки для разбора документа, пришло время заменить реализации по умолчанию, которые являются частью класса DefaultHandler, методами, которые на самом деле что-то делают, когда обработчик получает соответствующее событие. Начните с того, что в начале документа ничего не делается при помощи события startDocument(). Это событие, как и другие события SAX, выбрасывает SAXException:
...
import org.xml.sax.SAXException; 
public class SurveyReader extends DefaultHandler
{
  public SurveyReader() {
  }
  public void startDocument() throws SAXException {
    System.out.println("Tallying survey results...");
  }
  public static void main (String args[]) {
    XMLReader xmlReader = null;
...

startElement()

Теперь начнем рассмотрение действительно данных. Для каждого элемента пример возвращает имя, которое передается в событие startElement() (см. копию экрана для списка элементов, показанную ниже.).

На самом деле парсер передает несколько частей информации для каждого элемента:

Начнем с перечисления имен всех элементов:

...
import org.xml.sax.Attributes; 
public class SurveyReader extends DefaultHandler
{
...
  public void startDocument() throws SAXException {
    System.out.println("Tallying survey results...");
  }
  public void startElement(String namespaceURI, String localName,
        String qName, Attributes atts) throws SAXException {
    System.out.print("Start element: ");
    System.out.println(qName);
  }
  public static void main (String args[]) {
...

startElement(): выборка атрибутов

Событие startElement() также обеспечивает доступ к атрибутам элемента. Они передаются внутри структуры данных Attributes.

Вы можете выбрать значение атрибута по его позиции в массиве или по имени атрибута:

...
  public void startElement(String namespaceURI, String localName,
        String qName, Attributes atts) throws SAXException {
    System.out.print("Start element: ");
    System.out.println(qName);
    for (int att = 0; att < atts.getLength(); att++) {
      String attName = atts.getQName(att);
      System.out.println(" " + attName + ": " + atts.getValue(attName));
    }
  }
...

endElement()

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

...
  int indent = 0; 
  public void startDocument() throws SAXException {
    System.out.println("Tallying survey results...");
    indent = -4; 
  }
  public void printIndent(int indentSize) {
    for (int s = 0; s < indentSize; s++) {
      System.out.print(" ");
    }
  }
  public void startElement(String namespaceURI, String localName,
        String qName, Attributes atts) throws SAXException {
    indent = indent + 4;
    printIndent(indent); 
    System.out.print("Start element: ");
    System.out.println(qName);
    for (int att = 0; att < atts.getLength(); att++) {
      printIndent(indent + 4); 
      String attName = atts.getLocalName(att);
      System.out.println(" " + attName + ": " + atts.getValue(attName));
    }
  }
  public void endElement(String namespaceURI, String localName, String qName)
        throws SAXException {
    printIndent(indent);
    System.out.println("End Element: "+localName);
    indent = indent - 4;
  }
...

characters()

Теперь, когда вы получили элемент, пойдем дальше и выберем его данные при помощи characters(). Взглянем на сигнатуру этого метода:

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

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

Заметьте две важные вещи:

Это всегда учитывается проверяющим парсером.

...
  public void printIndent(int indentSize) {
    for (int s = 0; s < indentSize; s++) {
      System.out.print(" ");
    }
  }
  String thisQuestion = "";
  String thisElement = "";
  public void startElement(String namespaceURI, String localName,
        String qName, Attributes atts) throws SAXException {
    if (qName == "response") {
      System.out.println("User: " + atts.getValue("username"));
    } else if (qName == "question") {
      thisQuestion = atts.getValue("subject");
    }
  thisElement = qName; 
  }
  public void endElement(String namespaceURI, String localName, String qName)
        throws SAXException {
    thisQuestion = "";
    thisElement = "";
  }
  public void characters(char[] ch, int start, int length)
        throws SAXException {
    if (thisElement == "question") {
      printIndent(4);
      System.out.print(thisQuestion + ": ");
      System.out.println(new String(ch, start, length));
    }
  }
...

Учет ответов

Теперь, когда вы получили данные, пойдем дальше и произведем подсчет.

Вот пример того, как строить строки для анализа, когда опрос закончен.

...
  String appearance = null;
  String communication = null;
  String ship = null;
  String inside = null;
  String implant = null; 
  public void characters(char[] ch,
        int start,
        int length)
        throws SAXException {
    if (thisElement == "question") {
      if (thisQuestion.equals("appearance")) {
      appearance = appearance + new String(ch, start, length);
    }
    if (thisQuestion.equals("communication")) {
      communication = communication + new String(ch, start, length);
    }
    if (thisQuestion.equals("ship")) {
      ship = ship + new String(ch, start, length);
    }
    if (thisQuestion.equals("inside")) {
      inside = inside + new String(ch, start, length);
    }
    if (thisQuestion.equals("implant")) {
      implant = implant + new String(ch, start, length);
    }
  }
}
...

Важно помнить, что единственной причиной, по которой вы можете выполнить это в методе characters(), является то, что ответы, которые вы рассматриваете, имеют длину только в один символ. Если содержимое, которое вы рассматриваете, длиннее, вы должны собрать данные из каждого вызова вместе и анализировать их в конце элемента, в методе endElement().

ignorableWhitespace()

XML-документы, выработанные людьми (в отличие от выработанных программами) часто содержат пропуски, добавляемые для того, чтобы сделать документ более легким для чтения. Под пропусками подразумеваются переводы строк, символы табуляции и пробелы. В большинстве случаев пропуски являются лишними и должны игнорироваться при обработке данных.

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

Но что, если вам действительно нужны пропуски? В таком случает вы устанавливаете в элементе атрибут, который сигнализирует процессору о том, что не нужно игнорировать символы пропусков. Этот атрибут - xml:space, и он обычно предполагается default. (Это значит, что поведение процессора по умолчанию состоит в игнорировании пропусков.)

Чтобы сообщить процессору, что не нужно игнорировать пропуски, установите его значение в preserve так:

<code-snippet xml:space="preserve">
 <line> public void endElement(</line>
 <line> String namespaceURI,</line>
 <line> String localName,</line>
 <line> String qName)</line>
 <line> throws SAXException</line>
</code-snippet>

endDocument()

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

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

...
      if (thisQuestion.equals("implant")) {
        implant = implant + new String(ch, start, length);
      }
    }
  }
  public int getInstances (String all,
        String choice) {
...
    return total;
  }
  public void endDocument() {
    System.out.println("Appearance of the aliens:");
    System.out.println("A: " + getInstances(appearance, "A"));
    System.out.println("B: " + getInstances(appearance, "B"));
...
  }
  public static void main (String args[]) {
...

processingInstruction()

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

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="survey.xsl" version="1.0" type="text/xsl" ?> 
<surveys>
...

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

<?xml version="1.0" encoding="UTF-8"?>
<?SurveyReader factor="2" ?> 
<surveys>
...

Эта запись будет подхвачена событием processingInstruction(), которое разделит ее на target и data:

...
  public void processingInstruction(String target, String data)
        throws SAXException {
    System.out.println("Target = ("+target+")");
    System.out.println("Data = ("+data+")");
  }
...

События ErrorHandler

Так же, как ContentHandler имеет предопределенные события для обработки содержимого, ErrorHandler имеет предопределенные события для обработки ошибок. Поскольку вы определили в качестве и обработчика ошибок, и обработчика содержимого SurveyReader, вам нужно заместить реализацию этих методов по умолчанию.

Вам нужно побеспокоиться о трех событиях: warning, error и fatalError:

...
import org.xml.sax.SAXParseException; 
public class SurveyReader
        extends DefaultHandler
{
  public SurveyReader() {
  }
  public void error (SAXParseException e) {
    System.out.println("Error parsing the file: "+e.getMessage());
  }
  public void warning (SAXParseException e) {
    System.out.println("Problem parsing the file: "+e.getMessage());
  }
  public void fatalError (SAXParseException e) {
    System.out.println("Error parsing the file: "+e.getMessage());
    System.out.println("Cannot continue.");
    System.exit(1);
  }
...

 

Раздел 5. SAX и пространства имен

Пространства имен

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

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

Например, я работаю в офисе и у меня то же имя, что и у клиента. Если я где-то в офисе, и секретарь говорит: "Ник, возьми трубку на 1-й линии", - каждый понимает, что она имеет в виду меня, потому что я нахожусь в "пространстве имен офиса". Аналогично, если она говорит "Ник звонит по 1-й линии", - каждый знает, что она говорит о клиенте, потому что звонящий находится вне пространства имен офиса.

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

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

Создание пространства имен

Поскольку идентификаторы для пространств имен должны быть уникальными, они обозначаются при помощи Унифицированных Идентификаторов Ресурсов, Uniform Resource Identifiers или URI. Например, пространство имен по умолчанию для данных примера будет обозначено при помощи атрибута xmlns:

<?xml version="1.0"?>
 <surveys xmlns="http://www.nicholaschase.com/surveys/" >
  <response username="bob">
  <question subject="appearance">A</question>
...

Любые элементы, для которых не указано пространство имен, находятся в пространстве имен по умолчанию, http://www.nicholaschase.com/orderSystem.html. В действительности сам URI ничего не означает. Информация может находиться или не находиться по этому адресу, но что важно, так это то, что он уникален.

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

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

Определение пространств имен

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

Пространство имен вместе с алиасом создаются обычно (но не обязательно) в корневом элементе документа. Этот алиас используется как префикс для элементов и атрибутов - при необходимости, если используется более одного пространства имен, - чтобы задать правильное пространство имен.

Рассмотрим код, приведенный ниже:

<?xml version="1.0"?>
 <surveys xmlns="http://www.nicholaschase.com/surveys/"
        xmlns:revised="http://www.nicholaschase.com/surveys/revised/">
  <response username="bob">
   <question subject="appearance">A</question>
   <question subject="communication">B</question>
   <question subject="ship">A</question>
   <question subject="inside">D</question>
   <question subject="implant">B</question>
   <revised:question subject="appearance">D</revised:question>
   <revised:question subject="communication">A</revised:question>
   <revised:question subject="ship">A</revised:question>
   <revised:question subject="inside">D</revised:question>
   <revised:question subject="implant">A</revised:question> 
  </response>
  <response username="sue">
...

Пространство имен и алиас, revised, использованы для создания дополнительного элемента question.

Помните, что revised - не пространство имен, а алиас! Настоящее пространство имен - http://www.nicholaschase.com/surveys/revised/.

Проверка пространств имен

Версия SAX 2.0 добавляет функциональность распознавания разных пространств имен, как было вкратце отмечено а startElement().

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

Поскольку ответы не будут учитываться, пока thisElement не "question", вы должны просто выполнить проверку перед установкой переменной.

...
  public void startElement(
        String namespaceURI,
        String localName,
        String qName,
        Attributes atts)
        throws SAXException {
    if (namespaceURI ==
          "http://www.nicholaschase.com/surveys/") {
      if (localName == "question") {
        thisQuestion = atts.getValue("subject");
      }
    }
  thisElement = localName;
  }
...

Заметьте, что приложение рассматривает URI пространства имен (или в данном случае URL), а не алиас.

Пространства имен на атрибутах

Атрибуты тоже могут принадлежать к определенному пространству имен. Например, если имя вопроса изменяется второй раз, вы можете добавить второй относящийся к нему атрибут, revised:subject, так:

<?xml version="1.0"?>
<surveys xmlns="http://www.nicholaschase.com/surveys/"
        xmlns:revised="http://www.nicholaschase.com/surveys/revised/">
 <response username="bob">
  <question subject="appearance">A</question>
  <question subject="communication">B</question>
  <question subject="ship">A</question>
  <question subject="inside">D</question>
  <question subject="implant">B</question>
  <revised:question subject="appearance"
  revised:subject="looks" >D</revised:question>
  <revised:question subject="communication">A</revised:question>
  <revised:question subject="ship">A</revised:question>
  <revised:question subject="inside">D</revised:question>
  <revised:question subject="implant">A</revised:question>
 </response>
 <response username="sue">
...

Несколько странно, но атрибуты никогда не находятся в пространстве имен по умолчанию. Даже если Even было объявлено пространство имен по умолчанию, атрибут без префикса рассматривается как вообще не имеющий пространства имен. Это означает, что response находится в пространстве имен http://www.nicholaschase.com/surveys/, но его атрибут username - нет. Эта странность определена в самой Рекомендации XML.

Список Attributes имеет методы, которые позволяют вам определять пространство имен атрибута. Эти методы, getURI() и getQName, используются так же, как методы qname и localName для самого элемента.

Странности пространств имен

Способ, которым парсер SAX обрабатывает локальные имена и квалифицированные имена, может быть немного странным. Например, парсер Java по умолчанию не будет сообщать о значениях локальных имен, если специально не включена обработка пространств имен:

...
    try {
      SAXParserFactory spfactory = SAXParserFactory.newInstance();
      spfactory.setValidating(false);
      spfactory.setFeature("http://xml.org/sax/features/namespace-prefixes",
                true);
      spfactory.setFeature("http://xml.org/sax/features/namespaces",
                true); 
      SAXParser saxParser = spfactory.newSAXParser();
...

Если у вас возникают трудности с получением информации, попробуйте включить это свойство парсера.

 

Раздел 6. Работа с потоком SAX

Сериализация потока SAX

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

Вывод потока событий SAX в файл называется его сериализацией. Вы можете написать файл сами, но гораздо легче просто объект Serializer:

import org.apache.xalan.serialize.Serializer;
import org.apache.xalan.serialize.SerializerFactory;
import org.apache.xalan.templates.OutputProperties;
import java.io.FileOutputStream; 
...
  public static void main (String args[]) {
    XMLReader xmlReader = null;
    try {
      SAXParserFactory spfactory = SAXParserFactory.newInstance();
      spfactory.setValidating(false);
      SAXParser saxParser = spfactory.newSAXParser();
      xmlReader = saxParser.getXMLReader();
      Serializer serializer = SerializerFactory.getSerializer(
            OutputProperties.getDefaultMethodProperties("xml"));
            serializer.setOutputStream(new FileOutputStream("output.xml"));
            xmlReader.setContentHandler(
            serializer.asContentHandler()
            );
      InputSource source = new InputSource("surveys.xml");
      xmlReader.parse(source);
    } catch (Exception e) {
      System.err.println(e);
      System.exit(1);
    }
  }
...

Создайте объект Serializer - допустимыми значениями для OutputProperties являются xml, text и html - и установите его OutputStream. Этот поток может быть виртуально любым объектом потокового типа, таким как файл (как здесь) или System.out.

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

XMLFilters

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

Эту проблему решают XMLFilter. Хотя они являются новшеством версии SAX 2.0, tони на самом деле использовались и в версии 1.0 умными программистами, которые реализовывали их путем соединения потоков SAX в цепочку и манипулирования ими прежде, чем они достигнут финального приемника.

В основном это работает таким образом:

  1. Создайте XMLFilter. Обычно это - отдельный класс.
  2. Создайте экземпляр XMLFilter и установите его предком XMLReader, который будет обычным образом разбирать файл.
  3. Установите обработчик содержимого фильтра как обычный обработчик содержимого.
  4. Разберите файл. Фильтр находится между XMLReader и обработчиком содержимого.

Создание фильтра

Теперь вы хотите создать фильтр, который уничтожает исходные ответы и вместо них использует исправленные ответы. Чтобы сделать это, вам нужно стереть оригиналы, а затем изменить пространство имен на исправленные ответы, так что SurveyReader сможет подхватить их.

Это реализовано путем создания нового класса, который расширяет XMLFilterImpl.

Взгляните, что происходит здесь. Когда происходит событие startElement(), оно проверяет URI исходного пространства имен. Если это элемент revised, пространство имен изменяется на пространство имен по умолчанию. Если нет, имя элемента изменяется так, что процедура подсчета (в SurveyReader) не будет распознавать его как вопрос и, следовательно, не будет учитывать ответ.

Эти измененные данные передаются в startElement() для предка (исходного XMLReader), и ими занимается его обработчик содержимого.

import org.xml.sax.helpers.XMLFilterImpl;
import org.xml.sax.XMLReader;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
public class SurveyFilter extends XMLFilterImpl
{
  public SurveyFilter ()
  {}
  public SurveyFilter (XMLReader parent)
  {
    super(parent);
  }
  public void startElement (String uri,
        String localName,
        String qName,
        Attributes atts)
        throws SAXException
  {
    if (uri == "http://www.nicholaschase.com/surveys/revised/") {
      uri = "http://www.nicholaschase.com/surveys/";
      qName = "question";
    } else {
      localName = "REJECT";
    }
    super.startElement(uri, localName, qName, atts);
  }
}

Вызов фильтра

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

Затем установите обработчики содержимого и ошибок фильтра, отличные от обработчиков XMLReader.

Наконец, используйте фильтр для разбора файла вместо XMLReader.

Поскольку XMLReader определен как предок фильтра, он будет по-прежнему обрабатывать информацию.

...
  public static void main (String args[]) {
    XMLReader xmlReader = null;
    try {
      SAXParserFactory spfactory =
            SAXParserFactory.newInstance();
      spfactory.setValidating(false);
      SAXParser saxParser =
            spfactory.newSAXParser();
      xmlReader = saxParser.getXMLReader();
      SurveyFilter xmlFilter = new SurveyFilter();
      xmlFilter.setParent(xmlReader);
      xmlFilter. 
            setContentHandler(new SurveyReader());
      xmlFilter. 
            setErrorHandler(new SurveyReader());
      InputSource source = new InputSource("surveys.xml");
      xmlFilter. 
            parse(source);
    } catch (Exception e) {
      System.err.println(e);
      System.exit(1);
    }
  }
...

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

Использование XMLFilter для преобразования данных

XMLFilters может также использоваться для быстрого и легкого преобразования данных при помощи XSLT.

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

import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import org.xml.sax.XMLFilter; 
...
  public static void main (String args[]) {
    XMLReader xmlReader = null;
    try {
      SAXParserFactory spfactory = SAXParserFactory.newInstance();
      spfactory.setValidating(false);
      SAXParser saxParser = spfactory.newSAXParser();
      xmlReader = saxParser.getXMLReader();
      TransformerFactory tFactory = TransformerFactory.newInstance();
      SAXTransformerFactory saxTFactory = 
            ((SAXTransformerFactory) tFactory);
      XMLFilter xmlFilter =
            saxTFactory.newXMLFilter(new StreamSource("surveys.xsl"));
      xmlFilter.setParent(xmlReader);
      Serializer serializer =
          SerializerFactory.getSerializer(
                OutputProperties.getDefaultMethodProperties("xml"));
      serializer.setOutputStream(System.out); 
      xmlFilter.setContentHandler(
            serializer.asContentHandler() ); 
      InputSource source = new InputSource("surveys.xml");
      xmlFilter.parse(source);
    } catch (Exception e) {
      System.err.println(e);
      System.exit(1);
    }
  }
...

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

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

В основном фильтр выполняет преобразование, затем обрабатывает события на XMLReader. Однако, в конечном счете приемником являетсяSerializer.

 

Раздел 7. Итоги по SAX

Резюме

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

 

Ресурсы

Обучение

Получение продуктов и технологий

 

Об авторе

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.


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