Module: ROXML::ClassMethods::Declarations

Defined in:
lib/roxml.rb

Overview

:nodoc:

Instance Method Summary collapse

Instance Method Details

#roxml_naming_conventionObject

:nodoc:



149
150
151
# File 'lib/roxml.rb', line 149

def roxml_naming_convention # :nodoc:
  (@roxml_naming_convention || superclass.try(:roxml_naming_convention)).freeze
end

#xml(sym, writable = false, type_and_or_opts = :text, opts = nil, &block) ⇒ Object

:nodoc:



425
426
427
# File 'lib/roxml.rb', line 425

def xml(sym, writable = false, type_and_or_opts = :text, opts = nil, &block) #:nodoc:
  xml_reference(writable, sym, type_and_or_opts, opts, &block)
end

#xml_accessor(sym, type_and_or_opts = :text, opts = nil, &block) ⇒ Object

Declares a writable xml reference. See xml for details.

Note that while xml_accessor does create a setter for this attribute, you can use the :frozen option to prevent its value from being modified indirectly via methods.



444
445
446
# File 'lib/roxml.rb', line 444

def xml_accessor(sym, type_and_or_opts = :text, opts = nil, &block)
  xml_reference true, sym, type_and_or_opts, opts, &block
end

#xml_construct(*args) ⇒ Object

This method is deprecated, please use xml_initialize instead



449
450
451
452
453
454
455
456
457
458
# File 'lib/roxml.rb', line 449

def xml_construct(*args) # :nodoc:
  present_tags = tag_refs_without_deprecation.map(&:accessor)
  missing_tags = args - present_tags
  unless missing_tags.empty?
    raise ArgumentError, "All construction tags must be declared first using xml, " +
                         "xml_reader, or xml_accessor. #{missing_tags.join(', ')} is missing. " +
                         "#{present_tags.join(', ')} are declared."
  end
  @xml_construction_args = args
end

#xml_convention(to_proc_able = nil, &block) ⇒ Object

Most xml documents have a consistent naming convention, for example, the node and and attribute names might appear in CamelCase. xml_convention enables you to adapt the roxml default names for this object to suit this convention. For example, if I had a document like so:

<XmlDoc>
  <MyPreciousData />
  <MoreToSee InAttrs="" />
</XmlDoc>

Then I could access it’s contents by defining the following class:

class XmlDoc
  include ROXML
  xml_convention :camelcase
  xml_reader :my_precious_data
  xml_reader :in_attrs, :in => 'MoreToSee'
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:

xml_convention :upcase
xml_convention &:camelcase
xml_convention {|val| val.gsub('_', '').downcase }

See ActiveSupport::CoreExtensions::String::Inflections for more prepackaged formats

Note that the xml_convention is also applied to the default root-level tag_name, but in this case an underscored version of the name is applied, for convenience.

Raises:

  • (ArgumentError)


142
143
144
145
146
147
# File 'lib/roxml.rb', line 142

def xml_convention(to_proc_able = nil, &block)
  raise ArgumentError, "conventions are already set" if @roxml_naming_convention
  raise ArgumentError, "only one conventions can be set" if to_proc_able.respond_to?(:to_proc) && block_given?
  @roxml_naming_convention = to_proc_able.try(:to_proc)
  @roxml_naming_convention = block if block_given?
end

#xml_name(name) ⇒ Object

Sets the name of the XML element that represents this class. Use this to override the default lowercase class name.

Example:

class BookWithPublisher
 xml_name :book
end

Without the xml_name annotation, the XML mapped tag would have been “bookwithpublisher”.



107
108
109
# File 'lib/roxml.rb', line 107

def xml_name(name)
  @roxml_tag_name = name
end

#xml_reader(sym, type_and_or_opts = :text, opts = nil, &block) ⇒ Object

Declares a read-only xml reference. See xml for details.

Note that while xml_reader does not create a setter for this attribute, its value can be modified indirectly via methods. For more complete protection, consider the :frozen option.



435
436
437
# File 'lib/roxml.rb', line 435

def xml_reader(sym, type_and_or_opts = :text, opts = nil, &block)
  xml_reference false, sym, type_and_or_opts, opts, &block
end

#xml_reference(writable, sym, type_and_or_opts = :text, opts = nil, &block) ⇒ Object

Declares an accesser to a certain xml element, whether an attribute, a node, or a typed collection of nodes. Typically you should call xml_reader or xml_accessor rather than calling this method directly, but the instructions below apply to both.

Sym Option

sym

Symbol representing the name of the accessor.

Default naming

This name will be the default node or attribute name searched for, if no other is declared. For example,

xml_reader   :bob
xml_accessor :pony, :attr

are equivalent to:

xml_reader   :bob, :from => 'bob'
xml_accessor :pony, :attr => 'pony'

Boolean attributes

If the name ends in a ?, ROXML will attempt to coerce the value to true or false, with True, TRUE, true and 1 mapping to true and False, FALSE, false and 0 mapping to false, as shown below:

xml_reader :desirable?
xml_reader :bizzare?, :attr => 'BIZZARE'

x = #from_xml(%{
  <object BIZZARE="1">
    <desirable>False</desirable>
  </object>
})
x.desirable?
=> false
x.bizzare?
=> true

If an unexpected value is encountered, the attribute will be set to nil, unless you provide a block, in which case the block will recived the actual unexpected value.

