yob-roxml 3.1.6

Sign up to get free protection for your applications and to get access to all the features.
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