Empact-roxml 2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/MIT-LICENSE +18 -0
  2. data/README.rdoc +122 -0
  3. data/Rakefile +104 -0
  4. data/lib/roxml.rb +362 -0
  5. data/lib/roxml/array.rb +15 -0
  6. data/lib/roxml/options.rb +175 -0
  7. data/lib/roxml/string.rb +35 -0
  8. data/lib/roxml/xml.rb +243 -0
  9. data/lib/roxml/xml/libxml.rb +63 -0
  10. data/lib/roxml/xml/rexml.rb +59 -0
  11. data/roxml.gemspec +78 -0
  12. data/test/fixtures/book_malformed.xml +5 -0
  13. data/test/fixtures/book_pair.xml +8 -0
  14. data/test/fixtures/book_text_with_attribute.xml +5 -0
  15. data/test/fixtures/book_valid.xml +5 -0
  16. data/test/fixtures/book_with_authors.xml +7 -0
  17. data/test/fixtures/book_with_contributions.xml +9 -0
  18. data/test/fixtures/book_with_contributors.xml +7 -0
  19. data/test/fixtures/book_with_contributors_attrs.xml +7 -0
  20. data/test/fixtures/book_with_default_namespace.xml +9 -0
  21. data/test/fixtures/book_with_depth.xml +6 -0
  22. data/test/fixtures/book_with_publisher.xml +7 -0
  23. data/test/fixtures/dictionary_of_attrs.xml +6 -0
  24. data/test/fixtures/dictionary_of_mixeds.xml +4 -0
  25. data/test/fixtures/dictionary_of_texts.xml +10 -0
  26. data/test/fixtures/library.xml +30 -0
  27. data/test/fixtures/library_uppercase.xml +30 -0
  28. data/test/fixtures/nameless_ageless_youth.xml +2 -0
  29. data/test/fixtures/person.xml +1 -0
  30. data/test/fixtures/person_with_guarded_mothers.xml +13 -0
  31. data/test/fixtures/person_with_mothers.xml +10 -0
  32. data/test/mocks/dictionaries.rb +56 -0
  33. data/test/mocks/mocks.rb +212 -0
  34. data/test/test_helper.rb +16 -0
  35. data/test/unit/options_test.rb +62 -0
  36. data/test/unit/roxml_test.rb +24 -0
  37. data/test/unit/string_test.rb +11 -0
  38. data/test/unit/to_xml_test.rb +75 -0
  39. data/test/unit/xml_attribute_test.rb +34 -0
  40. data/test/unit/xml_construct_test.rb +19 -0
  41. data/test/unit/xml_hash_test.rb +54 -0
  42. data/test/unit/xml_name_test.rb +14 -0
  43. data/test/unit/xml_namespace_test.rb +36 -0
  44. data/test/unit/xml_object_test.rb +94 -0
  45. data/test/unit/xml_text_test.rb +57 -0
  46. metadata +110 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2004-2008 by Ben Woosley, Zak Mandhro and Anders Engstrom
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6
+ and associated documentation files (the "Software"), to deal in the Software without restriction,
7
+ including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
8
+ and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
9
+ subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,122 @@
1
+ ROXML Ruby Object to XML mapping library. For more information
2
+ visit http://roxml.rubyforge.org
3
+
4
+ =Quick Start Guide
5
+
6
+ This is a short usage example. See ROXML::ROXML_Class and packaged test cases for more information.
7
+
8
+ ==Basic Mapping
9
+
10
+ Consider an XML document representing a Library containing a number of Books. You
11
+ can map this structure to Ruby classes that provide addition useful behavior. With
12
+ ROXML, you can annotate the Ruby classes as follows:
13
+
14
+ class Book
15
+ include ROXML
16
+
17
+ xml_reader :isbn, :attr => "ISBN" # attribute with name 'ISBN'
18
+ xml_reader :title
19
+ xml_reader :description, :as => :cdata # text node with cdata protection
20
+ xml_reader :author
21
+ end
22
+
23
+ class Library
24
+ include ROXML
25
+
26
+ xml_accessor :name, :from => "NAME", :as => :cdata
27
+ xml_accessor :books, [Book], :in => "books"
28
+ end
29
+
30
+ To create a library and put a number of books in it we could run the following code:
31
+
32
+ book = Book.new()
33
+ book.isbn = "0201710897"
34
+ book.title = "The PickAxe"
35
+ book.description = "Best Ruby book out there!"
36
+ book.author = "David Thomas, Andrew Hunt, Dave Thomas"
37
+
38
+ lib = Library.new()
39
+ lib.name = "Favorite Books"
40
+ lib << book
41
+
42
+ To save this information to an XML file:
43
+
44
+ File.open("library.xml", "w") do |f|
45
+ lib.to_xml.write(f, 0)
46
+ end
47
+
48
+ To later populate the library object from the XML file:
49
+
50
+ lib = Library.parse(File.read("library.xml"))
51
+
52
+ Similarly, to do a one-to-one mapping between XML objects, such as book and publisher,
53
+ you would add a reference to another ROXML class. For example:
54
+
55
+ <book isbn="0974514055">
56
+ <title>Programming Ruby - 2nd Edition</title>
57
+ <description>Second edition of the great book.</description>
58
+ <publisher>
59
+ <name>Pragmatic Bookshelf</name>
60
+ </publisher>
61
+ </book>
62
+
63
+ can be mapped using the following code:
64
+
65
+ class BookWithPublisher
66
+ include ROXML
67
+
68
+ xml_name :book
69
+ xml_reader :publisher, Publisher
70
+ end
71
+
72
+ Note: In the above example, _xml_name_ annotation tells ROXML to set the element
73
+ name to "book" for mapping to XML. The default is XML element name is the class name in lowercase; "bookwithpublisher"
74
+ in this case.
75
+
76
+ == Manipulation
77
+
78
+ Extending the above examples, say you want to parse a book's page count and have it available as an Integer.
79
+ In such a case, you can extend any object with a block to manipulate it's value at parse time. For example:
80
+
81
+ class Child
82
+ include ROXML
83
+
84
+ xml_reader :age, :attr do |val|
85
+ Integer(val)
86
+ end
87
+ end
88
+
89
+ The result of the block above is stored, rather than the actual value parsed from the document.
90
+
91
+ == Construction
92
+
93
+ Complicated initialization may require action on multiple attributes of an object. As such, you can
94
+ use xml_construct to cause your ROXML object to call its own constructor. For example:
95
+
96
+ class Measurement
97
+ include ROXML
98
+
99
+ xml_reader :units, :attr
100
+ xml_reader :value, :content
101
+
102
+ xml_construct :value, :units
103
+
104
+ def initialize(value, units)
105
+ # translate units & value into metric, for example
106
+ end
107
+ end
108
+
109
+ Will, on parse, read all listed xml attributes (units and value, in this case), then call initialize
110
+ with the arguments listed after the xml_construct call.
111
+
112
+ == Selecting a parser ==
113
+
114
+ By default, ROXML will use LibXML if it is available, or otherwise REXML. If you'd like to
115
+ explicitly require one or the other, you may do the following:
116
+
117
+ module ROXML
118
+ XML_PARSER = 'libxml' # or 'rexml'
119
+ end
120
+ require 'roxml'
121
+
122
+ For more information on available annotations, see ROXML::ROXML_Class
data/Rakefile ADDED
@@ -0,0 +1,104 @@
1
+ # Rake libraries used
2
+ require "rubygems"
3
+ require "rails_plugin_package_task"
4
+ require "rake/rdoctask"
5
+ require "rake/contrib/rubyforgepublisher"
6
+ require "rake/contrib/publisher"
7
+ require 'rake/gempackagetask'
8
+ require 'rake/testtask'
9
+
10
+ # load settings
11
+ spec = eval(IO.read("roxml.gemspec"))
12
+
13
+ # Provide the username used to upload website etc.
14
+ RubyForgeConfig = {
15
+ :unix_name=>"roxml",
16
+ :user_name=>"zakmandhro"
17
+ }
18
+
19
+ task :default => :test
20
+
21
+ Rake::RDocTask.new do |rd|
22
+ rd.rdoc_dir = "doc"
23
+ rd.rdoc_files.include('MIT-LICENSE', 'README.rdoc', "lib/**/*.rb")
24
+ rd.options << '--main' << 'README.rdoc' << '--title' << 'ROXML Documentation'
25
+ end
26
+
27
+ Rake::RailsPluginPackageTask.new(spec.name, spec.version) do |p|
28
+ p.package_files = FileList[
29
+ "lib/**/*.rb", "*.txt", "README.rdoc", "Rakefile",
30
+ "rake/**/*", "test/**/*.rb", "test/**/*.xml"]
31
+ p.plugin_files = FileList["rails_plugin/**/*"]
32
+ p.extra_links = {"Project page" => spec.homepage,
33
+ "Author: Zak Mandhro" => 'http://rubyforge.org/users/zakmandhro/'}
34
+ p.verbose = true
35
+ end
36
+ task :rails_plugin=>:clobber
37
+
38
+ desc "Publish Ruby on Rails plug-in on RubyForge"
39
+ task :release_plugin=>:rails_plugin do |task|
40
+ pub = Rake::SshDirPublisher.new("#{RubyForgeConfig[:user_name]}@rubyforge.org",
41
+ "/var/www/gforge-projects/#{RubyForgeConfig[:unix_name]}",
42
+ "pkg/rails_plugin")
43
+ pub.upload()
44
+ end
45
+
46
+ desc "Publish and plugin site on RubyForge"
47
+ task :publish do |task|
48
+ pub = Rake::RubyForgePublisher.new(RubyForgeConfig[:unix_name], RubyForgeConfig[:user_name])
49
+ pub.upload()
50
+ end
51
+
52
+ desc "Install the gem"
53
+ task :install => [:package] do
54
+ sh %{sudo gem install pkg/#{spec.name}-#{spec.version}}
55
+ end
56
+
57
+ Rake::TestTask.new(:bugs) do |t|
58
+ t.libs << 'test'
59
+ t.test_files = FileList['test/bugs/*_bugs.rb']
60
+ t.verbose = true
61
+ end
62
+
63
+ task :test => :'test:rexml'
64
+
65
+ namespace :test do
66
+ desc "Test ROXML under the LibXML parser"
67
+ task :libxml do
68
+ module ROXML
69
+ XML_PARSER = 'libxml'
70
+ end
71
+ require 'lib/roxml'
72
+ require 'rake/runtest'
73
+ Rake.run_tests 'test/unit/*_test.rb'
74
+ end
75
+
76
+ desc "Test ROXML under the REXML parser"
77
+ task :rexml do
78
+ module ROXML
79
+ XML_PARSER = 'rexml'
80
+ end
81
+ require 'lib/roxml'
82
+ require 'rake/runtest'
83
+ Rake.run_tests 'test/unit/*_test.rb'
84
+ end
85
+ end
86
+
87
+ desc "Create the ZIP package"
88
+ Rake::PackageTask.new(spec.name, spec.version) do |p|
89
+ p.need_zip = true
90
+ p.package_files = FileList[
91
+ "lib/**/*.rb", "*.txt", "README.rdoc", "Rakefile",
92
+ "rake/**/*","test/**/*.rb", "test/**/*.xml", "html/**/*"]
93
+ end
94
+
95
+ desc "Create the plugin package"
96
+
97
+ task :package=>:rdoc
98
+ task :rdoc=>:test
99
+
100
+ desc "Create a RubyGem project"
101
+ Rake::GemPackageTask.new(spec).define
102
+
103
+ desc "Clobber generated files"
104
+ task :clobber=>[:clobber_package, :clobber_rdoc]
data/lib/roxml.rb ADDED
@@ -0,0 +1,362 @@
1
+ require 'rubygems'
2
+ require 'extensions/enumerable'
3
+ require 'extensions/array'
4
+ require 'activesupport'
5
+
6
+ %w(array string options xml).each do |file|
7
+ require File.join(File.dirname(__FILE__), 'roxml', file)
8
+ end
9
+
10
+ module ROXML
11
+ # This class defines the annotation methods that are mixed into your
12
+ # Ruby classes for XML mapping information and behavior.
13
+ #
14
+ # See xml_name, xml_construct, xml, xml_reader and xml_accessor for
15
+ # available annotations.
16
+ #
17
+ module ROXML_Class
18
+ #
19
+ # Creates a new Ruby object from XML using mapping information
20
+ # annotated in the class.
21
+ #
22
+ # The input data is either an XML::Node or a String representing
23
+ # the XML document.
24
+ #
25
+ # Example
26
+ # book = Book.parse(File.read("book.xml"))
27
+ # or
28
+ # book = Book.parse("<book><name>Beyond Java</name></book>")
29
+ #
30
+ # See also: xml_construct
31
+ #
32
+ def parse(data)
33
+ xml = (data.kind_of?(XML::Node) ? data : XML::Parser.parse(data).root)
34
+
35
+ unless xml_construction_args.empty?
36
+ args = xml_construction_args.map do |arg|
37
+ tag_refs.find {|ref| ref.name == arg.to_s }
38
+ end.map {|ref| ref.value(xml) }
39
+ new(*args)
40
+ else
41
+ returning allocate do |inst|
42
+ tag_refs.each do |ref|
43
+ ref.populate(xml, inst)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
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
+ @tag_name = name
61
+ end
62
+
63
+ # Declares an accesser to a certain xml element, whether an attribute, a node,
64
+ # or a typed collection of nodes
65
+ #
66
+ # [sym] Symbol representing the name of the accessor
67
+ #
68
+ # == Type options
69
+ # All type arguments may be used as the type argument to indicate just type,
70
+ # or used as :from, pointing to a xml name to indicate both type and attribute name.
71
+ # Also, any type may be passed via an array to indicate that multiple instances
72
+ # of the object should be returned as an array.
73
+ #
74
+ # === :attr
75
+ # Declare an accessor that represents an XML attribute.
76
+ #
77
+ # Example:
78
+ # class Book
79
+ # xml_reader :isbn, :attr => "ISBN" # 'ISBN' is used to specify :from
80
+ # xml_accessor :title, :attr # :from defaults to :title
81
+ # end
82
+ #
83
+ # To map:
84
+ # <book ISBN="0974514055" title="Programming Ruby: the pragmatic programmers' guide" />
85
+ #
86
+ # === :text
87
+ # The default type, if none is specified. Declares an accessor that
88
+ # represents a text node from XML.
89
+ #
90
+ # Example:
91
+ # class Book
92
+ # xml :author, false, :text => 'Author'
93
+ # xml_accessor :description, :text, :as => :cdata
94
+ # xml_reader :title
95
+ # end
96
+ #
97
+ # To map:
98
+ # <book>
99
+ # <title>Programming Ruby: the pragmatic programmers' guide</title>
100
+ # <description><![CDATA[Probably the best Ruby book out there]]></description>
101
+ # <Author>David Thomas</author>
102
+ # </book>
103
+ #
104
+ # Likewise, a number of :text node values can be collected in an array like so:
105
+ #
106
+ # Example:
107
+ # class Library
108
+ # xml_reader :books, [:text], :in => 'books'
109
+ # end
110
+ #
111
+ # To map:
112
+ # <library>
113
+ # <books>
114
+ # <book>To kill a mockingbird</book>
115
+ # <book>House of Leaves</book>
116
+ # <book>Gödel, Escher, Bach</book>
117
+ # </books>
118
+ # </library>
119
+ #
120
+ # === :content
121
+ # A special case of :text, this refers to the content of the current node,
122
+ # rather than a sub-node
123
+ #
124
+ # Example:
125
+ # class Contributor
126
+ # xml_reader :name, :content
127
+ # xml_reader :role, :attr
128
+ # end
129
+ #
130
+ # To map:
131
+ # <contributor role="editor">James Wick</contributor>
132
+ #
133
+ # === Hash
134
+ # Somewhere between the simplicity of a :text/:attr mapping, and the complexity of
135
+ # a full Object/Type mapping, lies the Hash mapping. It serves in the case where you have
136
+ # a collection of key-value pairs represented in your xml. You create a hash declaration by
137
+ # passing a hash mapping as the type argument. A few examples:
138
+ #
139
+ # ==== Hash of :attrs
140
+ # For xml such as this:
141
+ #
142
+ # <dictionary>
143
+ # <definitions>
144
+ # <definition dt="quaquaversally"
145
+ # dd="adjective: (of a geological formation) sloping downward from the center in all directions." />
146
+ # <definition dt="tergiversate"
147
+ # dd="To use evasions or ambiguities; equivocate." />
148
+ # </definitions>
149
+ # </dictionary>
150
+ #
151
+ # You can use the :attrs key in you has with a [:key, :value] name array:
152
+ #
153
+ # xml_reader :definitions, {:attrs => ['dt', 'dd']}, :in => :definitions
154
+ #
155
+ # ==== Hash of :texts
156
+ # For xml such as this:
157
+ #
158
+ # <dictionary>
159
+ # <definition>
160
+ # <word/>
161
+ # <meaning/>
162
+ # </definition>
163
+ # <definition>
164
+ # <word/>
165
+ # <meaning/>
166
+ # </definition>
167
+ # </dictionary>
168
+ #
169
+ # You can individually declare your key and value names:
170
+ # xml_reader :definitions, {:key => 'word',
171
+ # :value => 'meaning'}
172
+ #
173
+ # ==== Hash of :content &c.
174
+ # For xml such as this:
175
+ #
176
+ # <dictionary>
177
+ # <definition word="quaquaversally">adjective: (of a geological formation) sloping downward from the center in all directions.</definition>
178
+ # <definition word="tergiversate">To use evasions or ambiguities; equivocate.</definition>
179
+ # </dictionary>
180
+ #
181
+ # You can individually declare the key and value, but with the attr, you need to provide both the type
182
+ # and name of that type (i.e. {:attr => :word}), because omitting the type will result in ROXML
183
+ # defaulting to :text
184
+ # xml_reader :definitions, {:key => {:attr => 'word'},
185
+ # :value => :content}
186
+ #
187
+ # ==== Hash of :name &c.
188
+ # For xml such as this:
189
+ #
190
+ # <dictionary>
191
+ # <quaquaversally>adjective: (of a geological formation) sloping downward from the center in all directions.</quaquaversally>
192
+ # <tergiversate>To use evasions or ambiguities; equivocate.</tergiversate>
193
+ # </dictionary>
194
+ #
195
+ # You can pick up the node names (e.g. quaquaversally) using the :name keyword:
196
+ # xml_reader :definitions, {:key => :name,
197
+ # :value => :content}
198
+ #
199
+ # === Other ROXML Class
200
+ # Declares an accessor that represents another ROXML class as child XML element
201
+ # (one-to-one or composition) or array of child elements (one-to-many or
202
+ # aggregation) of this type. Default is one-to-one. Use :array option for one-to-many, or
203
+ # simply pass the class in an array.
204
+ #
205
+ # Composition example:
206
+ # <book>
207
+ # <publisher>
208
+ # <name>Pragmatic Bookshelf</name>
209
+ # </publisher>
210
+ # </book>
211
+ #
212
+ # Can be mapped using the following code:
213
+ # class Book
214
+ # xml_reader :publisher, Publisher
215
+ # end
216
+ #
217
+ # Aggregation example:
218
+ # <library>
219
+ # <books>
220
+ # <book/>
221
+ # <book/>
222
+ # </books>
223
+ # </library>
224
+ #
225
+ # Can be mapped using the following code:
226
+ # class Library
227
+ # xml_reader :books, [Book], :in => "books"
228
+ # end
229
+ #
230
+ # If you don't have the <books> tag to wrap around the list of <book> tags:
231
+ # <library>
232
+ # <name>Ruby books</name>
233
+ # <book/>
234
+ # <book/>
235
+ # </library>
236
+ #
237
+ # You can skip the wrapper argument:
238
+ # xml_reader :books, [Book]
239
+ #
240
+ # == Blocks
241
+ # For readonly attributes, you may pass a block which manipulates the associated parsed value.
242
+ #
243
+ # class Muffins
244
+ # include ROXML
245
+ #
246
+ # xml_reader :count, :from => 'bakers_dozens' {|val| val.to_i * 13 }
247
+ # end
248
+ #
249
+ # For hash types, the block recieves the key and value as arguments, and they should
250
+ # be returned as an array of [key, value]
251
+ #
252
+ # == Other options
253
+ # [: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.
254
+ # [:as] :cdata for character data
255
+ # [:in] An optional name of a wrapping tag for this XML accessor
256
+ # [:else] Default value for attribute, if missing
257
+ #
258
+ def xml(sym, writable = false, type_and_or_opts = :text, opts = nil, &block)
259
+ opts = Opts.new(sym, *[type_and_or_opts, opts].compact)
260
+
261
+ tag_refs << case opts.type
262
+ when :attr then XMLAttributeRef
263
+ when :content then XMLTextRef
264
+ when :text then XMLTextRef
265
+ when :hash then XMLHashRef
266
+ when Symbol then raise ArgumentError, "Invalid type argument #{opts.type}"
267
+ else XMLObjectRef
268
+ end.new(sym, opts, &block)
269
+
270
+ add_accessor(sym, writable, opts.array?, opts.default)
271
+ end
272
+
273
+ # Declares a read-only xml reference. See xml for details.
274
+ def xml_reader(sym, type_and_or_opts = :text, opts = nil, &block)
275
+ xml sym, false, type_and_or_opts, opts, &block
276
+ end
277
+
278
+ # Declares a writable xml reference. See xml for details.
279
+ def xml_accessor(sym, type_and_or_opts = :text, opts = nil, &block)
280
+ xml sym, true, type_and_or_opts, opts, &block
281
+ end
282
+
283
+ def xml_construction_args # ::nodoc::
284
+ @xml_construction_args ||= []
285
+ end
286
+
287
+ # On parse, call the target object's initialize function with the listed arguments
288
+ def xml_construct(*args)
289
+ if missing_tag = args.detect {|arg| !tag_refs.map(&:name).include?(arg.to_s) }
290
+ raise ArgumentError, "All construction tags must be declared first using xml, " +
291
+ "xml_reader, or xml_accessor. #{missing_tag} is missing. " +
292
+ tag_refs.map(&:name).join(', ') + ' are declared.'
293
+ end
294
+ @xml_construction_args = args
295
+ end
296
+
297
+ # Returns the tag name (also known as xml_name) of the class.
298
+ # If no tag name is set with xml_name method, returns default class name
299
+ # in lowercase.
300
+ def tag_name
301
+ @tag_name ||= name.split('::').last.downcase
302
+ end
303
+
304
+ # Returns array of internal reference objects, such as attributes
305
+ # and composed XML objects
306
+ def tag_refs
307
+ @xml_refs ||= []
308
+ end
309
+
310
+ private
311
+ def assert_accessor(name)
312
+ @tag_accessors ||= []
313
+ raise "Accessor #{name} is already defined as XML accessor in class #{self}" if @tag_accessors.include?(name)
314
+ @tag_accessors << name
315
+ end
316
+
317
+ def add_accessor(name, writable, as_array, default = nil)
318
+ assert_accessor(name)
319
+ unless instance_methods.include?(name)
320
+ default ||= Array.new if as_array
321
+
322
+ define_method(name) do
323
+ val = instance_variable_get("@#{name}")
324
+ if val.nil?
325
+ val = default
326
+ instance_variable_set("@#{name}", val)
327
+ end
328
+ val
329
+ end
330
+ end
331
+ if writable && !instance_methods.include?("#{name}=")
332
+ define_method("#{name}=") do |v|
333
+ instance_variable_set("@#{name}", v)
334
+ end
335
+ end
336
+ end
337
+ end
338
+
339
+ class << self
340
+ #
341
+ # Extends the klass with the ROXML_Class module methods.
342
+ #
343
+ def included(klass) # ::nodoc::
344
+ super
345
+ klass.__send__(:extend, ROXML_Class)
346
+ end
347
+ end
348
+
349
+ #
350
+ # To make it easier to reference the class's
351
+ # attributes all method calls to the instance that
352
+ # doesn't match an instance method are forwarded to the
353
+ # class's singleton instance. Only methods 'tag_name' and 'tag_refs' are delegated.
354
+ def method_missing(name, *args)
355
+ if [:tag_name, :tag_refs].include? name
356
+ self.class.__send__(name, *args)
357
+ else
358
+ super
359
+ end
360
+ end
361
+ end
362
+