Tentokrát se podíváme, jak zpracovat soubory XML pomocí jazyka Ruby
28.4.2012 21:00 | Jakub Lares | přečteno 6163×
XML nemá předem daná pravidla pro reprezentaci datových struktur - tyto struktury musíme sami navrhnout a implementovat. Výhodou je o něco větší flexibilita tohoto datového formátu a možnost nadefinovat si skutečně libovolné struktury přesně odpovídající datové doméně. Pomocí knihovny REXML nejdříve vytvoříme XML dokument s kořenovým elementem data, ve kterém jsou jednotlivé položky pole uložené ve vnořených elementech animal. Toto XML zapíšeme do řetězce, který potom následně opět pomocí knihovny REXML načteme, a převedeme zpět na původní pole. Zde, nalezneme soubory, se kterými budeme dneska pracovat.
require 'rexml/document' # Vstupni data data = ['Cat', 'Dog', 'Elepahnt'] puts 'Input data:' p data # Vytvorit novy XML dokument doc = REXML::Document.new # Vlozit hlavicku doc << REXML::XMLDecl.new # Vytvorit korenovy element 'data' root = REXML::Element.new('data') # Iterovat pres data data.each do |value| # Vytvorit novy element 'animal' new_element = REXML::Element.new('animal') # VLozit do elementu text (jmeno zvirete) new_element.text = value # Pridat novy element do korenoveho elementu root.elements << new_element end # Vlozit korenovy element do dokumentu doc.elements << root xml = "" # Zapsat XML dokument do retezce xml doc.write(xml,2,false) puts "Converted to XML" puts xml # Parsovani XML data2 = [] # Nacist retezec xml do dokumentu doc2 doc2 = REXML::Document.new(xml) # Iterovat pres elementy v korenovem elementu doc2.root.elements.each do |element| # Vlozit do pole textovy obsah elementu (orezat bile znaky) data2 << element.text.strip end puts "Parsed from XML:" p data2
Input data: ["Cat", "Dog", "Elepahnt"] Converted to XML <?xml version='1.0'?> <data> <animal> Cat </animal> <animal> Dog </animal> <animal> Elepahnt </animal> </data> Parsed from XML: ["Cat", "Dog", "Elepahnt"]
V tomto příkladu zkusíme do XML zapsat datovou strukturu ze souboru quiz_object.yaml. Načtení této datové struktury do objektů provedeme metodou YAML.load_file. Aby toto načtení fungovalo, tak musíme mít přístupné definice příslušných tříd - o toto se stará příkaz require. REXML API pro vytváření XML je poměrně jednoduché, je to kombinace vytváření nových elementů pomocí Element.new, nastavování atributů elementům pomocí přístupové metody attributes, a vkládání podřazených elementů operátorem << do pole elements. Výsledné XML se uloží do souboru voláním doc.write.
require 'rexml/document' require 'yaml' # Musime nacist soubor s tridami Quiz, Question a Answer require 'quiz_classes' source_file = 'quiz_object.yaml' target_file = 'quiz_object_rexml.xml' # Nahrat YAML data kvizu do quiz_object quiz_object = YAML.load_file(source_file) puts "Quiz data loaded from #{source_file}" # Vytvorit novy dokument doc = REXML::Document.new doc << REXML::XMLDecl.new # Vytvorit korenovy element root = REXML::Element.new('quiz') # Nastavit atributy root.attributes['name'] = quiz_object.name root.attributes['author'] = quiz_object.author # Iterovat pres otazky quiz_object.questions.each do |question| # Vytvorit novou otazku new_question = REXML::Element.new('question') new_question.attributes['text'] = question.text # Iterovat pres odpovedi question.answers.each do |answer| # Vytvorit novou odpoved new_answer = REXML::Element.new('answer') # Nastavit text uvnitr elementu new_answer.text = answer.text # Pokud je odpoved pravdiva, tak nastavit hodnotu na "1" correct = "0" correct = "1" if answer.correct new_answer.attributes['correct'] = correct # Element odpoved vlozit do elementu otazka new_question.elements << new_answer end # Vlozit otazku do korenoveho elementu root.elements << new_question end # Vlozit korenovy element do dokumentu doc.elements << root # Zapsat XML do souboru File.open("quiz_object_rexml.xml","w") do |file| doc.write(file,2) end puts "Quiz data written to #{target_file}" puts File.open(target_file,"r") do |file| file.each_line do |line| puts line end end
Quiz data loaded from quiz_object.yaml Quiz data written to quiz_rexml.xml <?xml version='1.0'?> <quiz name='Test vseobecnych znalosti' author='Tomas Marny'> <question text='Hlavni mesto San Marina?'> <answer correct='0'> Torino </answer> <answer correct='1'> San Marino </answer> <answer correct='0'> Vatikan </answer> <answer correct='0'> Terst </answer> </question> ...
Stejné XML jako v předchozím příkladu můžeme o něco úsporněji vytvořit pomocí knihovny builder. Pokud tuto knihovnu nemáte, tak si ji nainstalujte v NetBeans v menu Tools > Ruby Gems > New Gems > Search: builder. Knihovna builder nabízí oproti REXML o něco úspornější API, které využívá dynamických vlastností jazyka Ruby. XML vytváříme voláním metod na objektu Builder::XmlMarkup, kde jméno volané metody je přímo jménem vytvářeného elementu, a případný textový obsah a atributy elementu se předají jako parametry této metody. Pro odlišení speciální metody (jako např instruct! pro vložení XML hlavičky) končí vykřičníkem. XML se zapisuje přímo do souboru, který jsme předali objektu Builder::XmlMarkup v parametru target.
require 'rubygems' require 'builder' require 'yaml' # Musime nacist soubor s tridami Quiz, Question a Answer require 'quiz_classes' source_file = 'quiz_object.yaml' target_file = 'quiz_object_builder.xml' # Nahrat YAML data kvizu do quiz_object quiz_object = YAML.load_file(source_file) puts "Quiz data loaded from #{source_file}" # Otevrit cilovy soubor File.open(target_file,'w') do |file| # Vytvorit novy XML dokument xml = Builder::XmlMarkup.new({:target => file, :indent => 2}) # Vlozit hlavicku xml.instruct! # Vytvorit korenovy element 'quiz' vcetne atributu xml.quiz({'name' => quiz_object.name, 'author' => quiz_object.author}) do # Iterovat pres otazky quiz_object.questions.each do |question| # Vytvorit zde (uvnitr xml elementu quiz) element question xml.question({'text' => question.text}) do # Iterovat pres odpovedi question.answers.each do |answer| # Zapsat 1 do atributu correct pokud je odpoved spravna correct = 0 correct = 1 if answer.correct # Vytvorit zde element answer xml.answer(answer.text, {'correct' => correct}) end # Konec iterace answers end # Konec elemetu question end # Konec iterace pres quiestions end # Konec elementu quiz end # Zavrit soubor puts "Quiz data written to #{target_file}" puts File.open(target_file,"r") do |file| file.each_line do |line| puts line end end
Quiz data loaded from quiz_object.yaml Quiz data written to quiz_builder.xml <?xml version="1.0" encoding="UTF-8"?> <quiz name="Test vseobecnych znalosti" author="Tomas Marny"> <question text="Hlavni mesto San Marina?"> <answer correct="0">Torino</answer> <answer correct="1">San Marino</answer> <answer correct="0">Vatikan</answer> <answer correct="0">Terst</answer> </question> ...
V tomto příkladu načítáme XML stejným způsobem jako v prvním příkladu, pouze se jedná trochu složitější soubor. Čteme právě to XML, které jsme vytvořili v minulém příkladu. Základem jsou dvě vnořené smyčky, z nichž jedna iteruje přes otázky (root.elements) a druhá přes odpovědi (question.elements). V průběhu iterace vytváříme výsledné objekty. Seznam otázek vypíšeme metodou print_quiz.
require 'rexml/document' require 'yaml' # Musime nacist soubor s tridami Quiz, Question a Answer require 'quiz_classes' source_file = 'quiz_builder.xml' puts "Parsing source file #{source_file}" File.open(source_file,"r") do |file| # Otevrit soubor jako XML dokument doc = REXML::Document.new(file) # Precist jmeno a autora z atributu korenoveho elementu name = doc.root.attributes['name'] author = doc.root.attributes['author'] # Vytvorit novy Quiz objekt quiz = Quiz.new(name,author,Array.new) # Iterovat pres vnitrni elementy korenoveho elementu doc.root.elements.each do |question| # Vytvorit novy Question objekt, vlozit text z atributu new_question = Question.new(question.attributes['text'],Array.new) # Iterovat pres vnitrni elementy elementu 'question' question.elements.each_with_index do |answer,i| # Zjistit jestli je otazka spravna a pripadne prevest "1" na true correct = false correct = true if answer.attributes['correct']=="1" # Pridat do objektu otazky novou odpoved new_question.answers << Answer.new(answer.text.strip,correct) end # Pridat novou otazku do kvizu quiz.questions << new_question end # Vytisknout kviz quiz.print_quiz end
Parsing source file quiz_builder.xml Test vseobecnych znalosti Tomas Marny 1. Hlavni mesto San Marina? a) Torino b) San Marino c) Vatikan d) Terst
Toto je pro dnešek vše, příště si řekneme něco o unit testech a dokumentaci.