yob-roxml 3.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. data/.gitignore +7 -0
  2. data/.gitmodules +3 -0
  3. data/History.txt +354 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +195 -0
  6. data/Rakefile +117 -0
  7. data/TODO +37 -0
  8. data/VERSION +1 -0
  9. data/examples/amazon.rb +35 -0
  10. data/examples/current_weather.rb +27 -0
  11. data/examples/dashed_elements.rb +20 -0
  12. data/examples/library.rb +40 -0
  13. data/examples/posts.rb +27 -0
  14. data/examples/rails.rb +70 -0
  15. data/examples/twitter.rb +37 -0
  16. data/examples/xml/active_record.xml +70 -0
  17. data/examples/xml/amazon.xml +133 -0
  18. data/examples/xml/current_weather.xml +89 -0
  19. data/examples/xml/dashed_elements.xml +52 -0
  20. data/examples/xml/posts.xml +23 -0
  21. data/examples/xml/twitter.xml +422 -0
  22. data/lib/roxml.rb +556 -0
  23. data/lib/roxml/definition.rb +238 -0
  24. data/lib/roxml/hash_definition.rb +25 -0
  25. data/lib/roxml/xml.rb +40 -0
  26. data/lib/roxml/xml/parsers/libxml.rb +85 -0
  27. data/lib/roxml/xml/parsers/nokogiri.rb +82 -0
  28. data/lib/roxml/xml/references.rb +322 -0
  29. data/roxml.gemspec +206 -0
  30. data/spec/definition_spec.rb +494 -0
  31. data/spec/examples/active_record_spec.rb +40 -0
  32. data/spec/examples/amazon_spec.rb +54 -0
  33. data/spec/examples/current_weather_spec.rb +37 -0
  34. data/spec/examples/dashed_elements_spec.rb +20 -0
  35. data/spec/examples/library_spec.rb +46 -0
  36. data/spec/examples/post_spec.rb +24 -0
  37. data/spec/examples/twitter_spec.rb +32 -0
  38. data/spec/roxml_spec.rb +372 -0
  39. data/spec/shared_specs.rb +15 -0
  40. data/spec/spec.opts +1 -0
  41. data/spec/spec_helper.rb +14 -0
  42. data/spec/support/libxml.rb +3 -0
  43. data/spec/support/nokogiri.rb +3 -0
  44. data/spec/xml/array_spec.rb +36 -0
  45. data/spec/xml/attributes_spec.rb +71 -0
  46. data/spec/xml/encoding_spec.rb +52 -0
  47. data/spec/xml/namespace_spec.rb +270 -0
  48. data/spec/xml/namespaces_spec.rb +67 -0
  49. data/spec/xml/object_spec.rb +82 -0
  50. data/spec/xml/parser_spec.rb +21 -0
  51. data/spec/xml/text_spec.rb +71 -0
  52. data/test/fixtures/book_malformed.xml +5 -0
  53. data/test/fixtures/book_pair.xml +8 -0
  54. data/test/fixtures/book_text_with_attribute.xml +5 -0
  55. data/test/fixtures/book_valid.xml +5 -0
  56. data/test/fixtures/book_with_authors.xml +7 -0
  57. data/test/fixtures/book_with_contributions.xml +9 -0
  58. data/test/fixtures/book_with_contributors.xml +7 -0
  59. data/test/fixtures/book_with_contributors_attrs.xml +7 -0
  60. data/test/fixtures/book_with_default_namespace.xml +9 -0
  61. data/test/fixtures/book_with_depth.xml +6 -0
  62. data/test/fixtures/book_with_octal_pages.xml +4 -0
  63. data/test/fixtures/book_with_publisher.xml +7 -0
  64. data/test/fixtures/book_with_wrapped_attr.xml +3 -0
  65. data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
  66. data/test/fixtures/dictionary_of_attrs.xml +6 -0
  67. data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
  68. data/test/fixtures/dictionary_of_mixeds.xml +4 -0
  69. data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
  70. data/test/fixtures/dictionary_of_names.xml +4 -0
  71. data/test/fixtures/dictionary_of_texts.xml +10 -0
  72. data/test/fixtures/library.xml +30 -0
  73. data/test/fixtures/library_uppercase.xml +30 -0
  74. data/test/fixtures/muffins.xml +3 -0
  75. data/test/fixtures/nameless_ageless_youth.xml +2 -0
  76. data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
  77. data/test/fixtures/node_with_name_conflicts.xml +4 -0
  78. data/test/fixtures/numerology.xml +4 -0
  79. data/test/fixtures/person.xml +1 -0
  80. data/test/fixtures/person_with_guarded_mothers.xml +13 -0
  81. data/test/fixtures/person_with_mothers.xml +10 -0
  82. data/test/load_test.rb +6 -0
  83. data/test/mocks/dictionaries.rb +57 -0
  84. data/test/mocks/mocks.rb +279 -0
  85. data/test/support/fixtures.rb +11 -0
  86. data/test/test_helper.rb +34 -0
  87. data/test/unit/definition_test.rb +235 -0
  88. data/test/unit/deprecations_test.rb +24 -0
  89. data/test/unit/to_xml_test.rb +81 -0
  90. data/test/unit/xml_attribute_test.rb +39 -0
  91. data/test/unit/xml_block_test.rb +81 -0
  92. data/test/unit/xml_bool_test.rb +122 -0
  93. data/test/unit/xml_convention_test.rb +150 -0
  94. data/test/unit/xml_hash_test.rb +115 -0
  95. data/test/unit/xml_initialize_test.rb +49 -0
  96. data/test/unit/xml_name_test.rb +141 -0
  97. data/test/unit/xml_namespace_test.rb +31 -0
  98. data/test/unit/xml_object_test.rb +205 -0
  99. data/test/unit/xml_required_test.rb +94 -0
  100. data/test/unit/xml_text_test.rb +71 -0
  101. data/website/index.html +98 -0
  102. metadata +300 -0
