Как стать автором
Обновить

Пишем whois-клиент под Android

Время на прочтение 7 мин
Количество просмотров 5.5K
Логотип

В своей прошлой статье я рассказал, что есть довольно много библиотек для парсинга html, в этот раз я решил показать каким образом можно извлекать информацию из текста с помощью регулярных выражений, где невозможно «зацепиться» за теги и воспользоваться упомянутыми библиотеками. Изначально всё начиналось с небольшого приложения, но постепенно я придумывал что-то новое и в итоге как мне кажется, получилось довольно интересно.

Под катом я расскажу о ходе разработке, покажу примеры работы и варианты развития.

Для начала я решил изучить, что есть на рынке, поэтому заглянул в маркет, и по ключевому слову «whois» скачал несколько приложений, сразу понял, что моё приложение будет уникально тем, что оно будет работать с IDN доменами, т.е. доменами с кириллическими именами.

Разработка


Первая отличительная особенность моего приложения — это поддержка кириллических доменов, а вторая — это то, что я буду выводить «распознанные» данные. Я чётко буду знать, в какой строке, какие данные находятся, а не как у скачанных приложений, где они банально берут и парсят блок с выводом данных и выводят его в WebView, чтобы было понятно, зачем мне регулярные выражения, то я покажу кусок документа с интересующей меня информацией:

whois блок

На скриншоте видно, что теги практически отсутствуют и никакой xpath тут не поможет для разбиения данного текста, поэтому самое время начать создавать проект и за работу:

новый проект

В этот раз я решил выбрать минимальную версию, для которой я качал SDK, чуть позже на хабре читал, что 2.1 и 2.2 — самые популярные сейчас версии, сразу скажу, что разрабатываю для 2.1 (тестирование на эмуляторе), а использовать буду на 2.2.

В этот раз я уделил больше внимания интерфейсу, по этой причине полные xml файлы интерфейса выкладывать не могу, по причине того, что они получились довольно внушительные.

Основные элементы — это:
  <EditText android:layout_height="wrap_content"
   android:layout_width="fill_parent"
   android:id="@+id/site"
   android:maxLines="1"
   android:text="">
  </EditText>
  <Button android:text="@string/get"
   android:id="@+id/getData"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:background="@layout/custom_button">
  </Button>
  <ListView android:id="@+id/whoisList"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content">
  </ListView>


* This source code was highlighted with Source Code Highlighter.

В EditText мы будем указывать сайт, тут я указал android:maxLines=«1», чтобы нельзя было вписать несколько строк. Button служит для запуска парсинга, позже выяснилось, что для того, чтобы задать простые стили для кнопки нужно несколько больше телодвижений, чем во многих прикладных приложениях для ПК, поэтому я буду использовать отдельный файл для настроек android:background="@layout/custom_button". ListView также не такой простой, как может показаться на первый взгляд, для него я разработал отдельную строку, которая состоит из двух столбцов.

Файлы интерфейса:
main.xml — главный файл
custom_button.xml — стили кнопки
color.xml — цвета
row.xml — строка ListView

В итоге у нас получится левый скриншот (правда строки констант Вам придётся задать самим или перейти к концу статьи и скачать проект):

Интерфейс

Все скриншоты я объединил по два, чтобы пост не вышел слишком вытянутым, поэтому сразу скажу, что справа — это результат работы ProgressDialog после нажатия кнопки «Получить whois данные». Как я уже писал выше, то доступен ввод только одной строки, поэтому при попытке нажать кнопку переноса на новую строку мы будет заканчивать ввод, что будет сразу прятать и клавиатуру:
EditText edittext = (EditText)findViewById(R.id.site);
edittext.setImeOptions(EditorInfo.IME_ACTION_DONE);


Но так как пользователь может и не прятать клавиатуру и нажать кнопку «Получить whois данные», то мы её спрячем программно:
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0);


Также хотелось попробовать вызовы сообщений на экран:

Сообщения

Слева у нас:
Toast.makeText(MainActivity.this, "Информация отсутствует...", Toast.LENGTH_LONG).show();

А справа отображается:
AlertDialog.Builder alertbox = new AlertDialog.Builder(MainActivity.this);
alertbox.setMessage("Невозможно подключиться к серверу");
alertbox.setNeutralButton("Ok", new DialogInterface.OnClickListener() {
  public void onClick(DialogInterface arg0, int arg1) {
    //обработка нажатия кнопки Ок
  }
});
alertbox.show();


* This source code was highlighted with Source Code Highlighter.

Второе сообщение более критичное, поэтому нужно убедиться, что пользователь его прочитает, для этого используем AlertDialog.

