SAXParser.parse: примеры (JAVA)

Парсинг XML через SAX-парсер
Раздел: Обработка XML, SAX
SAXParser.parse(File f, DefaultHandler dh): void

Общее описание метода SAXParser.parse()

Класс javax.xml.parsers.SAXParser предоставляет несколько перегруженных версий метода parse() для последовательного (SAX) чтения XML-документов. Метод сам по себе возвращает void и при разборе вызывает методы зарегистрированного обработчика событий (например, org.xml.sax.helpers.DefaultHandler). Используется при обработке больших XML-потоков, когда важна экономия памяти и требуется реакция на события в процессе чтения.

Основные сигнатуры (обобщённо):

  • void parse(File f, DefaultHandler handler)
  • void parse(InputStream is, DefaultHandler handler)
  • void parse(InputStream is, DefaultHandler handler, String systemId)
  • void parse(InputSource input, DefaultHandler handler)
  • void parse(String uri, DefaultHandler handler)

Аргументы:

  • File f - файл с XML; путь к ресурсу, читаемый средствами файловой системы.
  • InputStream is - байтовый поток; полезен при чтении из сети или архива. Кодировка должна быть указана в XML-прологе или через InputSource.
  • InputSource input - более гибкий источник; позволяет передать Reader, InputStream и системный идентификатор (systemId) для корректного разрешения относительных URI.
  • String uri - системный идентификатор; метод сам откроет поток по указанному URI.
  • DefaultHandler handler - обработчик событий SAX, реализующий ContentHandler, ErrorHandler, EntityResolver и DTDHandler. Можно передать свою реализацию для обработки элементов, текста и ошибок.
  • String systemId (в перегрузке) - значение для разрешения относительных ссылок в случае использования InputStream без явного systemId.

Возвращаемое значение: void. Результат работы выражается через побочные эффекты в обработчике событий (вызовы методов startElement, characters, endElement и т. п.).

Типичные исключения и поведение при ошибках:

  • SAXException - ошибки разбора XML (содержит часто объект SAXParseException с номером строки и колонки).
  • IOException - проблемы ввода/вывода при чтении источника.

Дополнительные возможности через SAXParser.getXMLReader() для установки фич и пропертей, например, включение пространств имён, валидация или отключение внешних сущностей для защиты от XXE.

Короткие примеры использования

Пример 1: чтение из файла с выводом событий

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.Attributes;

public class SimpleSax {
  public static void main(String[] args) throws Exception {
    SAXParserFactory f = SAXParserFactory.newInstance();
    SAXParser p = f.newSAXParser();
    DefaultHandler h = new DefaultHandler() {
      public void startElement(String uri, String localName, String qName, Attributes atts) {
        System.out.println("start:" + qName);
      }
      public void characters(char[] ch, int start, int length) {
        System.out.println("text:" + new String(ch, start, length).trim());
      }
      public void endElement(String uri, String localName, String qName) {
        System.out.println("end:" + qName);
      }
    };
    p.parse(new java.io.File("example.xml"), h);
  }
}
start:root
text:
start:item
text:Hello
end:item
start:item
text:World
end:item
end:root

Пример 2: разбор из InputStream с указанием systemId (важно для относительных ссылок)

