Module: HappyMapper::ClassMethods

Defined in:
lib/happymapper.rb

Instance Method Summary collapse

Instance Method Details

#attribute(name, type, options = {}) ⇒ Object

The xml has the following attributes defined.

Examples:


"<country code='de'>Germany</country>"

# definition of the 'code' attribute within the class
attribute :code, String

Parameters:

  • name (Symbol)

    the name of the accessor that is created

  • type (String, Class)

    the class name of the name of the class whcih the object will be converted upon parsing

  • options (Hash) (defaults to: {})

    additional parameters to send to the relationship



37
38
39
40
41
42
# File 'lib/happymapper.rb', line 37

def attribute(name, type, options={})
  attribute = Attribute.new(name, type, options)
  @attributes[to_s] ||= []
  @attributes[to_s] << attribute
  attr_accessor attribute.method_name.intern
end

#attributesArray<Attribute>

The elements defined through #attribute.

Returns:

  • (Array<Attribute>)

    a list of the attributes defined for this class; an empty array is returned when there have been no attributes defined.



50
51
52
# File 'lib/happymapper.rb', line 50

def attributes
  @attributes[to_s] || []
end

#content(name, type, options = {}) ⇒ Object

The value stored in the text node of the current element.

Examples:


"<firstName>Michael Jackson</firstName>"

# definition of the 'firstName' text node within the class

content :first_name, String

Parameters:

  • name (Symbol)

    the name of the accessor that is created

  • type (String, Class)

    the class name of the name of the class whcih the object will be converted upon parsing

  • options (Hash) (defaults to: {})

    additional parameters to send to the relationship



127
128
129
130
# File 'lib/happymapper.rb', line 127

def content(name, type, options={})
  @content = TextNode.new(name, type, options)
  attr_accessor @content.method_name.intern
end

#element(name, type, options = {}) ⇒ Object

An element defined in the XML that is parsed.

Examples:


"<address location='home'>
   <city>Oldenburg</city>
 </address>"

# definition of the 'city' element within the class

element :city, String

Parameters:

  • name (Symbol)

    the name of the accessor that is created

  • type (String, Class)

    the class name of the name of the class whcih the object will be converted upon parsing

  • options (Hash) (defaults to: {})

    additional parameters to send to the relationship



93
94
95
96
97
98
# File 'lib/happymapper.rb', line 93

def element(name, type, options={})
  element = Element.new(name, type, options)
  @elements[to_s] ||= []
  @elements[to_s] << element
  attr_accessor element.method_name.intern
end

#elementsArray<Element>

The elements defined through #element, #has_one, and #has_many.

Returns:

  • (Array<Element>)

    a list of the elements contained defined for this class; an empty array is returned when there have been no elements defined.



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

def elements
  @elements[to_s] || []
end

#has_many(name, type, options = {}) ⇒ Object

The object has many of these elements in the XML.

Parameters:

  • name (Symbol)

    the name of accessor that is created

  • type (String, Class)

    the class name or the name of the class which the object will be converted upon parsing.

  • options (Hash) (defaults to: {})

    additional parameters to send to the relationship

See Also:



167
168
169
# File 'lib/happymapper.rb', line 167

def has_many(name, type, options={})
  element name, type, {:single => false}.merge(options)
end

#has_one(name, type, options = {}) ⇒ Object

The object has one of these elements in the XML. If there are multiple, the last one will be set to this value.

Parameters:

  • name (Symbol)

    the name of the accessor that is created

  • type (String, Class)

    the class name of the name of the class whcih the object will be converted upon parsing

  • options (Hash) (defaults to: {})

    additional parameters to send to the relationship

See Also:



153
154
155
# File 'lib/happymapper.rb', line 153

def has_one(name, type, options={})
  element name, type, {:single => true}.merge(options)
end

#has_xml_contentObject

Sets the object to have xml content, this will assign the XML contents that are parsed to the attribute accessor xml_content. The object will respond to the method #xml_content and will return the XML data that it has parsed.



138
139
140
# File 'lib/happymapper.rb', line 138

def has_xml_content
  attr_accessor :xml_content
end

#namespace(namespace = nil) ⇒ Object

Specify a namespace if a node and all its children are all namespaced elements. This is simpler than passing the :namespace option to each defined element.

Parameters:

  • namespace (String) (defaults to: nil)

    the namespace to set as default for the class element.



179
180
181
182
# File 'lib/happymapper.rb', line 179

def namespace(namespace = nil)
  @namespace = namespace if namespace
  @namespace
end

#parse(xml, options = {}) ⇒ Object

Parameters:

  • xml (Nokogiri::XML::Node, Nokogiri:XML::Document, String)

    the XML contents to convert into Object.

  • options (Hash) (defaults to: {})

    additional information for parsing. :single => true if requesting a single object, otherwise it defaults to retuning an array of multiple items. :xpath information where to start the parsing :namespace is the namespace to use for additional information.



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/happymapper.rb', line 208

