roxml 1.2 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/MIT-LICENSE +18 -0
  2. data/README.rdoc +126 -0
  3. data/Rakefile +100 -0
  4. data/lib/roxml.rb +479 -485
  5. data/lib/roxml/extensions/active_support.rb +32 -0
  6. data/lib/roxml/extensions/array.rb +5 -0
  7. data/lib/roxml/extensions/array/conversions.rb +25 -0
  8. data/lib/roxml/extensions/deprecation.rb +28 -0
  9. data/lib/roxml/extensions/string.rb +21 -0
  10. data/lib/roxml/extensions/string/conversions.rb +44 -0
  11. data/lib/roxml/extensions/string/iterators.rb +12 -0
  12. data/lib/roxml/options.rb +266 -0
  13. data/lib/roxml/xml.rb +223 -0
  14. data/lib/roxml/xml/libxml.rb +63 -0
  15. data/lib/roxml/xml/rexml.rb +64 -0
  16. data/roxml.gemspec +105 -0
  17. data/test/fixtures/book_text_with_attribute.xml +1 -1
  18. data/test/fixtures/book_valid.xml +2 -2
  19. data/test/fixtures/book_with_authors.xml +7 -0
  20. data/test/fixtures/book_with_contributors_attrs.xml +7 -0
  21. data/test/fixtures/book_with_default_namespace.xml +9 -0
  22. data/test/fixtures/book_with_depth.xml +6 -0
  23. data/test/fixtures/book_with_wrapped_attr.xml +3 -0
  24. data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
  25. data/test/fixtures/dictionary_of_attrs.xml +6 -0
  26. data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
  27. data/test/fixtures/dictionary_of_mixeds.xml +4 -0
  28. data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
  29. data/test/fixtures/dictionary_of_names.xml +4 -0
  30. data/test/fixtures/dictionary_of_texts.xml +10 -0
  31. data/test/fixtures/library.xml +1 -1
  32. data/test/fixtures/library_uppercase.xml +30 -0
  33. data/test/fixtures/muffins.xml +3 -0
  34. data/test/fixtures/nameless_ageless_youth.xml +2 -0
  35. data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
  36. data/test/fixtures/node_with_name_conflicts.xml +4 -0
  37. data/test/fixtures/numerology.xml +4 -0
  38. data/test/fixtures/person_with_guarded_mothers.xml +13 -0
  39. data/test/fixtures/person_with_mothers.xml +10 -0
  40. data/test/mocks/dictionaries.rb +56 -0
  41. data/test/mocks/mocks.rb +244 -48
  42. data/test/release/dependencies_test.rb +32 -0
  43. data/test/test_helper.rb +44 -0
  44. data/test/unit/inheritance_test.rb +19 -0
  45. data/test/unit/options_test.rb +102 -0
  46. data/test/unit/roxml_test.rb +24 -0
  47. data/test/unit/string_test.rb +11 -0
  48. data/test/unit/to_xml_test.rb +52 -0
  49. data/test/unit/xml_attribute_test.rb +39 -0
  50. data/test/unit/xml_block_test.rb +81 -0
  51. data/test/unit/xml_bool_test.rb +105 -0
  52. data/test/unit/xml_construct_test.rb +77 -0
  53. data/test/unit/xml_hash_test.rb +65 -0
  54. data/test/unit/xml_initialize_test.rb +50 -0
  55. data/test/unit/xml_name_test.rb +108 -0
  56. data/test/unit/xml_namespace_test.rb +38 -0
  57. data/test/unit/xml_object_test.rb +116 -0
  58. data/test/unit/xml_required_test.rb +93 -0
  59. data/test/unit/xml_text_test.rb +68 -0
  60. metadata +138 -87
  61. data/MIT-LICENSE.txt +0 -9
  62. data/README +0 -2
  63. data/doc/classes/ROXML.html +0 -374
  64. data/doc/classes/ROXML.src/M000003.html +0 -19
  65. data/doc/classes/ROXML.src/M000004.html +0 -25
  66. data/doc/classes/ROXML.src/M000005.html +0 -22
  67. data/doc/classes/ROXML/ROXML_Class.html +0 -429
  68. data/doc/classes/ROXML/ROXML_Class.src/M000006.html +0 -27
  69. data/doc/classes/ROXML/ROXML_Class.src/M000007.html +0 -18
  70. data/doc/classes/ROXML/ROXML_Class.src/M000008.html +0 -19
  71. data/doc/classes/ROXML/ROXML_Class.src/M000009.html +0 -25
  72. data/doc/classes/ROXML/ROXML_Class.src/M000010.html +0 -24
  73. data/doc/classes/ROXML/ROXML_Class.src/M000011.html +0 -18
  74. data/doc/classes/ROXML/ROXML_Class.src/M000012.html +0 -18
  75. data/doc/classes/ROXML/XMLAttributeRef.html +0 -175
  76. data/doc/classes/ROXML/XMLAttributeRef.src/M000015.html +0 -19
  77. data/doc/classes/ROXML/XMLAttributeRef.src/M000016.html +0 -19
  78. data/doc/classes/ROXML/XMLObjectRef.html +0 -175
  79. data/doc/classes/ROXML/XMLObjectRef.src/M000013.html +0 -26
  80. data/doc/classes/ROXML/XMLObjectRef.src/M000014.html +0 -32
  81. data/doc/classes/ROXML/XMLRef.html +0 -166
  82. data/doc/classes/ROXML/XMLRef.src/M000017.html +0 -21
  83. data/doc/classes/ROXML/XMLTextRef.html +0 -198
  84. data/doc/classes/ROXML/XMLTextRef.src/M000018.html +0 -28
  85. data/doc/classes/ROXML/XMLTextRef.src/M000019.html +0 -34
  86. data/doc/classes/String.html +0 -165
  87. data/doc/classes/String.src/M000001.html +0 -23
  88. data/doc/classes/String.src/M000002.html +0 -23
  89. data/doc/created.rid +0 -1
  90. data/doc/files/lib/roxml_rb.html +0 -234
  91. data/doc/fr_class_index.html +0 -33
  92. data/doc/fr_file_index.html +0 -27
  93. data/doc/fr_method_index.html +0 -45
  94. data/doc/index.html +0 -24
  95. data/doc/rdoc-style.css +0 -208
  96. data/test/fixture_helper.rb +0 -5
  97. data/test/test_roxml.rb +0 -105
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,126 @@
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::ClassMethods::Declarations 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.from_xml(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
+ define method xml_initialize to perform initialization after instantiation and parsing, including
95
+ causing your ROXML object to call its own constructor, as in the following:
96
+
97
+ class Measurement
98
+ include ROXML
99
+
100
+ xml_reader :units, :attr
101
+ xml_reader :value, :content
102
+
103
+ def xml_initialize
104
+ # xml attributes of self are already valid
105
+ initialize(value, units)
106
+ end
107
+
108
+ def initialize(value, units)
109
+ # translate units & value into metric, for example
110
+ end
111
+ end
112
+
113
+ One important use of this approach is to make ROXML object which may or may not include an xml backing,
114
+ which may be used via _new_ construction as well as _from_xml_ construction.
115
+
116
+ == Selecting a parser
117
+
118
+ By default, ROXML will use LibXML if it is available, or otherwise REXML. If you'd like to
119
+ explicitly require one or the other, you may do the following:
120
+
121
+ module ROXML
122
+ XML_PARSER = 'libxml' # or 'rexml'
123
+ end
124
+ require 'roxml'
125
+
126
+ For more information on available annotations, see ROXML::ClassMethods::Declarations
data/Rakefile ADDED
@@ -0,0 +1,100 @@
1
+ # Rake libraries used
2
+ require "rubygems"
3
+ require "rake/rdoctask"
4
+ require "rake/contrib/rubyforgepublisher"
5
+ require "rake/contrib/publisher"
6
+ require 'rake/gempackagetask'
7
+ require 'rake/testtask'
8
+
9
+ # load settings
10
+ spec = eval(IO.read("roxml.gemspec"))
11
+
12
+ # Provide the username used to upload website etc.
13
+ RubyForgeConfig = {
14
+ :unix_name=>"roxml",
15
+ :user_name=>"zakmandhro"
16
+ }
17
+
18
+ task :default => :test
19
+
20
+ Rake::RDocTask.new do |rd|
21
+ rd.rdoc_dir = "doc"
22
+ rd.rdoc_files.include('MIT-LICENSE', 'README.rdoc', "lib/**/*.rb")
23
+ rd.options << '--main' << 'README.rdoc' << '--title' << 'ROXML Documentation'
24
+ end
25
+
26
+ desc "Publish Ruby on Rails plug-in on RubyForge"
27
+ task :release_plugin=>:rails_plugin do |task|
28
+ pub = Rake::SshDirPublisher.new("#{RubyForgeConfig[:user_name]}@rubyforge.org",
29
+ "/var/www/gforge-projects/#{RubyForgeConfig[:unix_name]}",
30
+ "pkg/rails_plugin")
31
+ pub.upload()
32
+ end
33
+
34
+ desc "Publish and plugin site on RubyForge"
35
+ task :publish do |task|
36
+ pub = Rake::RubyForgePublisher.new(RubyForgeConfig[:unix_name], RubyForgeConfig[:user_name])
37
+ pub.upload()
38
+ end
39
+
40
+ desc "Install the gem"
41
+ task :install => [:package] do
42
+ sh %{sudo gem install pkg/#{spec.name}-#{spec.version}}
43
+ end
44
+
45
+ Rake::TestTask.new(:bugs) do |t|
46
+ t.libs << 'test'
47
+ t.test_files = FileList['test/bugs/*_bugs.rb']
48
+ t.verbose = true
49
+ end
50
+
51
+ @test_files = 'test/unit/*_test.rb'
52
+ desc "Test ROXML using the default parser selection behavior"
53
+ task :test do
54
+ module ROXML
55
+ SILENCE_XML_NAME_WARNING = true
56
+ end
57
+ require 'lib/roxml'
58
+ require 'rake/runtest'
59
+ Rake.run_tests @test_files
60
+ end
61
+
62
+ namespace :test do
63
+ desc "Test ROXML under the LibXML parser"
64
+ task :libxml do
65
+ module ROXML
66
+ XML_PARSER = 'libxml'
67
+ end
68
+ Rake::Task["test"].invoke
69
+ end
70
+
71
+ desc "Test ROXML under the REXML parser"
72
+ task :rexml do
73
+ module ROXML
74
+ XML_PARSER = 'rexml'
75
+ end
76
+ Rake::Task["test"].invoke
77
+ end
78
+
79
+ desc "Runs tests under RCOV"
80
+ task :rcov do
81
+ system "rcov -T --no-html -x '^/' #{FileList[@test_files]}"
82
+ end
83
+ end
84
+
85
+ desc "Create the ZIP package"
86
+ Rake::PackageTask.new(spec.name, spec.version) do |p|
87
+ p.need_zip = true
88
+ p.package_files = FileList[
89
+ "lib/**/*.rb", "*.txt", "README.rdoc", "Rakefile",
90
+ "rake/**/*","test/**/*.rb", "test/**/*.xml", "html/**/*"]
91
+ end
92
+
93
+ task :package=>:rdoc
94
+ task :rdoc=>:test
95
+
96
+ desc "Create a RubyGem project"
97
+ Rake::GemPackageTask.new(spec).define
98
+
99
+ desc "Clobber generated files"
100
+ task :clobber=>[:clobber_package, :clobber_rdoc]
data/lib/roxml.rb CHANGED
@@ -1,509 +1,503 @@
1
- # ROXML Ruby Object to XML mapping library. For more information
2
- # visit http://roxml.rubyforge.org
3
- #
4
- # Copyright (c) 2004-2006 Zak Mandhro and Anders Engstrom
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
- #
8
- # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
- #
10
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11
- #
12
- # =Quick Start Guide
13
- #
14
- # This is a short usage example. See ROXML::ROXML_Class and packaged test cases for more information.
15
- #
16
- # Consider an XML document representing a Library containing a number of Books. You
17
- # can map this structure to Ruby classes that provide addition useful behavior. With
18
- # ROXML, you can annotate the Ruby classes as follows:
19
- #
20
- # class Book
21
- # include ROXML
22
- #
23
- # xml_attribute :isbn, "ISBN"
24
- # xml_text :title
25
- # xml_text :description, nil, ROXML::TAG_CDATA
26
- # xml_text :author
27
- # end
28
- #
29
- # class Library
30
- # include ROXML
31
- #
32
- # xml_text :name, "NAME", ROXML::TAG_CDATA
33
- # xml_object :books, Book, ROXML::TAG_ARRAY, "books"
34
- # end
35
- #
36
- # To create a library and put a number of books in it we could run the following code:
37
- #
38
- # book = Book.new()
39
- # book.isbn = "0201710897"
40
- # book.title = "The PickAxe"
41
- # book.description = "Best Ruby book out there!"
42
- # book.author = "David Thomas, Andrew Hunt, Dave Thomas"
43
- #
44
- # lib = Library.new()
45
- # lib.name = "Favorite Books"
46
- # lib << book
47
- #
48
- # To save this information to an XML file:
49
- #
50
- # File.open("library.xml", "w") do |f|
51
- # lib.to_xml.write(f, 0)
52
- # end
53
- #
54
- # To later populate the library object from the XML file:
55
- #
56
- # lib = Library.parse(File.read("library.xml"))
57
- #
58
- # Similarly, to do a one-to-one mapping between XML objects, such as book and publisher,
59
- # you would use the *xml_object* annotation. For example:
60
- #
61
- # <book isbn="0974514055">
62
- # <title>Programming Ruby - 2nd Edition</title>
63
- # <description>Second edition of the great book.</description>
64
- # <publisher>
65
- # <name>Pragmatic Bookshelf</name>
66
- # </publisher>
67
- # </book>
68
- #
69
- # can be mapped using the following code:
70
- #
71
- # class BookWithPublisher
72
- # include ROXML
73
- #
74
- # xml_name :book
75
- # xml_object :publisher, Publisher
76
- # end
77
- #
78
- # Note: In the above example, _xml_name_ annotation tells ROXML to set the element
79
- # name to "book" for mapping to XML. The default is XML element name is the class name in lowercase; "bookwithpublisher"
80
- # in this case.
81
- #
82
- # For more information on available annotations, see ROXML::ROXML_Class
83
- module ROXML
84
- require 'rexml/document'
85
-
86
- # Default tag behavior declaration with single
87
- # read and write.
88
- TAG_DEFAULT = 0
89
-
90
- # Option that may be used to declare that
91
- # a variable accessor should be read-only (no "accessor=(val)" is generated).
92
- TAG_READONLY = 1
93
-
94
- # Option that declares that an XML text element's value should be
95
- # wrapped in a CDATA section.
96
- TAG_CDATA = 2
97
-
98
- # Option that declares an accessor as an array (referencing "many"
99
- # items).
100
- TAG_ARRAY = 4
101
-
102
- # Option that declares an xml_text annotation to define the text
103
- # content of the container tag
104
- TEXT_CONTENT = 8
105
-
106
- #
107
- # Internal base class that represents an XML - Class binding.
108
- #
109
- class XMLRef
110
- attr_accessor :accessor, :name, :array
1
+ %w(extensions/active_support extensions/deprecation extensions/array extensions/string options xml).each do |file|
2
+ require File.join(File.dirname(__FILE__), 'roxml', file)
3
+ end
111
4
 
112
- def initialize(accessor, name = nil)
113
- @accessor = accessor
114
- @name = (name || accessor.id2name)
115
- yield self if block_given?
116
- @array = false unless @array
117
- end
5
+ module ROXML # :nodoc:
6
+ def self.included(base) # :nodoc:
7
+ base.extend ClassMethods::Accessors
8
+ base.extend ClassMethods::Declarations
9
+ base.extend ClassMethods::Operations
10
+ base.class_eval do
11
+ include InstanceMethods::Accessors
12
+ include InstanceMethods::Construction
13
+ include InstanceMethods::Conversions
118
14
  end
119
-
120
- # Interal class representing an XML attribute binding
121
- #
122
- # In context:
123
- # <element attribute="XMLAttributeRef">
124
- # XMLTextRef
125
- # </element>
126
- class XMLAttributeRef < XMLRef
127
- # Updates the attribute in the given XML block to
128
- # the value provided.
129
- def update_xml(xml, value)
130
- xml.attributes[name] = value.to_s.to_utf
131
- xml
132
- end
133
-
134
- # Reads data from the XML element and populates the object
135
- # instance accordingly.
136
- def populate(xml, instance)
137
- instance.instance_variable_set("@#{accessor}", xml.attributes[name])
138
- instance
139
- end
15
+ end
16
+
17
+ module InstanceMethods # :nodoc:
18
+ # Instance method equivalents of the Class method accessors
19
+ module Accessors
20
+ # Provides access to ROXML::ClassMethods::Accessors::tag_name directly from an instance of a ROXML class
21
+ def tag_name
22
+ self.class.tag_name
23
+ end
24
+
25
+ # Provides access to ROXML::ClassMethods::Accessors::tag_refs directly from an instance of a ROXML class
26
+ def tag_refs
27
+ self.class.tag_refs
28
+ end
140
29
  end
141
30
 
142
- # Interal class representing XML content text binding
143
- #
144
- # In context:
145
- # <element attribute="XMLAttributeRef">
146
- # XMLTextRef
147
- # </element>
148
- class XMLTextRef < XMLRef
149
- attr_accessor :cdata, :wrapper, :text_content
150
-
151
- # Updates the text in the given _xml_ block to
152
- # the _value_ provided.
153
- def update_xml(xml, value)
154
- parent = (wrapper ? xml.add_element(wrapper) : xml)
155
- if text_content
156
- parent.text = (cdata ? REXML::CData.new(value.to_s.to_utf) : value.to_s.to_utf)
157
- elsif array
158
- value.each do |v|
159
- parent.add_element(name).text = (cdata ? REXML::CData.new(v.to_s.to_utf) : v.to_s.to_utf)
160
- end
161
- else
162
- parent.add_element(name).text = (cdata ? REXML::CData.new(value.to_s.to_utf) : value.to_s.to_utf)
163
- end
164
- xml
165
- end
166
-
167
- # Reads data from the XML element and populates the text
168
- # accordingly.
169
- def populate(xml, instance)
170
- data = nil
171
- if text_content
172
- data = xml.text
173
- elsif array
174
- xpath = (wrapper ? "#{wrapper}/#{name}" : "#{name}")
175
- data = []
176
- xml.each_element(xpath) do |e|
177
- if e.text
178
- data << e.text.strip.to_latin
179
- end
180
- end
181
- else
182
- child = xml.elements[1, name]
183
- data = child.text if child && child.text
184
- end
185
- instance.instance_variable_set("@#{accessor}", data) if data
186
- instance
187
- end
31
+ module Construction
32
+ # xml_initialize is called at the end of the #from_xml operation on objects
33
+ # where xml_construct is not in place. Override xml_initialize in order to establish
34
+ # post-import behavior. For example, you can use xml_initialize to map xml attribute
35
+ # values into the object standard initialize function, thus enabling a ROXML object
36
+ # to freely be either xml-backed or instantiated directly via #new.
37
+ # An example of this follows:
38
+ #
39
+ # class Measurement
40
+ # include ROXML
41
+ #
42
+ # xml_reader :units, :attr
43
+ # xml_reader :value, :content
44
+ #
45
+ # def xml_initialize
46
+ # # the object is instantiated, and all xml attributes are imported
47
+ # # and available, i.e., value and units below are the same value and units
48
+ # # found in the xml via the xml_reader declarations above.
49
+ # initialize(value, units)
50
+ # end
51
+ #
52
+ # def initialize(value, units = 'pixels')
53
+ # @value = Float(value)
54
+ # @units = units.to_s
55
+ # if @units.starts_with? 'hundredths-'
56
+ # @value /= 100
57
+ # @units = @units.split('hundredths-')[1]
58
+ # end
59
+ # end
60
+ # end
61
+ #
62
+ # #xml_initialize may be written to take arguments, in which case extra arguments
63
+ # from from_xml will be passed into the function.
64
+ #
65
+ def xml_initialize
66
+ end
188
67
  end
189
68
 
190
- class XMLObjectRef < XMLTextRef
191
- attr_accessor :klass
192
-
193
- # Updates the composed XML object in the given XML block to
194
- # the value provided.
195
- def update_xml(xml, value)
196
- parent = (wrapper ? xml.add_element(wrapper) : xml)
197
- unless array
198
- parent.add_element(value.to_xml)
199
- else
200
- value.each do |v|
201
- parent.add_element(v.to_xml)
202
- end
203
- end
204
- xml
205
- end
206
-
207
- # Reads data from the XML element and populates the references XML
208
- # object accordingly.
209
- def populate(xml, instance)
210
- data = nil
211
- unless array
212
- child = xml.elements[1, klass.tag_name]
213
- if child
214
- data = klass.parse(child)
215
- end
216
- else
217
- xpath = (wrapper ? "#{wrapper}/#{klass.tag_name}" : "#{klass.tag_name}")
218
- data = []
219
- xml.each_element(xpath) do |e|
220
- data << klass.parse(e)
221
- end
69
+ module Conversions
70
+ # Returns a LibXML::XML::Node or a REXML::Element representing this object
71
+ def to_xml(name = nil)
72
+ returning XML::Node.new_element(name || tag_name) do |root|
73
+ tag_refs.each do |ref|
74
+ v = __send__(ref.accessor)
75
+ unless v.nil?
76
+ ref.update_xml(root, v)
222
77
  end
223
- instance.instance_variable_set("@#{accessor}", data) if data
224
- instance
78
+ end
225
79
  end
80
+ end
226
81
  end
227
-
228
-
229
- # This class defines the annotation methods that are mixed into your
230
- # Ruby classes for XML mapping information and behavior.
231
- #
232
- # See xml_name, xml_text, xml_attribute and xml_object for available
233
- # annotations.
234
- #
235
- module ROXML_Class
236
- #
237
- # Creates a new Ruby object from XML using mapping information
238
- # annotated in the class.
239
- #
240
- # The input data is either a REXML::Element or a String representing
241
- # the XML document.
242
- #
243
- # Example
244
- # book = Book.parse(File.read("book.xml"))
245
- # or
246
- # book = Book.parse("<book><name>Beyond Java</name></book>")
247
- #
248
- def parse(data)
249
-
250
- xml = (data.kind_of?(REXML::Element) ? data : REXML::Document.new(data).root)
251
-
252
- inst = self.allocate
253
-
254
- tag_refs.each do |ref|
255
- ref.populate(xml, inst)
256
- end
257
-
258
- return inst
82
+ end
83
+
84
+ # This class defines the annotation methods that are mixed into your
85
+ # Ruby classes for XML mapping information and behavior.
86
+ #
87
+ # See xml_name, xml_initialize, xml, xml_reader and xml_accessor for
88
+ # available annotations.
89
+ #
90
+ module ClassMethods # :nodoc:
91
+ module Declarations
92
+ # A helper which enables us to detect when the xml_name has been explicitly set
93
+ def xml_name? #:nodoc:
94
+ @xml_name
95
+ end
96
+
97
+ # Sets the name of the XML element that represents this class. Use this
98
+ # to override the default lowercase class name.
99
+ #
100
+ # Example:
101
+ # class BookWithPublisher
102
+ # xml_name :book
103
+ # end
104
+ #
105
+ # Without the xml_name annotation, the XML mapped tag would have been "bookwithpublisher".
106
+ #
107
+ def xml_name(name)
108
+ @xml_name = true
109
+ @tag_name = name
110
+ end
111
+
112
+ # Declares an accesser to a certain xml element, whether an attribute, a node,
113
+ # or a typed collection of nodes. Typically you should call xml_reader or xml_accessor
114
+ # rather than calling this method directly, but the instructions below apply to both.
115
+ #
116
+ # == Sym Option
117
+ # [sym] Symbol representing the name of the accessor.
118
+ #
119
+ # === Default naming
120
+ # This name will be the default node or attribute name searched for,
121
+ # if no other is declared. For example,
122
+ #
123
+ # xml_reader :bob, :from => 'bob'
124
+ # xml_accessor :pony, :attr => 'pony'
125
+ #
126
+ # are equivalent to:
127
+ #
128
+ # xml_reader :bob
129
+ # xml_accessor :pony, :attr
130
+ #
131
+ # === Boolean attributes
132
+ # If the name ends in a ?, ROXML will attempt to coerce the value to true or false,
133
+ # with True, TRUE, true and 1 mapping to true and False, FALSE, false and 0 mapping
134
+ # to false, as shown below:
135
+ #
136
+ # xml_reader :desirable?
137
+ # xml_reader :bizzare?, :attr => 'BIZZARE'
138
+ #
139
+ # x = #from_xml(%{
140
+ # <object BIZZARE="1">
141
+ # <desirable>False</desirable>
142
+ # </object>
143
+ # })
144
+ # x.desirable?
145
+ # => false
146
+ # x.bizzare?
147
+ # => true
148
+ #
149
+ # If an unexpected value is encountered, the attribute will be set to nil,
150
+ # unless you provide a block, in which case the block will recived
151
+ # the actual unexpected value.
152
+ #
153
+ # #from_xml(%{
154
+ # <object>
155
+ # <desirable>Dunno</desirable>
156
+ # </object>
157
+ # }).desirable?
158
+ # => nil
159
+ #
160
+ # xml_reader :strange? do |val|
161
+ # val.upcase
162
+ # end
163
+ #
164
+ # #from_xml(%{
165
+ # <object>
166
+ # <strange>Dunno</strange>
167
+ # </object>
168
+ # }).strange?
169
+ # => DUNNO
170
+ #
171
+ # == Type options
172
+ # All type arguments may be used as the type argument to indicate just type,
173
+ # or used as :from, pointing to a xml name to indicate both type and attribute name.
174
+ # Also, any type may be passed via an array to indicate that multiple instances
175
+ # of the object should be returned as an array.
176
+ #
177
+ # === :attr
178
+ # Declare an accessor that represents an XML attribute.
179
+ #
180
+ # Example:
181
+ # class Book
182
+ # xml_reader :isbn, :attr => "ISBN" # 'ISBN' is used to specify :from
183
+ # xml_accessor :title, :attr # :from defaults to :title
184
+ # end
185
+ #
186
+ # To map:
187
+ # <book ISBN="0974514055" title="Programming Ruby: the pragmatic programmers' guide" />
188
+ #
189
+ # === :text
190
+ # The default type, if none is specified. Declares an accessor that
191
+ # represents a text node from XML.
192
+ #
193
+ # Example:
194
+ # class Book
195
+ # xml :author, false, :text => 'Author'
196
+ # xml_accessor :description, :text, :as => :cdata
197
+ # xml_reader :title
198
+ # end
199
+ #
200
+ # To map:
201
+ # <book>
202
+ # <title>Programming Ruby: the pragmatic programmers' guide</title>
203
+ # <description><![CDATA[Probably the best Ruby book out there]]></description>
204
+ # <Author>David Thomas</author>
205
+ # </book>
206
+ #
207
+ # Likewise, a number of :text node values can be collected in an array like so:
208
+ #
209
+ # Example:
210
+ # class Library
211
+ # xml_reader :books, [:text], :in => 'books'
212
+ # end
213
+ #
214
+ # To map:
215
+ # <library>
216
+ # <books>
217
+ # <book>To kill a mockingbird</book>
218
+ # <book>House of Leaves</book>
219
+ # <book>Gödel, Escher, Bach</book>
220
+ # </books>
221
+ # </library>
222
+ #
223
+ # === :content
224
+ # A special case of :text, this refers to the content of the current node,
225
+ # rather than a sub-node
226
+ #
227
+ # Example:
228
+ # class Contributor
229
+ # xml_reader :name, :content
230
+ # xml_reader :role, :attr
231
+ # end
232
+ #
233
+ # To map:
234
+ # <contributor role="editor">James Wick</contributor>
235
+ #
236
+ # === Hash
237
+ # Somewhere between the simplicity of a :text/:attr mapping, and the complexity of
238
+ # a full Object/Type mapping, lies the Hash mapping. It serves in the case where you have
239
+ # a collection of key-value pairs represented in your xml. You create a hash declaration by
240
+ # passing a hash mapping as the type argument. A few examples:
241
+ #
242
+ # ==== Hash of :attrs
243
+ # For xml such as this:
244
+ #
245
+ # <dictionary>
246
+ # <definitions>
247
+ # <definition dt="quaquaversally"
248
+ # dd="adjective: (of a geological formation) sloping downward from the center in all directions." />
249
+ # <definition dt="tergiversate"
250
+ # dd="To use evasions or ambiguities; equivocate." />
251
+ # </definitions>
252
+ # </dictionary>
253
+ #
254
+ # You can use the :attrs key in you has with a [:key, :value] name array:
255
+ #
256
+ # xml_reader :definitions, {:attrs => ['dt', 'dd']}, :in => :definitions
257
+ #
258
+ # ==== Hash of :texts
259
+ # For xml such as this:
260
+ #
261
+ # <dictionary>
262
+ # <definition>
263
+ # <word/>
264
+ # <meaning/>
265
+ # </definition>
266
+ # <definition>
267
+ # <word/>
268
+ # <meaning/>
269
+ # </definition>
270
+ # </dictionary>
271
+ #
272
+ # You can individually declare your key and value names:
273
+ # xml_reader :definitions, {:key => 'word',
274
+ # :value => 'meaning'}
275
+ #
276
+ # ==== Hash of :content &c.
277
+ # For xml such as this:
278
+ #
279
+ # <dictionary>
280
+ # <definition word="quaquaversally">adjective: (of a geological formation) sloping downward from the center in all directions.</definition>
281
+ # <definition word="tergiversate">To use evasions or ambiguities; equivocate.</definition>
282
+ # </dictionary>
283
+ #
284
+ # You can individually declare the key and value, but with the attr, you need to provide both the type
285
+ # and name of that type (i.e. {:attr => :word}), because omitting the type will result in ROXML
286
+ # defaulting to :text
287
+ # xml_reader :definitions, {:key => {:attr => 'word'},
288
+ # :value => :content}
289
+ #
290
+ # ==== Hash of :name &c.
291
+ # For xml such as this:
292
+ #
293
+ # <dictionary>
294
+ # <quaquaversally>adjective: (of a geological formation) sloping downward from the center in all directions.</quaquaversally>
295
+ # <tergiversate>To use evasions or ambiguities; equivocate.</tergiversate>
296
+ # </dictionary>
297
+ #
298
+ # You can pick up the node names (e.g. quaquaversally) using the :name keyword:
299
+ # xml_reader :definitions, {:key => :name,
300
+ # :value => :content}
301
+ #
302
+ # === Other ROXML Class
303
+ # Declares an accessor that represents another ROXML class as child XML element
304
+ # (one-to-one or composition) or array of child elements (one-to-many or
305
+ # aggregation) of this type. Default is one-to-one. Use :array option for one-to-many, or
306
+ # simply pass the class in an array.
307
+ #
308
+ # Composition example:
309
+ # <book>
310
+ # <publisher>
311
+ # <name>Pragmatic Bookshelf</name>
312
+ # </publisher>
313
+ # </book>
314
+ #
315
+ # Can be mapped using the following code:
316
+ # class Book
317
+ # xml_reader :publisher, Publisher
318
+ # end
319
+ #
320
+ # Aggregation example:
321
+ # <library>
322
+ # <books>
323
+ # <book/>
324
+ # <book/>
325
+ # </books>
326
+ # </library>
327
+ #
328
+ # Can be mapped using the following code:
329
+ # class Library
330
+ # xml_reader :books, [Book], :in => "books"
331
+ # end
332
+ #
333
+ # If you don't have the <books> tag to wrap around the list of <book> tags:
334
+ # <library>
335
+ # <name>Ruby books</name>
336
+ # <book/>
337
+ # <book/>
338
+ # </library>
339
+ #
340
+ # You can skip the wrapper argument:
341
+ # xml_reader :books, [Book]
342
+ #
343
+ # == Blocks
344
+ # You may also pass a block which manipulates the associated parsed value.
345
+ #
346
+ # class Muffins
347
+ # include ROXML
348
+ #
349
+ # xml_reader(:count, :from => 'bakers_dozens') {|val| val.to_i * 13 }
350
+ # end
351
+ #
352
+ # For hash types, the block recieves the key and value as arguments, and they should
353
+ # be returned as an array of [key, value]
354
+ #
355
+ # For array types, the entire array is passed in, and must be returned in the same fashion.
356
+ #
357
+ # === Block Shorthands
358
+ #
359
+ # Alternatively, you may use block shorthands to specify common coercions, such that:
360
+ #
361
+ # xml_reader :count, :as => Integer
362
+ #
363
+ # is equivalent to:
364
+ #
365
+ # xml_reader(:count) {|val| Integer(val) }
366
+ #
367
+ # Block shorthands :float, Float, :integer and Integer are currently available,
368
+ # but only for non-Hash declarations.
369
+ #
370
+ # == Other options
371
+ # [: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.
372
+ # [:as] :cdata for character data; :integer, Integer, :float, Float to coerce to Integer or Float respectively
373
+ # [:in] An optional name of a wrapping tag for this XML accessor
374
+ # [:else] Default value for attribute, if missing
375
+ # [:required] If true, throws RequiredElementMissing when the element isn't present
376
+ #
377
+ def xml(sym, writable = false, type_and_or_opts = :text, opts = nil, &block)
378
+ opts = Opts.new(sym, *[type_and_or_opts, opts].compact, &block)
379
+
380
+ ref = case opts.type
381
+ when :attr then XMLAttributeRef
382
+ when :content then XMLTextRef
383
+ when :text then XMLTextRef
384
+ when :hash then XMLHashRef
385
+ when Symbol then raise ArgumentError, "Invalid type argument #{opts.type}"
386
+ else XMLObjectRef
387
+ end.new(opts)
388
+
389
+ add_accessor(ref, writable)
390
+ end
391
+
392
+ # Declares a read-only xml reference. See xml for details.
393
+ def xml_reader(sym, type_and_or_opts = :text, opts = nil, &block)
394
+ xml sym, false, type_and_or_opts, opts, &block
395
+ end
396
+
397
+ # Declares a writable xml reference. See xml for details.
398
+ def xml_accessor(sym, type_and_or_opts = :text, opts = nil, &block)
399
+ xml sym, true, type_and_or_opts, opts, &block
400
+ end
401
+
402
+ # This method is deprecated, please use xml_initialize instead
403
+ def xml_construct(*args)
404
+ present_tags = tag_refs.map(&:accessor)
405
+ missing_tags = args - present_tags
406
+ unless missing_tags.empty?
407
+ raise ArgumentError, "All construction tags must be declared first using xml, " +
408
+ "xml_reader, or xml_accessor. #{missing_tags.join(', ')} is missing. " +
409
+ "#{present_tags.join(', ')} are declared."
259
410
  end
260
-
261
- # Sets the name of the XML element that represents this class. Use this
262
- # to override the default lowercase class name.
263
- #
264
- # Example:
265
- # class BookWithPublisher
266
- # xml_name :book
267
- # end
268
- #
269
- # Without the xml_name annotation, the XML mapped tag would have been "bookwithpublisher".
270
- #
271
- def xml_name(name)
272
- @tag_name = name
411
+ @xml_construction_args = args
412
+ end
413
+ deprecate :xml_construct => :xml_initialize
414
+
415
+ private
416
+ def add_accessor(ref, writable)
417
+ if tag_refs.map(&:accessor).include? ref.accessor
418
+ raise "Accessor #{ref.accessor} is already defined as XML accessor in class #{self.name}"
273
419
  end
274
-
275
- #
276
- # Declare an accessor for the included class that should be
277
- # represented as an XML attribute.
278
- #
279
- # [sym] Symbol representing the name of the accessor
280
- # [name] An optional name that should be used for the attribute in XML.
281
- # Default is sym.id2name.
282
- # [options] Valid options are TAG_READONLY to attribute as read-only
283
- #
284
- # Example:
285
- # class Book
286
- # xml_attribute :isbn, "ISBN"
287
- # end
288
- #
289
- # To map:
290
- # <book ISBN="0974514055"></book>
291
- #
292
- def xml_attribute(sym, name = nil, options = 0)
293
- add_ref(XMLAttributeRef.new(sym, name))
294
- add_accessor(sym, (TAG_READONLY & options != TAG_READONLY))
420
+ tag_refs << ref
421
+
422
+ define_method(ref.accessor) do
423
+ result = instance_variable_get("@#{ref.variable_name}")
424
+ if result.nil?
425
+ result = ref.default
426
+ instance_variable_set("@#{ref.variable_name}", result)
427
+ end
428
+ result
295
429
  end
296
430
 
297
- #
298
- # Declares an accessor that represents one or more XML text elements.
299
- #
300
- # [sym] Symbol representing the name of the accessor.
301
- # [name] An optional name that should be used for the attribute in XML.
302
- # Default is sym.id2name.
303
- # [options] TAG_CDATA for character data, TAG_ARRAY for one-to-many,
304
- # TEXT_CONTENT to declare main text content for containing tag,
305
- # and TAG_READONLY for read-only access.
306
- # [wrapper] An optional name of a wrapping tag for this XML accessor.
307
- #
308
- # Example:
309
- # class Author
310
- # xml_attribute :role
311
- # xml_text :text, nil, ROXML::TEXT_CONTENT
312
- # end
313
- #
314
- # class Book
315
- # xml_text :description, nil, ROXML::TAG_CDATA
316
- # end
317
- #
318
- # To map:
319
- # <book>
320
- # <description><![CDATA[Probably the best Ruby book out there]]></description>
321
- # <author role="primary">David Thomas</author>
322
- # </book>
323
- def xml_text(sym, name = nil, options = TAG_DEFAULT, wrapper = nil)
324
- ref = XMLTextRef.new(sym, name) do |r|
325
- r.text_content = (TEXT_CONTENT & options==TEXT_CONTENT)
326
- r.cdata = (TAG_CDATA & options==TAG_CDATA)
327
- r.array = (TAG_ARRAY & options==TAG_ARRAY)
328
- r.wrapper = wrapper if wrapper
329
- end
330
- add_ref(ref)
331
- add_accessor(sym, (TAG_READONLY & options != TAG_READONLY), ref.array)
332
- end
333
-
334
- #
335
- # Declares an accessor that represents another ROXML class as child XML element
336
- # (one-to-one or composition) or array of child elements (one-to-many or
337
- # aggregation). Default is one-to-one. Use TAG_ARRAY option for one-to-many.
338
- #
339
- # [sym] Symbol representing the name of the accessor.
340
- # [name] An optional name that should be used for the attribute in XML.
341
- # Default is sym.id2name.
342
- # [options] TAG_ARRAY for one-to-many, and TAG_READONLY for read-only access.
343
- # [wrapper] An optional name of a wrapping tag for this XML accessor.
344
- #
345
- # Composition example:
346
- # <book>
347
- # <publisher>
348
- # <name>Pragmatic Bookshelf</name>
349
- # </publisher>
350
- # </book>
351
- #
352
- # Can be mapped using the following code:
353
- # class Book
354
- # xml_object :publisher, Publisher
355
- # end
356
- #
357
- # Aggregation example:
358
- # <library>
359
- # <name>Ruby books</name>
360
- # <books>
361
- # <book/>
362
- # <book/>
363
- # </books>
364
- # </library>
365
- #
366
- # Can be mapped using the following code:
367
- # class Library
368
- # xml_text :name, nil, ROXML::TAG_CDATA
369
- # xml_object :books, Book, ROXML::TAG_ARRAY, "books"
370
- # end
371
- #
372
- # If you don't have the <books> tag to wrap around the list of <book> tags:
373
- # <library>
374
- # <name>Ruby books</name>
375
- # <book/>
376
- # <book/>
377
- # </library>
378
- #
379
- # You can skip the wrapper argument:
380
- # xml_object :books, Book, ROXML::TAG_ARRAY
381
- #
382
- def xml_object(sym, klass, options = 0, wrapper = nil)
383
- ref = XMLObjectRef.new(sym, nil) do |r|
384
- r.array = (TAG_ARRAY & options == TAG_ARRAY)
385
- r.wrapper = wrapper if wrapper
386
- r.klass = klass
387
- end
388
- add_ref(ref)
389
- add_accessor(sym, (TAG_READONLY & options != TAG_READONLY), ref.array)
390
- end
391
-
392
- # Returns the tag name (also known as xml_name) of the class.
393
- # If no tag name is set with xml_name method, returns default class name
394
- # in lowercase.
395
- def tag_name
396
- @tag_name ||= self.name.split('::').last.downcase
397
- end
398
-
399
- # Returns array of internal reference objects, such as attributes
400
- # and composed XML objects
401
- def tag_refs
402
- @xml_refs || []
403
- end
404
-
405
- private
406
-
407
- def add_ref(xml_ref)
408
- @xml_refs = [] unless @xml_refs
409
- @xml_refs << xml_ref
410
- end
411
-
412
- def assert_accessor(name)
413
- @tag_accessors = [] unless @tag_accessors
414
- raise "Accessor #{name} is already defined as XML accessor in class #{self}" if @tag_accessors.include?(name)
415
- @tag_accessors << name
416
- end
417
-
418
- def add_accessor(name, writable = true, is_array = false)
419
- assert_accessor(name)
420
- unless instance_methods.include?(name)
421
- define_method(name) do
422
- val = instance_variable_get("@#{name}")
423
- if val.nil? && is_array
424
- val = Array.new
425
- instance_variable_set("@#{name}", val)
426
- end
427
- val
428
- end
429
- end
430
- if writable
431
- unless instance_methods.include?("#{name}=")
432
- define_method("#{name}=") do |v|
433
- instance_variable_set("@#{name}", v)
434
- end
435
- end
436
- end
437
- end
438
-
439
- end ## End ROXML_Class module ##############
440
-
441
- class << self
442
- #
443
- # Extends the klass with the ROXML_Class module methods.
444
- #
445
- def included(klass)
446
- super
447
- klass.__send__(:extend, ROXML_Class)
448
- end
449
- end
450
-
451
- #
452
- # Returns an REXML::Element representing this object.
453
- #
454
- def to_xml
455
- root = REXML::Element.new(tag_name)
456
- tag_refs.each do |ref|
457
- v = __send__(ref.accessor)
458
- if v
459
- root = ref.update_xml(root, v)
460
- end
431
+ if writable && !instance_methods.include?("#{ref.accessor}=")
432
+ define_method("#{ref.accessor}=") do |v|
433
+ instance_variable_set("@#{ref.accessor}", v)
434
+ end
461
435
  end
462
- root
436
+ end
463
437
  end
464
438
 
465
- #
466
- # To make it easier to reference the class's
467
- # attributes all method calls to the instance that
468
- # doesn't match an instance method are forwarded to the
469
- # class's singleton instance. Only methods starting with 'tag_' are delegated.
470
- def method_missing(name, *args)
471
- if name.id2name =~ /^tag_/
472
- self.class.__send__(name, *args)
473
- else
474
- super
475
- end
439
+ module Accessors
440
+ def xml_construction_args # :nodoc:
441
+ @xml_construction_args ||= []
442
+ end
443
+ deprecate :xml_construction_args
444
+
445
+ # Returns the tag name (also known as xml_name) of the class.
446
+ # If no tag name is set with xml_name method, returns default class name
447
+ # in lowercase.
448
+ def tag_name
449
+ @tag_name ||= name.split('::').last.downcase
450
+ end
451
+
452
+ # Returns array of internal reference objects, such as attributes
453
+ # and composed XML objects
454
+ def tag_refs
455
+ @xml_refs ||= superclass.respond_to?(:tag_refs) ? superclass.tag_refs.clone : []
456
+ end
476
457
  end
477
458
 
478
- # Extension of String class to handle conversion from/to
479
- # UTF-8/ISO-8869-1
480
- class ::String
481
- require 'iconv'
482
-
483
- #
484
- # Return an utf-8 representation of this string.
485
- #
486
- def to_utf
487
- begin
488
- Iconv.new("utf-8", "iso-8859-1").iconv(self)
489
- rescue Iconv::IllegalSequence => e
490
- STDERR << "!! Failed converting from UTF-8 -> ISO-8859-1 (#{self}). Already the right charset?"
491
- self
459
+ module Operations
460
+ #
461
+ # Creates a new Ruby object from XML using mapping information
462
+ # annotated in the class.
463
+ #
464
+ # The input data is either an XML::Node or a String representing
465
+ # the XML document.
466
+ #
467
+ # Example
468
+ # book = Book.from_xml(File.read("book.xml"))
469
+ # or
470
+ # book = Book.from_xml("<book><name>Beyond Java</name></book>")
471
+ #
472
+ # _initialization_args_ passed into from_xml will be passed into
473
+ # the object #xml_initialize method.
474
+ #
475
+ # See also: xml_initialize
476
+ #
477
+ def from_xml(data, *initialization_args)
478
+ xml = (data.kind_of?(XML::Node) ? data : XML::Parser.parse(data).root)
479
+
480
+ unless xml_construction_args_without_deprecation.empty?
481
+ args = xml_construction_args_without_deprecation.map do |arg|
482
+ tag_refs.find {|ref| ref.accessor == arg }
483
+ end.map {|ref| ref.value(xml) }
484
+ new(*args)
485
+ else
486
+ returning allocate do |inst|
487
+ tag_refs.each do |ref|
488
+ ref.populate(xml, inst)
492
489
  end
490
+ inst.send(:xml_initialize, *initialization_args)
491
+ end
493
492
  end
493
+ end
494
494
 
495
- #
496
- # Convert this string to iso-8850-1
497
- #
498
- def to_latin
499
- begin
500
- Iconv.new("iso-8859-1", "utf-8").iconv(self)
501
- rescue Iconv::IllegalSequence => e
502
- STDERR << "!! Failed converting from ISO-8859-1 -> UTF-8 (#{self}). Already the right charset?"
503
- self
504
- end
505
- end
495
+ # Deprecated in favor of #from_xml
496
+ def parse(data)
497
+ from_xml(data)
498
+ end
499
+ deprecate :parse => :from_xml
506
500
  end
507
-
501
+ end
508
502
  end
509
503