XMLStreamReader.next(): примеры (JAVA)

Метод XMLStreamReader.next(): синтаксис, примеры и альтернативы
Раздел: Обработка XML, StAX
XMLStreamReader.next(): int

Описание метода XMLStreamReader.next()

Метод next() интерфейса javax.xml.stream.XMLStreamReader используется для перехода к следующему событию в потоке XML. Он не принимает аргументов и возвращает целое число (int), которое соответствует типу текущего события после перехода. Типы событий определены как константы в классе XMLStreamConstants:

  • START_ELEMENT (1) – начало элемента
  • END_ELEMENT (2) – конец элемента
  • CHARACTERS (4) – текст
  • CDATA (12) – секция CDATA
  • COMMENT (5) – комментарий
  • SPACE (6) – пробельные символы
  • PROCESSING_INSTRUCTION (3) – инструкция обработки
  • ENTITY_REFERENCE (9) – ссылка на сущность
  • DTD (11) – DTD
  • END_DOCUMENT (8) – конец документа

Метод применяется для последовательного чтения XML-документа. Перед первым вызовом next() состояние reader соответствует константе START_DOCUMENT (7). Каждый вызов перемещает указатель на одно событие вперёд. Если достигнут конец документа и попытаться вызвать next() повторно, будет выброшено исключение java.util.NoSuchElementException.

Метод может выбросить XMLStreamException при ошибках парсинга или ввода-вывода.

Примеры использования XMLStreamReader.next()

Пример 1: Подсчёт элементов

import javax.xml.stream.*;
import java.io.*;

public class Example1 {
    public static void main(String[] args) throws Exception {
        String xml = "<root><a/><b>text</b></root>";
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xml));
        int count = 0;
        while (reader.hasNext()) {
            int event = reader.next();
            if (event == XMLStreamConstants.START_ELEMENT) {
                count++;
            }
        }
        reader.close();
        System.out.println("Стартовых элементов: " + count);
    }
}
Стартовых элементов: 2

Пример 2: Вывод всех событий с типом

import javax.xml.stream.*;
import java.io.*;

public class Example2 {
    public static void main(String[] args) throws Exception {
        String xml = "<?xml version='1.0'?><doc id='1'>Hello<!-- comment --></doc>";
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xml));
        while (reader.hasNext()) {
            int event = reader.next();
            switch (event) {
                case XMLStreamConstants.START_ELEMENT:
                    System.out.println("START_ELEMENT: " + reader.getLocalName());
                    break;
                case XMLStreamConstants.END_ELEMENT:
                    System.out.println("END_ELEMENT: " + reader.getLocalName());
                    break;
                case XMLStreamConstants.CHARACTERS:
                    System.out.println("CHARACTERS: '" + reader.getText() + "'");
                    break;
                case XMLStreamConstants.COMMENT:
                    System.out.println("COMMENT");
                    break;
                case XMLStreamConstants.END_DOCUMENT:
                    System.out.println("END_DOCUMENT");
                    break;
            }
        }
        reader.close();
    }
}
START_ELEMENT: doc
CHARACTERS: 'Hello'
COMMENT
END_ELEMENT: doc
END_DOCUMENT

Похожие функции в Java

XMLStreamReader.nextTag() – переходит к следующему стартовому или конечному тегу, пропуская пробельные символы и комментарии. Полезно, когда нужно быстро добраться до элемента, игнорируя незначимые события. В отличие от next(), nextTag() выбрасывает XMLStreamException, если следующее событие не является START_ELEMENT или END_ELEMENT.

SAXParser (пакет org.xml.sax) предлагает событийно-ориентированный подход с колбэками, где не нужно вручную вызывать next(). Удобен для больших документов, но сложнее в управлении состоянием.

DocumentBuilder (DOM) – загружает весь документ в память, затем доступ через методы getChildNodes() и т.д. Не требует итерации событиями, но потребляет много памяти.

Выбор между next() и nextTag() зависит от необходимости обрабатывать пробелы, комментарии и другие события. Для потокового чтения с детальным контролем используется next().

Альтернативы в других языках программирования

PHP

