Ответ 1
Итак, вы хотите создать парсер XML для синтаксического анализа RSS-канала, подобного этому.
<rss version="0.92">
<channel>
<title>MyTitle</title>
<link>http://myurl.com</link>
<description>MyDescription</description>
<lastBuildDate>SomeDate</lastBuildDate>
<docs>http://someurl.com</docs>
<language>SomeLanguage</language>
<item>
<title>TitleOne</title>
<description><![CDATA[Some text.]]></description>
<link>http://linktoarticle.com</link>
</item>
<item>
<title>TitleTwo</title>
<description><![CDATA[Some other text.]]></description>
<link>http://linktoanotherarticle.com</link>
</item>
</channel>
</rss>
Теперь у вас есть две реализации SAX, с которыми вы можете работать. Либо вы используете реализацию org.xml.sax
, либо android.sax
. Я собираюсь объяснить pro и con как после публикации короткого примера.
реализация android.sax
Начните с реализации android.sax
.
Сначала вам нужно определить структуру XML, используя объекты RootElement
и Element
.
В любом случае я буду работать с POJO (обычными старыми объектами Java), которые будут содержать ваши данные. Здесь нужны POJO.
Channel.java
public class Channel implements Serializable {
private Items items;
private String title;
private String link;
private String description;
private String lastBuildDate;
private String docs;
private String language;
public Channel() {
setItems(null);
setTitle(null);
// set every field to null in the constructor
}
public void setItems(Items items) {
this.items = items;
}
public Items getItems() {
return items;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
// rest of the class looks similar so just setters and getters
}
Этот класс реализует интерфейс Serializable
, поэтому вы можете поместить его в Bundle
и сделать с ним что-то.
Теперь нам нужен класс для хранения наших элементов. В этом случае я просто собираюсь расширить класс ArrayList
.
Items.java
public class Items extends ArrayList<Item> {
public Items() {
super();
}
}
Это для нашего контейнера товаров. Теперь нам нужен класс для хранения данных каждого отдельного элемента.
Item.java
public class Item implements Serializable {
private String title;
private String description;
private String link;
public Item() {
setTitle(null);
setDescription(null);
setLink(null);
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
// same as above.
}
Пример:
public class Example extends DefaultHandler {
private Channel channel;
private Items items;
private Item item;
public Example() {
items = new Items();
}
public Channel parse(InputStream is) {
RootElement root = new RootElement("rss");
Element chanElement = root.getChild("channel");
Element chanTitle = chanElement.getChild("title");
Element chanLink = chanElement.getChild("link");
Element chanDescription = chanElement.getChild("description");
Element chanLastBuildDate = chanElement.getChild("lastBuildDate");
Element chanDocs = chanElement.getChild("docs");
Element chanLanguage = chanElement.getChild("language");
Element chanItem = chanElement.getChild("item");
Element itemTitle = chanItem.getChild("title");
Element itemDescription = chanItem.getChild("description");
Element itemLink = chanItem.getChild("link");
chanElement.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
channel = new Channel();
}
});
// Listen for the end of a text element and set the text as our
// channel title.
chanTitle.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
channel.setTitle(body);
}
});
// Same thing happens for the other elements of channel ex.
// On every <item> tag occurrence we create a new Item object.
chanItem.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
item = new Item();
}
});
// On every </item> tag occurrence we add the current Item object
// to the Items container.
chanItem.setEndElementListener(new EndElementListener() {
public void end() {
items.add(item);
}
});
itemTitle.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
item.setTitle(body);
}
});
// and so on
// here we actually parse the InputStream and return the resulting
// Channel object.
try {
Xml.parse(is, Xml.Encoding.UTF_8, root.getContentHandler());
return channel;
} catch (SAXException e) {
// handle the exception
} catch (IOException e) {
// handle the exception
}
return null;
}
}
Теперь это был очень быстрый пример, как вы можете видеть. Основное преимущество использования реализации android.sax
SAX заключается в том, что вы можете определить структуру XML, которую вы должны проанализировать, а затем просто добавить прослушиватель событий к соответствующим элементам. Недостатком является то, что код становится довольно повторяющимся и раздутым.
реализация org.xml.sax
Реализация обработчика org.xml.sax
SAX немного отличается.
Здесь вы не указываете или не объявляете структуру XML, а просто слушаете события. Наиболее широко используются следующие события:
- Начало документа
- Окончание документа
- Начало элемента
- Элемент End
- Символы между Element Start и Element End
Пример реализации обработчика с использованием объекта Channel выше выглядит следующим образом.
Пример
public class ExampleHandler extends DefaultHandler {
private Channel channel;
private Items items;
private Item item;
private boolean inItem = false;
private StringBuilder content;
public ExampleHandler() {
items = new Items();
content = new StringBuilder();
}
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
content = new StringBuilder();
if(localName.equalsIgnoreCase("channel")) {
channel = new Channel();
} else if(localName.equalsIgnoreCase("item")) {
inItem = true;
item = new Item();
}
}
public void endElement(String uri, String localName, String qName)
throws SAXException {
if(localName.equalsIgnoreCase("title")) {
if(inItem) {
item.setTitle(content.toString());
} else {
channel.setTitle(content.toString());
}
} else if(localName.equalsIgnoreCase("link")) {
if(inItem) {
item.setLink(content.toString());
} else {
channel.setLink(content.toString());
}
} else if(localName.equalsIgnoreCase("description")) {
if(inItem) {
item.setDescription(content.toString());
} else {
channel.setDescription(content.toString());
}
} else if(localName.equalsIgnoreCase("lastBuildDate")) {
channel.setLastBuildDate(content.toString());
} else if(localName.equalsIgnoreCase("docs")) {
channel.setDocs(content.toString());
} else if(localName.equalsIgnoreCase("language")) {
channel.setLanguage(content.toString());
} else if(localName.equalsIgnoreCase("item")) {
inItem = false;
items.add(item);
} else if(localName.equalsIgnoreCase("channel")) {
channel.setItems(items);
}
}
public void characters(char[] ch, int start, int length)
throws SAXException {
content.append(ch, start, length);
}
public void endDocument() throws SAXException {
// you can do something here for example send
// the Channel object somewhere or whatever.
}
}
Теперь, честно говоря, я не могу сказать вам реального преимущества этой реализации обработчика над android.sax
. Тем не менее, я могу сказать вам о недостатке, который должен быть довольно очевидным. Взгляните на инструкцию else if в методе startElement
. Из-за того, что у нас есть теги <title>
, link
и description
, мы должны отслеживать там в структуре XML, на которой мы сейчас находимся. То есть, если мы встретим стартовый тег <item>
, мы устанавливаем флаг inItem
на true
, чтобы убедиться, что мы сопоставляем правильные данные с правильным объектом, а в методе endElement
мы устанавливаем этот флаг на false
, если мы сталкиваемся с тегом </item>
. Чтобы сигнализировать, что мы закончили с этим тегом элемента.
В этом примере это довольно легко справиться, но необходимость анализировать более сложную структуру с повторяющимися тегами на разных уровнях становится сложной. Там вам нужно будет либо использовать Enums, например, чтобы установить текущее состояние и множество переключателей/случаев statemenets, чтобы проверить, где вы находитесь, или более элегантным решением будет какой-то тэг-трекер с использованием стека тегов.