Оглядові
документи XML - це ієрархічні документи, де однакові імена елементів та простори імен можуть траплятися в кількох місцях, що мають різне значення, та в інфінітивній глибині (рекурсивно). Як правило, вирішення великих проблем полягає в тому, щоб розділити їх на маленькі проблеми. У контексті аналізу XML це означає аналіз певних частин XML у методах, специфічних для цього XML. Наприклад, одна частина логіки розбере адресу:
<Address>
<Street>Odins vei</Street>
<Building>4</Building>
<Door>b</Door>
</Address>
тобто у вас був би метод
AddressType parseAddress(...);
або
void parseAddress(...);
десь у вашій логіці, беручи 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
}
У випадку початкового елемента "Побудова" вам потрібно буде визначити, що ви насправді аналізуєте адресу, а потім направити подію XML до методу, завданням якого є інтерпретація адреси.
StAX
StAX "витягує" XML- події , залишаючи за вами самостійно визначати, де у вашій програмі / даних приймати XML-події.
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
}
Звичайно, ви завжди хочете отримати подію "Побудова" в методі, завданням якого є інтерпретація адреси.
Обговорення
Різниця між SAX та StAX полягає в тому, що натискати та тягнути. В обох випадках із станом синтаксичного аналізу потрібно якось обробляти.
Це означає метод B як типовий для SAX та метод A для StAX. Крім того, SAX повинен подавати B окремих подій XML, тоді як StAX може подавати кілька подій (передаючи екземпляр XMLStreamReader).
Таким чином, B спочатку перевіряє попередній стан розбору, а потім обробляє кожну окрему подію XML, а потім зберігає стан (у полі). Метод A може просто обробляти події XML одночасно, отримуючи доступ до XMLStreamReader кілька разів, доки це не буде виконано.
Висновок
StAX дозволяє структурувати код синтаксичного аналізу (прив'язки даних) відповідно до структури XML ; тому щодо SAX, "стан" є неявним із потоку програми для StAX, тоді як у SAX для більшості викликів подій завжди потрібно зберігати якусь змінну стану + маршрутизувати потік відповідно до цього стану.
Я рекомендую StAX для всіх, крім найпростіших документів. Швидше перейдіть до SAX як оптимізація пізніше (але ви, мабуть, захочете перейти в бінарний файл до того часу).
Дотримуйтесь цього шаблону при розборі за допомогою StAX:
public MyDataBindingObject parse(..) {
XMLStreamReader reader = ....;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
break;
}
} while(reader.hasNext());
MyDataBindingObject object = new MyDataBindingObject();
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
if(reader.getLocalName().equals("Whatever1")) {
WhateverObject child = parseSubTreeForWhatever(reader);
level --;
object.setWhatever(child);
}
if(level == 2) {
parseSubTreeForWhateverAtRelativeLevel2(reader);
level --;
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
}
} while(level > 0);
return object;
}
Отже, підметод використовує приблизно той самий підхід, тобто рівень підрахунку:
private MySubTreeObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySubTreeObject object = new MySubTreeObject();
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
if(reader.getLocalName().equals("Whatever2")) {
MyWhateverObject child = parseMySubelementTree(reader);
level --;
object.setWhatever(child);
}
if(level == 2) {
MyWhateverObject child = parseMySubelementTree(reader);
level --;
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
}
} while(level > 0);
return object;
}
І тоді зрештою ви досягнете рівня, на якому ви будете читати базові типи.
private MySetterGetterObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySetterGetterObject myObject = new MySetterGetterObject();
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
if(reader.getLocalName().equals("FirstName")) {
String text = reader.getElementText()
if(text.length() > 0) {
myObject.setName(text)
}
level--;
} else if(reader.getLocalName().equals("LastName")) {
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
}
} while(level > 0);
return myObject;
}
Це досить просто, і тут немає місця для непорозумінь. Тільки пам’ятайте, щоб правильно зменшити рівень:
А. після того, як ви очікували символів, але отримали END_ELEMENT в якомусь тезі, який повинен містити символи (за наведеним вище зразком):
<Name>Thomas</Name>
був замість цього
<Name></Name>
Те саме стосується і відсутнього піддерева, ви розумієте.
Б. після виклику методів субпарсингу, які викликаються на початкових елементах, і повертається ПІСЛЯ відповідного кінцевого елемента, тобто синтаксичний аналізатор знаходиться на один рівень нижче, ніж до виклику методу (наведений вище шаблон).
Зверніть увагу, як цей підхід повністю ігнорує пробіли, що не можна ігнорувати, для більш надійної реалізації.
Парсери
Go з Woodstox для більшості функцій або Aaalto-XML для швидкості.