xmlable 0.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +245 -0
  4. data/lib/xmlable/attribute.rb +16 -0
  5. data/lib/xmlable/builder.rb +189 -0
  6. data/lib/xmlable/document.rb +16 -0
  7. data/lib/xmlable/element.rb +19 -0
  8. data/lib/xmlable/exports/base.rb +78 -0
  9. data/lib/xmlable/exports/json_exporter.rb +208 -0
  10. data/lib/xmlable/exports/xml_exporter.rb +179 -0
  11. data/lib/xmlable/exports.rb +11 -0
  12. data/lib/xmlable/handlers/attribute.rb +41 -0
  13. data/lib/xmlable/handlers/attribute_none.rb +10 -0
  14. data/lib/xmlable/handlers/base.rb +103 -0
  15. data/lib/xmlable/handlers/document.rb +33 -0
  16. data/lib/xmlable/handlers/element.rb +89 -0
  17. data/lib/xmlable/handlers/element_none.rb +10 -0
  18. data/lib/xmlable/handlers/elements.rb +15 -0
  19. data/lib/xmlable/handlers/mixins/described.rb +19 -0
  20. data/lib/xmlable/handlers/mixins/namespace.rb +40 -0
  21. data/lib/xmlable/handlers/mixins/tag.rb +24 -0
  22. data/lib/xmlable/handlers/namespace.rb +26 -0
  23. data/lib/xmlable/handlers/root.rb +9 -0
  24. data/lib/xmlable/handlers/root_none.rb +10 -0
  25. data/lib/xmlable/handlers/storage.rb +104 -0
  26. data/lib/xmlable/handlers.rb +23 -0
  27. data/lib/xmlable/mixins/attributes_storage.rb +195 -0
  28. data/lib/xmlable/mixins/bare_value.rb +41 -0
  29. data/lib/xmlable/mixins/castable.rb +93 -0
  30. data/lib/xmlable/mixins/container.rb +71 -0
  31. data/lib/xmlable/mixins/content_storage.rb +138 -0
  32. data/lib/xmlable/mixins/document_storage.rb +136 -0
  33. data/lib/xmlable/mixins/elements_storage.rb +219 -0
  34. data/lib/xmlable/mixins/export.rb +45 -0
  35. data/lib/xmlable/mixins/instantiable.rb +47 -0
  36. data/lib/xmlable/mixins/namespace_definitions_storage.rb +105 -0
  37. data/lib/xmlable/mixins/object.rb +95 -0
  38. data/lib/xmlable/mixins/options_storage.rb +39 -0
  39. data/lib/xmlable/mixins/root_storage.rb +162 -0
  40. data/lib/xmlable/mixins/standalone_attribute.rb +34 -0
  41. data/lib/xmlable/mixins/standalone_element.rb +47 -0
  42. data/lib/xmlable/mixins/value_storage.rb +84 -0
  43. data/lib/xmlable/mixins/wrapper.rb +37 -0
  44. data/lib/xmlable/mixins.rb +25 -0
  45. data/lib/xmlable/options/nokogiri_export.rb +19 -0
  46. data/lib/xmlable/options/storage.rb +97 -0
  47. data/lib/xmlable/options.rb +9 -0
  48. data/lib/xmlable/types.rb +31 -0
  49. data/lib/xmlable/version.rb +4 -0
  50. data/lib/xmlable.rb +49 -0
  51. metadata +149 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8dac25c2580eed75d306634e799b8dc03d104da5
