Обработка XML документов в J2ME с использованием kXML 2.1

Автор Eugene Grudina
24.02.2005 г.

Обработка XML документов в J2ME с использованием kXML 2.1


XML стал де-факто стандартом обмена данных между приложениями. Приложения, написанные на J2ME, не являются исключением. Однако в "телефонной" Яве нет встроенных XML парсеров. Работы в этом направлении ведутся (JSR 173)[1], но до того времени, когда XML парсер будет зашит в каждый телефон, нам еще далеко.

Мотивация.

XML стал де-факто стандартом обмена данных между приложениями. Приложения, написанные на J2ME, не являются исключением. Однако в "телефонной" Яве нет встроенных XML парсеров. Работы в этом направлении ведутся (JSR 173)[1], но до того времени, когда XML парсер будет зашит в каждый телефон, нам еще далеко.
К счастью, на данный момент существует несколько программных продуктов с открытым кодом, позволяющих работать с XML в J2ME приложениях. В отличие от большинства авторов, я не буду делать их краткий обзор, а просто попрошу мне поверить, что kXML 2.1 – это лучшее из того, что на данный момент есть. "Лучшее" означает следующее:
  1. проект (в отличие от большинства других) живет и развивается; вот-вот выйдет следующий релиз - kXML3, архитектура которого существенно переработана.
  2. kXML разрабатывался с учетом всех ограничений J2ME, но при этом он обладает весьма богатым набором опций.
  3. Я читал, хотя лично и не проверял, что kXML работает быстрее всех.
Как всегда в бочке меда есть ложка дегтя. В данном случае картину портит то, что по kXML нет почти никакой документации, есть только примитивный javadoc. Все статьи, которые мне удалось нарыть, уже устарели, так как относятся к первой версии.
Однако ничего сложного в использовании kXML нет – для этого надо всего лишь освоить несколько фундаментальных понятий, лежащих в его основе. В этом Вам поможет данная статья.

Предполагается, что читатель знает XML, прочитал книгу Б. Эккеля "Thinking in Java" или любой другой хороший учебник по Java, уже умеет писать простейшие мидлеты и пользоваться Sun J2ME Wireless Toolkit (WTK).

Pull-parsing API для XML.

Как известно, на данный момент наиболее распространены две модели обработки XML документов – Sax и DOM. Sax, на мой (и не только на мой) взгляд излишне усложнен, DOM же, при всем своем изяществе, плохо подходит для устройств, имеющих жесткие ограничения по памяти.
kXML базируется на принципиально иной модели, известной как Xml Pull[2]. Она очень проста и изящна. Согласно Xml Pull API, к парсеру привязывается XML документ, после чего курсор (на рис. 1 обозначен красным кружком) устанавливается в начало документа.
Далее, при каждом вызове методов парсера next() или nextToken(), курсор переводится на следующий элемент XML-документа. Разница между этими методами лишь в том, что next() автоматически проскакивает такие поля как COMMENT, CDATA, DOCDECL, ENTITY_REF, PROCESSING_INSTRUCTION и IGNORABLE_WHITESPACE.


Рис. 1 Курсор устанавливается в начало документа



Методы next() и nextToken() в ответ на каждый вызов возвращают событие. Событие есть просто код (типа int), дающий разнообразную информацию о том элементе XML документа, на который указывает курсор. Например, если 0 означает, что курсор находится в начале XML документа, 1 – что достигнут конец. Коды всех событий записаны в интерфейсе org.xmlpull.v1.XmlPullParser в виде констант. Этот интерфейс мы рассмотрим чуть позже.
Вызов методов next() и nextToken может также вызвать исключение типа XmlPullParserException. Исключение возникнет, например, если в XML документе была допущена синтаксическая ошибка.

Наконец, в парсере должны быть реализованы методы, позволяющие нам получить всю информацию об элементе XML-документа, на который в данный момент указывает курсор. Например, метод парсера getName() вернет имя текущего тэга (в виде String). Сигнатуры всех методов описаны опять-таки в org.xmlpull.v1.XmlPullParser.

Сборка kXML из исходников.

На мой взгляд, лучший способ понять как работает kXML – это самому собрать его из исходников, хотя бы бегло прочитав при этом последние. Ни одной строчки кода переписывать при этом не придется! Последовательность действий будет примерно такая:
  1. Скачиваем архив с исходниками с www.kxml.org или непосредственно с http://sourceforge.net/projects/kxml/ . На момент написания статьи самой свежей была версия 2.1.9.
  2. Создаем в WTK новый проект, называем его MobileXmlParser (рис. 2).

Рис. 2 Создаем в WTK новый проект


  1. Вернемся к дистрибутиву kXML. Он довольно богат, но нас интересует только содержимое папки src/org (рис. 3). В ней находятся три директории. Две из них - kdom и wap надо удалить, они нам не понадобятся. После этого копируем папку org в папку src проекта MobileXmlParser.

