yob-roxml 3.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. data/.gitignore +7 -0
  2. data/.gitmodules +3 -0
  3. data/History.txt +354 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +195 -0
  6. data/Rakefile +117 -0
  7. data/TODO +37 -0
  8. data/VERSION +1 -0
  9. data/examples/amazon.rb +35 -0
  10. data/examples/current_weather.rb +27 -0
  11. data/examples/dashed_elements.rb +20 -0
  12. data/examples/library.rb +40 -0
  13. data/examples/posts.rb +27 -0
  14. data/examples/rails.rb +70 -0
  15. data/examples/twitter.rb +37 -0
  16. data/examples/xml/active_record.xml +70 -0
  17. data/examples/xml/amazon.xml +133 -0
  18. data/examples/xml/current_weather.xml +89 -0
  19. data/examples/xml/dashed_elements.xml +52 -0
  20. data/examples/xml/posts.xml +23 -0
  21. data/examples/xml/twitter.xml +422 -0
  22. data/lib/roxml.rb +556 -0
  23. data/lib/roxml/definition.rb +238 -0
  24. data/lib/roxml/hash_definition.rb +25 -0
  25. data/lib/roxml/xml.rb +40 -0
  26. data/lib/roxml/xml/parsers/libxml.rb +85 -0
  27. data/lib/roxml/xml/parsers/nokogiri.rb +82 -0
  28. data/lib/roxml/xml/references.rb +322 -0
  29. data/roxml.gemspec +206 -0
  30. data/spec/definition_spec.rb +494 -0
  31. data/spec/examples/active_record_spec.rb +40 -0
  32. data/spec/examples/amazon_spec.rb +54 -0
  33. data/spec/examples/current_weather_spec.rb +37 -0
  34. data/spec/examples/dashed_elements_spec.rb +20 -0
  35. data/spec/examples/library_spec.rb +46 -0
  36. data/spec/examples/post_spec.rb +24 -0
  37. data/spec/examples/twitter_spec.rb +32 -0
  38. data/spec/roxml_spec.rb +372 -0
  39. data/spec/shared_specs.rb +15 -0
  40. data/spec/spec.opts +1 -0
  41. data/spec/spec_helper.rb +14 -0
  42. data/spec/support/libxml.rb +3 -0
  43. data/spec/support/nokogiri.rb +3 -0
  44. data/spec/xml/array_spec.rb +36 -0
  45. data/spec/xml/attributes_spec.rb +71 -0
  46. data/spec/xml/encoding_spec.rb +52 -0
  47. data/spec/xml/namespace_spec.rb +270 -0
  48. data/spec/xml/namespaces_spec.rb +67 -0
  49. data/spec/xml/object_spec.rb +82 -0
  50. data/spec/xml/parser_spec.rb +21 -0
  51. data/spec/xml/text_spec.rb +71 -0
  52. data/test/fixtures/book_malformed.xml +5 -0
  53. data/test/fixtures/book_pair.xml +8 -0
  54. data/test/fixtures/book_text_with_attribute.xml +5 -0
  55. data/test/fixtures/book_valid.xml +5 -0
  56. data/test/fixtures/book_with_authors.xml +7 -0
  57. data/test/fixtures/book_with_contributions.xml +9 -0
  58. data/test/fixtures/book_with_contributors.xml +7 -0
  59. data/test/fixtures/book_with_contributors_attrs.xml +7 -0
  60. data/test/fixtures/book_with_default_namespace.xml +9 -0
  61. data/test/fixtures/book_with_depth.xml +6 -0
  62. data/test/fixtures/book_with_octal_pages.xml +4 -0
  63. data/test/fixtures/book_with_publisher.xml +7 -0
  64. data/test/fixtures/book_with_wrapped_attr.xml +3 -0
  65. data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
  66. data/test/fixtures/dictionary_of_attrs.xml +6 -0
  67. data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
  68. data/test/fixtures/dictionary_of_mixeds.xml +4 -0
  69. data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
  70. data/test/fixtures/dictionary_of_names.xml +4 -0
  71. data/test/fixtures/dictionary_of_texts.xml +10 -0
  72. data/test/fixtures/library.xml +30 -0
  73. data/test/fixtures/library_uppercase.xml +30 -0
  74. data/test/fixtures/muffins.xml +3 -0
  75. data/test/fixtures/nameless_ageless_youth.xml +2 -0
  76. data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
  77. data/test/fixtures/node_with_name_conflicts.xml +4 -0
  78. data/test/fixtures/numerology.xml +4 -0
  79. data/test/fixtures/person.xml +1 -0
  80. data/test/fixtures/person_with_guarded_mothers.xml +13 -0
  81. data/test/fixtures/person_with_mothers.xml +10 -0
  82. data/test/load_test.rb +6 -0
  83. data/test/mocks/dictionaries.rb +57 -0
  84. data/test/mocks/mocks.rb +279 -0
  85. data/test/support/fixtures.rb +11 -0
  86. data/test/test_helper.rb +34 -0
  87. data/test/unit/definition_test.rb +235 -0
  88. data/test/unit/deprecations_test.rb +24 -0
  89. data/test/unit/to_xml_test.rb +81 -0
  90. data/test/unit/xml_attribute_test.rb +39 -0
  91. data/test/unit/xml_block_test.rb +81 -0
  92. data/test/unit/xml_bool_test.rb +122 -0
  93. data/test/unit/xml_convention_test.rb +150 -0
  94. data/test/unit/xml_hash_test.rb +115 -0
  95. data/test/unit/xml_initialize_test.rb +49 -0
  96. data/test/unit/xml_name_test.rb +141 -0
  97. data/test/unit/xml_namespace_test.rb +31 -0
  98. data/test/unit/xml_object_test.rb +205 -0
  99. data/test/unit/xml_required_test.rb +94 -0
  100. data/test/unit/xml_text_test.rb +71 -0
  101. data/website/index.html +98 -0
  102. metadata +300 -0
