Телеграм чат начинающих программистов. Общаемся и помогаем друг другу
Если ссылка не открывается, можно найти нас в поиске по чатам @rubyrush
или
пойти другим путем
Этим уроком мы начинаем блок курса, посвященный тому, как в реальном мире хранятся данные. В следующих уроках мы рассмотрим форматы данных XML, JSON и познакомимся с базами данных.
Программы работают с информацией, как человек с пищей — создают, готовят, потребляют, перерабатывают, показывают. Для того, чтобы программам (и программистам) было удобнее с ней работать, информацию нужно как-то хранить, в каком-то удобном виде предоставлять пользователям или другими программам.
Информация бывает очень сложная. Нельзя просто взять и записать в всё в один файл, как попало. Чтобы избежать неразберихи, люди договариваются о стандартах: розетки, батарейки, usb-входы. Программисты — тем более люди, они договорились о форматах и способах хранения данных.
Цель любого формата — структурировать данные, чтобы с ними было проще эффективнее работать: чтобы сразу было понятно, где файл начинается, где хранится служебная информация, как в нём упакованы данные. Форматов и способов хранения данных много, в нашем курсе мы познакомимся с самыми актуальными: XML, JSON и базы данных.
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-файлами во всех современных языках есть парсеры. Парсер — это инструмент (чаще всего библиотека) для чтения данных из текста и представления их в удобной для работы с ними форме. Обычно их в каждом языке даже несколько. Какие-то быстрее, какие-то удобнее. Один из таких парсеров (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 — грубо говоря, одна из разновидностей XML. Требования к разметке в некоторых версиях HTML, однако (а версий HTML много, сейчас, например, самая актуальная HTML5), могут существенно отличаться о того, что мы рассказали про XML.
Когда браузер заходит на какой-то сайт, он отправляет запрос и в качестве ответа получает в том числе текст в формате html. Браузер его обрабатывает и превращает в текст (также как Word превращает xml в те файлы, с которыми мы привыкли работать).
Давайте зайдём на сайт rubyrush.ru и посмотрим его html-разметку. Для этого нажмите на любой неактивный элемент на сайте правой кнопкой мыши и выберите пункт «Исходный код страницы», или просто нажмите Ctrl+U
.
Те блоки гипертекста, которые вы видите, вам должны показаться знакомыми — тоже тэги, вложенные друг в друга, атрибуты и так далее. Нам это знание пригодится в последующих уроках, когда будем работать с сетью.
Итак, мы узнали о формате XML, научились читать данные из XML-файлов и узнали немного про HTML. На следующем уроке мы научимся писать свои XML файлы и сделаем так, чтобы нам не приходилось добавлять траты в файл expenses.xml
руками.