representable 0.0.1.alpha1

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 (103) hide show
  1. data/.gitignore +7 -0
  2. data/.gitmodules +3 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +3 -0
  5. data/History.txt +354 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +186 -0
  8. data/Rakefile +10 -0
  9. data/TODO +37 -0
  10. data/VERSION +1 -0
  11. data/examples/amazon.rb +35 -0
  12. data/examples/current_weather.rb +27 -0
  13. data/examples/dashed_elements.rb +20 -0
  14. data/examples/library.rb +40 -0
  15. data/examples/posts.rb +27 -0
  16. data/examples/rails.rb +70 -0
  17. data/examples/twitter.rb +37 -0
  18. data/examples/xml/active_record.xml +70 -0
  19. data/examples/xml/amazon.xml +133 -0
  20. data/examples/xml/current_weather.xml +89 -0
  21. data/examples/xml/dashed_elements.xml +52 -0
  22. data/examples/xml/posts.xml +23 -0
  23. data/examples/xml/twitter.xml +422 -0
  24. data/lib/representable.rb +257 -0
  25. data/lib/representable/definition.rb +109 -0
  26. data/lib/representable/nokogiri_extensions.rb +19 -0
  27. data/lib/representable/references.rb +153 -0
  28. data/lib/representable/version.rb +3 -0
  29. data/lib/representable/xml.rb +79 -0
  30. data/representable.gemspec +29 -0
  31. data/spec/definition_spec.rb +495 -0
  32. data/spec/examples/active_record_spec.rb +41 -0
  33. data/spec/examples/amazon_spec.rb +54 -0
  34. data/spec/examples/current_weather_spec.rb +37 -0
  35. data/spec/examples/dashed_elements_spec.rb +20 -0
  36. data/spec/examples/library_spec.rb +46 -0
  37. data/spec/examples/post_spec.rb +24 -0
  38. data/spec/examples/twitter_spec.rb +32 -0
  39. data/spec/roxml_integration_test.rb +289 -0
  40. data/spec/roxml_spec.rb +372 -0
  41. data/spec/shared_specs.rb +15 -0
  42. data/spec/spec_helper.rb +5 -0
  43. data/spec/support/libxml.rb +3 -0
  44. data/spec/support/nokogiri.rb +3 -0
  45. data/spec/xml/array_spec.rb +36 -0
  46. data/spec/xml/attributes_spec.rb +71 -0
  47. data/spec/xml/encoding_spec.rb +53 -0
  48. data/spec/xml/namespace_spec.rb +270 -0
  49. data/spec/xml/namespaces_spec.rb +67 -0
  50. data/spec/xml/object_spec.rb +82 -0
  51. data/spec/xml/parser_spec.rb +21 -0
  52. data/spec/xml/text_spec.rb +71 -0
  53. data/test/fixtures/book_malformed.xml +5 -0
  54. data/test/fixtures/book_pair.xml +8 -0
  55. data/test/fixtures/book_text_with_attribute.xml +5 -0
  56. data/test/fixtures/book_valid.xml +5 -0
  57. data/test/fixtures/book_with_authors.xml +7 -0
  58. data/test/fixtures/book_with_contributions.xml +9 -0
  59. data/test/fixtures/book_with_contributors.xml +7 -0
  60. data/test/fixtures/book_with_contributors_attrs.xml +7 -0
  61. data/test/fixtures/book_with_default_namespace.xml +9 -0
  62. data/test/fixtures/book_with_depth.xml +6 -0
  63. data/test/fixtures/book_with_octal_pages.xml +4 -0
  64. data/test/fixtures/book_with_publisher.xml +7 -0
  65. data/test/fixtures/book_with_wrapped_attr.xml +3 -0
  66. data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
  67. data/test/fixtures/dictionary_of_attrs.xml +6 -0
  68. data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
  69. data/test/fixtures/dictionary_of_mixeds.xml +4 -0
  70. data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
  71. data/test/fixtures/dictionary_of_names.xml +4 -0
  72. data/test/fixtures/dictionary_of_texts.xml +10 -0
  73. data/test/fixtures/library.xml +30 -0
  74. data/test/fixtures/library_uppercase.xml +30 -0
  75. data/test/fixtures/muffins.xml +3 -0
  76. data/test/fixtures/nameless_ageless_youth.xml +2 -0
  77. data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
  78. data/test/fixtures/node_with_name_conflicts.xml +4 -0
  79. data/test/fixtures/numerology.xml +4 -0
  80. data/test/fixtures/person.xml +1 -0
  81. data/test/fixtures/person_with_guarded_mothers.xml +13 -0
  82. data/test/fixtures/person_with_mothers.xml +10 -0
  83. data/test/mocks/dictionaries.rb +57 -0
  84. data/test/mocks/mocks.rb +279 -0
  85. data/test/roxml_test.rb +58 -0
  86. data/test/support/fixtures.rb +11 -0
  87. data/test/test_helper.rb +6 -0
  88. data/test/unit/definition_test.rb +235 -0
  89. data/test/unit/deprecations_test.rb +24 -0
  90. data/test/unit/to_xml_test.rb +81 -0
  91. data/test/unit/xml_attribute_test.rb +39 -0
  92. data/test/unit/xml_block_test.rb +81 -0
  93. data/test/unit/xml_bool_test.rb +122 -0
  94. data/test/unit/xml_convention_test.rb +150 -0
  95. data/test/unit/xml_hash_test.rb +115 -0
  96. data/test/unit/xml_initialize_test.rb +49 -0
  97. data/test/unit/xml_name_test.rb +141 -0
  98. data/test/unit/xml_namespace_test.rb +31 -0
  99. data/test/unit/xml_object_test.rb +206 -0
  100. data/test/unit/xml_required_test.rb +94 -0
  101. data/test/unit/xml_text_test.rb +71 -0
  102. data/website/index.html +98 -0
  103. metadata +248 -0