#from_xml(%{
  <object>
    <desirable>Dunno</desirable>
  </object>
}).desirable?
=> nil

xml_reader :strange? do |val|
  val.upcase
end

#from_xml(%{
  <object>
    <strange>Dunno</strange>
  </object>
}).strange?
=> DUNNO

Type options

All type arguments may be used as the type argument to indicate just type, or used as :from, pointing to a xml name to indicate both type and attribute name. Also, any type may be passed via an array to indicate that multiple instances of the object should be returned as an array.

:attr

Declare an accessor that represents an XML attribute.

Example:

class Book
 xml_reader :isbn, :attr => "ISBN" # 'ISBN' is used to specify :from
 xml_accessor :title, :attr        # :from defaults to :title
end

To map:

<book ISBN="0974514055" title="Programming Ruby: the pragmatic programmers' guide" />

:text

The default type, if none is specified. Declares an accessor that represents a text node from XML.

Example:

class Book
  xml_reader :author, :text => 'Author'
  xml_accessor :description, :text, :as => :cdata
  xml_reader :title
end

To map:

<book>
 <title>Programming Ruby: the pragmatic programmers' guide</title>
 <description><![CDATA[Probably the best Ruby book out there]]></description>
 <Author>David Thomas</author>
</book>

Likewise, a number of :text node values can be collected in an array like so:

Example:

class Library
  xml_reader :books, [:text], :in => 'books'
end

To map:

<library>
  <books>
    <book>To kill a mockingbird</book>
    <book>House of Leaves</book>
    <book>Gödel, Escher, Bach</book>
  </books>
</library>

:content

A special case of :text, this refers to the content of the current node, rather than a sub-node

Example:

class Contributor
  xml_reader :name, :content
  xml_reader :role, :attr
end

To map:

<contributor role="editor">James Wick</contributor>

Hash

Somewhere between the simplicity of a :text/:attr mapping, and the complexity of a full Object/Type mapping, lies the Hash mapping. It serves in the case where you have a collection of key-value pairs represented in your xml. You create a hash declaration by passing a hash mapping as the type argument. A few examples:

Hash of :attrs

For xml such as this:

<dictionary>
  <definitions>
    <definition dt="quaquaversally"
                dd="adjective: (of a geological formation) sloping downward from the center in all directions." />
    <definition dt="tergiversate"
                dd="To use evasions or ambiguities; equivocate." />
  </definitions>
</dictionary>

You can use the :attrs key in you has with a [:key, :value] name array:

xml_reader :definitions, {:attrs => ['dt', 'dd']}, :in => :definitions

Hash of :texts

For xml such as this:

<dictionary>
  <definition>
    <word/>
    <meaning/>
  </definition>
  <definition>
    <word/>
    <meaning/>
  </definition>
</dictionary>

You can individually declare your key and value names:

xml_reader :definitions, {:key => 'word',
                          :value => 'meaning'}

Hash of :content &c.

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>

You can individually declare the key and value, but with the attr, you need to provide both the type and name of that type (i.e. => :word), because omitting the type will result in ROXML defaulting to :text

xml_reader :definitions, {:key => {:attr => 'word'},
                          :value => :content}

Hash of :name &c.

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>

You can pick up the node names (e.g. quaquaversally) using the :name keyword:

xml_reader :definitions, {:key => :name,
                          :value => :content}

Other ROXML Class

Declares an accessor that represents another ROXML 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. Use :array option for one-to-many, or simply pass the class in an array.

Composition example:

<book>
 <publisher>
   <name>Pragmatic Bookshelf</name>
 </publisher>
</book>

Can be mapped using the following code:

class Book
  xml_reader :publisher, Publisher
end

Aggregation example:

<library>
 <books>
  <book/>
  <book/>
 </books>
</library>

Can be mapped using the following code:

class Library
  xml_reader :books, [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:

xml_reader :books, [Book]

Blocks

You may also pass a block which manipulates the associated parsed value.

class Muffins
  include ROXML

  xml_reader(:count, :from => 'bakers_dozens') {|val| val.to_i * 13 }
end

For hash types, the block recieves the key and value as arguments, and they should be returned as an array of [key, value]

For array types, the entire array is passed in, and must be returned in the same fashion.

Block Shorthands

Alternatively, you may use block shorthands to specify common coercions, such that:

xml_reader :count, :as => Integer

is equivalent to:

xml_reader(:count) {|val| Integer(val) }

Block shorthands :float, Float, :integer and Integer are currently available, but only for non-Hash declarations.

Other options

:from

The name by which the xml value will be found, either an attribute or tag name in XML. Default is sym, or the singular form of sym, in the case of arrays and hashes.

:as

:cdata for character data; :integer, Integer, :float, Float to coerce to Integer or Float respectively

:in

An optional name of a wrapping tag for this XML accessor

:else

Default value for attribute, if missing

:required

If true, throws RequiredElementMissing when the element isn’t present

:frozen

If true, all results are frozen (using #freeze) at parse-time.



419
420
421
422
423
# File 'lib/roxml.rb', line 419

def xml_reference(writable, sym, type_and_or_opts = :text, opts = nil, &block)
  opts = Opts.new(sym, *[type_and_or_opts, opts].compact, &block)

  add_accessor(opts, writable)
end