Рис. 3 В дистрибутиве kXML интересует только содержимое папки src/org

  1. Как мы уже знаем, kXML использует Xml Pull API. Скомпилированные библиотеки лежат в папке дистрибутива lib, [в моем случае] в файле xmlpull_1_1_3_1.jar. Мы могли бы положить этот файл в папку lib проекта MobileXmlParser, после чего его можно было бы скомпилировать. Однако это не прибавило бы нам понимания того, как работает kXML, и поэтому мы поступим по-другому:
  • Скачаем исходники Xml Pull c http://xmlpull.org/v1/download/ - на данный момент последняя версия содержится в архиве xmlpull_1_1_3_4a_src.zip
  • Раскроем архив, идем в /src/java/api. Там находится единственная папка org – копируем ее в папку src проекта MobileXmlParser.
В результате файловая структура нашего проекта должна выглядеть так, как на рис.4. В файле XmlPullParser Вы найдете описание всех методов и событий парсера – естественно на английском :).

Рис. 4 Файловая структура нашего проекта должна выглядеть так

Рассмотрим конкретный пример.

Если у Вас хватило терпения внимательно дочитать до этого абзаца и проделать все рекомендуемые действия, то рассмотренный пример Вы поймете практически сразу. Для пущей уверенности я снабдил подробными комментариями почти каждую строчку кода. Sun WTK позволяет печатать данные на консоль, чем мы и воспользуемся, чтобы не отвлекаться на вывод результатов на экран телефона. В данном примере "распарсивается" файл data.xml вот с таким содержимым:

<?xml version="1.0" standalone="yes"?>

<root>

    <trunk id="0">

        <branch name="branch1" id="1">

            <!--This is a branch that belongs to our tree-->

        </branch>

        <branch name="branch2" id="2">

            <leaf name="leaf1" id="3"></leaf>

            <leaf name="leaf2" id="4"></leaf>

            <leaf name="leaf3" id="5"></leaf>

        </branch>

    </trunk>

</root>


Сам же пример выглядит так:

import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParserException;
import javax.microedition.midlet.MIDlet;
import java.io.InputStreamReader;
import java.io.IOException;


public class MobileXmlParser extends MIDlet {
  private KXmlParser parser;
  private InputStreamReader reader;
  /* класс InputStreamReader используется для чтения символов из байтовых потоков */
  private boolean doJob = true;
  private int event;
  private String name;

  public MobileXmlParser() {
    parser = new KXmlParser();
    reader = new InputStreamReader(this.getClass().getResourceAsStream("data.xml"));
    /* файл data.xml нужно перед сборкой проекта положить в папку res
    после этого к его содержимому можно будет обращаться как к InputStream
    Заметьте, что метод getResourceAsStream является profile specific - говоря
    по-русски, в J2SE работает совсем не так как в J2ME
    */
    /* часто XML данные хранятся в переменной типа String. В этом случае мы бы могли
    создать reader, например, так:
    String string="<?xml....";
    reader = new InputStreamReader(new ByteArrayInputStream(string.getBytes()));
    */
  }

  public void startApp() {

    try {
      parser.setInput(reader); //назначим парсеру источник данных
    } catch (XmlPullParserException e) {
      System.out.println("Не удалось связать парсер с потоком данных");
    }

    /* ну а теперь распрасим документ и выведем содержимое на консоль */
    while (doJob) {
      try {
        event = parser.next(); //переместим курсор на следующий элемент документа
        name = parser.getName(); //получим имя этого элемента
        if (event == KXmlParser.END_DOCUMENT) {
          doJob = false;
          break;
        }
        if ((event == KXmlParser.START_TAG) && (name.startsWith("trunk"))) {
          /* Achtung! В моей WTK2.2 проверка условия (name == "trunk")
          работает неправильно, я так и не смог понять почему. Поэтому
          приходится делать проверку методом startWith()
          */
          System.out.println(name + ":" + parser.getAttributeValue(0));
        } else if ((event == KXmlParser.START_TAG) && (name.startsWith("branch"))) {
          System.out.print("    " + name + ":" + parser.getAttributeValue(0) + "-");
          System.out.println(parser.getAttributeValue(1));
        }
      } catch (XmlPullParserException e) {
        System.out.println("Во время парсинга с парсером случилось что-то нехорошее");
      } catch (IOException e) {
        System.out.println("Во время парсинга парсер потерял связь с источником данных");
      }
    }
  }

  public void pauseApp() {
  }

  public void destroyApp(boolean unconditional) {
  }

}


После его успешной компиляции и запуска на консоли WTK появится что-то похожее на рис. 5

Рис. 5 Консоль WTK после успешной компиляции и запуска



Как видите, все достаточно просто. Единственный нюанс заключается в том, что не стоит помещать какую-то информацию между XML-тегами, лучше все записывать в атрибуты.
Иными словами, лучше писать:
<person name="Vasily"></person> 

а не
<person>Vasily</person>

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

Ссылки

[0] - http://www.kxml.org
[1] - http://www.jcp.org/
[2] - http://xmlpull.org

Автор выражает благодарность компании Алкор Пэйкэш и лично Станиславу Иванову

http://lib.juga.ru/article/articleview/209/1/3/

Последнее обновление ( 24.02.2005 г. )

Счетчики