@@ -0,0 +1,115 @@
1
+ require 'test/test_helper'
2
+
3
+ class BookWithContributorHash
4
+ include ROXML
5
+
6
+ xml_reader :contributors, :as => {:key => '@role',
7
+ :value => 'name'}
8
+ end
9
+
10
+ class TestXMLHash < ActiveSupport::TestCase
11
+ def setup
12
+ @contents = {'quaquaversally' => 'adjective: (of a geological formation) sloping downward from the center in all directions.',
13
+ 'tergiversate' => 'To use evasions or ambiguities; equivocate.'}
14
+ end
15
+
16
+ def test_hash_preserves_data
17
+ b = BookWithContributorHash.from_xml(%{
18
+ <book isbn="0974514055">
19
+ <contributor role="author"><name>David Thomas</name></contributor>
20
+ <contributor role="supporting author"><name>Andrew Hunt</name></contributor>
21
+ <contributor role="supporting author"><name>Chad Fowler</name></contributor>
22
+ </book>
23
+ })
24
+ assert_equal({'author' => 'David Thomas', 'supporting author' => ['Andrew Hunt', 'Chad Fowler']},
25
+ b.contributors)
26
+ end
27
+
28
+ def test_hash_with_object_key_fails
29
+ assert_raise ArgumentError do
30
+ Class.new do
31
+ include ROXML
32
+
33
+ xml_reader :object_key_to_text, :as => {:key => BookWithContributorHash,
34
+ :value => 'text_node'}
35
+ end
36
+ end
37
+ end
38
+
39
+ def test_hash_with_object_value_fails
40
+ assert_raise ArgumentError do
41
+ Class.new do
42
+ include ROXML
43
+
44
+ xml_reader :key_to_object_value, :as => {:key => '@text_node',
45
+ :value => BookWithContributorHash}
46
+ end
47
+ end
48
+ end
49
+
50
+ def test_attrs_hash
51
+ dict = DictionaryOfAttrs.from_xml(fixture(:dictionary_of_attrs))
52
+ assert_equal Hash, dict.definitions.class
53
+ assert_equal @contents, dict.definitions
54
+ end
55
+
56
+ def test_text_hash
57
+ dict = DictionaryOfTexts.from_xml(fixture(:dictionary_of_texts))
58
+ assert_equal Hash, dict.definitions.class
59
+ assert_equal @contents, dict.definitions
60
+ end
61
+
62
+ def test_mixed_content_hash
63
+ dict = DictionaryOfMixeds.from_xml(fixture(:dictionary_of_mixeds))
64
+ assert_equal Hash, dict.definitions.class
65
+ assert_equal @contents, dict.definitions
66
+ end
67
+
68
+ def test_name_hash
69
+ dict = DictionaryOfNames.from_xml(fixture(:dictionary_of_names))
70
+ assert_equal Hash, dict.definitions.class
71
+ assert_equal @contents, dict.definitions
72
+ end
73
+
74
+ def test_guarded_name_hash
75
+ dict = DictionaryOfGuardedNames.from_xml(fixture(:dictionary_of_guarded_names))
76
+ assert_equal Hash, dict.definitions.class
77
+ assert_equal @contents, dict.definitions
78
+ end
79
+
80
+ def test_text_name_clashes
81
+ dict = DictionaryOfNameClashes.from_xml(fixture(:dictionary_of_name_clashes))
82
+ assert_equal Hash, dict.definitions.class
83
+ assert_equal @contents, dict.definitions
84
+ end
85
+
86
+ def test_attr_name_clashes
87
+ dict = DictionaryOfAttrNameClashes.from_xml(fixture(:dictionary_of_attr_name_clashes))
88
+ assert_equal Hash, dict.definitions.class
89
+ assert_equal @contents, dict.definitions
90
+ end
91
+
92
+ def test_it_should_gracefully_handle_empty_hash
93
+ dict = Class.new do
94
+ include ROXML
95
+
96
+ xml_reader :missing_hash, :as => {:key => :name, :value => :content}, :in => 'EmptyDictionary'
97
+ end
98
+
99
+ assert_equal({}, dict.from_xml(%{
100
+ <dict>
101
+ <EmptyDictionary>
102
+ </EmptyDictionary>
103
+ </dict>
104
+ }).missing_hash)
105
+ end
106
+
107
+ def test_as_hash_of_as_type_not_deprecated
108
+ assert_not_deprecated do
109
+ opts = ROXML::Definition.new(:name, :as => {:key => :name, :value => {:from => 'value', :as => OctalInteger}})
110
+ assert opts.hash?
111
+ assert_equal OctalInteger, opts.hash.value.sought_type
112
+ assert_equal 'value', opts.hash.value.name
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,49 @@
1
+ require 'test/test_helper'
2
+
3
+ class InheritedBookWithDepth < Book
4
+ xml_reader :depth, :as => Measurement
5
+ end
6
+
7
+ class BookWithXmlInitialize < BookWithDepth
8
+ attr_reader :created_at, :creator
9
+
10
+ def initialize(created_at, creator = "Unknown")
11
+ @created_at = created_at
12
+ @creator = creator
13
+ end
14
+ end
15
+
16
+ class TestXMLInitialize < ActiveSupport::TestCase
17
+ def test_initialize_is_run
18
+ m = Measurement.from_xml('<measurement units="hundredths-meters">1130</measurement>')
19
+ assert_equal 11.3, m.value
20
+ assert_equal 'meters', m.units
21
+ end
22
+
23
+ def test_initialize_is_run_for_nested_type
24
+ b = BookWithDepth.from_xml(fixture(:book_with_depth))
25
+ assert_equal Measurement.new(11.3, 'meters'), b.depth
26
+ end
27
+
28
+ def test_initialize_is_run_for_nested_type_with_inheritance
29
+ b = InheritedBookWithDepth.from_xml(fixture(:book_with_depth))
30
+ assert_equal Measurement.new(11.3, 'meters'), b.depth
31
+ end
32
+
33
+ def test_initialize_fails_on_missing_required_arg
34
+ assert_raise ArgumentError do
35
+ BookWithXmlInitialize.from_xml(fixture(:book_with_depth))
36
+ end
37
+ end
38
+
39
+ def test_initialize_with_extra_args
40
+ now = Time.now
41
+ b = BookWithXmlInitialize.from_xml(fixture(:book_with_depth), now)
42
+ assert_equal now, b.created_at
43
+ assert_equal "Unknown", b.creator
44
+
45
+ b = BookWithXmlInitialize.from_xml(fixture(:book_with_depth), Time.now, "Joe Librarian")
46
+ assert now < b.created_at
47
+ assert_equal "Joe Librarian", b.creator
48
+ end
49
+ end
@@ -0,0 +1,141 @@
1
+ require 'test/test_helper'
2
+
3
+ # Parent | Child
4
+ # :from | no :from |
5
+ # -------------------|--------------
6
+ # :from | xml_name | xml_name-d
7
+ # value | value |
8
+ # -------------------|--------------
9
+ # :from | parent's |
10
+ # value | accessor | un-xml_name-d
11
+ # | name |
12
+
13
+ class Child
14
+ include ROXML
15
+ end
16
+
17
+ class NamedChild
18
+ include ROXML
19
+
20
+ xml_name :xml_name_of_child
21
+ end
22
+
23
+ class ParentOfNamedChild
24
+ include ROXML
25
+
26
+ xml_name :parent
27
+ xml_accessor :child_accessor_name, :as => NamedChild
28
+ end
29
+
30
+ class ParentOfNamedChildWithFrom
31
+ include ROXML
32
+
33
+ xml_name :parent
34
+ xml_accessor :child_accessor_name, :as => NamedChild, :from => 'child_from_name'
35
+ end
36
+
37
+ class ParentOfUnnamedChild
38
+ include ROXML
39
+
40
+ xml_name :parent
41
+ xml_accessor :child_accessor_name, :as => Child
42
+ end
43
+
44
+ class ParentOfUnnamedChildWithFrom
45
+ include ROXML
46
+
47
+ xml_name :parent
48
+ xml_accessor :child_accessor_name,:as => Child, :from => 'child_from_name'
49
+ end
50
+
51
+ class TestXMLName < ActiveSupport::TestCase
52
+ def test_from_always_dominates_attribute_name_xml_name_or_not
53
+ parent = ParentOfNamedChildWithFrom.new
54
+ parent.child_accessor_name = Child.new
55
+
56
+ assert_equal "<parent><child_from_name/></parent>", parent.to_xml.to_s.gsub(/[\n ]/, '')
57
+
58
+ parent = ParentOfUnnamedChildWithFrom.new
59
+ parent.child_accessor_name = Child.new
60
+
61
+ assert_equal "<parent><child_from_name/></parent>", parent.to_xml.to_s.gsub(/[\n ]/, '')
62
+ end
63
+
64
+ def test_attribute_name_comes_from_the_xml_name_value_if_present
65
+ parent = ParentOfNamedChild.new
66
+ parent.child_accessor_name = Child.new
67
+
68
+ assert_equal "<parent><xml_name_of_child/></parent>", parent.to_xml.to_s.gsub(/[\n ]/, '')
69
+ end
70
+
71
+ def test_attribute_name_comes_from_parent_accessor_by_default
72
+ parent = ParentOfUnnamedChild.new
73
+ parent.child_accessor_name = Child.new
74
+
75
+ assert_equal "<parent><child_accessor_name/></parent>", parent.to_xml.to_s.gsub(/[\n ]/, '')
76
+ end
77
+
78
+ def test_it_should_be_inherited
79
+ class_with_inherited_name = Class.new(ParentOfNamedChild)
80
+ assert_equal :parent, class_with_inherited_name.tag_name
81
+ end
82
+
83
+ def test_it_should_be_inherited_over_multiple_levels
84
+ class_with_inherited_name = Class.new(Class.new(ParentOfNamedChild))
85
+ assert_equal :parent, class_with_inherited_name.tag_name
86
+ end
87
+
88
+ def test_named_books_picked_up
89
+ named = Library.from_xml(fixture(:library))
90
+ assert named.books
91
+ assert_equal :book, named.books.first.class.tag_name
92
+ end
93
+
94
+ def test_nameless_books_missing
95
+ nameless = LibraryWithBooksOfUnderivableName.from_xml(fixture(:library))
96
+ assert nameless.novels.empty?
97
+ end
98
+
99
+ def test_tag_name
100
+ assert_equal :dictionary, DictionaryOfTexts.tag_name
101
+
102
+ dict = DictionaryOfTexts.from_xml(fixture(:dictionary_of_texts))
103
+
104
+ assert_equal :dictionary, dict.class.tag_name
105
+ end
106
+
107
+ def test_roxml_attrs
108
+ assert_equal 'definition', DictionaryOfTexts.roxml_attrs.first.name
109
+ assert_equal 'word', DictionaryOfTexts.roxml_attrs.first.hash.key.name
110
+ assert_equal 'meaning', DictionaryOfTexts.roxml_attrs.first.hash.value.name
111
+ end
112
+
113
+ def test_xml_name_should_not_be_conventionalized_if_explicitly_set
114
+ reference = ROXML::XMLTextRef.new(ROXML::Definition.new(:name, :from => 'georss:name'), WrapModule::InstanceStandin.new)
115
+ assert_equal "georss:name", reference.name
116
+ end
117
+
118
+ def test_xml_name_not_screwed_up_by_xml_convention
119
+ reference = ROXML::XMLTextRef.new(ROXML::Definition.new(:name, :in => './'), WrapModule::InstanceStandin.new)
120
+ assert_equal "name value", reference.value_in(ROXML::XML.parse_string(%(
121
+ <Wrapper>
122
+ <MoreStuff>
123
+ <DeepWrapper>
124
+ <Name>name value</Name>
125
+ </DeepWrapper>
126
+ </MoreStuff>
127
+ </Wrapper>
128
+ )).root)
129
+ end
130
+ end
131
+
132
+ module WrapModule
133
+ class BaseClass
134
+ include ROXML
135
+ xml_convention :camelcase
136
+ end
137
+
138
+ class InstanceStandin < BaseClass
139
+ xml_reader :name, :in => './'
140
+ end
141
+ end
@@ -0,0 +1,31 @@
1
+ require 'test/test_helper'
2
+
3
+ class TestDefaultXMLNamespaces < ActiveSupport::TestCase
4
+ def setup
5
+ @book = BookWithContributions.from_xml(fixture(:book_with_default_namespace))
6
+ end
7
+
8
+ def test_default_namespace_doesnt_interfere_with_normal_operation
9
+ assert_equal("Programming Ruby - 2nd Edition", @book.title)
10
+ end
11
+
12
+ def test_default_namespace_is_applied_to_in_element
13
+ expected_authors = ["David Thomas","Andrew Hunt","Chad Fowler"]
14
+ assert !@book.contributions.empty?
15
+ @book.contributions.each do |contributor|
16
+ assert expected_authors.include?(contributor.name)
17
+ end
18
+ end
19
+
20
+ def test_default_namespace_on_root_node_should_be_found
21
+ require 'libxml'
22
+ xml = LibXML::XML::Parser.string(
23
+ '<container xmlns="http://defaultnamespace.org"><node>Yeah, content</node><node><subnode>Another</subnode></node></container>').parse
24
+
25
+ assert_equal nil, xml.find_first('node')
26
+ assert_equal "Yeah, content", xml.find_first('ns:node', 'ns:http://defaultnamespace.org').content
27
+ assert_equal nil, xml.find_first('ns:node/subnode', 'ns:http://defaultnamespace.org')
28
+ assert_equal "Another", xml.find_first('ns:node/ns:subnode', 'ns:http://defaultnamespace.org').content
29
+ rescue LoadError
30
+ end
31
+ end
@@ -0,0 +1,205 @@
1
+ require 'test/test_helper'
2
+
3
+ class EmptyCart
4
+ include ROXML
5
+
6
+ xml_reader :id
7
+
8
+ def empty?
9
+ true
10
+ end
11
+ end
12
+
13
+ class CartHolder
14
+ include ROXML
15
+
16
+ xml_reader :cart, :as => EmptyCart, :required => true
17
+ end
18
+
19
+ class TestXMLObject < ActiveSupport::TestCase
20
+ # Test book with text and attribute
21
+ def test_book_author_text_attribute
22
+ book = BookWithAuthorTextAttribute.from_xml(fixture(:book_text_with_attribute))
23
+ assert_equal("primary",book.author.role)
24
+ assert_equal("David Thomas",book.author.text)
25
+ end
26
+
27
+ # Test XML object containing list of other XML objects (one-to-many)
28
+ # In this case, book with contibutions
29
+ def test_one_to_many_with_container
30
+ expected_authors = ["David Thomas","Andrew Hunt","Chad Fowler"]
31
+ book = BookWithContributions.from_xml(fixture(:book_with_contributions))
32
+ assert_equal("Programming Ruby - 2nd Edition", book.title)
33
+ book.contributions.each do |contributor|
34
+ assert expected_authors.include?(contributor.name)
35
+ end
36
+ end
37
+
38
+ # Test XML object containing 1-n other XML objects without container (one-to-many)
39
+ # In this case, book with contibutions
40
+ def test_one_to_many_without_container
41
+ expected_contributors = ["David Thomas","Andrew Hunt","Chad Fowler"]
42
+ book = BookWithContributors.from_xml(fixture(:book_with_contributors))
43
+ assert_equal("Programming Ruby - 2nd Edition", book.title)
44
+ book.contributors.each do |contributor|
45
+ assert(expected_contributors.include?(contributor.name))
46
+ end
47
+ end
48
+
49
+ # when building objects that contain arrays, the second object seems to
50
+ # inherit data from the first
51
+ #
52
+ def test_one_to_many_without_container_sequence
53
+ contrib = WriteableContributor.new
54
+ contrib.name = "David Thomas"
55
+
56
+ book_one = WriteableBookWithContributors.new
57
+ book_one.isbn = "9781843549161"
58
+ book_one.title = "Anathem"
59
+ book_one.description = "A new title from Neal Stephenson"
60
+ book_one.contributors = [contrib]
61
+
62
+ # this book should be completely empty
63
+ book_two = WriteableBookWithContributors.new
64
+
65
+ assert_equal(nil, book_two.isbn)
66
+ assert_equal(nil, book_two.title)
67
+ assert_equal(nil, book_two.description)
68
+ assert_equal(nil, book_two.contributors)
69
+ end
70
+
71
+ # Test XML object containing one other XML object (one-to-one)
72
+ # In this case, book with publisher
73
+ def test_one_to_one
74
+ book = BookWithPublisher.from_xml(fixture(:book_with_publisher))
75
+ assert_equal("Programming Ruby - 2nd Edition", book.title)
76
+ assert_equal("Pragmatic Bookshelf", book.publisher.name)
77
+ end
78
+
79
+ # Test XML object containing type of self (self-reference)
80
+ def test_self_reference
81
+ book = BookPair.from_xml(fixture(:book_pair))
82
+ assert_equal("Programming Ruby - 2nd Edition", book.title)
83
+ assert_equal("Agile Web Development with Rails", book.book.title)
84
+ end
85
+
86
+ # Test three-level composition (one-to-many-to-many)
87
+ def test_one_to_many_to_many
88
+ expected_contributors = ["David Thomas","Andrew Hunt","Chad Fowler", "David Heinemeier Hansson"]
89
+ expected_books = ["Programming Ruby - 2nd Edition", "Agile Web Development with Rails"]
90
+ library = Library.from_xml(fixture(:library))
91
+ assert_equal("Ruby library", library.name)
92
+ assert !library.books.empty?
93
+ library.books.each do |book|
94
+ assert expected_books.include?(book.title)
95
+ book.contributions.each do |contributor|
96
+ assert(expected_contributors.include?(contributor.name))
97
+ end
98
+ end
99
+ end
100
+
101
+ def test_without_needed_from
102
+ assert_equal [], UppercaseLibrary.from_xml(fixture(:library)).books
103
+ assert_equal [], Library.from_xml(fixture(:library_uppercase)).books
104
+ end
105
+
106
+ def test_with_needed_from
107
+ assert Library.from_xml(fixture(:library)).books
108
+ assert UppercaseLibrary.from_xml(fixture(:library_uppercase)).books
109
+ end
110
+
111
+ def test_with_recursion
112
+ p = PersonWithMother.from_xml(fixture(:person_with_mothers))
113
+ assert_equal 'Ben Franklin', p.name
114
+ assert_equal 'Abiah Folger', p.mother.name
115
+ assert_equal 'Madeup Mother', p.mother.mother.name
116
+ assert_equal nil, p.mother.mother.mother
117
+ end
118
+
119
+ class Node
120
+ include ROXML
121
+
122
+ xml_reader :name, :from => 'node_name'
123
+ xml_reader :nodes, :as => [Node]
124
+ end
125
+
126
+ class Taxonomy
127
+ include ROXML
128
+
129
+ xml_reader :name, :from => 'taxonomy_name'
130
+ xml_reader :nodes, :as => [Node]
131
+ end
132
+
133
+ class Taxonomies
134
+ include ROXML
135
+ xml_reader :taxonomies, :as => [Taxonomy]
136
+ end
137
+
138
+ def test_more_recursion
139
+ taxonomies = Taxonomies.from_xml(<<HERE)
140
+ <?xml version="1.0" encoding="UTF-8" ?>
141
+ <!DOCTYPE taxonomies SYSTEM "taxonomy.dtd">
142
+ <taxonomies>
143
+ <taxonomy>
144
+ <taxonomy_name>World</taxonomy_name>
145
+ <node node_id="2" content_object_id="82534" object_type_id="2">
146
+ <node_name lang_iso="eng">Africa</node_name>
147
+ <node node_id="331" content_object_id="11" object_type_id="4">
148
+ <node_name lang_iso="eng">Algeria</node_name>
149
+ <node node_id="7271" content_object_id="117629" object_type_id="8">
150
+ <node_name lang_iso="eng">Algiers</node_name>
151
+ </node>
152
+ <node node_id="7272" content_object_id="117630" object_type_id="8">
153
+ <node_name lang_iso="eng">Gharda&#239;a</node_name>
154
+ </node>
155
+ <node node_id="7871" content_object_id="1000713999" object_type_id="8">
156
+ <node_name lang_iso="eng">El Oued</node_name>
157
+ </node>
158
+ <node node_id="7872" content_object_id="1000714008" object_type_id="8">
159
+ <node_name lang_iso="eng">Timimoun</node_name>
160
+ </node>
161
+ <node node_id="8903" content_object_id="1000565565" object_type_id="8">
162
+ <node_name lang_iso="eng">Annaba</node_name>
163
+ </node>
164
+ </node>
165
+ </node>
166
+ </taxonomy>
167
+ </taxonomies>
168
+ HERE
169
+ assert_equal 1, taxonomies.taxonomies.size
170
+ assert_equal 'World', taxonomies.taxonomies.first.name
171
+ node = taxonomies.taxonomies.first.nodes.first
172
+ assert_equal 'Africa', node.name
173
+ assert_equal 'Algeria', node.nodes.first.name
174
+ assert_equal ['Algiers', "Gharda\303\257a", 'El Oued', 'Timimoun', 'Annaba'],
175
+ node.nodes.first.nodes.map(&:name)
176
+ end
177
+
178
+ def test_with_guarded_recursion
179
+ p = PersonWithGuardedMother.from_xml(fixture(:person_with_guarded_mothers))
180
+ assert_equal 'Ben "Benji" Franklin', p.name
181
+ assert_equal 'Abiah \'Abby\' Folger', p.mother.name
182
+ assert_equal 'Madeup Mother < the third >', p.mother.mother.name
183
+ assert_equal nil, p.mother.mother.mother
184
+ end
185
+
186
+ def test_recursive_with_default_initialization
187
+ p = PersonWithMotherOrMissing.from_xml(fixture(:person_with_mothers))
188
+ assert_equal 'Unknown', p.mother.mother.mother.name
189
+ assert_equal Person, p.mother.mother.mother.class
190
+ end
191
+
192
+ def test_defining_empty_on_object_doesnt_cause_it_to_be_seen_as_absent
193
+ # absent means defaulting, failing required
194
+
195
+ holder = CartHolder.from_xml(%{
196
+ <cartholder>
197
+ <cart>
198
+ <id>111111</id>
199
+ </cart>
200
+ </cartholder>
201
+ })
202
+
203
+ assert_equal "111111", holder.cart.id
204
+ end
205
+ end