Обзор
Документы XML являются иерархическими документами, в которых одинаковые имена элементов и пространства имен могут встречаться в нескольких местах, иметь разное значение и с бесконечной глубиной (рекурсивно). Как обычно, решение больших проблем, это разделить их на маленькие проблемы. В контексте синтаксического анализа XML это означает синтаксический анализ определенных частей XML в методах, специфичных для этого XML. Например, одна часть логики будет анализировать адрес:
<Address>
<Street>Odins vei</Street>
<Building>4</Building>
<Door>b</Door>
</Address>
т.е. у тебя был бы метод
AddressType parseAddress(...); // A
или
void parseAddress(...); // B
где-то в вашей логике, принимая входные аргументы XML и возвращая объект (результат B можно получить из поля позже).
SAX
SAX 'толкает' события XML , предоставляя вам право определять, где события XML принадлежат вашей программе / данным.
// method in stock SAX handler
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
// .. your logic here for start element
}
В случае начального элемента 'Building' вам нужно будет определить, что вы на самом деле анализируете Address, а затем направить событие XML методу, задача которого заключается в интерпретации Address.
StAX
StAX «извлекает» события XML , предоставляя вам возможность определить, где в вашей программе / данных получить события XML.
// method in standard StAX reader
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
// .. your logic here for start element
}
Конечно, вы всегда хотели бы получить событие 'Building' в методе, задачей которого является интерпретация Address.
Обсуждение
Разница между SAX и StAX состоит в том, что есть толчок и тяга. В обоих случаях состояние синтаксического анализа должно быть каким-то образом обработано.
Это означает, что метод B типичен для SAX, а метод A для StAX. Кроме того, SAX должен выдавать B отдельные события XML, а StAX может выдавать несколько событий A (путем передачи экземпляра XMLStreamReader).
Таким образом, B сначала проверяет предыдущее состояние синтаксического анализа, а затем обрабатывает каждое отдельное событие XML и затем сохраняет состояние (в поле). Метод A может просто обрабатывать события XML одновременно, обращаясь к XMLStreamReader несколько раз, пока не будет выполнено.
Заключение
StAX позволяет структурировать код анализа (привязки данных) в соответствии со структурой XML ; поэтому в отношении SAX «состояние» неявно подразумевается в потоке программы для StAX, тогда как в SAX вам всегда нужно сохранять какую-то переменную состояния + направлять поток в соответствии с этим состоянием для большинства вызовов событий.
Я рекомендую StAX для всех документов, кроме самых простых. Скорее перейдите к SAX в качестве оптимизации позже (но к тому времени вы, вероятно, захотите перейти на двоичный код).
Следуйте этой схеме при разборе с использованием StAX:
public MyDataBindingObject parse(..) { // provide input stream, reader, etc
// set up parser
// read the root tag to get to level 1
XMLStreamReader reader = ....;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
// check if correct root tag
break;
}
// add check for document end if you want to
} while(reader.hasNext());
MyDataBindingObject object = new MyDataBindingObject();
// read root attributes if any
int level = 1; // we are at level 1, since we have read the document header
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
// do stateful stuff here
// for child logic:
if(reader.getLocalName().equals("Whatever1")) {
WhateverObject child = parseSubTreeForWhatever(reader);
level --; // read from level 1 to 0 in submethod.
// do something with the result of subtree
object.setWhatever(child);
}
// alternatively, faster
if(level == 2) {
parseSubTreeForWhateverAtRelativeLevel2(reader);
level --; // read from level 1 to 0 in submethod.
// do something with the result of subtree
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
// do stateful stuff here, too
}
} while(level > 0);
return object;
}
Таким образом, подметод использует примерно такой же подход, то есть уровень подсчета:
private MySubTreeObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySubTreeObject object = new MySubTreeObject();
// read element attributes if any
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
// do stateful stuff here
// for child logic:
if(reader.getLocalName().equals("Whatever2")) {
MyWhateverObject child = parseMySubelementTree(reader);
level --; // read from level 1 to 0 in submethod.
// use subtree object somehow
object.setWhatever(child);
}
// alternatively, faster, but less strict
if(level == 2) {
MyWhateverObject child = parseMySubelementTree(reader);
level --; // read from level 1 to 0 in submethod.
// use subtree object somehow
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
// do stateful stuff here, too
}
} while(level > 0);
return object;
}
И затем, в конце концов, вы достигнете уровня, на котором вы будете читать базовые типы.
private MySetterGetterObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySetterGetterObject myObject = new MySetterGetterObject();
// read element attributes if any
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
// assume <FirstName>Thomas</FirstName>:
if(reader.getLocalName().equals("FirstName")) {
// read tag contents
String text = reader.getElementText()
if(text.length() > 0) {
myObject.setName(text)
}
level--;
} else if(reader.getLocalName().equals("LastName")) {
// etc ..
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
// do stateful stuff here, too
}
} while(level > 0);
// verify that all required fields in myObject are present
return myObject;
}
Это довольно просто, и здесь нет места для недоразумений. Просто не забудьте правильно уменьшить уровень:
A. после того, как вы ожидали символы, но получили END_ELEMENT в некотором теге, который должен содержать символы (в приведенном выше шаблоне):
<Name>Thomas</Name>
было вместо
<Name></Name>
То же самое относится и к отсутствующему поддереву, вы понимаете.
B. после вызова методов синтаксического анализа, которые вызываются для начальных элементов, и возвращает ПОСЛЕ соответствующего конечного элемента, то есть синтаксический анализатор находится на один уровень ниже, чем до вызова метода (шаблон выше).
Обратите внимание, что этот подход полностью игнорирует также «игнорируемые» пробелы для более надежной реализации.
Парсеры
Выберите Woodstox для большинства функций или Aaalto-xml для скорости.