4
+ data.tar.gz: f764b71e734d47c4505e89c4b065ac5ba03b743a
5
+ SHA512:
6
+ metadata.gz: 27a75c87ddd0b08449cd2e0e1da18885aec9b3dbfe113b1cfc6236d1e8e3afd23676aafabcae610eeac48b2525811ab1723c10c3b35e763c446b71e6bee8a4f3
7
+ data.tar.gz: 0203c91ea3564b0ee5d590bbc5f6ad38785e3943046a30c88e0c131105ca2e94b942bbd0ae6eaa5d25ecd8e854aef074ffbe33942c04952d91522ef18208ceed
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Oleg Yashchuk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,245 @@
1
+ # XMLable
2
+ [![Build Status](https://travis-ci.org/zoer/lexer.svg)](https://travis-ci.org/zoer/lexer)
3
+ [![Code Climate](https://codeclimate.com/github/zoer/xmlable/badges/gpa.svg)](https://codeclimate.com/github/zoer/xmlable)
4
+ [![Version Eye](https://www.versioneye.com/ruby/xmlable/badge.png)](https://www.versioneye.com/ruby/xmlable)
5
+ [![Inline docs](http://inch-ci.org/github/zoer/xmlable.png)](http://inch-ci.org/github/zoer/xmlable)
6
+ [![Gem Version](https://badge.fury.io/rb/xmlable.svg)](http://badge.fury.io/rb/xmlable)
7
+
8
+ XMLable provides an ability to convert XML to Ruby object and back.
9
+
10
+ This probject is in active development and isn't ready for production yet.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'xmlable'
18
+ ```
19
+
20
+ ## Examples
21
+
22
+ ### Basic usage
23
+ ```ruby
24
+ # Describe XML structure
25
+ class Catalog
26
+ include XMLable::Document
27
+
28
+ root :catalog do
29
+ attribute :date, Date
30
+ element :size, Integer
31
+ elements :items, tag: 'item' do
32
+ attribute :position, Integer
33
+ element :title
34
+ element :desc, tag: 'description'
35
+ element :amount, Integer
36
+ end
37
+ end
38
+ end
39
+
40
+ xml = <<-XML
41
+ <?xml version="1.0"?>
42
+ <catalog date="2014-12-20">
43
+ <size>10</size>
44
+ <item position="1">
45
+ <title>Sumac Shoes</title>
46
+ <description>Capture the beauty</description>
47
+ <amount>3</amount>
48
+ </item>
49
+ <item position="2">
50
+ <title>Channing Oxford Shoes</title>
51
+ <description>Hand finished with a wash of color</description>
52
+ </item>
53
+ </catalog>
54
+ XML
55
+
56
+ doc = Catalog.from_xml(xml)
57
+ doc.catalog.date # => #<Date: 2014-12-20 ((2457012j,0s,0n),+0s,2299161j)>
58
+ doc.catalog.size # => 10
59
+ doc.catalog.items.size # => 2
60
+ doc.catalog.items[0].title # => "Sumac Shoes"
61
+ doc.catalog.items[0].desc # => "Capture the beauty"
62
+ doc.catalog.items[0].amount # => 3
63
+ doc.catalog.items[0].position # => 1
64
+
65
+ doc.to_json
66
+ #{
67
+ # "catalog": {
68
+ # "date": "2014-12-20",
69
+ # "size": 10,
70
+ # "items": [
71
+ # { "title": "Sumac Shoes", "desc": "Capture the beauty", "amount": 3, "position": 1 },
72
+ # { "title": "Channing Oxford Shoes", "desc": "Hand finished with a wash of color", "position": 2 }
73
+ # ]
74
+ # }
75
+ #}
76
+
77
+ json = {
78
+ catalog: {
79
+ date: "2014-12-20",
80
+ size: 10,
81
+ items: [
82
+ { title: "Sumac Shoes", desc: "Capture the beauty", amount: 3, position: 1 },
83
+ { title: "Channing Oxford Shoes", desc: "Hand finished with a wash of color", position: 2 }
84
+ ]
85
+ }
86
+ }
87
+
88
+ # Initialize with JSON
89
+ doc = Catalog.new(json)
90
+ doc.catalog.date # => #<Date: 2014-12-20 ((2457012j,0s,0n),+0s,2299161j)>
91
+ doc.catalog.size # => 10
92
+ doc.catalog.items.size # => 2
93
+ doc.catalog.items[0].title # => "Sumac Shoes"
94
+ doc.catalog.items[0].desc # => "Capture the beauty"
95
+ doc.catalog.items[0].amount # => 3
96
+ doc.catalog.items[0].position # => 1
97
+
98
+ # By default all elements and attribute values are proxied. If you for some
99
+ # reason need to get real value just use the exclamation mark.
100
+ doc.catalog.date! # => #<Date: 2014-12-20 ((2457012j,0s,0n),+0s,2299161j)>
101
+
102
+ doc.to_xml
103
+ #<?xml version="1.0"?>
104
+ #<catalog date="2014-12-20">
105
+ # <size>10</size>
106
+ # <item position="1">
107
+ # <title>Sumac Shoes</title>
108
+ # <description>Capture the beauty</description>
109
+ # <amount>3</amount>
110
+ # </item>
111
+ # <item position="2">
112
+ # <title>Channing Oxford Shoes</title>
113
+ # <description>Hand finished with a wash of color</description>
114
+ # </item>
115
+ #</catalog>
116
+ ```
117
+
118
+ ### XML with namespaces
119
+ ```ruby
120
+ xml = <<-XML
121
+ <?xml version="1.0"?>
122
+ <d:student xmlns="http://example.com" xmlns:d="http://foo.com/student" d:id="11">
123
+ <d:name d:position="2">Jeff Smith</d:name>
124
+ <info>
125
+ <city number="7">Beijing</city>
126
+ </info>
127
+ </d:student>
128
+ XML
129
+
130
+ class Student
131
+ include XMLable::Document
132
+
133
+ root :student, namespace: 'd' do
134
+ namespace nil, 'http://example.com'
135
+ # the last defined namespace will be set as default for the all following
136
+ # and nested elements and attributes. If you need to overwrite namespace
137
+ # just pass namespace: 'prefix' (see the root definition above).
138
+ namespace :d, 'http://foo.com/student'
139
+
140
+ attribute :id, :int
141
+
142
+ element :name do
143
+ attribute :position, :int
144
+ # You can describe content method of element which has also attributes
145
+ # besides the content. By default content if present will be exported into JSON with
146
+ # '__content' key. You can pase 'false' instead of name to ignore it.
147
+ content :fullname
148
+ end
149
+
150
+ # If namespace setting is passed it will be set as default for the all
151
+ # nested element and attributes.
152
+ element :info, namespace: nil do
153
+ element :city do
154
+ attribute :number, Integer
155
+ content :name
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ student = Student.from_xml(xml)
162
+ student.to_json
163
+ #{
164
+ # "student": {
165
+ # "id": 11,
166
+ # "name": {
167
+ # "fullname": "Jeff Smith",
168
+ # "position": 2
169
+ # },
170
+ # "info": {
171
+ # "city": {
172
+ # "name": "Beijing",
173
+ # "number": 7
174
+ # }
175
+ # }
176
+ # }
177
+ #}
178
+
179
+ # Back direction
180
+ other = Student.new(student.to_h)
181
+
182
+ other.to_xml
183
+ #<?xml version="1.0"?>
184
+ #<d:student xmlns="http://example.com" xmlns:d="http://foo.com/student" d:id="11">
185
+ # <d:name d:position="2">Jeff Smith</d:name>
186
+ # <info>
187
+ # <city number="7">Beijing</city>
188
+ # </info>
189
+ #</d:student>
190
+ ```
191
+
192
+ ### Undescribed document
193
+ It's also possible to load undescribed XML. In this case each XML element(not
194
+ attribute) will be represented as an array, so you have to use indexes to
195
+ access element values.
196
+ ```ruby
197
+ xml = <<-XML
198
+ <?xml version="1.0"?>
199
+ <item id="11">
200
+ <name position="2">Bolt</name>
201
+ <info>
202
+ <color>Silver</color>
203
+ </info>
204
+ </item>
205
+ XML
206
+
207
+ class Item
208
+ include XMLable::Document
209
+ end
210
+
211
+ item = Item.from_xml(xml)
212
+
213
+ item.root.id # => "11"
214
+ item.root.name[0].position # => "2"
215
+ item.root.name[0] # => "Bolt"
216
+ item.root.info[0].color[0] # => "Silver"
217
+
218
+ item.to_h
219
+ #{
220
+ # "item": {
221
+ # "id": "11"
222
+ # "name": [{"position": 2", "__content": "Bolt"}]
223
+ # "info": [{ "color": ["Silver"]}]
224
+ # }
225
+ #}
226
+
227
+ item.to_xml
228
+ #<?xml version="1.0"?>
229
+ #<item id="11">
230
+ # <name position="2">Bolt</name>
231
+ # <info>
232
+ # <color>Silver</color>
233
+ # </info>
234
+ #</item>
235
+ ```
236
+
237
+ ## Contributing
238
+
239
+ Bug reports and pull requests are welcome on GitHub at https://github.com/zoer/xmlable.
240
+
241
+
242
+ ## License
243
+
244
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
245
+
@@ -0,0 +1,16 @@
1
+ module XMLable
2
+ #
3
+ # Attribute module contains logic for standalone attribute object
4
+ #
5
+ module Attribute
6
+ def self.included(base)
7
+ base.include Mixins::Object
8
+ base.include Mixins::Castable
9
+ base.include Mixins::ValueStorage
10
+ base.include Mixins::StandaloneAttribute
11
+ base.include Mixins::OptionsStorage
12
+ base.include Mixins::BareValue
13
+ base.include Mixins::Instantiable
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,189 @@
1
+ module XMLable
2
+ #
3
+ # Builder class builds object that represents XML attribute and values
4
+ #
5
+ class Builder
6
+ include Singleton
7
+
8
+ class << self
9
+ #
10
+ # Build document object from XML document
11
+ #
12
+ # @param [Nokogiri::XML::Document] document
13
+ # @param [XMLable::Handlers::Document] handler
14
+ #
15
+ # @return [XMLable::Mixins::Object]
16
+ #
17
+ def build_document(document, handler)
18
+ obj = handler.proxy.new({}, document, handler)
19
+ populate_document(obj, document)
20
+ end
21
+
22
+ #
23
+ # Build attribute object from XML attribute
24
+ #
25
+ # @param [Nogogiri::XML::Attribute] attribute
26
+ # @param [XMLable::Handlers::Base] handler
27
+ #
28
+ # @return [XMLable::Mixins::Object]
29
+ #
30
+ def build_attribute(attribute, handler)
31
+ obj = handler.proxy.new({}, attribute, handler)
32
+ populate_attribute(obj, attribute)
33
+ end
34
+
35
+ #
36
+ # Build element object from XML element
37
+ #
38
+ # @param [Nogogiri::XML::Element] node
39
+ # @param [XMLable::Handlers::Base] handler
40
+ #
41
+ # @return [XMLable::Mixins::Object]
42
+ #
43
+ def build_element(node, handler)
44
+ obj = handler.proxy.new({}, node, handler)
45
+ populate_element(obj, node)
46
+ end
47
+
48
+ #
49
+ # Populate document object
50
+ #
51
+ # @param [XMLable::Mixins::Object] obj
52
+ # @param [Nogogiri::XML::Document] node
53
+ #
54
+ # @return [XMLable::Mixins::Object]
55
+ #
56
+ def populate_document(obj, node)
57
+ obj.__set_root(node.root)
58
+ obj
59
+ end
60
+
61
+ #
62
+ # Populate element object
63
+ #
64
+ # @param [XMLable::Mixins::Object] obj
65
+ # @param [Nogogiri::XML::Element] node
66
+ #
67
+ # @return [XMLable::Mixins::Object]
68
+ #
69
+ def populate_element(obj, node)
70
+ node.namespace_definitions.each { |ns| obj.__set_namespace_definition(ns) }
71
+ node.elements.each { |el| obj.__set_element(el) }
72
+ node.attributes.each { |_, att| obj.__set_attribute(att) }
73
+ obj.__set_content(node)
74
+ obj
75
+ end
76
+
77
+ #
78
+ # Populate attribute object
79
+ #
80
+ # @param [XMLable::Mixins::Object] obj
81
+ # @param [Nogogiri::XML::Attr] node
82
+ #
83
+ # @return [XMLable::Mixins::Object]
84
+ #
85
+ def populate_attribute(obj, node)
86
+ obj.tap { |o| o.__set_value(node) }
87
+ end
88
+
89
+ #
90
+ # Find defined type
91
+ #
92
+ # @param [Object] type
93
+ #
94
+ # @return [Class] returns found type or define new one if it's not found
95
+ #
96
+ def find_type(type)
97
+ type = type.to_s if type.is_a?(Symbol)
98
+ klass = instance.defined_types[type]
99
+ klass ? klass : define_type(type)
100
+ end
101
+
102
+ #
103
+ # Wrap type with additional logic
104
+ #
105
+ # @param [Class] klass
106
+ #
107
+ # @return [Class] returns wrapped class
108
+ #
109
+ def wrap_type(klass, &block)
110
+ Class.new do
111
+ include Mixins::Object
112
+ include Mixins::Wrapper
113
+ include Mixins::Castable
114
+ include Mixins::OptionsStorage
115
+ class_eval(&block) if block_given?
116
+ end
117
+ end
118
+
119
+ #
120
+ # Inherit type and set additional settings
121
+ #
122
+ # @param [Class] klass
123
+ #
124
+ # @return [Class] returns wrapped class
125
+ #
126
+ def inherit_type(klass, &block)
127
+ Class.new(klass) do
128
+ class_eval(&block) if block_given?
129
+ end
130
+ end
131
+
132
+ #
133
+ # Define new type
134
+ #
135
+ # @param [Array<Object>] names type's names(aliases)
136
+ #
137
+ # @return [Class]
138
+ #
139
+ def define_type(*names, &block)
140
+ names = names.map { |n| n.is_a?(Symbol) ? n.to_s : n }
141
+ main = names.first
142
+ klass = wrapped_type?(main) ? wrap_type(main, &block) : inherit_type(main, &block)
143
+ names.each { |n| instance.defined_types[n] = klass }
144
+ klass
145
+ end
146
+
147
+ #
148
+ # Is the given class wrapped?
149
+ #
150
+ # @return [Boolean]
151
+ #
152
+ def wrapped_type?(klass)
153
+ return true if !klass.is_a?(Class)
154
+ return false if klass.ancestors.include?(Mixins::StandaloneElement)
155
+ return false if klass.ancestors.include?(Mixins::StandaloneAttribute)
156
+ true
157
+ end
158
+
159
+ #
160
+ # Get proxy for the given type
161
+ #
162
+ # @param [Object] type type's name
163
+ #
164
+ # @return [Class] returns type's proxy class
165
+ #
166
+ def proxy_for(type)
167
+ find_type(type).dup
168
+ end
169
+
170
+ #
171
+ # Get container proxy for the given class
172
+ #
173
+ # @param [Class] klass
174
+ #
175
+ # @return [Class]
176
+ #
177
+ def container_proxy_for(klass)
178
+ Class.new(klass) { include Mixins::Container }
179
+ end
180
+ end
181
+
182
+ # @return [Hash]
183
+ attr_reader :defined_types
184
+
185
+ def initialize
186
+ @defined_types = {}
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,16 @@
1
+ module XMLable
2
+ #
3
+ # Document module contains logic for XML document object
4
+ #
5
+ module Document
6
+ def self.included(base)
7
+ base.include Mixins::Object
8
+ base.include Mixins::Export
9
+ base.include Mixins::OptionsStorage
10
+ base.include Mixins::Instantiable
11
+ base.include Mixins::Castable
12
+ base.include Mixins::DocumentStorage
13
+ base.include Mixins::RootStorage
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ module XMLable
2
+ #
3
+ # Element module contains logic for standalone element object
4
+ #
5
+ module Element
6
+ def self.included(base)
7
+ base.include Mixins::Object
8
+ base.include Mixins::Castable
9
+ base.include Mixins::ElementsStorage
10
+ base.include Mixins::AttributesStorage
11
+ base.include Mixins::ContentStorage
12
+ base.include Mixins::StandaloneElement
13
+ base.include Mixins::OptionsStorage
14
+ base.include Mixins::NamespaceDefinitionsStorage
15
+ base.include Mixins::BareValue
16
+ base.include Mixins::Instantiable
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,78 @@
1
+ module XMLable
2
+ module Exports
3
+ #
4
+ # Base class contains the base export logic
5
+ #
6
+ class Base
7
+ #
8
+ # Initialize
9
+ #
10
+ # @param [XMLable::Mixins::Object] element
11
+ # @param [Hash] opts
12
+ #
13
+ def initialize(element, opts = {})
14
+ @element = element
15
+ @opts = opts
16
+ end
17
+
18
+ #
19
+ # Is this object empty?
20
+ #
21
+ # @param [Nokogiri::XML::Node] node
22
+ #
23
+ # @return [Boolean]
24
+ #
25
+ def empty?(node)
26
+ node.instance_variable_get(:@__element).__empty?
27
+ end
28
+
29
+ #
30
+ # Is the object described by user?
31
+ #
32
+ # @param [Nokogiri::XML::Node] node
33
+ #
34
+ # @return [Boolean]
35
+ #
36
+ def described?(node)
37
+ node.instance_variable_get(:@__handler).described?
38
+ end
39
+
40
+ #
41
+ # Get node's nested options
42
+ #
43
+ # @param [Nokogiri::XML::Node] node
44
+ #
45
+ # @return [XMLable::Options::Storage]
46
+ #
47
+ def node_nested_options(node)
48
+ return Options::Storage.new unless node
49
+ parent = node.respond_to?(:parent) ? node.parent : nil
50
+ node_nested_options(parent).merge_opts(node_options(node))
51
+ end
52
+
53
+ #
54
+ # Get node's options
55
+ #
56
+ # @param [Nokogiri::XML::Node] node
57
+ #
58
+ # @return [XMLable::Options::Storage]
59
+ #
60
+ def node_options(node)
61
+ h = node.instance_variable_get(:@__handler)
62
+ h && h.options? ? h.options : Options::Storage.new
63
+ end
64
+
65
+ #
66
+ # Merge node's options
67
+ #
68
+ # @param [Nokogiri::XML::Node] node
69
+ # @param [XMLable::Options::Storage] opts
70
+ #
71
+ # @return [XMLable::Options::Storage]
72
+ #
73
+ def node_merged_opts(node, opts = Options::Storage.new)
74
+ opts.merge_opts(node_options(node))
75
+ end
76
+ end
77
+ end
78
+ end