JAXB и XSLT с использованием StAX

    В одном из проектов понадобилось обрабатывать большие XML файлы, от сотен мегабайт до десятков гигабайт.
    Причем выдернуть надо было только некоторые тэги с расположенные на различной «глубине». XSLT «в лоб» ломался от недостатка памяти. Пришлось подумать и вспомнить о потоковом парсере.

    Существуют несколько моделей обработки XML. Наиболее известные — DOM и SAX.
    DOM грузит весь XML документ, строит его внутреннее представление и предоставляет возможность навигации по всему документу. SAX напротив, читает входной документ и при распознавании элемента вызывает handler'ы для обработки.

    В моем случае DOM отпал по причине потребляемой памяти. SAX API построено на handler'ах, в результате код получается менее читабельным. StAX представляет из себя потоковый парсер (как и SAX), но API построено на принципе pull. То есть распознанные элементы «вынимаются» из потока по требованию.

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

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

    И так, есть следующий
    XML документ
    <data>
        <dtype_one>
            <p1>p1_data_1</p1>
            <p2>p1_data_1</p2>
            <p3>p1_data_1</p3>
            <p4>p1_data_1</p4>
            <p5>p1_data_1</p5>
        </dtype_one>
        <dtype_two>
            <p1>p1_data_2</p1>
            <p2>p1_data_2</p2>
            <p3>p1_data_2</p3>
            <p4>p1_data_2</p4>
            <p5>p1_data_2</p5>
        </dtype_two>
        <WS>
            <dtype_three>
                <p1>p1_data_3</p1>
                <p2>p1_data_3</p2>
                <p3>p1_data_3</p3>
                <p4>p1_data_3</p4>
                <p5>p1_data_3</p5>
            </dtype_three>
        </WS>
    </data>
    

    из него нужно выделить и обработать тэги dtype_one, dtype_two и dtype_three. Тэги повторяются в документе. Берем
    схему документа
    <?xml version="1.0" encoding="UTF-8"?>
    <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
               xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <xs:element name="data" type="dataType"/>
        <xs:element name="dtype_one" type="dtype_oneType"/>
        <xs:element name="dtype_two" type="dtype_twoType"/>
        <xs:element name="dtype_three" type="dtype_threeType"/>
    
        <xs:complexType name="dtype_oneType">
            <xs:sequence>
                <xs:element type="xs:string" name="p1"/>
                <xs:element type="xs:string" name="p2"/>
                <xs:element type="xs:string" name="p3"/>
                <xs:element type="xs:string" name="p4"/>
                <xs:element type="xs:string" name="p5"/>
            </xs:sequence>
        </xs:complexType>
        <xs:complexType name="dataType">
            <xs:sequence>
                <xs:element type="dtype_oneType" name="dtype_one"/>
                <xs:element type="dtype_twoType" name="dtype_two"/>
                <xs:element type="WSType" name="WS"/>
            </xs:sequence>
        </xs:complexType>
        <xs:complexType name="WSType">
            <xs:sequence>
                <xs:element type="dtype_threeType" name="dtype_three"/>
            </xs:sequence>
        </xs:complexType>
        <xs:complexType name="dtype_twoType">
            <xs:sequence>
                <xs:element type="xs:string" name="p1"/>
                <xs:element type="xs:string" name="p2"/>
                <xs:element type="xs:string" name="p3"/>
                <xs:element type="xs:string" name="p4"/>
                <xs:element type="xs:string" name="p5"/>
            </xs:sequence>
        </xs:complexType>
        <xs:complexType name="dtype_threeType">
            <xs:sequence>
                <xs:element type="xs:string" name="p1"/>
                <xs:element type="xs:string" name="p2"/>
                <xs:element type="xs:string" name="p3"/>
                <xs:element type="xs:string" name="p4"/>
                <xs:element type="xs:string" name="p5"/>
            </xs:sequence>
        </xs:complexType>
    </xs:schema>
    


    и убеждаемся что в нем есть элементы «element» нужных нам тэгов:
        <xs:element name="dtype_one" type="dtype_oneType"/>
        <xs:element name="dtype_two" type="dtype_twoType"/>
        <xs:element name="dtype_three" type="dtype_threeType"/>
    

    если схемы нет, IDEA отлично может сгенерить ее по xml файлу.

    Это нужно для того, что бы XJC сгенерил аннотацию @XmlRootElement. Проект собирается maven, для вызова XJC используется maven-jaxb2-plugin. Для генерации @XmlRootElement для всех «element» в файле схемы, необходимо добавить следующие строки в файл bindings.xjb:
    <jaxb:bindings>
            <jaxb:globalBindings >
                <xjc:simple/>
            </jaxb:globalBindings>
        </jaxb:bindings>
    

    и подключить его в конфигурации плагина maven-jaxb2-plugin, в pom.xml
    <bindingDirectory>${project.basedir}/xjb</bindingDirectory>
    

    Теперь собственно к коду, класс TagEngine хранит список обработчиков тэгов и занимается разбором:
        public void process(InputStream inputStream) throws FileNotFoundException,
                XMLStreamException, TransformerException {
    // Создаем XMLStreamReader, поток разбора
            XMLInputFactory factory = XMLInputFactory.newFactory();
            XMLStreamReader streamReader = factory.createXMLStreamReader(inputStream);
    // Стэк тэгов
            Stack<String> tagStack = new Stack<String>();
    // Пока есть тэги
            while (streamReader.hasNext()) {
    // Берем следующий
                int eventType = streamReader.next();
    // Если старт элемента
                if(eventType == XMLStreamConstants.START_ELEMENT) {
    // Помещаем в стэк
                    tagStack.push(streamReader.getName().toString());
    // Ищем совпадение с обработчиком
                    TagProcessor t = processorMap.get(tagStack);
    
                    if(t != null) {
    // Нашли, обрабатываем
                        t.process(streamReader);
                        tagStack.pop();
                    }
                } else if(eventType == XMLStreamConstants.END_ELEMENT) {
                    tagStack.pop();
                }
            }
        }
    

    Класс JAXBProcessor занимается unmarshalling'ом выделенных элементов. Класс XSLTProcessor вызывает XSLT преобразования. Вот так выглядит класс выполняющий полезную работу:
    public class DataOne extends JAXBProcessor<DtypeOne> {
        private static final String TAG_NAME = "data/dtype_one";
    
    // Конструктор 
        public DataOne() throws JAXBException, SAXException {
            super(DtypeOne.class, TAG_NAME);
        }
    // Конструктор с подключением схемы для валидации
        public DataOne(String schemaFileName) throws JAXBException, SAXException {
            super(DtypeOne.class, TAG_NAME, schemaFileName);
        }
    // Здесь обрабатываем полученный из XML объект
        @Override
        public void doWork(DtypeOne element) {
    //        System.out.println(element.getP1());
        }
    }
    

    Пример применения XSLT DataThreeXSLT.

    Пример запуска (эмулируется обработка 277 мегабайтного файла):
    JAXB unmarshall without schema validation
    Runtime: 8034ms, 277000015 bytes processed
    Used Memory:80MB
    JAXB unmarshall with schema validation
    Runtime: 66180ms, 277000015 bytes processed
    Used Memory:56MB
    XSLT processing
    Runtime: 10604ms, 277000015 bytes processed
    Used Memory:231MB
    

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

    PS. Для тестов использовал Mockito (раньше использовал jmock). Понравилась возможность spy — перехват вызовов и их параметров при работе с живым (не mock) объектом.
    PPS. Код проекта на GitHub.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      +2
      Говорят, еще vtd-xml.sourceforge.net/ хорош для такого
        0
        Спасибо, обязательно посмотрю

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

      Самое читаемое