kind_dom 0.9.6 → 0.9.7
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/README +1 -1
- data/lib/kind_dom/base.rb +79 -83
- data/test/kind_dom_test.rb +26 -23
- metadata +2 -3
- data/lib/kind_dom/object_proxy.rb +0 -31
data/README
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
KindDom
|
1
|
+
KindDom: gracefully use XML content with node-type neutrality, content closure & default values.
|
2
2
|
|
3
3
|
See the source code comments, rdoc or ri for usage details; the tests also demonstrate use cases.
|
4
4
|
|
data/lib/kind_dom/base.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
|
-
require 'kind_dom/object_proxy'
|
2
1
|
require 'xml/libxml'
|
3
2
|
|
4
3
|
module KindDom
|
5
4
|
|
6
5
|
# KindDom provides graceful access to the the DOM of an XML document using
|
7
|
-
# three methods of
|
6
|
+
# three methods of kindness:
|
8
7
|
# - <tt>#collection_of</tt> to select a collection of nodes
|
9
8
|
# - <tt>#first_of</tt> to select one node
|
10
9
|
# - <tt>#content_for</tt> to get node content.
|
11
10
|
#
|
12
|
-
# The original libxml behavior of the
|
11
|
+
# The original libxml behavior of the XML document is preserved through the #dom accessor.
|
13
12
|
#
|
14
13
|
# As a contrived example, in the controller:
|
15
14
|
# @results = KindDom::Base.new(xml_data)
|
@@ -31,105 +30,102 @@ module KindDom
|
|
31
30
|
# <% end -%>
|
32
31
|
# <% end -%>
|
33
32
|
#
|
34
|
-
class Base
|
33
|
+
class Base
|
35
34
|
|
36
|
-
|
35
|
+
attr_reader :dom
|
37
36
|
|
38
37
|
# A new KindDom object may be created from raw XML [string] data, or
|
39
|
-
# an already instantiated XML::Node or XML::Document.
|
38
|
+
# an already instantiated KindDom::Base, XML::Node or XML::Document.
|
40
39
|
#
|
41
40
|
def initialize(xml_in=nil)
|
42
|
-
unless xml_in.
|
41
|
+
unless xml_in.nil?
|
43
42
|
case
|
43
|
+
when xml_in.kind_of?(KindDom::Base)
|
44
|
+
@dom = xml_in.dom
|
45
|
+
when xml_in.kind_of?(XML::Document), xml_in.kind_of?(XML::Node)
|
46
|
+
@dom = xml_in
|
44
47
|
when xml_in.kind_of?(String)
|
45
48
|
parser = XML::Parser.new
|
46
49
|
parser.string = xml_in
|
47
50
|
@dom = parser.parse
|
48
|
-
when xml_in.kind_of?(XML::Document), xml_in.kind_of?(XML::Node)
|
49
|
-
@dom = xml_in
|
50
51
|
end
|
51
52
|
end
|
52
53
|
rescue XML::Parser::ParseError
|
54
|
+
end
|
55
|
+
|
56
|
+
# Retrieve the contents of the first node or attribute selected by the XPath;
|
57
|
+
# defaults to the current node, "."
|
58
|
+
#
|
59
|
+
# Optional second argument is the default value to return if the DOM find fails.
|
60
|
+
#
|
61
|
+
# When a block is provided, it will be called with the found content
|
62
|
+
# (or default) before returning.
|
63
|
+
#
|
64
|
+
def content_for(xpath='.', default=nil) # :yields: found_content
|
65
|
+
node = case @dom.class.to_s
|
66
|
+
when 'XML::Document' then
|
67
|
+
@dom.root.find_first(xpath)
|
68
|
+
else # 'XML::Node'
|
69
|
+
@dom.find_first(xpath)
|
70
|
+
end
|
71
|
+
content = case node.class.to_s
|
72
|
+
when 'XML::Attr' then
|
73
|
+
node.value
|
74
|
+
else
|
75
|
+
node.content
|
76
|
+
end
|
77
|
+
rescue NoMethodError
|
53
78
|
ensure
|
54
|
-
|
55
|
-
|
79
|
+
content = content.blank? ? default : content
|
80
|
+
if block_given? and !content.blank?
|
81
|
+
return yield(content)
|
82
|
+
else
|
83
|
+
return content
|
84
|
+
end
|
56
85
|
end
|
57
86
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
when 'XML::Attr' then
|
75
|
-
node.value
|
76
|
-
else
|
77
|
-
node.content
|
78
|
-
end
|
79
|
-
rescue NoMethodError
|
80
|
-
ensure
|
81
|
-
content = content.blank? ? default : content
|
82
|
-
if block_given? and !content.blank?
|
83
|
-
return yield(content)
|
84
|
-
else
|
85
|
-
return content
|
86
|
-
end
|
87
|
+
# Retrieve a collection (Array) of DOM nodes selected by the XPath.
|
88
|
+
#
|
89
|
+
# Each node is returned as KindDom to support #content_for, #collection_of & #first_of.
|
90
|
+
#
|
91
|
+
# When a block is provided, it will be called with the found collection
|
92
|
+
# (or default) before returning.
|
93
|
+
#
|
94
|
+
def collection_of(xpath, default=nil) # :yields: found_collection
|
95
|
+
c = @dom.find(xpath).collect {|n| self.class.new(n) }
|
96
|
+
rescue NoMethodError
|
97
|
+
ensure
|
98
|
+
c = c.blank?||c.size<1 ? default : c
|
99
|
+
if block_given? and !c.nil?
|
100
|
+
return yield(c)
|
101
|
+
else
|
102
|
+
return c
|
87
103
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
else
|
104
|
-
return c
|
105
|
-
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Retrieve the first DOM node selected by the XPath.
|
107
|
+
#
|
108
|
+
# The node is returned as KindDom to support #content_for, #collection_of & #first_of.
|
109
|
+
#
|
110
|
+
# When a block is provided, it will be called with the found node
|
111
|
+
# (or default) before returning.
|
112
|
+
#
|
113
|
+
def first_of(xpath, default=nil) # :yields: found_node
|
114
|
+
n = case @dom.class.to_s
|
115
|
+
when 'XML::Document' then
|
116
|
+
@dom.root.find_first(xpath)
|
117
|
+
else # 'XML::Node'
|
118
|
+
@dom.find_first(xpath)
|
106
119
|
end
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
#
|
115
|
-
def first_of(xpath, default=nil) # :yields: found_node
|
116
|
-
n = case self.class.to_s
|
117
|
-
when 'XML::Document' then
|
118
|
-
root.find_first(xpath)
|
119
|
-
else # 'XML::Node'
|
120
|
-
find_first(xpath)
|
121
|
-
end
|
122
|
-
n.extend Kindness
|
123
|
-
rescue NoMethodError
|
124
|
-
ensure
|
125
|
-
n = n.blank? ? default : n
|
126
|
-
if block_given? and !n.blank?
|
127
|
-
return yield(n)
|
128
|
-
else
|
129
|
-
return n
|
130
|
-
end
|
120
|
+
rescue NoMethodError
|
121
|
+
ensure
|
122
|
+
n = n.blank? ? default : self.class.new(n)
|
123
|
+
if block_given? and !n.blank?
|
124
|
+
return yield(n)
|
125
|
+
else
|
126
|
+
return n
|
131
127
|
end
|
132
128
|
end
|
133
129
|
|
134
130
|
end
|
135
|
-
end
|
131
|
+
end
|
data/test/kind_dom_test.rb
CHANGED
@@ -3,102 +3,105 @@ require File.dirname(__FILE__) + '/../lib/kind_dom'
|
|
3
3
|
|
4
4
|
class KindDomTest < Test::Unit::TestCase
|
5
5
|
|
6
|
-
@@
|
6
|
+
@@test_doc = KindDom::Base.new(File.read(File.dirname(__FILE__) + '/flickr_rss.xml'))
|
7
7
|
|
8
8
|
def test_initialization_from_raw_xml
|
9
|
-
assert_kind_of
|
10
|
-
|
9
|
+
assert_kind_of KindDom::Base, @@test_doc
|
10
|
+
assert_kind_of XML::Document, @@test_doc.dom
|
11
|
+
assert @@test_doc.respond_to?(:content_for)
|
11
12
|
end
|
12
13
|
|
13
14
|
def test_initialization_from_xml_document
|
14
15
|
parsed = parse_xml File.read(File.dirname(__FILE__) + '/flickr_rss.xml')
|
15
16
|
k = KindDom::Base.new(parsed)
|
16
|
-
assert_kind_of
|
17
|
+
assert_kind_of KindDom::Base, k
|
18
|
+
assert_kind_of XML::Document, k.dom
|
17
19
|
assert k.respond_to?(:content_for)
|
18
20
|
end
|
19
21
|
|
20
22
|
def test_initialization_from_xml_node
|
21
23
|
parsed = parse_xml File.read(File.dirname(__FILE__) + '/flickr_rss.xml')
|
22
24
|
k = KindDom::Base.new(parsed.root.find_first('channel/item'))
|
23
|
-
assert_kind_of
|
25
|
+
assert_kind_of KindDom::Base, k
|
26
|
+
assert_kind_of XML::Node, k.dom
|
24
27
|
assert k.respond_to?(:content_for)
|
25
28
|
end
|
26
29
|
|
27
30
|
|
28
31
|
def test_content_for__success
|
29
|
-
assert_equal 'Photos from marsi', @@
|
32
|
+
assert_equal 'Photos from marsi', @@test_doc.content_for('channel/title')
|
30
33
|
end
|
31
34
|
|
32
35
|
def test_content_for__success_with_closure
|
33
|
-
assert_equal 'PHOTOS FROM MARSI', @@
|
36
|
+
assert_equal 'PHOTOS FROM MARSI', @@test_doc.content_for('channel/title') {|content| content.upcase}
|
34
37
|
end
|
35
38
|
|
36
39
|
def test_content_for__default
|
37
|
-
assert_equal 'a flickr stream', @@
|
40
|
+
assert_equal 'a flickr stream', @@test_doc.content_for('//blank_or_non_existent', 'a flickr stream')
|
38
41
|
end
|
39
42
|
|
40
43
|
def test_content_for__default_with_closure
|
41
|
-
assert_equal 'A FLICKR STREAM', @@
|
44
|
+
assert_equal 'A FLICKR STREAM', @@test_doc.content_for('//blank_or_non_existent', 'a flickr stream') {|content| content.upcase}
|
42
45
|
end
|
43
46
|
|
44
47
|
def test_content_for__failure
|
45
|
-
assert_nil @@
|
48
|
+
assert_nil @@test_doc.content_for('//blank_or_non_existent')
|
46
49
|
end
|
47
50
|
|
48
51
|
def test_content_for__failure_with_closure
|
49
|
-
assert_nil @@
|
52
|
+
assert_nil @@test_doc.content_for('//blank_or_non_existent') {|content| 'this closure is not called'}
|
50
53
|
end
|
51
54
|
|
52
55
|
|
53
56
|
def test_collection_of__success
|
54
|
-
items = @@
|
57
|
+
items = @@test_doc.collection_of('channel/item')
|
55
58
|
assert_kind_of Enumerable, items
|
56
59
|
assert 20 == items.size
|
57
60
|
end
|
58
61
|
|
59
62
|
def test_collection_of__success_with_closure
|
60
|
-
items = @@
|
63
|
+
items = @@test_doc.collection_of('channel/item') {|collection| collection[0..-2]}
|
61
64
|
assert_kind_of Enumerable, items
|
62
65
|
assert 19 == items.size
|
63
66
|
end
|
64
67
|
|
65
68
|
def test_collection_of__default
|
66
|
-
items = @@
|
69
|
+
items = @@test_doc.collection_of('//blank_or_non_existent', ['A lonely default item'])
|
67
70
|
assert_kind_of Enumerable, items
|
68
71
|
assert 1 == items.size
|
69
72
|
end
|
70
73
|
|
71
74
|
def test_collection_of__default_with_closure
|
72
|
-
items = @@
|
75
|
+
items = @@test_doc.collection_of('//blank_or_non_existent', Array.new) {|collection| collection << 'I was empty' }
|
73
76
|
assert_kind_of Enumerable, items
|
74
77
|
assert 1 == items.size
|
75
78
|
end
|
76
79
|
|
77
80
|
def test_collection_of__failure
|
78
|
-
assert_nil @@
|
81
|
+
assert_nil @@test_doc.collection_of('//blank_or_non_existent')
|
79
82
|
end
|
80
83
|
|
81
84
|
def test_collection_of__failure_with_closure
|
82
|
-
assert_nil @@
|
85
|
+
assert_nil @@test_doc.collection_of('//blank_or_non_existent') {|collection| 'this closure is not called'}
|
83
86
|
end
|
84
87
|
|
85
88
|
|
86
89
|
def test_first_of__success
|
87
|
-
assert_kind_of
|
90
|
+
assert_kind_of KindDom::Base, @@test_doc.first_of('channel/item')
|
88
91
|
end
|
89
92
|
|
90
93
|
def test_first_of__success_with_closure
|
91
|
-
n = @@
|
92
|
-
assert_kind_of
|
93
|
-
assert_equal '
|
94
|
+
n = @@test_doc.first_of('channel/item') {|node| node.first_of('title') }
|
95
|
+
assert_kind_of KindDom::Base, n
|
96
|
+
assert_equal 'Alaya Cove', n.content_for
|
94
97
|
end
|
95
98
|
|
96
99
|
def test_first_of__failure
|
97
|
-
assert_nil @@
|
100
|
+
assert_nil @@test_doc.first_of('//blank_or_non_existent')
|
98
101
|
end
|
99
102
|
|
100
103
|
def test_first_of__failure_with_closure
|
101
|
-
assert_nil @@
|
104
|
+
assert_nil @@test_doc.first_of('//blank_or_non_existent') {|node| 'this closure is not called'}
|
102
105
|
end
|
103
106
|
|
104
107
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kind_dom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mars Hall
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-07-
|
12
|
+
date: 2008-07-05 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -41,7 +41,6 @@ extra_rdoc_files:
|
|
41
41
|
files:
|
42
42
|
- lib/kind_dom
|
43
43
|
- lib/kind_dom/base.rb
|
44
|
-
- lib/kind_dom/object_proxy.rb
|
45
44
|
- lib/kind_dom.rb
|
46
45
|
- test/flickr_rss.xml
|
47
46
|
- test/kind_dom_test.rb
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module KindDom
|
2
|
-
|
3
|
-
# The foundation of an object proxy is the BlankSlate.
|
4
|
-
#
|
5
|
-
# BlankSlate is included with Rails ActiveSupport
|
6
|
-
# as active_support/vendor/builder-2.1.2/blankslate.rb
|
7
|
-
#
|
8
|
-
# From the Rails rdoc: "BlankSlate provides an abstract base class with
|
9
|
-
# no predefined methods (except for __send__ and __id__). BlankSlate is useful
|
10
|
-
# as a base class when writing classes that depend upon method_missing
|
11
|
-
# (e.g. dynamic proxies)."
|
12
|
-
#
|
13
|
-
# ObjectProxy is a bare-bones proxy. Override #method_missing in your subclass
|
14
|
-
# to handle method calls in the proxy.
|
15
|
-
#
|
16
|
-
class ObjectProxy < BlankSlate
|
17
|
-
|
18
|
-
# Create a proxy to the provided object.
|
19
|
-
def initialize(target)
|
20
|
-
@target = target
|
21
|
-
end
|
22
|
-
|
23
|
-
# Override #method_missing in your subclass
|
24
|
-
# to handle method calls in the proxy; call `super(sym, *args, &block)`
|
25
|
-
# therein to get a response from the proxied object.
|
26
|
-
def method_missing(sym, *args, &block)
|
27
|
-
@target.__send__(sym, *args, &block)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|