Module: OxMlk::ClassMethods
- Defined in:
- lib/oxmlk.rb
Overview
Instance Attribute Summary collapse
-
#ox_attrs ⇒ Object
Returns the value of attribute ox_attrs.
-
#ox_elems ⇒ Object
Returns the value of attribute ox_elems.
-
#tag_proc ⇒ Object
Returns the value of attribute tag_proc.
Instance Method Summary collapse
-
#from_xml(data) ⇒ Object
Returns a new instance from XML.
-
#ox? ⇒ Boolean
Used to tell if this is an OxMlk Object.
- #ox_accessor(sym) ⇒ Object
-
#ox_attr(name, o = {}, &block) ⇒ Object
Declares a reference to a certain xml attribute.
-
#ox_elem(name, o = {}, &block) ⇒ Object
Declares a reference to a certain xml element or a typed collection of elements.
-
#ox_tag(tag = nil, &block) ⇒ Object
Sets the name of the XML element that represents this class.
-
#to_xml(data) ⇒ XML::Node
Returns XML generated from an instance based on Attr & Elem definitions.
Instance Attribute Details
#ox_attrs ⇒ Object
Returns the value of attribute ox_attrs.
37 38 39 |
# File 'lib/oxmlk.rb', line 37 def ox_attrs @ox_attrs end |
#ox_elems ⇒ Object
Returns the value of attribute ox_elems.
37 38 39 |
# File 'lib/oxmlk.rb', line 37 def ox_elems @ox_elems end |
#tag_proc ⇒ Object
Returns the value of attribute tag_proc.
37 38 39 |
# File 'lib/oxmlk.rb', line 37 def tag_proc @tag_proc end |
Instance Method Details
#from_xml(data) ⇒ Object
Returns a new instance from XML
405 406 407 408 409 410 411 412 413 |
# File 'lib/oxmlk.rb', line 405 def from_xml(data) xml = XML::Node.from(data) raise 'invalid XML' unless xml.name == ox_tag returning new do |ox| (ox_attrs + ox_elems).each {|e| ox.send(e.setter,e.from_xml(xml))} ox.send(:after_parse) if ox.respond_to?(:after_parse) end end |
#ox? ⇒ Boolean
Used to tell if this is an OxMlk Object.
396 397 398 |
# File 'lib/oxmlk.rb', line 396 def ox? true end |
#ox_accessor(sym) ⇒ Object
415 416 417 418 419 420 421 422 423 424 |
# File 'lib/oxmlk.rb', line 415 def ox_accessor(sym) define_method(sym) do @attributes ||= {} @attributes[sym.to_s] end define_method("#{sym}=") do |val| @attributes ||= {} @attributes[sym.to_s] = val end end |
#ox_attr(name, o = {}, &block) ⇒ Object
Declares a reference to a certain xml attribute.
Sym Option
- sym
-
Symbol representing the name of the accessor
Default naming
This is what it will use to lookup the attribute if a name isn’t defined in :from. For example:
ox_attr :bob
ox_attr :pony
are equivalent to:
ox_attr :bob, :from => 'bob'
ox_attr :pony, :from => 'pony'
Boolean attributes
If the name ends in a ?, OxMlk will attempt to coerce the value to true or false, with true, yes, t and 1 mapping to true and false, no, f and 0 mapping to false, as shown below:
ox_elem :desirable?
ox_attr :bizzare?, :from => 'BIZZARE'
x = #from_xml(%{
<object BIZZARE="1">
<desirable>False</desirable>
</object>
})
x.desirable?
=> false
x.bizzare?
=> true
When a block is provided the value will be passed to the block where unexpected values can be handled. If no block is provided the unexpected value will be returned.
#from_xml(%{
<object BIZZARE="Dunno"\>
}).desirable?
=> "Dunno"
ox_attr :bizzare? do |val|
val.upcase
end
#from_xml(%{
<object BIZZARE="Dunno"\>
}).strange?
=> "DUNNO"
Blocks
You may also pass a block which manipulates the associated parsed value.
class Muffins
include OxMlk
ox_attr(:count, :from => 'bakers_dozens') {|val| val.to_i * 13 }
end
Blocks are always passed the value after being manipulated by the :as option and are the last thing to manipulate the XML. This is different than the ox_elem annotation that is passed an Array.
Options
:as
Basic Types
Allows you to specify one of several basic types to return the value as. For example
ox_elem :count, :as => Integer
is equivalent to:
ox_elem(:count) {|val| Integer(val) unless val.empty?}
Such block shorthands for Integer, Float, String, Symbol and Time are currently available.
If an Array is passed each Type in the array will be applied to the value in order.
:from
The name by which the xml value will be found in XML. Default is sym.
123 124 125 126 127 |
# File 'lib/oxmlk.rb', line 123 def ox_attr(name,o={},&block) new_attr = Attr.new(name, o.reverse_merge(:tag_proc => @tag_proc),&block) @ox_attrs << new_attr ox_accessor new_attr.accessor end |
#ox_elem(name, o = {}, &block) ⇒ Object
Declares a reference to a certain xml element or a typed collection of elements.
Sym Option
- sym
-
Symbol representing the name of the accessor.
Default naming
This name will be the default node searched for, if no other is declared. For example:
ox_elem :bob
ox_elem :pony
are equivalent to:
ox_elem :bob, :from => 'bob'
ox_elem :pony, :from => 'pony'
Boolean attributes
If the name ends in a ?, OxMlk will attempt to coerce the value to true or false, with true, yes, t and 1 mapping to true and false, no, f and 0 mapping to false, as shown below:
ox_elem :desirable?
ox_attr :bizzare?, :from => 'BIZZARE'
x = #from_xml(%{
<object BIZZARE="1">
<desirable>False</desirable>
</object>
})
x.desirable?
=> false
x.bizzare?
=> true
When a block is provided the value will be passed to the block where unexpected values can be handled. If no block is provided the unexpected value will be returned.
#from_xml(%{
<object>
<desirable>Dunno</desirable>
</object>
}).desirable?
=> "Dunno"
ox_elem :strange? do |val|
val.upcase
end
#from_xml(%{
<object>
<strange>Dunno</strange>
</object>
}).strange?
=> "DUNNO"
Blocks
You may also pass a block which manipulates the associated parsed value.
class Muffins
include OxMlk
ox_elem(:count, :from => 'bakers_dozens') {|val| val.first.to_i * 13 }
end
Blocks are always passed an Array and are the last thing to manipulate the XML. The array will include all elements already manipulated by anything passed to the :as option. If the :as option is an Array and no block is passed then the Array is returned unmodified. If the :as option is nil or is not an Array then only the first element is
Options
:as
Basic Types
Allows you to specify one of several basic types to return the value as. For example
ox_elem :count, :as => Integer
is equivalent to:
ox_elem(:count) {|val| Integer(val.first) unless val.first.empty?}
Such block shorthands for Integer, Float, String, Symbol and Time are currently available.
To reference many elements, put the desired type in a literal array. e.g.:
ox_elem :counts, :as => [Integer]
Even an array of text nodes can be specified with :as => []
ox_elem :quotes, :as => []
Other OxMlk Classes
Declares an accessor that represents another OxMlk class as child XML element (one-to-one or composition) or array of child elements (one-to-many or aggregation) of this type. Default is one-to-one. For one-to-many, simply pass the class as the only element in an array. You can also put several OxMlk classes in an Array that will act like a polymorphic one-to-many association.
Composition example:
<book>
<publisher>
<name>Pragmatic Bookshelf</name>
</publisher>
</book>
Can be mapped using the following code:
class Book
include OxMlk
ox_elem :publisher, :as => Publisher
end
Aggregation example:
<library>
<books>
<book/>
<book/>
</books>
</library>
Can be mapped using the following code:
class Library
include OxMlk
ox_elem :books, :as => [Book], :in => 'books'
end
If you don’t have the <books> tag to wrap around the list of <book> tags:
<library>
<name>Ruby books</name>
<book/>
<book/>
</library>
You can skip the wrapper argument:
ox_elem :books, :as => [Book]
Hash
Unlike ROXML, OxMlk doesn’t do anything special for hashes. However OxMlk applies :as to each element and the block once to the Array of elements. This means OxMlk can support setting hashes but it is more of a manual process. I am looking for a easier method but for now here are a few examples:
Hash of element contents
For xml such as this:
<dictionary>
<definition>
<word/>
<meaning/>
</definition>
<definition>
<word/>
<meaning/>
</definition>
</dictionary>
This is actually one of the more complex ones. It uses the :raw keyword to pass the block an array of LibXML::XML::Nodes
ox_elem(:definitions, :from => 'definition', :as => [:raw]) do |vals|
Hash[*vals.map {}|val| [val.search('word').content,val.search('meaning').content]}.flatten]
end
Hash of :content
For xml such as this:
<dictionary>
<definition word="quaquaversally">adjective: (of a geological formation) sloping downward from the center in all directions.</definition>
<definition word="tergiversate">To use evasions or ambiguities; equivocate.</definition>
</dictionary>
This one can also be accomplished with the :raw keyword and a fancy block.
ox_elem(:definitions, :from => 'definition', :as => [:raw]) {|x| Hash[*x.map {|val| [val['word'],val.content] }.flatten]}
Hash of :name
For xml such as this:
<dictionary>
<quaquaversally>adjective: (of a geological formation) sloping downward from the center in all directions.</quaquaversally>
<tergiversate>To use evasions or ambiguities; equivocate.</tergiversate>
</dictionary>
This one requires some fancy xpath in :from to grab all the elements
ox_elem(:definitions, :from => './*', :as => [:raw]) {|x| Hash[*x.map {|val| [val.name,val.content] }.flatten]}
:from
The name by which the xml value will be found in XML. Default is sym.
This value may also include XPath notation.
:from => :content
When :from is set to :content, this refers to the content of the current node, rather than a sub-node. It is equivalent to :from => ‘.’
Example:
class Contributor
ox_elem :name, :from => :content
ox_attr :role
end
To map:
<contributor role="editor">James Wick</contributor>
:in
An optional name of a wrapping tag for this XML accessor. This can include other xpath values, which will be joined with :from with a ‘/’
335 336 337 338 339 |
# File 'lib/oxmlk.rb', line 335 def ox_elem(name,o={},&block) new_elem = Elem.new(name, o.reverse_merge(:tag_proc => @tag_proc),&block) @ox_elems << new_elem ox_accessor new_elem.accessor end |
#ox_tag(tag = nil, &block) ⇒ Object
Sets the name of the XML element that represents this class. Use this to override the default camelcase class name.
Example:
class BookWithPublisher
ox_tag 'book'
end
Without the ox_tag annotation, the XML mapped tag would have been ‘BookWithPublisher’.
Most xml documents have a consistent naming convention, for example, the node and and attribute names might appear in CamelCase. ox_tag enables you to adapt the oxmlk default names for this object to suit this convention. For example, if I had a document like so:
<XmlDoc>
<MyPreciousData />
<MoreToSee />
</XmlDoc>
Then I could access it’s contents by defining the following class:
class XmlDoc
include OxMlk
ox_tag :camelcase
ox_elem :my_precious_data
ox_elem :more_to_see
end
You may supply a block or any #to_proc-able object as the argument, and it will be called against the default node and attribute names before searching the document. Here are some example declaration:
ox_tag :upcase
ox_tag &:camelcase
ox_tag {|val| val.gsub('_', '').downcase }
See ActiveSupport::CoreExtensions::String::Inflections for more prepackaged formats
380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
# File 'lib/oxmlk.rb', line 380 def ox_tag(tag=nil,&block) raise 'you can only set tag or a block, not both.' if tag && block @base_tag ||= self.to_s.split('::').last @ox_tag ||= case tag when String tag when Proc, Symbol, nil @tag_proc = (block || tag || :to_s).to_proc @tag_proc.call(@base_tag) rescue tag.to_s else raise 'you passed something weird' end end |
#to_xml(data) ⇒ XML::Node
Returns XML generated from an instance based on Attr & Elem definitions.
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/oxmlk.rb', line 429 def to_xml(data) ox = XML::Node.new(ox_tag) wrappers = {} ox_elems.each do |elem| if elem.in wrappers[elem.in] ||= XML::Node.new elem.in elem.to_xml(data).each {|e| wrappers[elem.in] << e} else elem.to_xml(data).each {|e| ox << e} end end wrappers.each {|k,v| ox << v} ox_attrs.each do |a| val = data.send(a.accessor).to_s ox[a.tag]= val if val.present? end ox end |