InputStream is = new FileInputStream("/data/doc.xml");
SAXParserFactory f = SAXParserFactory.newInstance();
SAXParser p = f.newSAXParser();
DefaultHandler h = new MyHandler();
p.parse(is, h, "file:///data/doc.xml");
is.close();
(обработчик получает события; если в документе есть внешние ссылки, они будут разрешаться относительно file:///data/doc.xml)

Пример 3: разбор из InputSource с указанием Reader и кодировки

Reader r = new InputStreamReader(new FileInputStream("u.xml"), "UTF-8");
InputSource src = new InputSource(r);
src.setSystemId("http://example.com/u.xml");
SAXParserFactory f = SAXParserFactory.newInstance();
SAXParser p = f.newSAXParser();
p.parse(src, new MyHandler());
(разбор с корректным учетом кодировки и разрешением относительных ссылок)

Похожие API в Java и отличия

Краткое сравнение основных подходов к XML в Java:

  • DOM (DocumentBuilder.parse) - загружает весь документ в память в виде дерева. Удобен при частых обратных обращениях к разным частям документа, не подходит для больших файлов.
  • StAX (XMLStreamReader / XMLEventReader) - pull-парсер, дающий управление чтением потока. Часто удобнее для сложной логики обработки и более гибкого управления ресурсами по сравнению с SAX.
  • JAXB - привязка XML к объектам Java (аннотации). Подходит при строгой схеме и желании работать с объектной моделью, скрывает низкоуровневые события SAX.
  • XMLFilterImpl / XMLReader - возможность фильтрации/модификации потоковых событий SAX. Используется при промежуточной обработке перед конечным обработчиком.

Когда предпочесть:

  • SAX - при работе с большими потоками, низкой памяти и событийной модели.
  • StAX - при необходимости более детерминированного управления чтением и возможности «просить» следующий токен.
  • DOM - при удобстве манипуляции деревом и небольших размерах документов.
  • JAXB - при наличии XSD и желании получать объекты напрямую.

Аналоги в других языках и отличительные особенности

Короткие примеры и комментарии по другим платформам:

  • Python (xml.sax)
    import xml.sax
    
    class H(xml.sax.ContentHandler):
        def startElement(self, name, attrs):
            print('start:'+name)
        def characters(self, ch):
            print('text:'+ch.strip())
        def endElement(self, name):
            print('end:'+name)
    
    xml.sax.parse('example.xml', H())
    start:root
    text:
    start:item
    text:Hello
    end:item
    start:item
    text:World
    end:item
    end:root
    Комментарий: API близок по модели событий к Java SAX.
  • JavaScript (Node.js, sax)
    const sax = require('sax');
    const fs = require('fs');
    const parser = sax.createStream(true);
    parser.on('opentag', t=>console.log('start:'+t.name));
    parser.on('text', t=>console.log('text:'+t.trim()));
    parser.on('closetag', t=>console.log('end:'+t));
    fs.createReadStream('example.xml').pipe(parser);
    start:root
    text:
    start:item
    text:Hello
    end:item
    start:item
    text:World
    end:item
    end:root
    Комментарий: sax-js обеспечивает SAX-подобный потоковый парсер для Node.
  • PHP (XMLReader)
    $r = new XMLReader();
    $r->open('example.xml');
    while ($r->read()) {
      if ($r->nodeType == XMLReader::ELEMENT) echo "start:{$r->name}\n";
      if ($r->nodeType == XMLReader::TEXT) echo "text:{$r->value}\n";
      if ($r->nodeType == XMLReader::END_ELEMENT) echo "end:{$r->name}\n";
    }
    $r->close();
    start:root
    text:
    start:item
    text:Hello
    end:item
    start:item
    text:World
    end:item
    end:root
    Комментарий: XMLReader - pull-парсер, похож на StAX.
  • C# (XmlReader)
    using (var r = XmlReader.Create("example.xml")) {
      while (r.Read()) {
        if (r.NodeType == XmlNodeType.Element) Console.WriteLine("start:"+r.Name);
        if (r.NodeType == XmlNodeType.Text) Console.WriteLine("text:"+r.Value.Trim());
        if (r.NodeType == XmlNodeType.EndElement) Console.WriteLine("end:"+r.Name);
      }
    }
    start:root
    text:
    start:item
    text:Hello
    end:item
    start:item
    text:World
    end:item
    end:root
    Комментарий: XmlReader - pull-парсер, быстрый и низкоуровневый.
  • Go (encoding/xml.Decoder)
    dec := xml.NewDecoder(bytes.NewReader(data))
    for {
      t, _ := dec.Token()
      if t == nil { break }
      switch v := t.(type) {
      case xml.StartElement: fmt.Println("start:"+v.Name.Local)
      case xml.CharData: fmt.Println("text:"+strings.TrimSpace(string(v)))
      case xml.EndElement: fmt.Println("end:"+v.Name.Local)
      }
    }
    start:root
    text:
    start:item
    text:Hello
    end:item
    start:item
    text:World
    end:item
    end:root
    Комментарий: Decoder предоставляет токенизированный поток, похожий на StAX.
  • Kotlin - в большинстве случаев используется Java-реализация SAXParser или сокращённые Kotlin-обёртки над ней; синтаксис более компактный, поведение совпадает с Java.
  • Lua (LuaExpat / lxp)} - lxp предоставляет SAX-подобный интерфейс с callback-функциями для начала/конца элементов и текста.

Типичные ошибки и их симптомы

Список распространённых проблем с примерами и пояснениями:

  • SAXParseException при некорректном XML
    // example_bad.xml содержит: <root><item>Hello</root>
    SAXParser p = SAXParserFactory.newInstance().newSAXParser();
    p.parse(new File("example_bad.xml"), new DefaultHandler());
    org.xml.sax.SAXParseException: The element type "item" must be terminated by the matching end-tag "</item>".
      at ... (line:1 column:...)
    Пояснение: сообщается строка и колонка, где обнаружена ошибка.
  • IOException при отсутствии файла или проблемах с доступом
    p.parse(new File("missing.xml"), handler);
    java.io.FileNotFoundException: missing.xml (No such file or directory)
    Пояснение: проверка существования и прав доступа до вызова parse актуальна.
  • Внешние сущности и XXE (уязвимость)
    SAXParserFactory f = SAXParserFactory.newInstance();
    SAXParser p = f.newSAXParser();
    p.parse(new File("xxe.xml"), handler);
    (возможна утечка локальных файлов или выполнение удалённых запросов при наличии DOCTYPE)
    Пояснение: по умолчанию поведение зависит от реализации; рекомендуется явно отключать внешние сущности и включать secure processing.
  • Фрагментация текста
    public void characters(char[] ch, int start, int length) {
      System.out.println(new String(ch, start, length));
    }
    (текст может приходить несколькими вызовами characters; строки могут быть разделены)
    Пояснение: объединение текста с помощью StringBuilder требуется для корректного получения полного содержимого элемента.
  • Проблемы потокобезопасности
    SAXParser p = factory.newSAXParser();
    // повторное использование одного обработчика и парсера из разных потоков
    (непредсказуемое поведение, ConcurrentModification и т.д.)
    Пояснение: парсер и обработчики не гарантированно потокобезопасны; использовать отдельные экземпляры в потоках.

