Полезное для программистов:

Фриланс
Новости
Статьи
   
Рубрики:


Парсинг XML с помощью StAX

Поиск:
Технологии парсинга XML можно разделить на 2 групппы:

1. Вначале весь XML парсится и в памяти строится его дерево, а затем уже с ним может работать пользователь. К этому классу относятся: DOM и JAXB.

2. XML парсится постепенно, тег за тегом. По мере парсинга программа получает информацию о текущем элементе. Приложение сам обрабатывает данные по мере поступления. К этом классу относятся: SAX и StAX парсеры.

Основным преимуществом парсеров 2-го типа является, то что при правильном подходе они позволяют минимизировать потребление памяти.



Подход у SAX и StAX парсеров очень похож, все различия состоят в том, кто контролирует процесс. В случае с SAX парсером приложение может только определить действия на события (начало документа, конец документа, начало элемента, конец элемента и т.д.) реализовав интерфейс листенера.

В случае же со StAX парсерсером, приложение само управляет разбором, выдавая парсеру команду перейти к следующему элементу. Т.е. фактически StAX парсер представляет собой специализированный итератор (он даже имплементирует интерфейс Iterator).


А теперь перейдем к практике, предположим у нас есть система которой требуется определять страну по IP адресу. И есть файл с данными о принадлежности IP адресов следующего формата:
Код

<?xml version="1.0" encoding="UTF-8"?>
<countries>
  <country ip_start="2.6.190.56" ip_end="2.6.190.63" flag="GB">United Kingdom</country>
  <country ip_start="3.0.0.0" ip_end="4.17.135.31" flag="US">United States</country>
  <country ip_start="4.17.135.32" ip_end="4.17.135.63" flag="CA">Canada</country>
  <country ip_start="4.17.135.64" ip_end="4.17.142.255" flag="US">United States</country>
  <country ip_start="4.17.143.0" ip_end="4.17.143.15" flag="CA">Canada</country>
</countries>



Теперь рассмотрим класс который будет хранить данные о диапазонах IP адресов:
Код

package test;

public class GeoIPv4Adress
{
  private static final int SIZE = 4;
  private static final long MASK = 0xFFFFFFFFL;

  private final int startAdress;
  private final int endAdress;
  private final String countryIsoCode;
  private final String countryName;

  public GeoIPv4Adress(int startAdress, int endAdress, String countryIsoCode, String countryName)
  {
    this.startAdress = startAdress;
    this.endAdress = endAdress;
    this.countryIsoCode = countryIsoCode.intern();
    this.countryName = countryName.intern();
  }

  public GeoIPv4Adress(byte[] startAdress, byte[] endAdress, String countryIsoCode, String countryName)
  {
    this(toInt(startAdress), toInt(endAdress), countryIsoCode, countryName);
  }

  public String getCountryIsoCode()
  {
    return countryIsoCode;
  }

  public String getCountryName()
  {
    return countryName;
  }

  public int getStartAsInt()
  {
    return startAdress;
  }

  public byte[] getStartAsBytes()
  {
    return toByteArray(startAdress);
  }

  public int getEndAsInt()
  {
    return endAdress;
  }

  public byte[] getEndAsBytes()
  {
    return toByteArray(endAdress);
  }

  public boolean isInRange(final int ip)
  {
    long lip = MASK & ip;
    return ((startAdress & MASK) <= lip) && (lip >= (endAdress & MASK));
  }

  public boolean isInRange(final byte[] ip)
  {
    return isInRange(toInt(ip));
  }

  @Override
  public String toString()
  {
    StringBuilder buffer = new StringBuilder(getClass().getSimpleName()).append("[");
    buffer.append(countryName).append(" (").append(countryIsoCode).append("): ");
    appendIpAdress(buffer, getStartAsBytes());
    buffer.append(" - ");
    appendIpAdress(buffer, getEndAsBytes());
    buffer.append(']');
    return buffer.toString();
  }

  private static void appendIpAdress(StringBuilder buffer, byte[] bytes)
  {
    for(int i = 0; i < bytes.length; i++)
    {
      if(i > 0)
        buffer.append('.');
      buffer.append(Integer.toString(bytes[i] & 0xFF));
    }
  }

  public static GeoIPv4Adress parse(String startIp, String endIp, String countryIsoCode, String countryName)
  {
    return new GeoIPv4Adress(parse(startIp), parse(endIp), countryIsoCode, countryName);
  }

