Java SAX Parsing
Есть поток XML, который мне нужно проанализировать. Поскольку мне нужно только сделать это один раз и построить мои объекты Java, SAX выглядит как естественный выбор. Я расширяю DefaultHandler и реализую методы startElement, endElement и characters, имея членов в моем классе, где я сохраняю текущее значение чтения (взятое в методе символов).
У меня нет проблем с тем, что мне нужно, но мой код стал довольно сложным, и я уверен, что нет причин для этого и что я могу делать что-то по-другому.
Структура моего XML выглядит примерно так:
<players>
<player>
<id></id>
<name></name>
<teams total="2">
<team>
<id></id>
<name></name>
<start-date>
<year>2009</year>
<month>9</month>
</start-date>
<is-current>true</is-current>
</team>
<team>
<id></id>
<name></name>
<start-date>
<year>2007</year>
<month>11</month>
</start-date>
<end-date>
<year>2009</year>
<month>7</month>
</end-date>
</team>
</teams>
</player>
</players>
Моя проблема началась, когда я понял, что те же имена тегов используются в нескольких областях файла. Например, id и name существуют как для игрока, так и для команды. Я хочу создать экземпляры моих классов java Player и Team. Во время разбора я сохранил логические флаги, говорящие мне, есть ли я в разделе команд, чтобы в конце этого элемента я узнал, что имя - это имя команды, а не имя игрока и т.д.
Вот как выглядит мой код:
public class MyParser extends DefaultHandler {
private String currentValue;
private boolean inTeamsSection = false;
private Player player;
private Team team;
private List<Team> teams;
public void characters(char[] ch, int start, int length) throws SAXException {
currentValue = new String(ch, start, length);
}
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
if(name.equals("player")){
player = new Player();
}
if (name.equals("teams")) {
inTeamsSection = true;
teams = new ArrayList<Team>();
}
if (name.equals("team")){
team = new Team();
}
}
public void endElement(String uri, String localName, String name) throws SAXException {
if (name.equals("id")) {
if(inTeamsSection){
team.setId(currentValue);
}
else{
player.setId(currentValue);
}
}
if (name.equals("name")){
if(inTeamsSection){
team.setName(currentValue);
}
else{
player.setName(currentValue);
}
}
if (name.equals("team")){
teams.add(team);
}
if (name.equals("teams")){
player.setTeams(teams);
inTeamsSection = false;
}
}
}
Поскольку в моем реальном сценарии у меня есть больше узлов для игрока в дополнение к командам, и эти узлы также имеют теги, такие как имя и идентификатор, я обнаружил, что меня перепутали с несколькими булерами, подобными inTeamsSection, и мой метод endElement становится длинным и комплекс со многими условиями.
Что мне делать по-другому? Как я могу узнать, что, например, тег имени принадлежит?
Спасибо!
Ответы
Ответ 1
При написании SAX-анализатора существует один аккуратный трюк: разрешено изменять
ContentHandler
XMLReader во время разбора. Это позволяет отделить
логическая разборка для разных элементов в несколько классов, что делает
синтаксический анализ более модульного и многоразового использования. Когда один обработчик видит свой конечный элемент,
переключается обратно на родителя. Сколько обработчиков, которые вы реализуете, будет
вы. Код будет выглядеть так:
public class RootHandler extends DefaultHandler {
private XMLReader reader;
private List<Team> teams;
public RootHandler(XMLReader reader) {
this.reader = reader;
this.teams = new LinkedList<Team>();
}
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
if (name.equals("team")) {
// Switch handler to parse the team element
reader.setContentHandler(new TeamHandler(reader, this));
}
}
}
public class TeamHandler extends DefaultHandler {
private XMLReader reader;
private RootHandler parent;
private Team team;
private StringBuilder content;
public TeamHandler(XMLReader reader, RootHandler parent) {
this.reader = reader;
this.parent = parent;
this.content = new StringBuilder();
this.team = new Team();
}
// characters can be called multiple times per element so aggregate the content in a StringBuilder
public void characters(char[] ch, int start, int length) throws SAXException {
content.append(ch, start, length);
}
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
content.setLength(0);
}
public void endElement(String uri, String localName, String name) throws SAXException {
if (name.equals("name")) {
team.setName(content.toString());
} else if (name.equals("team")) {
parent.addTeam(team);
// Switch handler back to our parent
reader.setContentHandler(parent);
}
}
}
Ответ 2
Трудно советовать, не зная больше о ваших требованиях, но тот факт, что вы удивлены тем, что "мой код стал довольно сложным", говорит о том, что вы не были хорошо осведомлены, когда выбрали SAX. SAX - это низкоуровневый программный интерфейс, способный к очень высокой производительности, но это потому, что парсер делает гораздо меньше работы для вас, и поэтому вам нужно сделать гораздо больше работы самостоятельно.
Ответ 3
Я настоятельно рекомендую прекратить парсинг самостоятельно и захватить хорошую библиотеку привязки XML-данных. XStream (http://x-stream.github.io/) может быть личным фаворитом, но там много разных библиотек. Он может быть даже способен анализировать ваши POJO на месте, без какой-либо конфигурации (если вы используете имена свойств и плюрализацию для соответствия структуре XML).
Ответ 4
Я делаю что-то очень похожее, но вместо того, чтобы иметь флаги boolean
, чтобы сообщить мне, в каком состоянии я вхожу, я тестирую player
или team
как null
. Делает вещи немного аккуратнее. Это требует, чтобы вы установили их в null
, когда вы обнаруживаете конец каждого элемента, после того как вы добавили его в соответствующий список.
Ответ 5
Если вам нужен более красивый код, используйте StAX, это сравнение всех XML-синтаксических API говорит о том, что StAX - намного лучший вариант.
Производительность StAX в большинстве тестов лучше, чем в любой другой реализации API.
Поэтому я лично не вижу никаких оснований для продолжения работы SAX, если вы не выполняете некоторые связанные с устаревшим программированием.