Изменения и рекомендации по безопасности в последних версиях

API SAXParser как часть JAXP остаётся стабильным длительное время. Основные моменты, на которые стоит обратить внимание:

  • Модульная структура JDK (с Java 9+) распределила пакеты по модулям; SAX остаётся в модуле java.xml, что обычно не требует дополнительных шагов при обычном использовании.
  • Рост внимания к безопасности: рекомендуется явно включать XMLConstants.FEATURE_SECURE_PROCESSING и отключать разрешение внешних сущностей через установки фич XMLReader, чтобы защититься от XXE.
  • Для валидации по XSD предпочтение отдаётся использованию SchemaFactory и установки схемы в SAXParserFactory.setSchema(...), вместо старой DTD-валидации.
  • Сам API не претерпел значительных семантических изменений; основные улучшения связаны с безопасностью, совместимостью модулей и рекомендациями по использованию Schema для валидации.

Расширенные и редкие сценарии использования

Примеры с пояснениями для сложных случаев.

1) Валидация по XSD через SAXParserFactory с Schema

Пример java
SchemaFactory sf = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = sf.newSchema(new File("schema.xsd"));
SAXParserFactory f = SAXParserFactory.newInstance();
f.setNamespaceAware(true);
f.setSchema(schema);
SAXParser p = f.newSAXParser();
p.parse(new File("doc.xml"), new MyValidationHandler());
(при нарушении схемы вызывается SAXException с информацией о несоответствии)

Пояснение: использование Schema даёт проверку по XSD. Необходимо setNamespaceAware(true).

2) Отключение внешних сущностей и включение secure processing

Пример java
SAXParserFactory f = SAXParserFactory.newInstance();
f.setFeature("http://xml.org/sax/features/external-general-entities", false);
f.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
SAXParser p = f.newSAXParser();
p.parse(new File("possibly_malicious.xml"), handler);
(внешние сущности игнорируются, риск XXE снижен)

Пояснение: рекомендуется для обработки внешнего или непроверенного XML.

3) Кастомный EntityResolver для локального разрешения DTD

Пример java
DefaultHandler h = new DefaultHandler() {
  public InputSource resolveEntity(String publicId, String systemId) {
    if (systemId.endsWith("my.dtd")) {
      return new InputSource(new StringReader(""));
    }
    return null; // дефолтное поведение
  }
};
SAXParserFactory f = SAXParserFactory.newInstance();
SAXParser p = f.newSAXParser();
p.parse(new File("doc_with_dtd.xml"), h);
(DTD разрешается локально, внешние запросы не выполняются)

Пояснение: полезно при контроле и кэшировании внешних ресурсов.

4) Обработка больших XML-потоков и агрегирование данных

Пример java
// обработчик собирает записи и периодически записывает в БД
class BatchHandler extends DefaultHandler {
  StringBuilder cur = new StringBuilder();
  List<String> items = new ArrayList<>();
  public void startElement(String uri, String localName, String qName, Attributes atts) { if (qName.equals("item")) cur.setLength(0); }
  public void characters(char[] ch, int start, int length) { cur.append(ch, start, length); }
  public void endElement(String uri, String localName, String qName) {
    if (qName.equals("item")) {
      items.add(cur.toString().trim());
      if (items.size() >= 1000) { saveToDb(items); items.clear(); }
    }
  }
}
// p.parse(stream, new BatchHandler());
(партии по 1000 записей сохраняются в БД, память ограничена)

Пояснение: SAX хорош при потоковой обработке без загрузки всего файла в память.

5) Использование XMLFilterImpl для изменения потока событий

Пример java
XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
XMLFilterImpl filter = new XMLFilterImpl(reader) {
  public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
    if (qName.equals("oldName")) qName = "newName"; // пример изменения
    super.startElement(uri, localName, qName, atts);
  }
};
filter.setContentHandler(new MyHandler());
filter.parse(new InputSource(new FileInputStream("in.xml")));
(в событиях обработчика встречается уже изменённое имя элемента)

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

6) Интеграция с XSLT как SAXSource

Пример java
SAXSource source = new SAXSource(new InputSource(new FileInputStream("in.xml")));
Transformer t = TransformerFactory.newInstance().newTransformer(new StreamSource(new File("style.xsl")));
StringWriter out = new StringWriter();
t.transform(source, new StreamResult(out));
System.out.println(out.toString());
(результат применения XSLT к поточному XML)

Пояснение: SAX-поток может служить источником для XSLT без предварительной загрузки в DOM.

джава SAXParser.parse function comments

En
SAXParser.parse Парсит XML-документ с использованием SAX