@@ -0,0 +1,556 @@
1
+ require 'uri'
2
+ require 'active_support'
3
+ require 'active_support/core_ext'
4
+
5
+ require 'roxml/definition'
6
+ require 'roxml/xml'
7
+
8
+ module ROXML # :nodoc:
9
+ VERSION = '3.1.5'
10
+
11
+ def self.included(base) # :nodoc:
12
+ base.class_eval do
13
+ extend ClassMethods::Accessors,
14
+ ClassMethods::Declarations,
15
+ ClassMethods::Operations
16
+ include InstanceMethods
17
+
18
+ attr_accessor :roxml_references
19
+ end
20
+ end
21
+
22
+ module InstanceMethods # :nodoc:
23
+ # Returns an XML object representing this object
24
+ def to_xml(params = {})
25
+ params.reverse_merge!(:name => self.class.tag_name, :namespace => self.class.roxml_namespace)
26
+ params[:namespace] = nil if ['*', 'xmlns'].include?(params[:namespace])
27
+ XML.new_node([params[:namespace], params[:name]].compact.join(':')).tap do |root|
28
+ refs = (self.roxml_references.present? \
29
+ ? self.roxml_references \
30
+ : self.class.roxml_attrs.map {|attr| attr.to_ref(self) })
31
+ refs.each do |ref|
32
+ value = ref.to_xml(self)
33
+ unless value.nil?
34
+ ref.update_xml(root, value)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # This class defines the annotation methods that are mixed into your
42
+ # Ruby classes for XML mapping information and behavior.
43
+ #
44
+ # See xml_name, xml_initialize, xml, xml_reader and xml_accessor for
45
+ # available annotations.
46
+ #
47
+ module ClassMethods # :nodoc:
48
+ module Declarations
49
+ # Sets the name of the XML element that represents this class. Use this
50
+ # to override the default lowercase class name.
51
+ #
52
+ # Example:
53
+ # class BookWithPublisher
54
+ # xml_name :book
55
+ # end
56
+ #
57
+ # Without the xml_name annotation, the XML mapped tag would have been "bookwithpublisher".
58
+ #
59
+ def xml_name(name)
60
+ @roxml_tag_name = name
61
+ end
62
+
63
+ # Sets the namemespace for attributes and elements of this class. You can override
64
+ # this value on individual elements via the :from option
65
+ #
66
+ # Example:
67
+ # class Book
68
+ # xml_namespace :aws
69
+ #
70
+ # xml_reader :default_namespace
71
+ # xml_reader :different_namespace, :from => 'different:namespace'
72
+ # xml_reader :no_namespace, :from => 'no_namespace', :namespace => false
73
+ # end
74
+ #
75
+ # <aws:book xmlns:aws="http://www.aws.com/aws" xmlns:different="http://www.aws.com/different">
76
+ # <aws:default_namespace>value</aws:default_namespace>
77
+ # <different:namespace>value</different:namespace>
78
+ # <no_namespace>value</no_namespace>
79
+ # </aws:book>
80
+ #
81
+ def xml_namespace(namespace)
82
+ @roxml_namespace = namespace.to_s
83
+ end
84
+
85
+ # Sets up a mapping of namespace prefixes to hrefs, to be used by this class.
86
+ # These namespace prefixes are independent of what appears in the xml, only
87
+ # the namespace hrefs themselves need to match
88
+ #
89
+ # Example:
90
+ # class Tires
91
+ # include ROXML
92
+ #
93
+ # xml_namespaces \
94
+ # :bobsbike => 'http://bobsbikes.example.com',
95
+ # :alicesauto => 'http://alicesautosupply.example.com/'
96
+ #
97
+ # xml_reader :bike_tires, :as => [], :from => '@name', :in => 'bobsbike:tire'
98
+ # xml_reader :car_tires, :as => [], :from => '@name', :in => 'alicesauto:tire'
99
+ # end
100
+ #
101
+ # >> xml = %{
102
+ # <?xml version="1.0"?>
103
+ # <inventory xmlns="http://alicesautosupply.example.com/" xmlns:bike="http://bobsbikes.example.com">
104
+ # <tire name="super slick racing tire" />
105
+ # <tire name="all weather tire" />
106
+ # <bike:tire name="skinny street" />
107
+ # </inventory>
108
+ # }
109
+ # >> Tires.from_xml(xml).bike_tires
110
+ # => ['skinny street']
111
+ #
112
+ def xml_namespaces(namespaces)
113
+ @roxml_namespaces = namespaces.inject({}) do |all, (prefix, href)|
114
+ all[prefix.to_s] = href.to_s
115
+ all
116
+ end
117
+ end
118
+
119
+ def roxml_namespaces # :nodoc:
120
+ @roxml_namespaces || {}
121
+ end
122
+
123
+ # Most xml documents have a consistent naming convention, for example, the node and
124
+ # and attribute names might appear in CamelCase. xml_convention enables you to adapt
125
+ # the roxml default names for this object to suit this convention. For example,
126
+ # if I had a document like so:
127
+ #
128
+ # <XmlDoc>
129
+ # <MyPreciousData />
130
+ # <MoreToSee InAttrs="" />
131
+ # </XmlDoc>
132
+ #
133
+ # Then I could access it's contents by defining the following class:
134
+ #
135
+ # class XmlDoc
136
+ # include ROXML
137
+ # xml_convention :camelcase
138
+ # xml_reader :my_precious_data
139
+ # xml_reader :in_attrs, :in => 'MoreToSee'
140
+ # end
141
+ #
142
+ # You may supply a block or any #to_proc-able object as the argument,
143
+ # and it will be called against the default node and attribute names before searching
144
+ # the document. Here are some example declaration:
145
+ #
146
+ # xml_convention :upcase
147
+ # xml_convention &:camelcase
148
+ # xml_convention {|val| val.gsub('_', '').downcase }
149
+ #
150
+ # See ActiveSupport::CoreExtensions::String::Inflections for more prepackaged formats
151
+ #
152
+ # Note that the xml_convention is also applied to the default root-level tag_name,
153
+ # but in this case an underscored version of the name is applied, for convenience.
154
+ def xml_convention(to_proc_able = nil, &block)
155
+ raise ArgumentError, "conventions are already set" if @roxml_naming_convention
156
+ @roxml_naming_convention =
157
+ if to_proc_able
158
+ raise ArgumentError, "only one conventions can be set" if block_given?
159
+ to_proc_able.to_proc
160
+ elsif block_given?
161
+ block
162
+ end
163
+ end
164
+
165
+ def roxml_naming_convention # :nodoc:
166
+ (@roxml_naming_convention || begin
167
+ superclass.roxml_naming_convention if superclass.respond_to?(:roxml_naming_convention)
168
+ end).freeze
169
+ end
170
+
171
+ # Declares a reference to a certain xml element, whether an attribute, a node,
172
+ # or a typed collection of nodes. This method does not add a corresponding accessor
173
+ # to the object. For that behavior see the similar methods: .xml_reader and .xml_accessor.
174
+ #
175
+ # == Sym Option
176
+ # [sym] Symbol representing the name of the accessor.
177
+ #
178
+ # === Default naming
179
+ # This name will be the default node or attribute name searched for,
180
+ # if no other is declared. For example,
181
+ #
182
+ # xml_reader :bob
183
+ # xml_accessor :pony, :from => :attr
184
+ #
185
+ # are equivalent to:
186
+ #
187
+ # xml_reader :bob, :from => 'bob'
188
+ # xml_accessor :pony, :from => '@pony'
189
+ #
190
+ # === Boolean attributes
191
+ # If the name ends in a ?, ROXML will attempt to coerce the value to true or false,
192
+ # with True, TRUE, true and 1 mapping to true and False, FALSE, false and 0 mapping
193
+ # to false, as shown below:
194
+ #
195
+ # xml_reader :desirable?
196
+ # xml_reader :bizzare?, :from => '@BIZZARE'
197
+ #
198
+ # x = #from_xml(%{
199
+ # <object BIZZARE="1">
200
+ # <desirable>False</desirable>
201
+ # </object>
202
+ # })
203
+ # x.desirable?
204
+ # => false
205
+ # x.bizzare?
206
+ # => true
207
+ #
208
+ # If an unexpected value is encountered, the attribute will be set to nil,
209
+ # unless you provide a block, in which case the block will recived
210
+ # the actual unexpected value.
211
+ #
212
+ # #from_xml(%{
213
+ # <object>
214
+ # <desirable>Dunno</desirable>
215
+ # </object>
216
+ # }).desirable?
217
+ # => nil
218
+ #
219
+ # xml_reader :strange? do |val|
220
+ # val.upcase
221
+ # end
222
+ #
223
+ # #from_xml(%{
224
+ # <object>
225
+ # <strange>Dunno</strange>
226
+ # </object>
227
+ # }).strange?
228
+ # => DUNNO
229
+ #
230
+ # == Blocks
231
+ # You may also pass a block which manipulates the associated parsed value.
232
+ #
233
+ # class Muffins
234
+ # include ROXML
235
+ #
236
+ # xml_reader(:count, :from => 'bakers_dozens') {|val| val.to_i * 13 }
237
+ # end
238
+ #
239
+ # For hash types, the block recieves the key and value as arguments, and they should
240
+ # be returned as an array of [key, value]
241
+ #
242
+ # For array types, the entire array is passed in, and must be returned in the same fashion.
243
+ #
244
+ # == Options
245
+ # === :as
246
+ # ==== Basic Types
247
+ # Allows you to specify one of several basic types to return the value as. For example
248
+ #
249
+ # xml_reader :count, :as => Integer
250
+ #
251
+ # is equivalent to:
252
+ #
253
+ # xml_reader(:count) {|val| Integer(val) unless val.empty? }
254
+ #
255
+ # Such block shorthands for Integer, Float, Fixnum, BigDecimal, Date, Time, and DateTime
256
+ # are currently available, but only for non-Hash declarations.
257
+ #
258
+ # To reference many elements, put the desired type in a literal array. e.g.:
259
+ #
260
+ # xml_reader :counts, :as => [Integer]
261
+ #
262
+ # Even an array of text nodes can be specified with :as => []
263
+ #
264
+ # xml_reader :quotes, :as => []
265
+ #
266
+ # === Other ROXML Class
267
+ # Declares an accessor that represents another ROXML class as child XML element
268
+ # (one-to-one or composition) or array of child elements (one-to-many or
269
+ # aggregation) of this type. Default is one-to-one. For one-to-many, simply pass the class
270
+ # as the only element in an array.
271
+ #
272
+ # Composition example:
273
+ # <book>
274
+ # <publisher>
275
+ # <name>Pragmatic Bookshelf</name>
276
+ # </publisher>
277
+ # </book>
278
+ #
279
+ # Can be mapped using the following code:
280
+ # class Book
281
+ # xml_reader :publisher, :as => Publisher
282
+ # end
283
+ #
284
+ # Aggregation example:
285
+ # <library>
286
+ # <books>
287
+ # <book/>
288
+ # <book/>
289
+ # </books>
290
+ # </library>
291
+ #
292
+ # Can be mapped using the following code:
293
+ # class Library
294
+ # xml_reader :books, :as => [Book], :in => "books"
295
+ # end
296
+ #
297
+ # If you don't have the <books> tag to wrap around the list of <book> tags:
298
+ # <library>
299
+ # <name>Ruby books</name>
300
+ # <book/>
301
+ # <book/>
302
+ # </library>
303
+ #
304
+ # You can skip the wrapper argument:
305
+ # xml_reader :books, :as => [Book]
306
+ #
307
+ # ==== Hash
308
+ # Somewhere between the simplicity of a :text/:attr mapping, and the complexity of
309
+ # a full Object/Type mapping, lies the Hash mapping. It serves in the case where you have
310
+ # a collection of key-value pairs represented in your xml. You create a hash declaration by
311
+ # passing a hash mapping as the type argument. A few examples:
312
+ #
313
+ # ===== Hash of element contents
314
+ # For xml such as this:
315
+ #
316
+ # <dictionary>
317
+ # <definition>
318
+ # <word/>
319
+ # <meaning/>
320
+ # </definition>
321
+ # <definition>
322
+ # <word/>
323
+ # <meaning/>
324
+ # </definition>
325
+ # </dictionary>
326
+ #
327
+ # You can individually declare your key and value names:
328
+ # xml_reader :definitions, :as => {:key => 'word',
329
+ # :value => 'meaning'}
330
+ #
331
+ # ===== Hash of :content &c.
332
+ # For xml such as this:
333
+ #
334
+ # <dictionary>
335
+ # <definition word="quaquaversally">adjective: (of a geological formation) sloping downward from the center in all directions.</definition>
336
+ # <definition word="tergiversate">To use evasions or ambiguities; equivocate.</definition>
337
+ # </dictionary>
338
+ #
339
+ # You can individually declare the key and value, but with the attr, you need to provide both the type
340
+ # and name of that type (i.e. {:attr => :word}), because omitting the type will result in ROXML
341
+ # defaulting to :text
342
+ # xml_reader :definitions, :as => {:key => {:attr => 'word'},
343
+ # :value => :content}
344
+ #
345
+ # ===== Hash of :name &c.
346
+ # For xml such as this:
347
+ #
348
+ # <dictionary>
349
+ # <quaquaversally>adjective: (of a geological formation) sloping downward from the center in all directions.</quaquaversally>
350
+ # <tergiversate>To use evasions or ambiguities; equivocate.</tergiversate>
351
+ # </dictionary>
352
+ #
353
+ # You can pick up the node names (e.g. quaquaversally) using the :name keyword:
354
+ # xml_reader :definitions, :as => {:key => :name,
355
+ # :value => :content}
356
+ #
357
+ # === :from
358
+ # The name by which the xml value will be found, either an attribute or tag name in XML.
359
+ # Default is sym, or the singular form of sym, in the case of arrays and hashes.
360
+ #
361
+ # This value may also include XPath notation.
362
+ #
363
+ # ==== :from => :content
364
+ # When :from is set to :content, this refers to the content of the current node,
365
+ # rather than a sub-node. It is equivalent to :from => '.'
366
+ #
367
+ # Example:
368
+ # class Contributor
369
+ # xml_reader :name, :from => :content
370
+ # xml_reader :role, :from => :attr
371
+ # end
372
+ #
373
+ # To map:
374
+ # <contributor role="editor">James Wick</contributor>
375
+ #
376
+ # ==== :from => :attr
377
+ # When :from is set to :attr, this refers to the content of an attribute,
378
+ # rather than a sub-node. It is equivalent to :from => '@attribute_name'
379
+ #
380
+ # Example:
381
+ # class Book
382
+ # xml_reader :isbn, :from => "@ISBN"
383
+ # xml_accessor :title, :from => :attr # :from defaults to '@title'
384
+ # end
385
+ #
386
+ # To map:
387
+ # <book ISBN="0974514055" title="Programming Ruby: the pragmatic programmers' guide" />
388
+ #
389
+ # ==== :from => :text
390
+ # The default source, if none is specified, this means the accessor
391
+ # represents a text node from XML. This is documented for completeness
392
+ # only. You should just leave this option off when you want the default behavior,
393
+ # as in the examples below.
394
+ #
395
+ # :text is equivalent to :from => accessor_name, and you should specify the
396
+ # actual node name (and, optionally, a namespace) if it differs, as in the case of :author below.
397
+ #
398
+ # Example:
399
+ # class Book
400
+ # xml_reader :author, :from => 'Author'
401
+ # xml_accessor :description, :cdata => true
402
+ # xml_reader :title
403
+ # end
404
+ #
405
+ # To map:
406
+ # <book>
407
+ # <title>Programming Ruby: the pragmatic programmers' guide</title>
408
+ # <description><![CDATA[Probably the best Ruby book out there]]></description>
409
+ # <Author>David Thomas</Author>
410
+ # </book>
411
+ #
412
+ # Likewise, a number of :text node values can be collected in an array like so:
413
+ #
414
+ # Example:
415
+ # class Library
416
+ # xml_reader :books, :as => []
417
+ # end
418
+ #
419
+ # To map:
420
+ # <library>
421
+ # <book>To kill a mockingbird</book>
422
+ # <book>House of Leaves</book>
423
+ # <book>Gödel, Escher, Bach</book>
424
+ # </library>
425
+ #
426
+ # === Other Options
427
+ # [:in] An optional name of a wrapping tag for this XML accessor.
428
+ # This can include other xpath values, which will be joined with :from with a '/'
429
+ # [:else] Default value for attribute, if missing from the xml on .from_xml
430
+ # [:required] If true, throws RequiredElementMissing when the element isn't present
431
+ # [:frozen] If true, all results are frozen (using #freeze) at parse-time.
432
+ # [:cdata] true for values which should be input from or output as cdata elements
433
+ # [:to_xml] this proc is applied to the attributes value outputting the instance via #to_xml
434
+ # [:namespace] (false) disables or (string) overrides the default namespace declared with xml_namespace
435
+ #
436
+ def xml_attr(*syms, &block)
437
+ opts = syms.extract_options!
438
+ syms.map do |sym|
439
+ Definition.new(sym, opts, &block).tap do |attr|
440
+ if roxml_attrs.map(&:accessor).include? attr.accessor
441
+ raise "Accessor #{attr.accessor} is already defined as XML accessor in class #{self.name}"
442
+ end
443
+ @roxml_attrs << attr
444
+ end
445
+ end
446
+ end
447
+
448
+ # Declares a read-only xml reference. See xml_attr for details.
449
+ #
450
+ # Note that while xml_reader does not create a setter for this attribute,
451
+ # its value can be modified indirectly via methods. For more complete
452
+ # protection, consider the :frozen option.
453
+ def xml_reader(*syms, &block)
454
+ xml_attr(*syms, &block).each do |attr|
455
+ add_reader(attr)
456
+ end
457
+ end
458
+
459
+ # Declares a writable xml reference. See xml_attr for details.
460
+ #
461
+ # Note that while xml_accessor does create a setter for this attribute,
462
+ # you can use the :frozen option to prevent its value from being
463
+ # modified indirectly via methods.
464
+ def xml_accessor(*syms, &block)
465
+ xml_attr(*syms, &block).each do |attr|
466
+ add_reader(attr)
467
+ attr_writer(attr.attr_name)
468
+ end
469
+ end
470
+
471
+ private
472
+ def add_reader(attr)
473
+ define_method(attr.accessor) do
474
+ instance_variable_get(attr.instance_variable_name)
475
+ end
476
+ end
477
+ end
478
+
479
+ module Accessors
480
+ # Returns the tag name (also known as xml_name) of the class.
481
+ # If no tag name is set with xml_name method, returns default class name
482
+ # in lowercase.
483
+ #
484
+ # If xml_convention is set, it is called with an *underscored* version of
485
+ # the class name. This is because active support's inflector generally expects
486
+ # an underscored version, and several operations (e.g. camelcase(:lower), dasherize)
487
+ # do not work without one.
488
+ def tag_name
489
+ return roxml_tag_name if roxml_tag_name
490
+
491
+ if tag_name = name.split('::').last
492
+ roxml_naming_convention ? roxml_naming_convention.call(tag_name.underscore) : tag_name.downcase
493
+ end
494
+ end
495
+
496
+ def roxml_tag_name # :nodoc:
497
+ @roxml_tag_name || begin
498
+ superclass.roxml_tag_name if superclass.respond_to?(:roxml_tag_name)
499
+ end
500
+ end
501
+
502
+ def roxml_namespace # :nodoc:
503
+ @roxml_namespace || begin
504
+ superclass.roxml_namespace if superclass.respond_to?(:roxml_namespace)
505
+ end
506
+ end
507
+
508
+ # Returns array of internal reference objects, such as attributes
509
+ # and composed XML objects
510
+ def roxml_attrs
511
+ @roxml_attrs ||= []
512
+ (@roxml_attrs + (superclass.respond_to?(:roxml_attrs) ? superclass.roxml_attrs : [])).freeze
513
+ end
514
+ end
515
+
516
+ module Operations
517
+ #
518
+ # Creates a new Ruby object from XML using mapping information
519
+ # annotated in the class.
520
+ #
521
+ # The input data is either an XML::Node, String, Pathname, or File representing
522
+ # the XML document.
523
+ #
524
+ # Example
525
+ # book = Book.from_xml(File.read("book.xml"))
526
+ # or
527
+ # book = Book.from_xml("<book><name>Beyond Java</name></book>")
528
+ #
529
+ # _initialization_args_ passed into from_xml will be passed into
530
+ # the object's .new, prior to populating the xml_attrs.
531
+ #
532
+ # After the instatiation and xml population
533
+ #
534
+ # See also: xml_initialize
535
+ #
536
+ def from_xml(data, *initialization_args)
537
+ xml = XML::Node.from(data)
538
+
539
+ new(*initialization_args).tap do |inst|
540
+ inst.roxml_references = roxml_attrs.map {|attr| attr.to_ref(inst) }
541
+
542
+ inst.roxml_references.each do |ref|
543
+ value = ref.value_in(xml)
544
+ inst.respond_to?(ref.opts.setter) \
545
+ ? inst.send(ref.opts.setter, value) \
546
+ : inst.instance_variable_set(ref.opts.instance_variable_name, value)
547
+ end
548
+ inst.send(:after_parse) if inst.respond_to?(:after_parse, true)
549
+ end
550
+ rescue ArgumentError => e
551
+ raise e, e.message + " for class #{self}"
552
+ end
553
+ end
554
+ end
555
+ end
556
+