Телеграм чат начинающих программистов. Общаемся и помогаем друг другу
Если ссылка не открывается, можно найти нас в поиске по чатам @rubyrush
или
пойти другим путем
В прошлом уроке мы научились писать XML-файлы руками и читать XML в программах Ruby. Теперь научимся записывать XML-данные в файл с использованием нашего любимого gem-а: REXML.
Вот несколько причин, почему стоит избегать создания файлов с данными для программ руками:
Для записи XML в файлы мы воспользуемся тем же парсером, что и для чтения, только теперь будем действовать наоборот. Мы передаем XML-документ и объект файла нашему парсеру и он сам пишет в него данные как надо.
Итак, давайте поставим задачу. Мы хотим программу, которая позволит нам ввести в консоли трату (описание, категорию, дату и количество потраченных денег), а потом допишет её в наш файл expenses.xml
. Вспомним, заодно, как он у нас устроен.
<?xml version='1.0' encoding='UTF-8'?>
<expenses>
<expense amount='900' category='Образование' date='9.7.2015'>
Книжка по Ruby on Rails
</expense>
<expense amount='400' category='Хороший программист' date='1.7.2015'>
Петличка и провода
</expense>
<expense amount='3500' category='Хороший программист' date='23.6.2015'>
Софтбокс + штатив
</expense>
</expenses>
Давайте писать программу для записи расходов в наш файл expenses.xml
: для этого мы будем использовать всё тот же gem REXML. Создайте в вашей папке rubytut2/lesson9/expense_tracker
файл expense_writer.rb
:
require "rexml/document" # подключаем парсер
require "date" # будем использовать операции с данными
# Спросим у пользователя, на что он потратил деньги и сколько
puts "На что потратили деньги?"
expense_text = STDIN.gets.chomp
puts "Сколько потратили?"
expense_amount = STDIN.gets.chomp.to_i
# Спросим у пользователя, когда он потратил деньги
puts "Укажите дату траты в формате ДД.ММ.ГГГГ, например 12.05.2003 (пустое поле - сегодня)"
date_input = STDIN.gets.chomp
# Для того, чтобы записать дату в удобном формате, воспользуемся методом parse класса Time
expense_date = nil
# Если пользователь ничего не ввёл, значит он потратил деньги сегодня
if date_input == ''
expense_date = Date.today
else
begin
expense_date = Date.parse(date_input)
rescue ArgumentError # если дата введена неправильно, перехватываем исключение и выбираем "сегодня"
expense_date = Date.today
end
end
# Наконец, спросим категорию траты
puts "В какую категорию занести трату"
expense_category = STDIN.gets.chomp
# Сначала получим текущее содержимое файла
# И построим из него XML-структуру в переменной doc
current_path = File.dirname(__FILE__)
file_name = current_path + "/my_expenses.xml"
file = File.new(file_name, "r:UTF-8")
doc = nil
begin
doc = REXML::Document.new(file)
rescue REXML::ParseException => e # если парсер ошибся при чтении файла, придется закрыть прогу :(
puts "XML файл похоже битый :("
abort e.message
end
file.close
# Добавим трату в нашу XML-структуру в переменной doc
# Для этого найдём элемент expenses (корневой)
expenses = doc.elements.find('expenses').first
# И добавим элемент командой add_element
# Все аттрибуты пропишем с помощью параметра, передаваемого в виде АМ
expense = expenses.add_element 'expense', {
'amount' => expense_amount,
'category' => expense_category,
'date' => expense_date.strftime('%Y.%m.%d') # or Date#to_s
}
# А содержимое элемента меняется вызовом метода text
expense.text = expense_text
# Осталось только записать новую XML-структуру в файл методов write
# В качестве параметра методу передаётся указатель на файл
# Красиво отформатируем текст в файлике с отступами в два пробела
file = File.new(file_name, "w:UTF-8")
doc.write(file, 2)
file.close
puts "Информация успешно сохранена"
Отлично, осталось запустить этот файл и создать новую трату:
$ ruby expenses_writer.rb
На что потратили деньги?
Компот
Сколько потратили?
200
Укажите дату траты в формате ДД.ММ.ГГГГ, например 12.05.2003 (пустое поле - сегодня)
12.07.2015
В какую категорию занести трату
Развлечения
Информация успешно сохранена
Можно посмотреть содержимое файла my_expenses.xml
и убедиться, что информация действительно успешно сохранена.
<?xml version='1.0' encoding='UTF-8'?>
<expenses>
<expense amount='900' category='Образование' date='9.7.2015'>
Книжка по Ruby on Rails
</expense>
<expense amount='400' category='Хороший программист' date='1.7.2015'>
Петличка и провода
</expense>
<expense amount='3500' category='Хороший программист' date='23.6.2015'>
Софтбокс + штатив
</expense>
<expense amount='200' category='Развлечения' date='2015.07.12'>
Компот
</expense>
</expenses>
Действительно, новая трата добавилась. Поиграйтесь с программой, добавьте несколько новых трат и посмотрите, как можно с помощью двух программ: expense_reader.rb
и expense_writer.rb
вообще обойтись без открывания файла my_expenses.xml
в текстовом редакторе.
Если вы работаете с внешними данными, которые непонятно кто готовил, помните о возможности ошибки. Например, незакрытый тег или забытая треугольная скобка. Парсер сообщит вам о таких ошибках.
Давайте попробуем сломать файл my_expenses.xml
и, например, удалим закрывающий тег коревого контейнера </expenses>
.
$ ruby expenses_reader.rb
.../ruby-2.1.4/lib/ruby/2.1.0/rexml/parsers/treeparser.rb:27:in `parse': No close tag for /expenses (REXML::ParseException)
Парсер сразу же ругнулся и всё нам прямо так и написал: "Нет закрывающего тега у expenses". Чтобы в этой ситуации программа не вылетала с ошибкой, можно написать обработчик.
begin
doc = REXML::Document.new(file) # создаем новый документ REXML, построенный из открытого XML файла
rescue REXML::ParseException => e
puts "Похоже, файл #{file_name} испорчен:"
abort e.message
end
Теперь можно ломать наш файл как угодно и смотреть, что за сообщение напишет нам наш expense_reader.rb
. Программа для записи также будет спотыкаться о битые XML-файлы, но обработчик туда допишите уже сами.
Итак, мы научились не только читать XML-файлы но и с лёгкостью писать свои (или обновлять уже имеющиеся, что полезнее).