  public static byte[] parse(String ip)
  {
    final String[] strings = ip.split("\.");
    if(strings.length != SIZE)
      throw new IllegalArgumentException("Invalid input string - '" + ip + "'");

    byte[] bytes = new byte[SIZE];
    for(int i = 0; i < strings.length; i++)
    {
      String s = strings[i];
      int b = Integer.parseInt(s);
      if(b < 0 || b > 255)
        throw new IllegalArgumentException("Invalid input string - '" + ip + "'");

      bytes[i] = (byte) b;
    }
    return bytes;
  }

  private static byte[] toByteArray(final int i)
  {
    byte[] bytes = new byte[SIZE];
    bytes[0] = (byte) ((i >>> 24) & 0xFF);
    bytes[1] = (byte) ((i >>> 16) & 0xFF);
    bytes[2] = (byte) ((i >>> 8) & 0xFF);
    bytes[3] = (byte) (i & 0xFF);
    return bytes;
  }

  private static int toInt(final byte[] bytes)
  {
    if(bytes.length != SIZE)
      throw new IllegalArgumentException("Invalid array length, expected - 4, found - " + bytes.length);

    return ((bytes[0] & 0xFF) << 24) +
           ((bytes[1] & 0xFF) << 16) +
           ((bytes[2] & 0xFF) << 8) +
           (bytes[3] & 0xFF);
  }
}

На некоторых вещах имеет смысл остановится поподробней. Объем памяти занимаемой объектом можно оценить следующим образом (для 32-х разрядных JVM): 8 байт на заголовок объекта, и по 4 байта на каждое поле, кроме double и long которые занимают по 8 байт. Таким образом с точки зрения памяти два поля int намного лучше чем два поля byte[4]. Что же касается строк, то мы их интернируем, что позволяет избежать дублирования если одна и та же страна имеет несколько диапазонов адресов.
Остальные методы являются вспомогательными для преобразования из int в byte[4] и являются достаточно тривиальными. Единственное на что стоит обратить обратить внимание, это проверка что IP адрес попадает в диапазон
Код

  public boolean isInRange(final int ip)
  {
    long lip = MASK & ip;
    return ((startAdress & MASK) <= lip) && (lip >= (endAdress & MASK));
  }

если мы возьмем адрес 2.*.*.* и 234.*.*.*, то первый будет положительным int, а второй отрицательным и потому просто их сравнить их нельзя. Если бы в Java были беззнаковые числа, то это решило бы проблему, но поскольку их нет, то приходится преобразовывать к long оставляя только младшие 32 бита.


Теперь перейдем непосредственно к парсингу XML:
Код

package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.*;

public class GeoIpParser
{
  private static final QName countryTagName = new QName("country");
  private static final QName ipStartAttribName = new QName("ip_start");
  private static final QName ipEndAttribName = new QName("ip_end");
  private static final QName flagAttribName = new QName("flag");

  private XMLInputFactory factory = XMLInputFactory.newInstance();
  private Attribute ipStart;
  private Attribute ipEnd;
  private Attribute flag;
  private StringBuilder chars;
  private boolean isInsideCountryTag;
  private List<GeoIPv4Adress> adresses;

