roxml 1.2 → 2.3.2
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.
- data/MIT-LICENSE +18 -0
- data/README.rdoc +126 -0
- data/Rakefile +100 -0
- data/lib/roxml.rb +479 -485
- data/lib/roxml/extensions/active_support.rb +32 -0
- data/lib/roxml/extensions/array.rb +5 -0
- data/lib/roxml/extensions/array/conversions.rb +25 -0
- data/lib/roxml/extensions/deprecation.rb +28 -0
- data/lib/roxml/extensions/string.rb +21 -0
- data/lib/roxml/extensions/string/conversions.rb +44 -0
- data/lib/roxml/extensions/string/iterators.rb +12 -0
- data/lib/roxml/options.rb +266 -0
- data/lib/roxml/xml.rb +223 -0
- data/lib/roxml/xml/libxml.rb +63 -0
- data/lib/roxml/xml/rexml.rb +64 -0
- data/roxml.gemspec +105 -0
- data/test/fixtures/book_text_with_attribute.xml +1 -1
- data/test/fixtures/book_valid.xml +2 -2
- data/test/fixtures/book_with_authors.xml +7 -0
- data/test/fixtures/book_with_contributors_attrs.xml +7 -0
- data/test/fixtures/book_with_default_namespace.xml +9 -0
- data/test/fixtures/book_with_depth.xml +6 -0
- data/test/fixtures/book_with_wrapped_attr.xml +3 -0
- data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
- data/test/fixtures/dictionary_of_attrs.xml +6 -0
- data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
- data/test/fixtures/dictionary_of_mixeds.xml +4 -0
- data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
- data/test/fixtures/dictionary_of_names.xml +4 -0
- data/test/fixtures/dictionary_of_texts.xml +10 -0
- data/test/fixtures/library.xml +1 -1
- data/test/fixtures/library_uppercase.xml +30 -0
- data/test/fixtures/muffins.xml +3 -0
- data/test/fixtures/nameless_ageless_youth.xml +2 -0
- data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
- data/test/fixtures/node_with_name_conflicts.xml +4 -0
- data/test/fixtures/numerology.xml +4 -0
- data/test/fixtures/person_with_guarded_mothers.xml +13 -0
- data/test/fixtures/person_with_mothers.xml +10 -0
- data/test/mocks/dictionaries.rb +56 -0
- data/test/mocks/mocks.rb +244 -48
- data/test/release/dependencies_test.rb +32 -0
- data/test/test_helper.rb +44 -0
- data/test/unit/inheritance_test.rb +19 -0
- data/test/unit/options_test.rb +102 -0
- data/test/unit/roxml_test.rb +24 -0
- data/test/unit/string_test.rb +11 -0
- data/test/unit/to_xml_test.rb +52 -0
- data/test/unit/xml_attribute_test.rb +39 -0
- data/test/unit/xml_block_test.rb +81 -0
- data/test/unit/xml_bool_test.rb +105 -0
- data/test/unit/xml_construct_test.rb +77 -0
- data/test/unit/xml_hash_test.rb +65 -0
- data/test/unit/xml_initialize_test.rb +50 -0
- data/test/unit/xml_name_test.rb +108 -0
- data/test/unit/xml_namespace_test.rb +38 -0
- data/test/unit/xml_object_test.rb +116 -0
- data/test/unit/xml_required_test.rb +93 -0
- data/test/unit/xml_text_test.rb +68 -0
- metadata +138 -87
- data/MIT-LICENSE.txt +0 -9
- data/README +0 -2
- data/doc/classes/ROXML.html +0 -374
- data/doc/classes/ROXML.src/M000003.html +0 -19
- data/doc/classes/ROXML.src/M000004.html +0 -25
- data/doc/classes/ROXML.src/M000005.html +0 -22
- data/doc/classes/ROXML/ROXML_Class.html +0 -429
- data/doc/classes/ROXML/ROXML_Class.src/M000006.html +0 -27
- data/doc/classes/ROXML/ROXML_Class.src/M000007.html +0 -18
- data/doc/classes/ROXML/ROXML_Class.src/M000008.html +0 -19
- data/doc/classes/ROXML/ROXML_Class.src/M000009.html +0 -25
- data/doc/classes/ROXML/ROXML_Class.src/M000010.html +0 -24
- data/doc/classes/ROXML/ROXML_Class.src/M000011.html +0 -18
- data/doc/classes/ROXML/ROXML_Class.src/M000012.html +0 -18
- data/doc/classes/ROXML/XMLAttributeRef.html +0 -175
- data/doc/classes/ROXML/XMLAttributeRef.src/M000015.html +0 -19
- data/doc/classes/ROXML/XMLAttributeRef.src/M000016.html +0 -19
- data/doc/classes/ROXML/XMLObjectRef.html +0 -175
- data/doc/classes/ROXML/XMLObjectRef.src/M000013.html +0 -26
- data/doc/classes/ROXML/XMLObjectRef.src/M000014.html +0 -32
- data/doc/classes/ROXML/XMLRef.html +0 -166
- data/doc/classes/ROXML/XMLRef.src/M000017.html +0 -21
- data/doc/classes/ROXML/XMLTextRef.html +0 -198
- data/doc/classes/ROXML/XMLTextRef.src/M000018.html +0 -28
- data/doc/classes/ROXML/XMLTextRef.src/M000019.html +0 -34
- data/doc/classes/String.html +0 -165
- data/doc/classes/String.src/M000001.html +0 -23
- data/doc/classes/String.src/M000002.html +0 -23
- data/doc/created.rid +0 -1
- data/doc/files/lib/roxml_rb.html +0 -234
- data/doc/fr_class_index.html +0 -33
- data/doc/fr_file_index.html +0 -27
- data/doc/fr_method_index.html +0 -45
- data/doc/index.html +0 -24
- data/doc/rdoc-style.css +0 -208
- data/test/fixture_helper.rb +0 -5
- data/test/test_roxml.rb +0 -105
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'active_support/core_ext/symbol'
|
|
3
|
+
require 'active_support/core_ext/blank'
|
|
4
|
+
require 'active_support/core_ext/duplicable'
|
|
5
|
+
require 'active_support/core_ext/array/extract_options'
|
|
6
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
|
7
|
+
require 'active_support/core_ext/module/delegation'
|
|
8
|
+
require 'active_support/core_ext/module/aliasing'
|
|
9
|
+
require 'active_support/core_ext/object/misc' # returning
|
|
10
|
+
require 'active_support/inflector'
|
|
11
|
+
require 'active_support/core_ext/string/inflections'
|
|
12
|
+
require 'active_support/core_ext/string/starts_ends_with'
|
|
13
|
+
|
|
14
|
+
require 'extensions/enumerable'
|
|
15
|
+
require 'extensions/array'
|
|
16
|
+
|
|
17
|
+
class Module
|
|
18
|
+
include ActiveSupport::CoreExtensions::Module if ActiveSupport::CoreExtensions.const_defined? :Module
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class String #:nodoc:
|
|
22
|
+
include ActiveSupport::CoreExtensions::String::Inflections
|
|
23
|
+
include ActiveSupport::CoreExtensions::String::StartsEndsWith
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Array #:nodoc:
|
|
27
|
+
include ActiveSupport::CoreExtensions::Array::ExtractOptions
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Hash #:nodoc:
|
|
31
|
+
include ActiveSupport::CoreExtensions::Hash::ReverseMerge
|
|
32
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module ROXML
|
|
2
|
+
module CoreExtensions
|
|
3
|
+
module Array #:nodoc:
|
|
4
|
+
module Conversions
|
|
5
|
+
# Translates an array into a hash, where each element of the array is
|
|
6
|
+
# an array with 2 elements:
|
|
7
|
+
#
|
|
8
|
+
# >> [[:key, :value], [1, 2], ['key', 'value']].to_h
|
|
9
|
+
# => {:key => :value, 1 => 2, 'key' => 'value'}
|
|
10
|
+
#
|
|
11
|
+
def to_hash
|
|
12
|
+
inject({}) do |result, (k, v)|
|
|
13
|
+
result[k] = v
|
|
14
|
+
result
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_h #:nodoc:
|
|
19
|
+
to_hash
|
|
20
|
+
end
|
|
21
|
+
deprecate :to_h => :to_hash
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'active_support/core_ext/kernel/reporting'
|
|
2
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
|
3
|
+
require 'active_support/deprecation'
|
|
4
|
+
require 'active_support/version'
|
|
5
|
+
|
|
6
|
+
module ActiveSupport # :nodoc:all
|
|
7
|
+
module Deprecation
|
|
8
|
+
class << self
|
|
9
|
+
if VERSION::MAJOR <= 2 && VERSION::MINOR <= 1
|
|
10
|
+
def deprecation_message(callstack, message = nil)
|
|
11
|
+
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
|
|
12
|
+
"DEPRECATION WARNING: #{message}. #{deprecation_caller_message(callstack)}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
def deprecated_method_warning(method_name, message=nil)
|
|
19
|
+
warning = "#{method_name} is deprecated and will be removed from the next major or minor release."
|
|
20
|
+
case message
|
|
21
|
+
when Symbol then "#{warning} (use #{message} instead)"
|
|
22
|
+
when String then "#{warning} (#{message})"
|
|
23
|
+
else warning
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'string/conversions')
|
|
2
|
+
require File.join(File.dirname(__FILE__), 'string/iterators')
|
|
3
|
+
|
|
4
|
+
class String #:nodoc:
|
|
5
|
+
include ROXML::CoreExtensions::String::Conversions
|
|
6
|
+
include ROXML::CoreExtensions::String::Iterators
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Object #:nodoc:
|
|
10
|
+
# Deprecated in favor of explicit #to_s.to_utf
|
|
11
|
+
def to_utf
|
|
12
|
+
ActiveSupport::Deprecation.warn "This method will be removed from Object please use String#to_utf instead via explicit #to_s"
|
|
13
|
+
to_s.to_utf
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Deprecated in favor of explicit #to_s.to_latin
|
|
17
|
+
def to_latin
|
|
18
|
+
ActiveSupport::Deprecation.warn "This method will be removed from Object please use String#to_latin instead via explicit #to_s"
|
|
19
|
+
to_s.to_latin
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module ROXML
|
|
2
|
+
module CoreExtensions #:nodoc:
|
|
3
|
+
module String
|
|
4
|
+
# Extension of String class to handle conversion from/to
|
|
5
|
+
# UTF-8/ISO-8869-1
|
|
6
|
+
module Conversions
|
|
7
|
+
require 'iconv'
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# Return an utf-8 representation of this string.
|
|
11
|
+
#
|
|
12
|
+
def to_utf
|
|
13
|
+
begin
|
|
14
|
+
Iconv.new("utf-8", "iso-8859-1").iconv(to_s)
|
|
15
|
+
rescue Iconv::IllegalSequence
|
|
16
|
+
STDERR << "!! Failed converting from UTF-8 -> ISO-8859-1 (#{self}). Already the right charset?"
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#
|
|
22
|
+
# Convert this string to iso-8850-1
|
|
23
|
+
#
|
|
24
|
+
def to_latin
|
|
25
|
+
begin
|
|
26
|
+
Iconv.new("iso-8859-1", "utf-8").iconv(to_s)
|
|
27
|
+
rescue Iconv::IllegalSequence
|
|
28
|
+
STDERR << "!! Failed converting from ISO-8859-1 -> UTF-8 (#{self}). Already the right charset?"
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Object
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class String
|
|
41
|
+
def between(separator, &block)
|
|
42
|
+
split(separator).collect(&block).join(separator)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module ROXML
|
|
2
|
+
module CoreExtensions
|
|
3
|
+
module String #:nodoc:
|
|
4
|
+
module Iterators
|
|
5
|
+
# Allows you to iterate over and modify the sub-strings between _separator_. Returns the joined result of the modification.
|
|
6
|
+
def between(separator, &block)
|
|
7
|
+
split(separator).collect(&block).join(separator)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
module ROXML
|
|
2
|
+
HASH_KEYS = [:attrs, :key, :value].freeze
|
|
3
|
+
TYPE_KEYS = [:attr, :text, :hash, :content].freeze
|
|
4
|
+
|
|
5
|
+
class HashDesc # :nodoc:
|
|
6
|
+
attr_reader :key, :value, :wrapper
|
|
7
|
+
|
|
8
|
+
def initialize(opts, wrapper)
|
|
9
|
+
unless (invalid_keys = opts.keys - HASH_KEYS).empty?
|
|
10
|
+
raise ArgumentError, "Invalid Hash description keys: #{invalid_keys.join(', ')}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
@wrapper = wrapper
|
|
14
|
+
if opts.has_key? :attrs
|
|
15
|
+
@key = to_ref(opts, :attr, opts[:attrs][0])
|
|
16
|
+
@value = to_ref(opts, :attr, opts[:attrs][1])
|
|
17
|
+
else
|
|
18
|
+
@key = to_ref opts, *fetch_element(opts, :key)
|
|
19
|
+
@value = to_ref opts, *fetch_element(opts, :value)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def types
|
|
24
|
+
[@key.class, @value.class]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def names
|
|
28
|
+
[@key.name, @value.name]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
def fetch_element(opts, what)
|
|
33
|
+
case opts[what]
|
|
34
|
+
when Hash
|
|
35
|
+
raise ArgumentError, "Hash #{what} is over-specified: #{opts[what].pp_s}" unless opts[what].keys.one?
|
|
36
|
+
type = opts[what].keys.first
|
|
37
|
+
[type, opts[what][type]]
|
|
38
|
+
when :content
|
|
39
|
+
[:content, opts[:name]]
|
|
40
|
+
when :name
|
|
41
|
+
[:name, '*']
|
|
42
|
+
when String
|
|
43
|
+
[:text, opts[what]]
|
|
44
|
+
when Symbol
|
|
45
|
+
[:text, opts[what]]
|
|
46
|
+
else
|
|
47
|
+
raise ArgumentError, "unrecognized hash parameter: #{what} => #{opts[what]}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_ref(args, type, name)
|
|
52
|
+
case type
|
|
53
|
+
when :attr
|
|
54
|
+
XMLAttributeRef.new(to_hash_args(args, type, name))
|
|
55
|
+
when :text
|
|
56
|
+
XMLTextRef.new(to_hash_args(args, type, name))
|
|
57
|
+
when Symbol
|
|
58
|
+
XMLTextRef.new(to_hash_args(args, type, name))
|
|
59
|
+
else
|
|
60
|
+
raise ArgumentError, "Missing key description #{{:type => type, :name => name}.pp_s}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_hash_args(args, type, name)
|
|
65
|
+
args = [args] unless args.is_a? Array
|
|
66
|
+
|
|
67
|
+
if args.one? && !(args.only.keys & HASH_KEYS).empty?
|
|
68
|
+
opts = {type => name}
|
|
69
|
+
if type == :content
|
|
70
|
+
opts[:type] = :text
|
|
71
|
+
(opts[:as] ||= []) << :content
|
|
72
|
+
end
|
|
73
|
+
Opts.new(name, opts)
|
|
74
|
+
else
|
|
75
|
+
opts = args.extract_options!
|
|
76
|
+
raise opts.to_s
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class Opts # :nodoc:
|
|
82
|
+
attr_reader :name, :type, :hash, :blocks, :default, :accessor
|
|
83
|
+
|
|
84
|
+
class << self
|
|
85
|
+
def silence_xml_name_warning?
|
|
86
|
+
@silence_xml_name_warning || (ROXML.const_defined?('SILENCE_XML_NAME_WARNING') && ROXML::SILENCE_XML_NAME_WARNING)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def silence_xml_name_warning!
|
|
90
|
+
@silence_xml_name_warning = true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def initialize(sym, *args, &block)
|
|
95
|
+
@accessor = sym
|
|
96
|
+
@opts = extract_options!(args)
|
|
97
|
+
@default = @opts.delete(:else)
|
|
98
|
+
|
|
99
|
+
@opts.reverse_merge!(:as => [], :in => nil)
|
|
100
|
+
@opts[:as] = [*@opts[:as]]
|
|
101
|
+
|
|
102
|
+
@type = extract_type(args)
|
|
103
|
+
@opts[:as] << :bool if @accessor.to_s.ends_with?('?')
|
|
104
|
+
|
|
105
|
+
if @type.respond_to?(:xml_name?) && @type.xml_name?
|
|
106
|
+
unless self.class.silence_xml_name_warning?
|
|
107
|
+
warn "WARNING: As of 2.3, a breaking change has been in the naming of sub-objects. " +
|
|
108
|
+
"ROXML now considers the xml_name of the sub-object before falling back to the accessor name of the parent. " +
|
|
109
|
+
"Use :from on the parent declaration to override this behavior. Set ROXML::SILENCE_XML_NAME_WARNING to avoid this message."
|
|
110
|
+
self.class.silence_xml_name_warning!
|
|
111
|
+
end
|
|
112
|
+
@opts[:from] ||= @type.tag_name
|
|
113
|
+
else
|
|
114
|
+
@opts[:from] ||= variable_name
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
@blocks = collect_blocks(block, @opts[:as])
|
|
118
|
+
|
|
119
|
+
@name = @opts[:from].to_s
|
|
120
|
+
@name = @name.singularize if hash? || array?
|
|
121
|
+
if hash? && (hash.key.name? || hash.value.name?)
|
|
122
|
+
@name = '*'
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
raise ArgumentError, "Can't specify both :else default and :required" if required? && default
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def variable_name
|
|
129
|
+
accessor.to_s.ends_with?('?') ? accessor.to_s.chomp('?') : accessor.to_s
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def hash
|
|
133
|
+
@hash ||= HashDesc.new(@opts.delete(:hash), name) if hash?
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def hash?
|
|
137
|
+
@type == :hash
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def content?
|
|
141
|
+
@type == :content
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def array?
|
|
145
|
+
@opts[:as].include? :array
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def cdata?
|
|
149
|
+
@opts[:as].include? :cdata
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def wrapper
|
|
153
|
+
@opts[:in]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def required?
|
|
157
|
+
@opts[:required]
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
BLOCK_TO_FLOAT = lambda do |val|
|
|
162
|
+
if val.is_a? Array
|
|
163
|
+
val.collect do |v|
|
|
164
|
+
Float(v)
|
|
165
|
+
end
|
|
166
|
+
else
|
|
167
|
+
Float(val)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
BLOCK_TO_INT = lambda do |val|
|
|
172
|
+
if val.is_a? Array
|
|
173
|
+
val.collect do |v|
|
|
174
|
+
Integer(v)
|
|
175
|
+
end
|
|
176
|
+
else
|
|
177
|
+
Integer(val)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
TRUE_VALS = %w{TRUE True true 1}
|
|
182
|
+
FALSE_VALS = %w{FALSE False false 0}
|
|
183
|
+
|
|
184
|
+
BLOCK_SHORTHANDS = {
|
|
185
|
+
:integer => BLOCK_TO_INT,
|
|
186
|
+
Integer => BLOCK_TO_INT,
|
|
187
|
+
:float => BLOCK_TO_FLOAT,
|
|
188
|
+
Float => BLOCK_TO_FLOAT,
|
|
189
|
+
:bool => nil,
|
|
190
|
+
:bool_standalone => lambda do |val|
|
|
191
|
+
if TRUE_VALS.include? val
|
|
192
|
+
true
|
|
193
|
+
elsif FALSE_VALS.include? val
|
|
194
|
+
false
|
|
195
|
+
else
|
|
196
|
+
nil
|
|
197
|
+
end
|
|
198
|
+
end,
|
|
199
|
+
|
|
200
|
+
:bool_combined => lambda do |val|
|
|
201
|
+
if TRUE_VALS.include? val
|
|
202
|
+
true
|
|
203
|
+
elsif FALSE_VALS.include? val
|
|
204
|
+
false
|
|
205
|
+
else
|
|
206
|
+
val
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
def collect_blocks(block, as)
|
|
212
|
+
shorthands = as & BLOCK_SHORTHANDS.keys
|
|
213
|
+
if shorthands.size > 1
|
|
214
|
+
raise ArgumentError, "multiple block shorthands supplied #{shorthands.map(&:to_s).join(', ')}"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
shorthand = shorthands.first
|
|
218
|
+
if shorthand == :bool
|
|
219
|
+
# if a second block is present, and we can't coerce the xml value
|
|
220
|
+
# to bool, we need to be able to pass it to the user-provided block
|
|
221
|
+
shorthand = block ? :bool_combined : :bool_standalone
|
|
222
|
+
end
|
|
223
|
+
[BLOCK_SHORTHANDS[shorthand], block].compact
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def extract_options!(args)
|
|
227
|
+
opts = args.extract_options!
|
|
228
|
+
unless (opts.keys & HASH_KEYS).empty?
|
|
229
|
+
args.push(opts)
|
|
230
|
+
opts = {}
|
|
231
|
+
end
|
|
232
|
+
opts
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def extract_type(args)
|
|
236
|
+
types = (@opts.keys & TYPE_KEYS)
|
|
237
|
+
# type arg
|
|
238
|
+
if args.one? && types.empty?
|
|
239
|
+
if args.only.is_a? Array
|
|
240
|
+
@opts[:as] << :array
|
|
241
|
+
return args.only.only
|
|
242
|
+
elsif args.only.is_a? Hash
|
|
243
|
+
@opts[:hash] = args.only
|
|
244
|
+
return :hash
|
|
245
|
+
else
|
|
246
|
+
return args.only
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
unless args.empty?
|
|
251
|
+
raise ArgumentError, "too many arguments (#{(args + types).join(', ')}). Should be name, type, and " +
|
|
252
|
+
"an options hash, with the type and options optional"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# type options
|
|
256
|
+
if types.one?
|
|
257
|
+
@opts[:from] = @opts.delete(types.only)
|
|
258
|
+
types.only
|
|
259
|
+
elsif types.empty?
|
|
260
|
+
:text
|
|
261
|
+
else
|
|
262
|
+
raise ArgumentError, "more than one type option specified: #{types.join(', ')}"
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
data/lib/roxml/xml.rb
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
module ROXML
|
|
2
|
+
unless const_defined? 'XML_PARSER'
|
|
3
|
+
begin
|
|
4
|
+
require 'libxml'
|
|
5
|
+
XML_PARSER = 'libxml' # :nodoc:
|
|
6
|
+
rescue LoadError
|
|
7
|
+
XML_PARSER = 'rexml' # :nodoc:
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
require File.join(File.dirname(__FILE__), 'xml', XML_PARSER)
|
|
11
|
+
|
|
12
|
+
class RequiredElementMissing < Exception # :nodoc:
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# Internal base class that represents an XML - Class binding.
|
|
17
|
+
#
|
|
18
|
+
class XMLRef # :nodoc:
|
|
19
|
+
delegate :name, :required?, :array?, :wrapper, :blocks, :accessor, :variable_name, :to => :opts
|
|
20
|
+
alias_method :xpath_name, :name
|
|
21
|
+
|
|
22
|
+
def initialize(opts)
|
|
23
|
+
@opts = opts
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Reads data from the XML element and populates the instance
|
|
27
|
+
# accordingly.
|
|
28
|
+
def populate(xml, instance)
|
|
29
|
+
data = value(xml)
|
|
30
|
+
instance.instance_variable_set("@#{variable_name}", data)
|
|
31
|
+
instance
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def name?
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def update_xml(xml, value)
|
|
39
|
+
returning wrap(xml) do |xml|
|
|
40
|
+
write_xml(xml, value)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def default
|
|
45
|
+
@default ||= @opts.default || (@opts.array? ? Array.new : nil)
|
|
46
|
+
@default.duplicable? ? @default.dup : @default
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def value(xml)
|
|
50
|
+
value = fetch_value(xml)
|
|
51
|
+
if value.blank?
|
|
52
|
+
raise RequiredElementMissing, accessor.to_s if required?
|
|
53
|
+
value = default
|
|
54
|
+
end
|
|
55
|
+
apply_blocks(value)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
attr_reader :opts
|
|
60
|
+
|
|
61
|
+
def apply_blocks(val)
|
|
62
|
+
blocks.inject(val) {|val, block| block[val] }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def xpath
|
|
66
|
+
wrapper ? "#{wrapper}/#{xpath_name}" : xpath_name.to_s
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def wrap(xml)
|
|
70
|
+
(wrapper && xml.name != wrapper) ? xml.child_add(XML::Node.new_element(wrapper)) : xml
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Interal class representing an XML attribute binding
|
|
75
|
+
#
|
|
76
|
+
# In context:
|
|
77
|
+
# <element attribute="XMLAttributeRef">
|
|
78
|
+
# XMLTextRef
|
|
79
|
+
# </element>
|
|
80
|
+
class XMLAttributeRef < XMLRef # :nodoc:
|
|
81
|
+
private
|
|
82
|
+
# Updates the attribute in the given XML block to
|
|
83
|
+
# the value provided.
|
|
84
|
+
def write_xml(xml, value)
|
|
85
|
+
xml.attributes[name] = value.to_s.to_utf
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def fetch_value(xml)
|
|
89
|
+
attr = xml.search(xpath).first
|
|
90
|
+
attr && attr.value
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def xpath_name
|
|
94
|
+
"@#{name}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Interal class representing XML content text binding
|
|
99
|
+
#
|
|
100
|
+
# In context:
|
|
101
|
+
# <element attribute="XMLAttributeRef">
|
|
102
|
+
# XMLTextRef
|
|
103
|
+
# </element>
|
|
104
|
+
class XMLTextRef < XMLRef # :nodoc:
|
|
105
|
+
delegate :cdata?, :content?, :to => :opts
|
|
106
|
+
|
|
107
|
+
def name?
|
|
108
|
+
name == '*'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
# Updates the text in the given _xml_ block to
|
|
113
|
+
# the _value_ provided.
|
|
114
|
+
def write_xml(xml, value)
|
|
115
|
+
if content?
|
|
116
|
+
add(xml, value)
|
|
117
|
+
elsif name?
|
|
118
|
+
xml.name = value
|
|
119
|
+
elsif array?
|
|
120
|
+
value.each do |v|
|
|
121
|
+
add(xml.child_add(XML::Node.new_element(name)), v)
|
|
122
|
+
end
|
|
123
|
+
else
|
|
124
|
+
add(xml.child_add(XML::Node.new_element(name)), value)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def fetch_value(xml)
|
|
129
|
+
if content?
|
|
130
|
+
xml.content.strip
|
|
131
|
+
elsif name?
|
|
132
|
+
xml.name
|
|
133
|
+
elsif array?
|
|
134
|
+
xml.search(xpath).collect do |e|
|
|
135
|
+
e.content.strip.to_latin if e.content
|
|
136
|
+
end
|
|
137
|
+
else
|
|
138
|
+
child = xml.search(xpath).first
|
|
139
|
+
child.content if child
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def add(dest, value)
|
|
144
|
+
if cdata?
|
|
145
|
+
dest.child_add(XML::Node.new_cdata(value.to_s.to_utf))
|
|
146
|
+
else
|
|
147
|
+
dest.content = value.to_s.to_utf
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
class XMLHashRef < XMLTextRef # :nodoc:
|
|
153
|
+
delegate :hash, :to => :opts
|
|
154
|
+
|
|
155
|
+
def default
|
|
156
|
+
result = super
|
|
157
|
+
result.nil? ? {} : result
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
# Updates the composed XML object in the given XML block to
|
|
162
|
+
# the value provided.
|
|
163
|
+
def write_xml(xml, value)
|
|
164
|
+
value.each_pair do |k, v|
|
|
165
|
+
node = xml.child_add(XML::Node.new_element(hash.wrapper))
|
|
166
|
+
hash.key.update_xml(node, k)
|
|
167
|
+
hash.value.update_xml(node, v)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def fetch_value(xml)
|
|
172
|
+
xml.search(xpath).collect do |e|
|
|
173
|
+
[hash.key.value(e), hash.value.value(e)]
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def apply_blocks(vals)
|
|
178
|
+
unless blocks.empty?
|
|
179
|
+
vals.collect! do |kvp|
|
|
180
|
+
super(kvp)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
vals.to_hash if vals
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
class XMLObjectRef < XMLTextRef # :nodoc:
|
|
188
|
+
delegate :type, :to => :opts
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
# Updates the composed XML object in the given XML block to
|
|
192
|
+
# the value provided.
|
|
193
|
+
def write_xml(xml, value)
|
|
194
|
+
unless array?
|
|
195
|
+
xml.child_add(value.to_xml(name))
|
|
196
|
+
else
|
|
197
|
+
value.each do |v|
|
|
198
|
+
xml.child_add(v.to_xml(name))
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def fetch_value(xml)
|
|
204
|
+
unless array?
|
|
205
|
+
if child = xml.search(xpath).first
|
|
206
|
+
instantiate(child)
|
|
207
|
+
end
|
|
208
|
+
else
|
|
209
|
+
xml.search(xpath).collect do |e|
|
|
210
|
+
instantiate(e)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def instantiate(elem)
|
|
216
|
+
if type.respond_to? :from_xml
|
|
217
|
+
type.from_xml(elem)
|
|
218
|
+
else
|
|
219
|
+
type.new(elem)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|