XMLReader::read() – аналог next(). Возвращает true при успешном чтении, устанавливает тип узла в свойство nodeType.

<?php
$xml = '<root><item>val</item></root>';
$reader = new XMLReader();
$reader->XML($xml);
while ($reader->read()) {
    echo $reader->nodeType . ": " . $reader->name . "\n";
}
?>
1: root
1: item
3: val
15: item
15: root

JavaScript

DOMParser не имеет прямого аналога. Для потокового чтения можно использовать XMLHttpRequest с responseXML или сторонние SAX-библиотеки.

const parser = new DOMParser();
const xmlDoc = parser.parseFromString('<root><item/></root>', 'text/xml');
const items = xmlDoc.getElementsByTagName('item');
console.log(items.length);
1

Python

xml.etree.ElementTree.iterparse() порождает события start, end и т.д. Похож на SAX.

import xml.etree.ElementTree as ET
xml = '<root><a>text</a></root>'
events = [(event, elem.tag) for event, elem in ET.iterparse(xml, events=('start', 'end'))]
print(events)
[('start', 'root'), ('start', 'a'), ('end', 'a'), ('end', 'root')]

C#

XmlReader.Read() – прямой аналог. Возвращает bool, далее NodeType.

using System.Xml;
string xml = "<root><item>val</item></root>";
using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
{
    while (reader.Read())
    {
        Console.WriteLine(reader.NodeType + ": " + reader.Name);
    }
}
Element: root
Element: item
Text: 
EndElement: item
EndElement: root

Lua

luaXML – библиотека с методом xml:next(), возвращающим тип события.

local xml = require("xml")
local doc = xml.parse("<root><a/></root>")
for event, elem in doc:next() do
    print(event, elem)
end
start root
start a
end a
end root

Go

encoding/xml.Decoder.Token() возвращает токен (StartElement, CharData, EndElement) или ошибку.

package main
import (
    "encoding/xml"
    "fmt"
    "strings"
)
func main() {
    xmlStr := `<root><item>val</item></root>`
    decoder := xml.NewDecoder(strings.NewReader(xmlStr))
    for {
        t, err := decoder.Token()
        if err != nil { break }
        switch se := t.(type) {
        case xml.StartElement:
            fmt.Println("StartElement:", se.Name.Local)
        case xml.EndElement:
            fmt.Println("EndElement:", se.Name.Local)
        case xml.CharData:
            fmt.Println("CharData:", string(se))
        }
    }
}
StartElement: root
StartElement: item
CharData: val
EndElement: item
EndElement: root

Kotlin

Использует Java API, поэтому XMLStreamReader.next() доступен напрямую. Отличий нет.

Типичные ошибки при использовании XMLStreamReader.next()

1. Вызов после достижения конца документа

XMLStreamReader reader = ...;
while (reader.hasNext()) {
    reader.next();
}
// reader.hasNext() == false, но можно случайно вызвать reader.next() ещё раз
// reader.next(); // NoSuchElementException
Exception in thread "main" java.util.NoSuchElementException
	at com.sun.org.apache.xerces.internal.impl.XMLStreamReaderImpl.next(XMLStreamReaderImpl.java:570)

2. Игнорирование проверки состояния перед вызовом next()

Если не вызвать hasNext() перед next(), в конце документа будет исключение.

3. Попытка повторного использования закрытого reader

reader.close();
reader.next(); // XMLStreamException
javax.xml.stream.XMLStreamException: Stream closed

4. Неправильная обработка типов событий

Например, вызов getLocalName() на событии CHARACTERS приводит к IllegalStateException. Необходимо всегда проверять тип события перед вызовом соответствующих методов.

int event = reader.next();
String name = reader.getLocalName(); // если event == CHARACTERS, будет исключение
java.lang.IllegalStateException

Изменения в последних версиях Java

Метод XMLStreamReader.next() является частью спецификации StAX (JSR 173), которая входит в стандартную библиотеку Java начиная с Java 6 (в составе пакета javax.xml.stream). В последующих версиях Java (8, 9, 11, 17) поведение метода не претерпело существенных изменений. Однако в Java 9 была проведена модуляризация, и пакет javax.xml.stream был помещён в модуль java.xml. Это может потребовать указания модуля в module-info.java для модульных проектов. В остальном метод остаётся стабильным.