@@ -0,0 +1,81 @@
1
+ require_relative './../test_helper'
2
+
3
+ class ArrayWithBlockShorthand
4
+ include ROXML
5
+
6
+ xml_reader :array, :as => [Integer], :from => 'number'
7
+ end
8
+
9
+ class ArrayWithBlock
10
+ include ROXML
11
+
12
+ xml_reader :array, :as => [], :from => 'number' do |arr|
13
+ arr.map(&:to_i).reverse
14
+ end
15
+ end
16
+
17
+ class TestXMLBlocks < ActiveSupport::TestCase
18
+ def test_block_is_applied
19
+ muffins = Muffins.from_xml(fixture(:muffins))
20
+
21
+ assert muffins.count > 0
22
+ assert_equal 0, muffins.count % 13
23
+ end
24
+
25
+ def test_block_is_applied_to_hash
26
+ numerology = Numerology.from_xml(fixture(:numerology))
27
+
28
+ assert !numerology.predictions.keys.empty?
29
+ assert numerology.predictions.keys.all? {|k| k.is_a? Integer }
30
+ assert numerology.predictions.values.all? {|k| k.is_a? String }
31
+ end
32
+
33
+ def test_stacked_blocks_are_applied
34
+ muffins = MuffinsWithStackedBlocks.from_xml(fixture(:muffins))
35
+
36
+ assert muffins.count > 0
37
+ assert_equal 0, muffins.count % 13
38
+ end
39
+
40
+ def test_block_shorthand_applied_properly_to_array
41
+ obj = ArrayWithBlockShorthand.from_xml(%{
42
+ <array_with_block_shorthand>
43
+ <number>1</number>
44
+ <number>2</number>
45
+ <number>3</number>
46
+ </array_with_block_shorthand>
47
+ })
48
+
49
+ assert_equal [1, 2, 3], obj.array
50
+ end
51
+
52
+ def test_block_applied_properly_to_array
53
+ obj = ArrayWithBlock.from_xml(%{
54
+ <array_with_block>
55
+ <number>1</number>
56
+ <number>2</number>
57
+ <number>3</number>
58
+ </array_with_block>
59
+ })
60
+
61
+ assert_equal [3, 2, 1], obj.array
62
+ end
63
+
64
+ def test_block_shorthand_applied_properly_to_empty_array
65
+ obj = ArrayWithBlockShorthand.from_xml(%{
66
+ <array_with_block_shorthand>
67
+ </array_with_block_shorthand>
68
+ })
69
+
70
+ assert_equal [], obj.array
71
+ end
72
+
73
+ def test_block_applied_properly_to_empty_array
74
+ obj = ArrayWithBlock.from_xml(%{
75
+ <array_with_block>
76
+ </array_with_block>
77
+ })
78
+
79
+ assert_equal [], obj.array
80
+ end
81
+ end
@@ -0,0 +1,122 @@
1
+ require_relative './../test_helper'
2
+
3
+ PROC_TRUE = proc {|val| val ? 'TRUE' : 'FALSE'}
4
+ PROC_True = proc {|val| val ? 'True' : 'False'}
5
+ PROC_true = proc {|val| val.to_s}
6
+ PROC_1 = proc {|val| val ? 1 : 0}
7
+
8
+ class XmlBool
9
+ include ROXML
10
+
11
+ xml_name 'xml_bool'
12
+ xml_reader :true_from_TRUE?, :to_xml => PROC_TRUE
13
+ xml_reader :false_from_FALSE?, :from => 'text_for_FALSE', :to_xml => PROC_TRUE
14
+ xml_reader :true_from_one?, :from => '@attr_for_one', :to_xml => PROC_1
15
+ xml_reader :false_from_zero?, :from => 'text_for_zero', :in => 'container', :to_xml => PROC_1
16
+ xml_reader :true_from_True?, :from => '@attr_for_True', :in => 'container', :to_xml => PROC_True
17
+ xml_reader :false_from_False?, :from => 'false_from_cdata_False', :cdata => true, :to_xml => PROC_True
18
+ xml_reader :true_from_true?, :to_xml => PROC_true
19
+ xml_reader :false_from_false?, :to_xml => PROC_true
20
+ xml_reader :missing?
21
+ end
22
+
23
+ class XmlBoolRequired
24
+ include ROXML
25
+
26
+ xml_reader :required?, :required => true
27
+ end
28
+
29
+ class XmlBoolUnexpected
30
+ include ROXML
31
+
32
+ xml_reader :unexpected?
33
+ end
34
+
35
+ class XmlBoolUnexpectedWithBlock
36
+ include ROXML
37
+
38
+ xml_reader :unexpected? do |val|
39
+ val
40
+ end
41
+ end
42
+
43
+ BOOL_XML = %{
44
+ <xml_bool attr_for_one="1">
45
+ <true_from_TRUE>TRUE</true_from_TRUE>
46
+ <text_for_FALSE>FALSE</text_for_FALSE>
47
+ <container attr_for_True="True">
48
+ <text_for_zero>0</text_for_zero>
49
+ </container>
50
+ <false_from_cdata_False><![CDATA[False]]></false_from_cdata_False>
51
+ <true_from_true>true</true_from_true>
52
+ <false_from_false>false</false_from_false>
53
+ </xml_bool>
54
+ }
55
+ PRESENT = %{
56
+ <xml_bool_required>
57
+ <required>true</required>
58
+ </xml_bool_required>
59
+ }
60
+ ABSENT = %{
61
+ <xml_bool_required>
62
+ </xml_bool_required>
63
+ }
64
+ UNEXPECTED_VALUE_XML = %{
65
+ <xml_bool_unexpected>
66
+ <unexpected>Unexpected Value</unexpected>
67
+ </xml_bool_unexpected>
68
+ }
69
+
70
+
71
+ class TestXMLBool < ActiveSupport::TestCase
72
+ def test_bool_results_for_various_inputs
73
+ x = XmlBool.from_xml(BOOL_XML)
74
+ assert_equal true, x.true_from_TRUE?
75
+ assert_equal false, x.false_from_FALSE?
76
+ assert_equal true, x.true_from_one?
77
+ assert_equal false, x.false_from_zero?
78
+ assert_equal true, x.true_from_True?
79
+ assert_equal false, x.false_from_False?
80
+ assert_equal true, x.true_from_true?
81
+ assert_equal false, x.false_from_false?
82
+ end
83
+
84
+ def test_missing_results_in_nil
85
+ x = XmlBool.from_xml(BOOL_XML)
86
+ assert_equal nil, x.missing?
87
+ end
88
+
89
+ def test_unexpected_value_results_in_nil
90
+ x = XmlBoolUnexpected.from_xml(UNEXPECTED_VALUE_XML)
91
+ assert_equal nil, x.unexpected?
92
+ end
93
+
94
+ def test_block_recieves_unexpected_value_rather_than_nil
95
+ x = XmlBoolUnexpectedWithBlock.from_xml(UNEXPECTED_VALUE_XML)
96
+ assert_equal "Unexpected Value", x.unexpected?
97
+ end
98
+
99
+ def test_required_raises_on_missing
100
+ assert_nothing_raised do
101
+ XmlBoolRequired.from_xml(PRESENT)
102
+ end
103
+
104
+ assert_raise ROXML::RequiredElementMissing do
105
+ XmlBoolRequired.from_xml(ABSENT)
106
+ end
107
+ end
108
+
109
+ def test_writable_references_properly_handle_punctuation
110
+ klass = Class.new do
111
+ include ROXML
112
+ xml_accessor :punctuation?
113
+ end
114
+
115
+ instance = klass.from_xml("<xml><punctuation>True</punctuation></xml>")
116
+ assert_equal true, instance.punctuation?
117
+ instance.punctuation = false
118
+ assert_equal false, instance.punctuation?
119
+ end
120
+
121
+ to_xml_test XmlBool => BOOL_XML
122
+ end
@@ -0,0 +1,150 @@
1
+ require_relative './../test_helper'
2
+
3
+ XML_CAMELLOWER = %{
4
+ <bookCase name="Jonas' Books">
5
+ <bookCount>12</bookCount>
6
+ <bigBooks>
7
+ <bigBook>GED</bigBook>
8
+ <bigBook>House of Leaves</bigBook>
9
+ </bigBooks>
10
+ </bookCase>
11
+ }
12
+
13
+ XML_CAMELCASE = %{
14
+ <BookCase Name="Jonas' Books">
15
+ <BookCount>12</BookCount>
16
+ <BigBooks>
17
+ <BigBook>GED</BigBook>
18
+ <BigBook>House of Leaves</BigBook>
19
+ </BigBooks>
20
+ </BookCase>
21
+ }
22
+
23
+ XML_UNDERSCORE = %{
24
+ <book_case name="Jonas' Books">
25
+ <book_count>12</book_count>
26
+ <big_books>
27
+ <big_book>GED</big_book>
28
+ <big_book>House of Leaves</big_book>
29
+ </big_books>
30
+ </book_case>
31
+ }
32
+
33
+ XML_DASHES = %{
34
+ <book-case name="Jonas' Books">
35
+ <book-count>12</book-count>
36
+ <big-books>
37
+ <big-book>GED</big-book>
38
+ <big-book>House of Leaves</big-book>
39
+ </big-books>
40
+ </book-case>
41
+ }
42
+
43
+ XML_UPCASE = %{
44
+ <BOOKCASE NAME="Jonas' Books">
45
+ <BOOKCOUNT>12</BOOKCOUNT>
46
+ <BIGBOOKS>
47
+ <BIGBOOK>GED</BIGBOOK>
48
+ <BIGBOOK>House of Leaves</BIGBOOK>
49
+ </BIGBOOKS>
50
+ </BOOKCASE>
51
+ }
52
+
53
+ class BookCase
54
+ include ROXML
55
+
56
+ xml_reader :book_count, :as => Integer, :required => true
57
+ xml_reader :big_books, :as => [], :required => true
58
+ end
59
+
60
+ class BookCaseCamelCase < BookCase
61
+ xml_convention :camelcase
62
+ end
63
+
64
+ class BookCaseUnderScore < BookCase
65
+ xml_convention :underscore
66
+ end
67
+
68
+ class BookCaseDashes < BookCase
69
+ xml_convention &:dasherize
70
+ end
71
+
72
+ class BookCaseCamelLower < BookCase
73
+ xml_convention {|val| val.camelcase(:lower) }
74
+ end
75
+
76
+ class BookCaseUpCase < BookCase
77
+ xml_convention {|val| val.gsub('_', '').upcase }
78
+ end
79
+
80
+ class InheritedBookCaseCamelCase < BookCaseCamelCase
81
+ end
82
+
83
+ class InheritedBookCaseUpCase < BookCaseUpCase
84
+ end
85
+
86
+ # Same as BookCase. Needed to keep BookCase clean for other tests
87
+ class ParentBookCaseDefault
88
+ include ROXML
89
+
90
+ xml_reader :book_count, :as => Integer, :required => true
91
+ xml_reader :big_books, :as => [], :required => true
92
+ end
93
+
94
+ class InheritedBookCaseDefault < ParentBookCaseDefault
95
+ end
96
+
97
+ class TestXMLConvention < ActiveSupport::TestCase
98
+ # TODO: Test convention applies to xml_name as well...
99
+
100
+ def test_default_convention_is_underscore
101
+ bc = BookCase.from_xml(XML_UNDERSCORE)
102
+ assert_has_book_case_info(bc)
103
+ end
104
+
105
+ [BookCaseUpCase, BookCaseCamelLower, BookCaseDashes, BookCaseUnderScore, BookCaseCamelCase].each do |klass|
106
+ define_method(:"test_xml_convention_#{klass.to_s.underscore}") do
107
+ data = :"XML_#{klass.to_s.sub('BookCase', '').upcase}"
108
+ assert_equal Proc, klass.roxml_naming_convention.class
109
+
110
+ bc = klass.from_xml(Object.const_get(data))
111
+ assert_has_book_case_info(bc)
112
+ end
113
+ end
114
+
115
+ def test_child_should_inherit_convention_if_it_doesnt_declare_one
116
+ [InheritedBookCaseUpCase, InheritedBookCaseCamelCase].each do |klass|
117
+ data = :"XML_#{klass.to_s.sub('InheritedBookCase', '').upcase}"
118
+ assert_equal Proc, klass.roxml_naming_convention.class
119
+
120
+ bc = klass.from_xml(Object.const_get(data))
121
+ assert_has_book_case_info(bc)
122
+ end
123
+ end
124
+
125
+ def test_child_should_inherit_convention_even_if_it_is_added_after_child_declaration
126
+ bc = InheritedBookCaseDefault.from_xml(XML_UNDERSCORE)
127
+ assert_has_book_case_info(bc)
128
+
129
+ ParentBookCaseDefault.class_eval do
130
+ xml_convention :dasherize
131
+ end
132
+
133
+ bc = InheritedBookCaseDefault.from_xml(XML_DASHES)
134
+ assert_has_book_case_info(bc)
135
+ assert_raise ROXML::RequiredElementMissing do
136
+ InheritedBookCaseDefault.from_xml(XML_UNDERSCORE)
137
+ end
138
+ end
139
+
140
+ def test_tag_name_should_get_convention_treatment_as_well
141
+ assert_equal "book-case-dashes", BookCaseDashes.tag_name
142
+ assert_equal "INHERITEDBOOKCASEUPCASE", InheritedBookCaseUpCase.tag_name
143
+ assert_equal "InheritedBookCaseCamelCase", InheritedBookCaseCamelCase.tag_name
144
+ end
145
+
146
+ def assert_has_book_case_info(bc)
147
+ assert_equal 12, bc.book_count
148
+ assert_equal ['GED', 'House of Leaves'], bc.big_books
149
+ end
150
+ end
@@ -0,0 +1,115 @@
1
+ require_relative './../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