1.4.1.14. fejezet, YAML

A Yet Another Markup Language mint a neve is mutatja egy másik jelölő nyelv, ami teljesen eltér pl. az XML-től. Általánosságba véve szóközök, kettőspontok és kötőjelek az elválasztásra, új objektumra, objektum tulajdonságra használt jelölések. Alkalmas string, számsor, tömb, hash tábla strukturális átalakítására. Mielőtt használatba vennénk, szükség van a yaml modulra.

require 'yaml'
 
puts( "hello world".to_yaml )
puts( 123.to_yaml )
puts( ["a1", "a2" ].to_yaml )
puts( {:h1 => 1, :h2 => 2}.to_yaml )

Eredmények:

--- hello world
--- 123
--- 
- a1
- a2

Most nézzük meg ugyanezt tömbre és objektumra:

require 'yaml'
 
class CD
  def initialize( anArtist, aName, theNumTracks )
    @artist	= anArtist
    @name	= aName
    @numtracks	= theNumTracks
  end
end
 
arr1 =	[["The Groovesters", "Groovy Tunes", 12 ],
	 [  "Dolly Parton", "Greatest Hits", 38 ]
	]
 
arr2 = [ CD.new("The Beasts", "Beastly Tunes", 22),
	 CD.new("The Strolling Bones", "Songs For Senior Citizens", 38) 
       ]
 
y( arr1 )		
y( arr2 )

Eredmények:

--- 
- - The Groovesters
  - Groovy Tunes
  - 12
- - Dolly Parton
  - Greatest Hits
  - 38
--- 
- !ruby/object:CD 
  artist: The Beasts
  name: Beastly Tunes
  numtracks: 22
- !ruby/object:CD 
  artist: The Strolling Bones
  name: Songs For Senior Citizens
  numtracks: 38

y metódus olyan, mint a p, csak ez a to_yaml metódust hívja meg.

YAML adatok megnése

A YAML modul dump metódusával menthetjük az adatokat:

f = File.open( 'friends.yml', 'w' ) 
YAML.dump( ["fred", "bert", "mary"], f ) 
f.close

Vagy így

File.open( 'morefriends.yml', 'w' ){ |friendsfile| 
  YAML.dump( ["sally", "agnes", "john" ], friendsfile ) 
}

Ha ezt a blokk megoldást használjuk, a fájlt automatikusan megnyitja és lezárja a dump.

Visszatölteni egy tömböt a File modulon keresztül így tudjuk:

File.open( 'morefriends.yml' ){ |f|
  $arr= YAML.load(f) 
}

Ha szeretnénk saját szerializálást egy osztálynak, írjuk felül a to_yaml_properties-t

require 'yaml'
 
class Yclass
  def initialize(aNum, aStr, anArray)
    @num = aNum
    @str = aStr
    @arr = anArray
  end
 
  def to_yaml_properties
    ["@num", "@arr"]	
  end		
end	
 
ob = Yclass.new( 100, "fred", [1,2,3] )
p( ob )
 
yaml_ob = YAML.dump( ob )	
puts( yaml_ob )
 
ob2 = YAML.load( yaml_ob )
p( ob2 )
#<Yclass:0x9be42dc @num=100, @str="fred", @arr=[1, 2, 3]>
--- !ruby/object:Yclass 
num: 100
arr: 
- 1
- 2
- 3
#<Yclass:0x9bdee7c @num=100, @arr=[1, 2, 3]>

Több objektumot is szerializálhatunk. Például az első mintaprogramnál a tömböket így írhatjuk ki:

File.open( 'multidoc.yml', 'w' ){ |f|
  YAML.dump( arr1, f )
  YAML.dump( arr2, f )
}

Visszatölteni pedig a YAML.load_documents metódussal lehet:

File.open( 'multidoc.yml' ) {|f|
  YAML.load_documents( f ) { |doc|
    $new_arr << doc
  }
}

YAML adatbázis

Készítsünk egy egyszerű CD adatbázist. Legyen egy név, egy szerző, egy zenék száma tulajdonsága, és legyen két leszármazottja. Egy PopCD, ami a stílust írja le, mint pl.: rock, country tulajdonságában, és legyen egy ClassicCD, ami a karmester és a szerző tulajdonságát rögzíti.

require 'yaml'
 
#cd objektumok tömbje
$cd_arr = Array.new
 
# file név mentésre és visszatöltésre
$fn = 'cd_db.yml'
 
# generikus(szülő) CD osztály
class CD
  def initialize( arr )
    @name		= arr[0]
    @artist		= arr[1]
    @numtracks		= arr[2]
  end
 
  def getdetails
    return[@name, @artist, @numtracks]
  end
end
 