Расширенные примеры использования XMLStreamReader.next()

Пример 1: Обработка вложенных элементов с отслеживанием глубины

Пример java
import javax.xml.stream.*;
import java.io.*;

public class AdvExample1 {
    public static void main(String[] args) throws Exception {
        String xml = "<root><level1><level2 attr='x'/></level1></root>";
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xml));
        int depth = 0;
        while (reader.hasNext()) {
            int event = reader.next();
            if (event == XMLStreamConstants.START_ELEMENT) {
                depth++;
                System.out.println("Открытие " + reader.getLocalName() + " на глубине " + depth);
                for (int i = 0; i < reader.getAttributeCount(); i++) {
                    System.out.println("  Атрибут: " + reader.getAttributeLocalName(i) + "=" + reader.getAttributeValue(i));
                }
            } else if (event == XMLStreamConstants.END_ELEMENT) {
                System.out.println("Закрытие " + reader.getLocalName() + " на глубине " + depth);
                depth--;
            }
        }
        reader.close();
    }
}
Открытие root на глубине 1
Открытие level1 на глубине 2
Открытие level2 на глубине 3
  Атрибут: attr=x
Закрытие level2 на глубине 3
Закрытие level1 на глубине 2
Закрытие root на глубине 1

Пример 2: Пропуск неинтересующих элементов

Пример java
import javax.xml.stream.*;
import java.io.*;

public class AdvExample2 {
    public static void main(String[] args) throws Exception {
        String xml = "<data><a>1</a><skip>не нужно</skip><b>2</b></data>";
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xml));
        while (reader.hasNext()) {
            int event = reader.next();
            if (event == XMLStreamConstants.START_ELEMENT && "skip".equals(reader.getLocalName())) {
                // пропускаем содержимое элемента skip до его закрытия
                while (reader.hasNext()) {
                    int ev = reader.next();
                    if (ev == XMLStreamConstants.END_ELEMENT && "skip".equals(reader.getLocalName())) {
                        break;
                    }
                }
                continue;
            }
            if (event == XMLStreamConstants.CHARACTERS) {
                System.out.println("Текст: " + reader.getText());
            }
        }
        reader.close();
    }
}
Текст: 1
Текст: 2

Пример 3: Работа с пространствами имён

Пример java
import javax.xml.stream.*;
import java.io.*;

public class AdvExample3 {
    public static void main(String[] args) throws Exception {
        String xml = "<ns:root xmlns:ns='http://example.com'>" +
                     "<ns:child ns:attr='5'>text</ns:child></ns:root>";
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xml));
        while (reader.hasNext()) {
            int event = reader.next();
            if (event == XMLStreamConstants.START_ELEMENT) {
                System.out.println("Элемент: " + reader.getLocalName() +
                                   " namespace: " + reader.getNamespaceURI());
                for (int i = 0; i < reader.getAttributeCount(); i++) {
                    System.out.println("  Атрибут: " + reader.getAttributeLocalName(i) +
                                       " namespace: " + reader.getAttributeNamespace(i));
                }
            }
        }
        reader.close();
    }
}
Элемент: root namespace: http://example.com
Элемент: child namespace: http://example.com
  Атрибут: attr namespace: http://example.com

Пример 4: Обработка CDATA и комментариев

Пример java
import javax.xml.stream.*;
import java.io.*;

public class AdvExample4 {
    public static void main(String[] args) throws Exception {
        String xml = "<root><![CDATA[some & chars]]>" +
                     "<!-- comment --></root>";
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xml));
        while (reader.hasNext()) {
            int event = reader.next();
            switch (event) {
                case XMLStreamConstants.CDATA:
                    System.out.println("CDATA: '" + reader.getText() + "'");
                    break;
                case XMLStreamConstants.COMMENT:
                    System.out.println("COMMENT: " + reader.getText());
                    break;
            }
        }
        reader.close();
    }
}
CDATA: 'some & chars'
COMMENT:  comment 

джава XMLStreamReader.next() function comments

En
XMLStreamReader.next() Переход к следующему событию в XML-потоке