Для перевода домена будем использовать стороннюю библиотеку. Используя которую можно получить из «сайт.рф» набор символов «xn--80aswg.xn--p1ai».
import gnu.inet.encoding.IDNA;
IDNA.toASCII("сайт.рф");


Многие скажут, а почему бы не использовать java.net.idn, но в документации сказано, что оно есть только в 9 апи, а у нас 7, мой тест также показал, что в 7-8 апи import не работает, а вот в 9 действительно всё ОК и следующий код отлично работает без всяких библиотек.

Теперь переходим непосредственно парсингу, как уже говорилось, я буду использовать регулярные выражения:
    HashMap<String, String> map;
    Pattern p = Pattern.compile("created:\\s+(\\d{4}\\.\\d{2}\\.\\d{2})\n", Pattern.CASE_INSENSITIVE);
    Matcher matcher = p.matcher(str);
    if (matcher.find()) {
      map = new HashMap<String, String>();
      map.put("param", "создан:");
      map.put("value", matcher.group(1));
      list.add(map);
    }


* This source code was highlighted with Source Code Highlighter.

«created:\\s+(\\d{4}\\.\\d{2}\\.\\d{2})\n» — дата в формате yyyy.dd.MM или yyyy.MM.dd
«person:\\s+(.+)\n» — любой набор символов между пробелом и переносом каретки на новую строку
«phone:\\s+(\\+[\\s\\d]+)\n» — + (плюс) и пробелы с числами между пробелами и переносом каретки на новую строку
«mail:\\s+(.+)\n» — любой набор символов между пробелом и переносом каретки на новую строку
«registrar:\\s+(.+)\n» — любой набор символов между пробелом и переносом каретки на новую строку
«org:\\s+(.+)\n» — любой набор символов между пробелом и переносом каретки на новую строку

На самом деле варианта всего два — это воспользоваться библиотекой и получить только блок кода, где содержаться искомые данные и в добавок воспользоваться получением innertext, что в последующем даст довольно ощутимую гибкость, т.к. многие регистраторы добавляют свои теги — это и ссылки на mailto, ссылку на сайт как в примере и т.п. Либо прогонять через регулярные выражения весь ответ от сервера (я постарался выбрать регистратора, у которого не слишком сильно перегружена страница).

После того как мы получим результат, можно загрузить его в ListView:
ListView list = (ListView) findViewById(R.id.whoisList);
SimpleAdapter myAdapter = new SimpleAdapter(MainActivity.this, mylist, R.layout.row,
      new String[] { "param", "value"},
      new int[] {R.id.whoisParam, R.id.whoisData});
list.setAdapter(myAdapter);


* This source code was highlighted with Source Code Highlighter.

Результат работы для двух сайтов будет следующим:

Сайты

И последнее, что мне захотелось сделать — это добавить меню и ещё одну Activity:
  Menu myMenu = null;

  @Override
  public boolean onCreateOptionsMenu(Menu menu) 
  {
    //call the parent to attach any system level menus
    super.onCreateOptionsMenu(menu);
    
    this.myMenu = menu;
    
    int base = Menu.FIRST; // value is 1
    MenuItem item = menu.add(base, base, base, "Помощь");
    item.setIcon(R.drawable.help);
    
    //it must return true to show the menu
    //if it is false menu won't show
    return true;
  }
  
  @Override
  public boolean onOptionsItemSelected(MenuItem item)   {
    if (item.getItemId() == 1)    {
      Intent intent = new Intent(MainActivity.this, Help.class);
      startActivity(intent);
    }
    //should return true if the menu item
    //is handled
    return true;
  }


* This source code was highlighted with Source Code Highlighter.

Тут я создаю меню и делаю обработку нажатия кнопки меню, после чего увижу следующую картину (слева форма после вызова меню, а справа после нажатия на кнопку). Для закрытия достаточно просто вызвать finish() во второй Activity.

Меню

Единственное, что я чуть не забыл — это файл манифеста, где обязательно нужно упомянуть об использовании интернета и о существовании новой Activity. Готовый проект выглядит следующим образом:

Проект

Ссылка для скачивания: проект (зеркало)

Итог


Как мне кажется, получилась довольно интересное приложение, на этот раз вариантов развития просто уйма. Данное приложение может стать в перспективе довольно интересным seo инструментом, можно добавить сайты для других доменных зон, получение PageRank, ТИЦ, сохранение результатов в БД. В общем, идею как мне кажется можно бесконечно развивать, хотя я соглашусь и с теми, кто решит написать веб-сервис, а приложение для Андроид будет использовать как клиента для получения данных с одного единственного сайта, что конечно сэкономит трафик и скорее всего, будет работать быстрее.
Теги:
Хабы:
+28
Комментарии 27
Комментарии Комментарии 27

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн