Наш чатик

Телеграм чат начинающих программистов. Общаемся и помогаем друг другу

Если ссылка не открывается, можно найти нас в поиске по чатам @rubyrush или пойти другими путем

Хранение данных, XML, HTML

Этим уроком мы начинаем блок курса, посвященный тому, как в реальном мире хранятся данные. В следующих уроках мы рассмотрим форматы данных XML, JSON и познакомимся с базами данных.

План урока

  1. Как по-человечески хранить данные
  2. Формат данных XML
  3. Чтение XML в Ruby
  4. Немного про HTML

Хранение данных для программ

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

Данные в удобном для компьютера виде

Информация бывает очень сложная. Нельзя просто взять и записать в всё в один файл, как попало. Чтобы избежать неразберихи, люди договариваются о стандартах: розетки, батарейки, usb-входы. Программисты — тем более люди, они договорились о форматах и способах хранения данных.

Цель любого формата — структурировать данные, чтобы с ними было проще эффективнее работать: чтобы сразу было понятно, где файл начинается, где хранится служебная информация, как в нём упакованы данные. Форматов и способов хранения данных много, в нашем курсе мы познакомимся с самыми актуальными: XML, JSON и базы данных.

Формат XML

XML — это способ представить любую информацию в виде древовидной структуры. В жизни очень часто человек организует информацию именно в виде дерева. Давайте представим, как могла бы быть в виде XML упакована информация из вашего аккаунта в какой-нибудь социальной сети:

<?xml version="1.0" encoding="utf-8"?>

<account>

  <photos>
    <photo title="фоточка 1" />
    <photo title="фоточка 2" />
  </photos>

  <friends total="2">
    <friend number="1">Гоша</friend>
    <friend number="2">Петя</friend>
  </friends>

</account>

<!-- а вот так, кстати пишутся комментарии -->

Типичный XML файл начинается с заголовка, в котором указывается версия XML и кодировка файла <?xml version="1.0" encoding="utf-8"?>. Дальше идёт дерево так называемых тегов. Корень у дерева один. Так и в XML-файле корневой тег-контейнер (оборачивающий всё остальное) — один. В нашем случае <account></account>.

Теги записываются в треугольных скобках и бывают открывающие (<photos>) и закрывающие, в начале названия которых стоит слеш (</photos>). Если какой-то элемент XML-структуры состоит из двух таких тегов, то он называется контейнером, т.к. в нём могут находиться другие элементы:

<photos>
  <photo title="фоточка 1" />
  <photo title="фоточка 2" />
</photos>

Если же у тега нет закрывающего, то такой тег содержит слеш в самом конце: (<photo ... />).

Ещё у тегов бывают атрибуты, а у атрибутов — значения. Пары атрибут-значение записываются с помощью знака равно: (<photo title="фоточка 2" />)

Также тег-контейнер может содержать внутри себя кроме других тегов просто обычный текст.

<some_tag>
  Я текст внутри тега "some_tag"
</some_tag>

Вот вкратце и всё, что вам сейчас нужно знать про формат хранения данных XML.

Какие именно теги будут в вашей структуре — уже определяете вы, XML лишь накладывает требования на структуру, которые мы описали выше. Скажем, нельзя открыть тег и не закрыть его, нельзя оставить открой треугольную скобку вот так <error_tag.

XML настолько популярен, что многие программы в нем хранят данные. Например, популярный редактор текста Microsoft Word: его файлы *.docx по сути являются zip-архивами, если такой файл переименовать, поменяв расширение на .zip, то можно залезть внутрь такого архива и посмотреть, какие xml-файлы там лежат.

Проектируем программу для учета расходов

Давайте напишем программу, которая будет показывать, сколько на что мы потратили, а также выводить сумму всех трат за каждый месяц. Для этого нам как-то надо хранить данные о покупках.

Для начала давайте придумаем структуру, в которой мы будем описывать информацию о тратах. Этот XML-файл пока будем редактировать руками, а на следующем уроке научимся делать это с помощью программы.

expenses.xml:

<?xml version="1.0" encoding="utf-8"?>
<expenses>
  <expense date="9.7.2015" category="Образование" amount="900" >
    Книжка по Ruby on Rails
  </expense>

  <expense date="1.7.2015" category="Хороший программист" amount="400" >
    Петличка и провода
  </expense>

  <expense date="23.6.2015" category="Хороший программист" amount="3500" >
    Софтбокс + штатив
  </expense>
</expenses>

Вот так мы будем хранить эти данные. Как вы видите, у нас есть корневой тег expenses, в него вложены теги expense (по одному на каждую трату), внутри каждого такого тега, текст с информацией о покупке, а в атрибутах date, amount и category соответственно дата, сумма и категория покупки.

Как читать формат XML в ruby

Для того, чтобы удобно работать с XML-файлами во всех современных языках есть парсеры. Парсер — это инструмент (чаще всего библиотека) для чтения данных из текста и представления их в удобной для работы с ними форме. Обычно их в каждом языке даже несколько. Какие-то быстрее, какие-то удобнее. Один из таких парсеров (REXML) уже встроен в Ruby (начиная с версии 1.9). Им и воспользуемся.

Программа для учёта расходов

Сейчас мы напишем программу, которая будет идти по всем тратам и добавлять их в ассоциативный массив, в котором в виде ключа будут даты: то есть все траты, сделанные в один и тот же день, попадут в один и тот же элемент.

Объединяем траты по дням

Затем мы пройдёмся по всем дням и объединим их в месяцы:

Начинаем с первого дня

Как только увидели новый месяц - создаём другой ключ

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

Теперь можем достать все траты за любой месяц

Давайте писать программу!

expenses_reader.rb:

require "rexml/document" # подключаем парсер
require "date" # будем использовать операции с датами

current_path = File.dirname(__FILE__)
file_name = current_path + "/my_expenses.xml"

# UNLESS в руби - противоположный по смыслу оператору IF
# прерываем выполнение программы досрочно, если конечно файл не существует.
abort "Извиняемся, хозяин, файлик my_expenses.xml не найден." unless File.exist?(file_name)

file = File.new(file_name) # открыли файл

doc = REXML::Document.new(file) # создаем новый документ REXML, построенный из открытого XML файла

amount_by_day = Hash.new # пустой асс. массив, куда сложим все траты по дням

# выбираем из элементов документа все тэги <expense> внутри <expenses>
# и в цикле проходимся по ним
doc.elements.each("expenses/expense") do |item|

  # (Обратите внимание это локальная переменная объявленая в теле цикла, для каждой итерации
  # создается новая такая. За пределами цикла она не видна.)
  loss_sum = item.attributes["amount"].to_i # сколько потратили

  loss_date = Date.parse(item.attributes["date"]) # когда. Date.parse(...) создает из строки объект Date

  # иницилизируем нулем значение хэша, если этой даты еще не было
  amount_by_day[loss_date] ||= 0

  # в руби "a ||= b" эквивалентно "if a == nil  a = b"

  # добавили трату за этот день
  amount_by_day[loss_date] += loss_sum
end

file.close

# сделаем хэш, в который соберем сумму расходов за каждый месяц
sum_by_month = Hash.new

# в цикле по всем датам хэша amount_by_day накопим в хэше sum_by_month
# значения потраченных сумм каждого дня
amount_by_day.keys.sort.each do |key|
  # key.strftime("%B %Y") вернет одинаковую строку для всех дней одного месяца
  # поэтому можем использовать ее как уникальный для каждого месяца ключ
  sum_by_month[key.strftime("%B %Y")] ||= 0

  sum_by_month[key.strftime("%B %Y")] += amount_by_day[key] # приплюсовываем к тому что было сумму следующего дня
end

# пришло время выводить статистику на экран
# в цикле пройдемся по всем месяцам и начнем с первого
current_month = amount_by_day.keys.sort[0].strftime("%B %Y")

# выводим заголовок для первого месяца
puts "------[ #{current_month}, всего потрачено: #{sum_by_month[current_month]} р. ]--------"

# цикл по всем дням
amount_by_day.keys.sort.each do |key|

  # если текущий день принадлежит уже другому месяцу...
  if key.strftime("%B %Y") != current_month

    # то значит мы перешли на новый месяц и теперь он станет текущим
    current_month = key.strftime("%B %Y")

    # выводим заголовок для нового текущего месяца
    puts "------[ #{current_month}, всего потрачено: #{sum_by_month[current_month]} р. ]--------"
  end

  # выводим расходы за конкретный день
  puts "\t#{key.day}: #{amount_by_day[key]} р."
end

Немного об HTML

HTML — грубо говоря, одна из разновидностей XML. Требования к разметке в некоторых версиях HTML, однако (а версий HTML много, сейчас, например, самая актуальная HTML5), могут существенно отличаться о того, что мы рассказали про XML.

Когда браузер заходит на какой-то сайт, он отправляет запрос и в качестве ответа получает в том числе текст в формате html. Браузер его обрабатывает и превращает в текст (также как Word превращает xml в те файлы, с которыми мы привыкли работать).

HTML-это тоже XML, только особенный

Давайте зайдём на сайт rubyrush.ru и посмотрим его html-разметку. Для этого нажмите на любой неактивный элемент на сайте правой кнопкой мыши и выберите пункт «Исходный код страницы», или просто нажмите Ctrl+U.

Пример HTML-разметки

Те блоки гипертекста, которые вы видите, вам должны показаться знакомыми — тоже тэги, вложенные друг в друга, атрибуты и так далее. Нам это знание пригодится в последующих уроках, когда будем работать с сетью.

Итак, мы узнали о формате XML, научились читать данные из XML-файлов и узнали немного про HTML. На следующем уроке мы научимся писать свои XML файлы и сделаем так, чтобы нам не приходилось добавлять траты в файл expenses.xml руками.