yob-roxml 3.1.6
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/.gitignore +7 -0
- data/.gitmodules +3 -0
- data/History.txt +354 -0
- data/LICENSE +20 -0
- data/README.rdoc +195 -0
- data/Rakefile +117 -0
- data/TODO +37 -0
- data/VERSION +1 -0
- data/examples/amazon.rb +35 -0
- data/examples/current_weather.rb +27 -0
- data/examples/dashed_elements.rb +20 -0
- data/examples/library.rb +40 -0
- data/examples/posts.rb +27 -0
- data/examples/rails.rb +70 -0
- data/examples/twitter.rb +37 -0
- data/examples/xml/active_record.xml +70 -0
- data/examples/xml/amazon.xml +133 -0
- data/examples/xml/current_weather.xml +89 -0
- data/examples/xml/dashed_elements.xml +52 -0
- data/examples/xml/posts.xml +23 -0
- data/examples/xml/twitter.xml +422 -0
- data/lib/roxml.rb +556 -0
- data/lib/roxml/definition.rb +238 -0
- data/lib/roxml/hash_definition.rb +25 -0
- data/lib/roxml/xml.rb +40 -0
- data/lib/roxml/xml/parsers/libxml.rb +85 -0
- data/lib/roxml/xml/parsers/nokogiri.rb +82 -0
- data/lib/roxml/xml/references.rb +322 -0
- data/roxml.gemspec +206 -0
- data/spec/definition_spec.rb +494 -0
- data/spec/examples/active_record_spec.rb +40 -0
- data/spec/examples/amazon_spec.rb +54 -0
- data/spec/examples/current_weather_spec.rb +37 -0
- data/spec/examples/dashed_elements_spec.rb +20 -0
- data/spec/examples/library_spec.rb +46 -0
- data/spec/examples/post_spec.rb +24 -0
- data/spec/examples/twitter_spec.rb +32 -0
- data/spec/roxml_spec.rb +372 -0
- data/spec/shared_specs.rb +15 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/libxml.rb +3 -0
- data/spec/support/nokogiri.rb +3 -0
- data/spec/xml/array_spec.rb +36 -0
- data/spec/xml/attributes_spec.rb +71 -0
- data/spec/xml/encoding_spec.rb +52 -0
- data/spec/xml/namespace_spec.rb +270 -0
- data/spec/xml/namespaces_spec.rb +67 -0
- data/spec/xml/object_spec.rb +82 -0
- data/spec/xml/parser_spec.rb +21 -0
- data/spec/xml/text_spec.rb +71 -0
- data/test/fixtures/book_malformed.xml +5 -0
- data/test/fixtures/book_pair.xml +8 -0
- data/test/fixtures/book_text_with_attribute.xml +5 -0
- data/test/fixtures/book_valid.xml +5 -0
- data/test/fixtures/book_with_authors.xml +7 -0
- data/test/fixtures/book_with_contributions.xml +9 -0
- data/test/fixtures/book_with_contributors.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_octal_pages.xml +4 -0
- data/test/fixtures/book_with_publisher.xml +7 -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 +30 -0
- 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.xml +1 -0
- data/test/fixtures/person_with_guarded_mothers.xml +13 -0
- data/test/fixtures/person_with_mothers.xml +10 -0
- data/test/load_test.rb +6 -0
- data/test/mocks/dictionaries.rb +57 -0
- data/test/mocks/mocks.rb +279 -0
- data/test/support/fixtures.rb +11 -0
- data/test/test_helper.rb +34 -0
- data/test/unit/definition_test.rb +235 -0
- data/test/unit/deprecations_test.rb +24 -0
- data/test/unit/to_xml_test.rb +81 -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 +122 -0
- data/test/unit/xml_convention_test.rb +150 -0
- data/test/unit/xml_hash_test.rb +115 -0
- data/test/unit/xml_initialize_test.rb +49 -0
- data/test/unit/xml_name_test.rb +141 -0
- data/test/unit/xml_namespace_test.rb +31 -0
- data/test/unit/xml_object_test.rb +205 -0
- data/test/unit/xml_required_test.rb +94 -0
- data/test/unit/xml_text_test.rb +71 -0
- data/website/index.html +98 -0
- metadata +300 -0
@@ -0,0 +1,238 @@
|
|
1
|
+
require 'roxml/hash_definition'
|
2
|
+
|
3
|
+
class Module
|
4
|
+
def bool_attr_reader(*attrs)
|
5
|
+
attrs.each do |attr|
|
6
|
+
define_method :"#{attr}?" do
|
7
|
+
instance_variable_get(:"@#{attr}") || false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ROXML
|
14
|
+
class ContradictoryNamespaces < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
class Definition # :nodoc:
|
18
|
+
attr_reader :name, :sought_type, :wrapper, :hash, :blocks, :accessor, :to_xml, :attr_name, :namespace
|
19
|
+
bool_attr_reader :name_explicit, :array, :cdata, :required, :frozen
|
20
|
+
|
21
|
+
def initialize(sym, opts = {}, &block)
|
22
|
+
opts.assert_valid_keys(:from, :in, :as, :namespace,
|
23
|
+
:else, :required, :frozen, :cdata, :to_xml)
|
24
|
+
@default = opts.delete(:else)
|
25
|
+
@to_xml = opts.delete(:to_xml)
|
26
|
+
@name_explicit = opts.has_key?(:from) && opts[:from].is_a?(String)
|
27
|
+
@cdata = opts.delete(:cdata)
|
28
|
+
@required = opts.delete(:required)
|
29
|
+
@frozen = opts.delete(:frozen)
|
30
|
+
@wrapper = opts.delete(:in)
|
31
|
+
@namespace = opts.delete(:namespace)
|
32
|
+
|
33
|
+
@accessor = sym.to_s
|
34
|
+
opts[:as] ||=
|
35
|
+
if @accessor.ends_with?('?')
|
36
|
+
:bool
|
37
|
+
elsif @accessor.ends_with?('_on')
|
38
|
+
Date
|
39
|
+
elsif @accessor.ends_with?('_at')
|
40
|
+
DateTime
|
41
|
+
end
|
42
|
+
|
43
|
+
@array = opts[:as].is_a?(Array)
|
44
|
+
@blocks = collect_blocks(block, opts[:as])
|
45
|
+
|
46
|
+
@sought_type = extract_type(opts[:as])
|
47
|
+
if @sought_type.respond_to?(:roxml_tag_name)
|
48
|
+
opts[:from] ||= @sought_type.roxml_tag_name
|
49
|
+
end
|
50
|
+
|
51
|
+
if opts[:from] == :content
|
52
|
+
opts[:from] = '.'
|
53
|
+
elsif opts[:from] == :name
|
54
|
+
opts[:from] = '*'
|
55
|
+
elsif opts[:from] == :attr
|
56
|
+
@sought_type = :attr
|
57
|
+
opts[:from] = nil
|
58
|
+
elsif opts[:from] == :namespace
|
59
|
+
opts[:from] = '*'
|
60
|
+
@sought_type = :namespace
|
61
|
+
elsif opts[:from].to_s.starts_with?('@')
|
62
|
+
@sought_type = :attr
|
63
|
+
opts[:from].sub!('@', '')
|
64
|
+
end
|
65
|
+
|
66
|
+
@name = @attr_name = accessor.to_s.chomp('?')
|
67
|
+
@name = @name.singularize if hash? || array?
|
68
|
+
@name = (opts[:from] || @name).to_s
|
69
|
+
if hash? && (hash.key.name? || hash.value.name?)
|
70
|
+
@name = '*'
|
71
|
+
end
|
72
|
+
raise ContradictoryNamespaces if @name.include?(':') && (@namespace.present? || @namespace == false)
|
73
|
+
|
74
|
+
raise ArgumentError, "Can't specify both :else default and :required" if required? && @default
|
75
|
+
end
|
76
|
+
|
77
|
+
def instance_variable_name
|
78
|
+
:"@#{attr_name}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def setter
|
82
|
+
:"#{attr_name}="
|
83
|
+
end
|
84
|
+
|
85
|
+
def hash
|
86
|
+
if hash?
|
87
|
+
@sought_type.wrapper ||= name
|
88
|
+
@sought_type
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def hash?
|
93
|
+
@sought_type.is_a?(HashDefinition)
|
94
|
+
end
|
95
|
+
|
96
|
+
def name?
|
97
|
+
@name == '*'
|
98
|
+
end
|
99
|
+
|
100
|
+
def content?
|
101
|
+
@name == '.'
|
102
|
+
end
|
103
|
+
|
104
|
+
def default
|
105
|
+
if @default.nil?
|
106
|
+
@default = [] if array?
|
107
|
+
@default = {} if hash?
|
108
|
+
end
|
109
|
+
@default.duplicable? ? @default.dup : @default
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_ref(inst)
|
113
|
+
case sought_type
|
114
|
+
when :attr then XMLAttributeRef
|
115
|
+
when :text then XMLTextRef
|
116
|
+
when :namespace then XMLNameSpaceRef
|
117
|
+
when HashDefinition then XMLHashRef
|
118
|
+
when Symbol then raise ArgumentError, "Invalid type argument #{sought_type}"
|
119
|
+
else XMLObjectRef
|
120
|
+
end.new(self, inst)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
def self.all(items, &block)
|
125
|
+
array = items.is_a?(Array)
|
126
|
+
results = (array ? items : [items]).map do |item|
|
127
|
+
yield item
|
128
|
+
end
|
129
|
+
|
130
|
+
array ? results : results.first
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.fetch_bool(value, default)
|
134
|
+
value = value.to_s.downcase
|
135
|
+
if %w{true yes 1 t}.include? value
|
136
|
+
true
|
137
|
+
elsif %w{false no 0 f}.include? value
|
138
|
+
false
|
139
|
+
else
|
140
|
+
default
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
CORE_BLOCK_SHORTHANDS = {
|
145
|
+
# Core Shorthands
|
146
|
+
Integer => lambda do |val|
|
147
|
+
all(val) do |v|
|
148
|
+
Integer(v) unless v.blank?
|
149
|
+
end
|
150
|
+
end,
|
151
|
+
Float => lambda do |val|
|
152
|
+
all(val) do |v|
|
153
|
+
Float(v) unless v.blank?
|
154
|
+
end
|
155
|
+
end,
|
156
|
+
Fixnum => lambda do |val|
|
157
|
+
all(val) do |v|
|
158
|
+
v.to_i unless v.blank?
|
159
|
+
end
|
160
|
+
end,
|
161
|
+
Time => lambda do |val|
|
162
|
+
all(val) {|v| Time.parse(v) unless v.blank? }
|
163
|
+
end,
|
164
|
+
|
165
|
+
:bool => nil,
|
166
|
+
:bool_standalone => lambda do |val|
|
167
|
+
all(val) do |v|
|
168
|
+
fetch_bool(v, nil)
|
169
|
+
end
|
170
|
+
end,
|
171
|
+
:bool_combined => lambda do |val|
|
172
|
+
all(val) do |v|
|
173
|
+
fetch_bool(v, v)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
}
|
177
|
+
|
178
|
+
def self.block_shorthands
|
179
|
+
# dynamically load these shorthands at class definition time, but
|
180
|
+
# only if they're already availbable
|
181
|
+
CORE_BLOCK_SHORTHANDS.tap do |blocks|
|
182
|
+
blocks.reverse_merge!(BigDecimal => lambda do |val|
|
183
|
+
all(val) do |v|
|
184
|
+
BigDecimal.new(v) unless v.blank?
|
185
|
+
end
|
186
|
+
end) if defined?(BigDecimal)
|
187
|
+
|
188
|
+
blocks.reverse_merge!(DateTime => lambda do |val|
|
189
|
+
if defined?(DateTime)
|
190
|
+
all(val) {|v| DateTime.parse(v) unless v.blank? }
|
191
|
+
end
|
192
|
+
end) if defined?(DateTime)
|
193
|
+
|
194
|
+
blocks.reverse_merge!(Date => lambda do |val|
|
195
|
+
if defined?(Date)
|
196
|
+
all(val) {|v| Date.parse(v) unless v.blank? }
|
197
|
+
end
|
198
|
+
end) if defined?(Date)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def collect_blocks(block, as)
|
203
|
+
if as.is_a?(Array)
|
204
|
+
if as.size > 1
|
205
|
+
raise ArgumentError, "multiple :as types (#{as.map(&:inspect).join(', ')}) is not supported. Use a block if you want more complicated behavior."
|
206
|
+
end
|
207
|
+
|
208
|
+
as = as.first
|
209
|
+
end
|
210
|
+
|
211
|
+
if as == :bool
|
212
|
+
# if a second block is present, and we can't coerce the xml value
|
213
|
+
# to bool, we need to be able to pass it to the user-provided block
|
214
|
+
as = (block ? :bool_combined : :bool_standalone)
|
215
|
+
end
|
216
|
+
as = self.class.block_shorthands.fetch(as) do
|
217
|
+
unless (as == :text) || as.respond_to?(:from_xml) || (as.respond_to?(:first) && as.first.respond_to?(:from_xml)) || (as.is_a?(Hash) && !(as.keys & [:key, :value]).empty?)
|
218
|
+
raise ArgumentError, "Invalid :as argument #{as}" unless as.nil?
|
219
|
+
end
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
[as, block].compact
|
223
|
+
end
|
224
|
+
|
225
|
+
def extract_type(as)
|
226
|
+
if as.is_a?(Hash)
|
227
|
+
return HashDefinition.new(as)
|
228
|
+
elsif as.respond_to?(:from_xml)
|
229
|
+
return as
|
230
|
+
elsif as.is_a?(Array) && as.first.respond_to?(:from_xml)
|
231
|
+
@array = true
|
232
|
+
return as.first
|
233
|
+
else
|
234
|
+
:text
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ROXML
|
2
|
+
class HashDefinition # :nodoc:
|
3
|
+
attr_reader :key, :value
|
4
|
+
attr_accessor :wrapper
|
5
|
+
|
6
|
+
def initialize(opts)
|
7
|
+
opts.assert_valid_keys(:key, :value)
|
8
|
+
|
9
|
+
@key = Definition.new(nil, to_definition_options(opts, :key))
|
10
|
+
@value = Definition.new(nil, to_definition_options(opts, :value))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def to_definition_options(opts, what)
|
15
|
+
case opts[what]
|
16
|
+
when Hash
|
17
|
+
opts[what]
|
18
|
+
when String, Symbol
|
19
|
+
{:from => opts[what]}
|
20
|
+
else
|
21
|
+
raise ArgumentError, "unrecognized hash parameter: #{what} => #{opts[what]}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/roxml/xml.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module ROXML
|
2
|
+
PARSERS = %w[nokogiri libxml].freeze
|
3
|
+
unless const_defined? 'XML_PARSER'
|
4
|
+
parsers = PARSERS.dup
|
5
|
+
begin
|
6
|
+
require parsers.first
|
7
|
+
XML_PARSER = parsers.first # :nodoc:
|
8
|
+
rescue LoadError
|
9
|
+
parsers.shift
|
10
|
+
retry unless parsers.empty?
|
11
|
+
raise "Could not load a parser. Tried #{PARSERS.to_sentence}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require File.join('roxml/xml/parsers', XML_PARSER)
|
16
|
+
|
17
|
+
module XML
|
18
|
+
class Node
|
19
|
+
def self.from(data)
|
20
|
+
case data
|
21
|
+
when XML::Node
|
22
|
+
data
|
23
|
+
when XML::Document
|
24
|
+
data.root
|
25
|
+
when File, IO
|
26
|
+
XML.parse_io(data).root
|
27
|
+
else
|
28
|
+
if (defined?(URI) && data.is_a?(URI::Generic)) ||
|
29
|
+
(defined?(Pathname) && data.is_a?(Pathname))
|
30
|
+
XML.parse_file(data.to_s).root
|
31
|
+
else
|
32
|
+
XML.parse_string(data).root
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
require 'roxml/xml/references'
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'libxml'
|
2
|
+
|
3
|
+
module ROXML
|
4
|
+
module XML # :nodoc:all
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def set_attribute(node, name, value)
|
8
|
+
node.attributes[name] = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_content(node, content)
|
12
|
+
node.content = content.gsub('&', '&')
|
13
|
+
end
|
14
|
+
|
15
|
+
def new_node(name)
|
16
|
+
LibXML::XML::Node.new(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_node(parent, name)
|
20
|
+
add_child(parent, new_node(name))
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_cdata(parent, content)
|
24
|
+
add_child(parent, LibXML::XML::Node.new_cdata(content))
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_child(parent, child)
|
28
|
+
parent << child
|
29
|
+
child
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_string(str_data)
|
33
|
+
LibXML::XML::Parser.string(str_data).parse
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_file(path)
|
37
|
+
LibXML::XML::Parser.file(path).parse
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_io(stream)
|
41
|
+
LibXML::XML::Parser.io(stream).parse
|
42
|
+
end
|
43
|
+
|
44
|
+
def save_doc(doc, path)
|
45
|
+
doc.save(path)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Document = LibXML::XML::Document
|
50
|
+
Node = LibXML::XML::Node
|
51
|
+
|
52
|
+
module NamespacedSearch
|
53
|
+
def roxml_search(xpath, roxml_namespaces = {})
|
54
|
+
if namespaces.default
|
55
|
+
roxml_namespaces = {:xmlns => namespaces.default.href}.merge(roxml_namespaces)
|
56
|
+
end
|
57
|
+
if roxml_namespaces.present?
|
58
|
+
find(xpath, roxml_namespaces.map {|prefix, href| [prefix, href].join(':') })
|
59
|
+
else
|
60
|
+
find(xpath)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Document
|
66
|
+
include NamespacedSearch
|
67
|
+
|
68
|
+
def default_namespace
|
69
|
+
default = namespaces.default
|
70
|
+
default.prefix || 'xmlns' if default
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
delegate :namespaces, :to => :root
|
75
|
+
end
|
76
|
+
|
77
|
+
class Node
|
78
|
+
include NamespacedSearch
|
79
|
+
|
80
|
+
def default_namespace
|
81
|
+
doc.default_namespace
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module ROXML
|
4
|
+
module XML # :nodoc:all
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def set_attribute(node, name, value)
|
8
|
+
node[name] = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_content(node, content)
|
12
|
+
node.content = content
|
13
|
+
end
|
14
|
+
|
15
|
+
def new_node(name)
|
16
|
+
Nokogiri::XML::Node.new(name, Document.new)
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_node(parent, name)
|
20
|
+
add_child(parent, Nokogiri::XML::Node.new(name, parent.document))
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_cdata(parent, content)
|
24
|
+
parent.add_child(Nokogiri::XML::CDATA.new(parent.document, content))
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_child(parent, child)
|
28
|
+
parent.add_child(child)
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_string(string)
|
32
|
+
Nokogiri::XML(string)
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_file(path)
|
36
|
+
path = path.sub('file:', '') if path.starts_with?('file:')
|
37
|
+
parse_io(open(path))
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_io(stream)
|
41
|
+
Nokogiri::XML(stream)
|
42
|
+
end
|
43
|
+
|
44
|
+
def save_doc(doc, path)
|
45
|
+
open(path, 'w') do |file|
|
46
|
+
file << doc.serialize
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Document = Nokogiri::XML::Document
|
52
|
+
Element = Nokogiri::XML::Element
|
53
|
+
Node = Nokogiri::XML::Node
|
54
|
+
|
55
|
+
class Document
|
56
|
+
alias :roxml_search :search
|
57
|
+
|
58
|
+
def default_namespace
|
59
|
+
'xmlns' if root.namespaces['xmlns']
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module NodeExtensions
|
64
|
+
def roxml_search(xpath, roxml_namespaces = {})
|
65
|
+
xpath = "./#{xpath}"
|
66
|
+
(roxml_namespaces.present? ? search(xpath, roxml_namespaces) : search(xpath))
|
67
|
+
end
|
68
|
+
|
69
|
+
def default_namespace
|
70
|
+
document.default_namespace
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Element
|
75
|
+
include NodeExtensions
|
76
|
+
end
|
77
|
+
|
78
|
+
class Node
|
79
|
+
include NodeExtensions
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|