roxml 1.2 → 2.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +18 -0
- data/README.rdoc +126 -0
- data/Rakefile +100 -0
- data/lib/roxml.rb +479 -485
- data/lib/roxml/extensions/active_support.rb +32 -0
- data/lib/roxml/extensions/array.rb +5 -0
- data/lib/roxml/extensions/array/conversions.rb +25 -0
- data/lib/roxml/extensions/deprecation.rb +28 -0
- data/lib/roxml/extensions/string.rb +21 -0
- data/lib/roxml/extensions/string/conversions.rb +44 -0
- data/lib/roxml/extensions/string/iterators.rb +12 -0
- data/lib/roxml/options.rb +266 -0
- data/lib/roxml/xml.rb +223 -0
- data/lib/roxml/xml/libxml.rb +63 -0
- data/lib/roxml/xml/rexml.rb +64 -0
- data/roxml.gemspec +105 -0
- data/test/fixtures/book_text_with_attribute.xml +1 -1
- data/test/fixtures/book_valid.xml +2 -2
- data/test/fixtures/book_with_authors.xml +7 -0
- data/test/fixtures/book_with_contributors_attrs.xml +7 -0
- data/test/fixtures/book_with_default_namespace.xml +9 -0
- data/test/fixtures/book_with_depth.xml +6 -0
- data/test/fixtures/book_with_wrapped_attr.xml +3 -0
- data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
- data/test/fixtures/dictionary_of_attrs.xml +6 -0
- data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
- data/test/fixtures/dictionary_of_mixeds.xml +4 -0
- data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
- data/test/fixtures/dictionary_of_names.xml +4 -0
- data/test/fixtures/dictionary_of_texts.xml +10 -0
- data/test/fixtures/library.xml +1 -1
- data/test/fixtures/library_uppercase.xml +30 -0
- data/test/fixtures/muffins.xml +3 -0
- data/test/fixtures/nameless_ageless_youth.xml +2 -0
- data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
- data/test/fixtures/node_with_name_conflicts.xml +4 -0
- data/test/fixtures/numerology.xml +4 -0
- data/test/fixtures/person_with_guarded_mothers.xml +13 -0
- data/test/fixtures/person_with_mothers.xml +10 -0
- data/test/mocks/dictionaries.rb +56 -0
- data/test/mocks/mocks.rb +244 -48
- data/test/release/dependencies_test.rb +32 -0
- data/test/test_helper.rb +44 -0
- data/test/unit/inheritance_test.rb +19 -0
- data/test/unit/options_test.rb +102 -0
- data/test/unit/roxml_test.rb +24 -0
- data/test/unit/string_test.rb +11 -0
- data/test/unit/to_xml_test.rb +52 -0
- data/test/unit/xml_attribute_test.rb +39 -0
- data/test/unit/xml_block_test.rb +81 -0
- data/test/unit/xml_bool_test.rb +105 -0
- data/test/unit/xml_construct_test.rb +77 -0
- data/test/unit/xml_hash_test.rb +65 -0
- data/test/unit/xml_initialize_test.rb +50 -0
- data/test/unit/xml_name_test.rb +108 -0
- data/test/unit/xml_namespace_test.rb +38 -0
- data/test/unit/xml_object_test.rb +116 -0
- data/test/unit/xml_required_test.rb +93 -0
- data/test/unit/xml_text_test.rb +68 -0
- metadata +138 -87
- data/MIT-LICENSE.txt +0 -9
- data/README +0 -2
- data/doc/classes/ROXML.html +0 -374
- data/doc/classes/ROXML.src/M000003.html +0 -19
- data/doc/classes/ROXML.src/M000004.html +0 -25
- data/doc/classes/ROXML.src/M000005.html +0 -22
- data/doc/classes/ROXML/ROXML_Class.html +0 -429
- data/doc/classes/ROXML/ROXML_Class.src/M000006.html +0 -27
- data/doc/classes/ROXML/ROXML_Class.src/M000007.html +0 -18
- data/doc/classes/ROXML/ROXML_Class.src/M000008.html +0 -19
- data/doc/classes/ROXML/ROXML_Class.src/M000009.html +0 -25
- data/doc/classes/ROXML/ROXML_Class.src/M000010.html +0 -24
- data/doc/classes/ROXML/ROXML_Class.src/M000011.html +0 -18
- data/doc/classes/ROXML/ROXML_Class.src/M000012.html +0 -18
- data/doc/classes/ROXML/XMLAttributeRef.html +0 -175
- data/doc/classes/ROXML/XMLAttributeRef.src/M000015.html +0 -19
- data/doc/classes/ROXML/XMLAttributeRef.src/M000016.html +0 -19
- data/doc/classes/ROXML/XMLObjectRef.html +0 -175
- data/doc/classes/ROXML/XMLObjectRef.src/M000013.html +0 -26
- data/doc/classes/ROXML/XMLObjectRef.src/M000014.html +0 -32
- data/doc/classes/ROXML/XMLRef.html +0 -166
- data/doc/classes/ROXML/XMLRef.src/M000017.html +0 -21
- data/doc/classes/ROXML/XMLTextRef.html +0 -198
- data/doc/classes/ROXML/XMLTextRef.src/M000018.html +0 -28
- data/doc/classes/ROXML/XMLTextRef.src/M000019.html +0 -34
- data/doc/classes/String.html +0 -165
- data/doc/classes/String.src/M000001.html +0 -23
- data/doc/classes/String.src/M000002.html +0 -23
- data/doc/created.rid +0 -1
- data/doc/files/lib/roxml_rb.html +0 -234
- data/doc/fr_class_index.html +0 -33
- data/doc/fr_file_index.html +0 -27
- data/doc/fr_method_index.html +0 -45
- data/doc/index.html +0 -24
- data/doc/rdoc-style.css +0 -208
- data/test/fixture_helper.rb +0 -5
- 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
|
-
|
2
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
121
|
-
|
122
|
-
#
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
-
|
224
|
-
instance
|
78
|
+
end
|
225
79
|
end
|
80
|
+
end
|
226
81
|
end
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
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
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
436
|
+
end
|
463
437
|
end
|
464
438
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
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
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
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
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
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
|
|