# PopCD egy leszármazott osztálya a CD-nek
class PopCD < CD
  def initialize( arr )
    super( arr  )
    @genre = arr[3]
  end
 
  def getdetails
    return( super << @genre )
  end
end
 
class ClassicalCD < CD
  def initialize( arr )
    super( arr )
    @conductor  = arr[3]
    @composer   = arr[4]
  end
 
  def getdetails
    return( super << @conductor << @composer )
  end
end
 
# Ez néhány metódus a felhasználótól kéri be az adatokat
def otherCD
  print( "Enter CD name: " )
  cd_name = gets().chomp()
  print( "Enter artist's name: " )
  a_name = gets().chomp()
  print( "Enter number of tracks: " )
  num_tracks = gets().chomp().to_i
  return [cd_name, a_name, num_tracks ]
end
 
def classicCD
  cdInfo = otherCD
  print( "Enter conductor's name: " )
  con_name= gets().chomp()
  print( "Enter composer's name: " )
  comp_name = gets().chomp()
  cdInfo << con_name << comp_name
  return cdInfo
end
 
def popCD
  cdInfo = otherCD
  print( "Enter genre of music: " )
  genre = gets().chomp()
  cdInfo << genre
  return cdInfo
end
 
# A $cd_arr tömbhöz ad egy CD objekumot
def addCD( aCD )
  $cd_arr << aCD
end
 
# Adatokat ment YAML formában
def saveDB
  File.open( $fn, 'w' ) {
    |f|
    f.write($cd_arr.to_yaml)
  }
end
 
# visszatölti az adatokat a lemezről és újra létrehozza
# a cd objektumokat, és az adatok alapján a $cd_arr tömböt
def loadDB
  input_data = File.read( $fn )
  $cd_arr = YAML::load( input_data )
end
 
# A tömbből kiírja az adatokat a képernyőre olvasható
# (YAML) formában
def showData
  puts( $cd_arr.to_yaml )
end
 
# A program kezdete
 
if File.exist?( $fn ) then
  loadDB
  showData
else
  puts( "The file #{$fn} cannot be found.")
end
 
# 'main' loop
ans = ''
until ans == 'q' do
  puts( "Create (P)op CD (C)lassical CD, (O)ther CD - (S)ave or (Q)uit?" )
  print( "> " )
  ans = gets[0].chr().downcase()
  case ans
  when 'p' then addCD( PopCD.new( popCD() ) )
  when 'c' then addCD( ClassicalCD.new( classicCD() ) )
  when 'o' then addCD( CD.new( otherCD() ) )
  when 's' then saveDB
  end
  showData
end 

Két CD rögzítése után az eredmény:

--- 
- !ruby/object:PopCD 
  artist: Alpaca
  genre: rumba
  name: ACD
  numtracks: 15
- !ruby/object:CD 
  artist: Stone Huge
  name: Salsa in the jungle
  numtracks: 14

Mindegyik dokumentum három kötőjellel kezdődik. Ezután egy-egy kötőjel lista elemenként. A YAML kettőspontokkal elválasztva a tulajdonságokat, mint egy hash tábla, kulcs-érték párokat tárol.

Az egymásba ágyazott tömbökre mintapélda:

require 'yaml'
 
arr = [1,[2,3,[4,5,6,[7,8,9,10],"end3"],"end2"],"end1"]
y( arr)
--- 
- 1
- - 2
  - 3
  - - 4
    - 5
    - 6
    - - 7
      - 8
      - 9
      - 10
    - end3
  - end2
- end1

JSON adatkezelés

Még egy szöveges tárolási formára felhívom a figyelmet: ez a JSON.

require 'json'
 
class Mclass 
  def initialize(aNum, aStr, anArray)
    @num = aNum
    @str = aStr
    @arr = anArray
  end
 
  def to_json(*a)
       {
         'json_class'   => self.class.name,
         'data'         => [ @num, @str, @arr ]
       }.to_json(*a)
  end
 
  def self.json_create(o)
     new(*o['data'])
  end
end
 
ob = Mclass.new( 100, "fred", [1,2,3] )
p( ob )
 
json_data = JSON.generate(ob) 
p( json_data )
 
ob2 = JSON.parse(json_data)
p(ob2)

Eredmények:

#<Mclass:0x9831c5c @num=100, @str="fred", @arr=[1, 2, 3]>
"{\"json_class\":\"Mclass\",\"data\":[100,[1,2,3],\"fred\"]}"
#<Mclass:0x98317e8 @num=100, @str="fred", @arr=[1, 2, 3]>

Egy kis odafigyelést igényel a to_json metódusban a data összeállítása. Ugyan abba a sorrendbe kell lennie, mint a definiálás sorrendje, különben összekeverednek a visszatöltött tulajdonságok.