  public static void main(String[] args)
  {
    GeoIpParser parser = new GeoIpParser();
    try
    {
      List<GeoIPv4Adress> geoIPv4AdressList = parser.parse(new File("xml/geo-ip.xml"));
      for(GeoIPv4Adress adress : geoIPv4AdressList)
      {
        System.out.println(adress);
      }
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }

  public List<GeoIPv4Adress> parse(File file) throws IOException, XMLStreamException
  {
    FileInputStream in = new FileInputStream(file);
    XMLEventReader eventReader = factory.createXMLEventReader(in);

    while(eventReader.hasNext())
    {
      XMLEvent xmlEvent = eventReader.nextEvent();
      switch(xmlEvent.getEventType())
      {
        case XMLStreamConstants.START_DOCUMENT:
          init();
          break;

        case XMLStreamConstants.START_ELEMENT:
          processStartElement(xmlEvent.asStartElement());
          break;

        case XMLStreamConstants.CHARACTERS:
          processCharacters(xmlEvent.asCharacters());
          break;

        case XMLStreamConstants.END_ELEMENT:
          processEndElement(xmlEvent.asEndElement());
          break;

        case XMLStreamConstants.END_DOCUMENT:
          cleanup();
          break;
      }
    }

    List<GeoIPv4Adress> result = adresses;
    adresses = null;
    return result;
  }

  private void init()
  {
    adresses = new ArrayList<GeoIPv4Adress>();
    chars = new StringBuilder();
    isInsideCountryTag = false;
  }

  private void processStartElement(StartElement element)
  {
    if(element.getName().equals(countryTagName))
    {
      ipStart = element.getAttributeByName(ipStartAttribName);
      ipEnd = element.getAttributeByName(ipEndAttribName);
      flag = element.getAttributeByName(flagAttribName);
      isInsideCountryTag = true;
    }
  }

  private void processEndElement(EndElement element)
  {
    if(element.getName().equals(countryTagName))
    {
      createGeoIPv4Adress();

      ipStart = null;
      ipEnd = null;
      flag = null;
      chars.setLength(0);
      isInsideCountryTag = false;
    }
  }

  private void createGeoIPv4Adress()
  {
    try
    {
      adresses.add(GeoIPv4Adress.parse(ipStart.getValue(), ipEnd.getValue(), flag.getValue(), chars.toString()));
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }

  private void processCharacters(Characters characters)
  {
    if(isInsideCountryTag)
    {
      chars.append(characters.getData());
    }
  }

  private void cleanup()
  {
    ipStart = null;
    ipEnd = null;
    flag = null;
    chars = null;
    isInsideCountryTag = false;
  }
}

Вначале идет объявление констант с именами тегов и атрибутов которые нас будут интересовать, дальше внутренние переменные используемые во время парсинга. main() не рассматриваем, т.к. он тривиален, сразу переходим к parse().

Вначале создаем поток чтения из файла и создаем XMLEventReader для этого потока. Дальше в цикле парсим элементы XML, в данном случае XMLEventReader используется как простой итератор. Если текущее событие начало или конец документа, то выполняем инициализацию/очистку. Если это начало элемента, то в методе processStartElement() проверяем имя этого элемента и если это наш элемент, сохраняем во временные переменные значения атрибутов и стами флаг, что мы внутри нужного тега.

Событие Characters наступает идут данные тега или данные между тегами (пробелы, переносы строки, смешанный контент). Поэтому мы первым делом проверяем, что мы сейчас внутри интересующего тега. Если это так, то добавляем текущие данные в буфер. Дело в том, что если внутри тега много данных или нижележащий поток отдает данные малыми частями, то мы можем получить не одно событие Characters, а несколько последовательных. Поэтому мы накапливаем данные в буфере, до наступления события EndElement.

В методе processEndElement() мы проверяем, что закрыт интересующий нас тег, берем данные сохранные во временных полях и создаем объект GeoIPv4Adress и добавляем его в List. После чего очищаем временные поля и буфер.
Автор: LSD






Просмотров: 15580

 

 

Новые статьи:


Популярные:
  1. Как сделать цикличным проигрывание MIDI-файла?
  2. Создание AVI файла из рисунков
  3. Как устройство "отключить в данной конфигурации"?
  4. Kто в данный момент присоединен через Сеть?
  5. Как узнать количество доступной памяти?
  6. Как реализовать в RichEdit разноцветный текст?
  7. Как скрыть свое приложение от ProcessViewer
  8. Как программно нажать/скрыть/показ кнопку "Start"?
  9. Модуль работы с ресурсами в PE файлах
10. Функции вызова диалоговых окон выбора
11. Проверка граматики средствами Word'а из Delphi.
12. Модуль для упрощенного вызова сообщений
13. Функции для записи и чтение своих данных в, ЕХЕ- файле
14. Рекурсивный просмотр директорий
15. Network Traffic Monitor
16. Разные модули
17. Универсальная функция для обращения к любым экспортируем функциям DLL
18. Библиотека от VladS
19. Протектор для UPX'а
20. Еще об ICQ, сообщения по контакт листу?
21. Использование открытых интерфейсов
22. Теория и практика использования RTTI
23. Работа с TApplication
24. Примеры использования Drag and Drop для различных визуальных компонентов
25. Что такое порт? Правила для работы с портами
26. Симфония на клавиатуре
27. Загрузка DLL
28. Исправление автоинкремента
29. Взаимодействие с чужими окнами
30. Проверить дубляжи в столбце


 

 

 
 
На главную