def parse(xml, options = {})
  
  # create a local copy of the objects namespace value for this parse execution
  namespace = @namespace
  
  # If the XML specified is an Node then we have what we need.
  if xml.is_a?(Nokogiri::XML::Node)
    node = xml
  else
    
    # If xml is an XML document select the root node of the document
    if xml.is_a?(Nokogiri::XML::Document)
      node = xml.root
    else
      
      # Attempt to parse the xml value with Nokogiri XML as a document
      # and select the root element
      
      xml = Nokogiri::XML(xml)
      node = xml.root
    end

    # if the node name is equal to the tag name then the we are parsing the
    # root element and that is important to record so that we can apply
    # the correct xpath on the elements of this document.

    root = node.name == tag_name
  end

  # if any namespaces have been provied then we should capture those and then
  # merge them with any namespaces found on the xml node and merge all that
  # with any namespaces that have been registered on the object
  
  namespaces = options[:namespaces] || {}
  namespaces = namespaces.merge(xml.collect_namespaces) if xml.respond_to?(:collect_namespaces)
  namespaces = namespaces.merge(@registered_namespaces)
  
  # if a namespace has been provided then set the current namespace to it
  # or set the default namespace to the one defined under 'xmlns' 
  # or set the default namespace to the namespace that matches 'happymapper's

  if options[:namespace]
    namespace = options[:namespace]
  elsif namespaces.has_key?("xmlns")
    namespace ||= DEFAULT_NS
    namespaces[namespace] = namespaces.delete("xmlns")
  elsif namespaces.has_key?(DEFAULT_NS)
    namespace ||= DEFAULT_NS
  end
  
  # from the options grab any nodes present and if none are present then
  # perform the following to find the nodes for the given class 
  
  nodes = options.fetch(:nodes) do
    
    # when at the root use the xpath '/' otherwise use a more gready './/'
    # unless an xpath has been specified, which should overwrite default
    # and finally attach the current namespace if one has been defined
    # 
    
    xpath  = (root ? '/' : './/')
    xpath  = options[:xpath].to_s.sub(/([^\/])$/, '\1/') if options[:xpath]
    xpath += "#{namespace}:" if namespace

    nodes = []

    # when finding nodes, do it in this order:
    # 1. specified tag
    # 2. name of element
    # 3. tag_name (derived from class name by default)


    [options[:tag], options[:name], tag_name].compact.each do |xpath_ext|
      begin
        nodes = node.xpath(xpath + xpath_ext.to_s, namespaces)
      rescue
        break
      end
      break if nodes && !nodes.empty?
    end

    nodes
  end

  # If the :limit option has been specified then we are going to slice
  # our node results by that amount to allow us the ability to deal with
  # a large result set of data.

  limit = options[:in_groups_of] || nodes.size
  
  # If the limit of 0 has been specified then the user obviously wants
  # none of the nodes that we are serving within this batch of nodes.
  
  return [] if limit == 0

  collection = []
  
  nodes.each_slice(limit) do |slice|
    
    part = slice.map do |n|
      obj = new

      attributes.each do |attr|
        obj.send("#{attr.method_name}=",attr.from_xml_node(n, namespace, namespaces))
      end

      elements.each do |elem|
        obj.send("#{elem.method_name}=",elem.from_xml_node(n, namespace, namespaces))
      end

      if @content
        obj.send("#{@content.method_name}=",@content.from_xml_node(n, namespace, namespaces))
      end
      
      # If the HappyMapper class has the method #xml_value=, 
      # attr_writer :xml_value, or attr_accessor :xml_value then we want to
      # assign the current xml that we just parsed to the xml_value
    
      if obj.respond_to?('xml_value=')
        n.namespaces.each {|name,path| n[name] = path }
        obj.xml_value = n.to_xml
      end
      
      # If the HappyMapper class has the method #xml_content=,
      # attr_write :xml_content, or attr_accessor :xml_content then we want to
      # assign the child xml that we just parsed to the xml_content

      if obj.respond_to?('xml_content=')
        n = n.children if n.respond_to?(:children)
        obj.xml_content = n.to_xml
      end
    
      # collect the object that we have created
      
      obj
    end
    
    # If a block has been provided and the user has requested that the objects
    # be handled in groups then we should yield the slice of the objects to them
    # otherwise continue to lump them together

    if block_given? and options[:in_groups_of]
      yield part
    else
      collection += part
    end
    
  end

  # per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354
  nodes = nil

  # If the :single option has been specified or we are at the root element
  # then we are going to return the first item in the collection. Otherwise
  # the return response is going to be an entire array of items.

  if options[:single] or root
    collection.first
  else
    collection
  end
end

#register_namespace(namespace, ns) ⇒ Object

Register a namespace that is used to persist the object namespace back to XML.

Examples:


register_namespace 'prefix', 'http://www.unicornland.com/prefix'

# the output will contain the namespace defined

"<outputXML xmlns:prefix="http://www.unicornland.com/prefix">
...
</outputXML>"

Parameters:

  • namespace (String)

    the xml prefix

  • ns (String)

    url for the xml namespace



71
72
73
# File 'lib/happymapper.rb', line 71

def register_namespace(namespace, ns)
  @registered_namespaces.merge!({namespace => ns})
end

#tag(new_tag_name) ⇒ Object

Parameters:

  • new_tag_name (String)

    the name for the tag



187
188
189
# File 'lib/happymapper.rb', line 187

def tag(new_tag_name)
  @tag_name = new_tag_name.to_s unless new_tag_name.nil? || new_tag_name.to_s.empty?
end

#tag_nameString

The name of the tag

Returns:

  • (String)

    the name of the tag as a string, downcased



196
197
198
# File 'lib/happymapper.rb', line 196

def tag_name
  @tag_name ||= to_s.split('::')[-1].downcase
end