xmlable 0.0.0.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 (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