dusty-noko_parser 0.0.9 → 0.1.0
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/examples/monkey.rb +51 -0
- data/examples/monkeys.xml +52 -0
- data/lib/noko_parser.rb +29 -0
- data/lib/noko_parser/parser.rb +86 -0
- data/lib/noko_parser/properties.rb +199 -0
- data/test/test_noko_parser.rb +0 -0
- metadata +11 -4
data/examples/monkey.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Fruits; end
|
2
|
+
module Animals; end
|
3
|
+
|
4
|
+
class Fruits::Banana
|
5
|
+
include NokoParser::Properties
|
6
|
+
nodes :xpath => '//banana'
|
7
|
+
|
8
|
+
property :id, :xpath => 'id'
|
9
|
+
property :size, :xpath => 'size'
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
class Animals::Monkey
|
15
|
+
include NokoParser::Properties
|
16
|
+
nodes :xpath => '//monkey'
|
17
|
+
|
18
|
+
property :id, :xpath => '.', :attribute => 'id'
|
19
|
+
|
20
|
+
property :first_name, :xpath => 'contact/name[@type="first"]'
|
21
|
+
|
22
|
+
property :last_name, :xpath => 'contact/name[@type="last"]'
|
23
|
+
|
24
|
+
property :age, :xpath => 'yrsold'
|
25
|
+
|
26
|
+
property :funny, :xpath => 'personality', :attribute => :hilarious
|
27
|
+
|
28
|
+
property :loud, :xpath => 'personality'
|
29
|
+
|
30
|
+
property :street, :xpath => 'street'
|
31
|
+
|
32
|
+
property :city, :xpath => 'city'
|
33
|
+
|
34
|
+
property :state, :xpath => 'state'
|
35
|
+
|
36
|
+
property :zip, :xpath => 'zip'
|
37
|
+
|
38
|
+
embed :bananas, :xpath => 'bananas', :class => 'Fruits::Banana'
|
39
|
+
|
40
|
+
def address
|
41
|
+
[street, city, state].compact.join(", ") + " #{zip}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def age
|
45
|
+
@age.to_i
|
46
|
+
end
|
47
|
+
|
48
|
+
def funny=(value)
|
49
|
+
@funny = (value == "true" ? true : false)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<xml>
|
2
|
+
<animals>
|
3
|
+
<elephant id="3">
|
4
|
+
<name>elephant1</name>
|
5
|
+
<trunk>large</trunk>
|
6
|
+
</elephant>
|
7
|
+
<monkey id="1">
|
8
|
+
<contact>
|
9
|
+
<name type='first'>monkey1</name>
|
10
|
+
<name type='last'>monkeylastname</name>
|
11
|
+
</contact>
|
12
|
+
<bananas>
|
13
|
+
<banana>
|
14
|
+
<id>1</id>
|
15
|
+
<size>small</size>
|
16
|
+
</banana>
|
17
|
+
<banana>
|
18
|
+
<id>2</id>
|
19
|
+
<size>medium</size>
|
20
|
+
</banana>
|
21
|
+
</bananas>
|
22
|
+
<yrsold>23</yrsold>
|
23
|
+
<personality hilarious="true">quiet</personality>
|
24
|
+
<street>300 Monkey St</street>
|
25
|
+
<city>Cincinnati</city>
|
26
|
+
<state>Oh</state>
|
27
|
+
<zip>45219</zip>
|
28
|
+
</monkey>
|
29
|
+
<monkey id="2">
|
30
|
+
<contact>
|
31
|
+
<name type='first'>monkey2</name>
|
32
|
+
<name type='last'>monkeylastname</name>
|
33
|
+
</contact>
|
34
|
+
<bananas>
|
35
|
+
<banana>
|
36
|
+
<id>3</id>
|
37
|
+
<size>large</size>
|
38
|
+
</banana>
|
39
|
+
<banana>
|
40
|
+
<id>4</id>
|
41
|
+
<size>huge</size>
|
42
|
+
</banana>
|
43
|
+
</bananas>
|
44
|
+
<yrsold>33</yrsold>
|
45
|
+
<personality hilarious="false">loud</personality>
|
46
|
+
<street>301 Monkey St</street>
|
47
|
+
<city>Cincinnati</city>
|
48
|
+
<state>Oh</state>
|
49
|
+
<zip>45219</zip>
|
50
|
+
</monkey>
|
51
|
+
</animals>
|
52
|
+
</xml>
|
data/lib/noko_parser.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module NokoParser
|
4
|
+
|
5
|
+
class NokoParserError < StandardError; end
|
6
|
+
|
7
|
+
def self.version
|
8
|
+
"0.1.0"
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
## Stolen from merb-core
|
14
|
+
class Object
|
15
|
+
def full_const_get(name)
|
16
|
+
list = name.split("::")
|
17
|
+
list.shift if list.first.empty?
|
18
|
+
obj = self
|
19
|
+
list.each do |x|
|
20
|
+
# This is required because const_get tries to look for constants in the
|
21
|
+
# ancestor chain, but we only want constants that are HERE
|
22
|
+
obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x)
|
23
|
+
end
|
24
|
+
obj
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'noko_parser/parser'
|
29
|
+
require 'noko_parser/properties'
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module NokoParser
|
2
|
+
|
3
|
+
class Parser
|
4
|
+
|
5
|
+
##
|
6
|
+
# Initialize the parser by passing in a class that has included
|
7
|
+
# NokoParser::Properties
|
8
|
+
#
|
9
|
+
# @raises [NokoParserError] unless class included NokoParser::Properties
|
10
|
+
def initialize(klass, xml)
|
11
|
+
unless klass.respond_to?('_is_noko_parser?')
|
12
|
+
raise NokoParserError, 'Class must include NokoParser::Properties'
|
13
|
+
end
|
14
|
+
@object = klass
|
15
|
+
@xml = Nokogiri::XML(xml)
|
16
|
+
@nodes = []
|
17
|
+
@text_properties = klass._text_properties
|
18
|
+
@attribute_properties = klass._attribute_properties
|
19
|
+
@embedded_properties = klass._embedded_properties
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Runs the parser on the class. First finds the nodes that match
|
24
|
+
# and then parses the text and attribute properties on it
|
25
|
+
#
|
26
|
+
# return [Array<Object>] an array of objects
|
27
|
+
def run
|
28
|
+
@xml.xpath(@object._nodes_xpath).each do |node|
|
29
|
+
object = @object.new
|
30
|
+
parse_text_properties(node,object)
|
31
|
+
parse_attribute_properties(node,object)
|
32
|
+
parse_embedded_properties(node,object)
|
33
|
+
@nodes << object
|
34
|
+
end
|
35
|
+
@nodes
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
##
|
41
|
+
# Loop through the text properties of the class and use the defined
|
42
|
+
# xpath query to find the match. If found add to the object using
|
43
|
+
# the setter.
|
44
|
+
def parse_text_properties(node,object)
|
45
|
+
@text_properties.each do |property|
|
46
|
+
if match = node.xpath(property.xpath).first
|
47
|
+
unless match.inner_text.empty?
|
48
|
+
object.send("#{property.name}=",match.inner_text)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Loop through the attribute properties of the class and use the defined
|
56
|
+
# xpath query to find the match. If found add to the object using
|
57
|
+
# the setter.
|
58
|
+
def parse_attribute_properties(node,object)
|
59
|
+
@attribute_properties.each do |property|
|
60
|
+
if match = node.xpath(property.xpath).first
|
61
|
+
object.send("#{property.name}=",match.attribute(property.attribute))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Loop through the embedded properties of the class and use the defined
|
68
|
+
# xpath query to find the match. If found add to the object using the
|
69
|
+
# setter.
|
70
|
+
def parse_embedded_properties(node,object)
|
71
|
+
@embedded_properties.each do |property|
|
72
|
+
unless parser = Object.full_const_get(property.class_name) rescue nil
|
73
|
+
raise NokoParserError,
|
74
|
+
"Class #{property.class_name} not found for embed :#{property.name}"
|
75
|
+
end
|
76
|
+
if match = node.xpath(property.xpath).first
|
77
|
+
result = parser.parse(match.to_xml)
|
78
|
+
result = result.first if property.single
|
79
|
+
object.send("#{property.name}=", result)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
module NokoParser
|
2
|
+
|
3
|
+
module Properties
|
4
|
+
|
5
|
+
##
|
6
|
+
# Text property is used to find the innerhtml of an element
|
7
|
+
class TextProperty
|
8
|
+
attr_reader :name, :xpath
|
9
|
+
|
10
|
+
def initialize(name,xpath)
|
11
|
+
@name = name.to_s
|
12
|
+
@xpath = xpath.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Attribute property is used to find the value of an attribute of an
|
19
|
+
# element
|
20
|
+
class AttributeProperty
|
21
|
+
attr_reader :name, :xpath, :attribute
|
22
|
+
|
23
|
+
def initialize(name,xpath,attribute)
|
24
|
+
@name = name.to_s.downcase
|
25
|
+
@xpath = xpath.to_s
|
26
|
+
@attribute = attribute.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Embedded property is used to find the value of sub-elements inside the
|
33
|
+
# element
|
34
|
+
#
|
35
|
+
# If single is present, only the first element will be returned
|
36
|
+
# otherwise an array is returned
|
37
|
+
class EmbeddedProperty
|
38
|
+
attr_reader :name, :xpath, :class_name, :single
|
39
|
+
|
40
|
+
def initialize(name,xpath,class_name,single)
|
41
|
+
@name = name.to_s.downcase
|
42
|
+
@xpath = xpath.to_s
|
43
|
+
@class_name = class_name.to_s
|
44
|
+
@single = single || false
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.included(base)
|
50
|
+
base.instance_variable_set(:@_text_properties,[])
|
51
|
+
base.instance_variable_set(:@_attribute_properties,[])
|
52
|
+
base.instance_variable_set(:@_embedded_properties,[])
|
53
|
+
base.send(:extend, ClassMethods)
|
54
|
+
base.send(:include, InstanceMethods)
|
55
|
+
end
|
56
|
+
|
57
|
+
module ClassMethods
|
58
|
+
|
59
|
+
##
|
60
|
+
# Parse an xml string
|
61
|
+
#
|
62
|
+
# @return [Array<Object>] an array of objects
|
63
|
+
# @see NokoParser::Parser
|
64
|
+
def parse(xml)
|
65
|
+
parser = NokoParser::Parser.new(self,xml)
|
66
|
+
parser.run
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Parse an xml file
|
71
|
+
#
|
72
|
+
# @return [Array<Object>] an array of objects
|
73
|
+
# @see NokoParser::Parser
|
74
|
+
def parse_file(file)
|
75
|
+
parse(open(file))
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Make sure this class is a noko_parser compatible class
|
80
|
+
def _is_noko_parser?
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Returns the TextProperties defined on the class
|
86
|
+
#
|
87
|
+
# @return [Array<TextProperty>]
|
88
|
+
def _text_properties
|
89
|
+
@_text_properties
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Returns the AttributeProperties defined in the class
|
94
|
+
#
|
95
|
+
# @return [Array<AttributeProperty]
|
96
|
+
def _attribute_properties
|
97
|
+
@_attribute_properties
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Returns the EmbeddedProperties defined on the class
|
102
|
+
#
|
103
|
+
# @return [Array<EmbedProperty>]
|
104
|
+
def _embedded_properties
|
105
|
+
@_embedded_properties
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Returns the xpath required to find the nodes
|
110
|
+
def _nodes_xpath
|
111
|
+
@_nodes_xpath || (raise NokoParserError, 'nodes :xpath => "" is required')
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Set the xpath for finding the nodes.
|
116
|
+
#
|
117
|
+
# examples:
|
118
|
+
# nodes '//monkeys'
|
119
|
+
# nodes '//animals/monkeys/friendly'
|
120
|
+
def nodes(opts={})
|
121
|
+
unless opts[:xpath]
|
122
|
+
raise NokoParserError, ":xpath required for nodes definition"
|
123
|
+
end
|
124
|
+
@_nodes_xpath = opts[:xpath].to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Sets a property on the object. If :attribute is defined it will
|
129
|
+
# set an AttributeProperty. Otherwise, it will set a TextProperty
|
130
|
+
# :xpath => 'somexpathstring' is required. The xpath should be the
|
131
|
+
# query required to find this element inside the node.
|
132
|
+
#
|
133
|
+
# examples
|
134
|
+
# property :name, :xpath => 'MyName[@type="real_name"]'
|
135
|
+
# property :eye_color, :xpath => 'eyes', :attribute => 'color'
|
136
|
+
def property(name,opts={})
|
137
|
+
unless opts[:xpath]
|
138
|
+
raise NokoParserError, ":xpath required for #{name}"
|
139
|
+
end
|
140
|
+
if opts[:attribute]
|
141
|
+
_add_attribute_property(name,opts[:xpath],opts[:attribute])
|
142
|
+
else
|
143
|
+
_add_text_property(name,opts[:xpath])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Sets an embedded property on the option.
|
149
|
+
#
|
150
|
+
# examples:
|
151
|
+
# embed :names, :xpath => 'names', :class => Name
|
152
|
+
def embed(name,opts={})
|
153
|
+
unless opts[:class]
|
154
|
+
raise NokoParserError, ":xpath and :class required for #{name}"
|
155
|
+
end
|
156
|
+
_add_embedded_property(name,opts[:xpath],opts[:class],opts[:single])
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# Adds to the class variable @text_properties in addition to
|
161
|
+
# creating an accessor for this property
|
162
|
+
def _add_text_property(name,xpath)
|
163
|
+
@_text_properties << TextProperty.new(name,xpath)
|
164
|
+
send(:attr_accessor, name)
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Adds to the class variable @attribute_properties in addition to
|
169
|
+
# creating an accessor for this property
|
170
|
+
def _add_attribute_property(name,xpath,attribute)
|
171
|
+
@_attribute_properties << AttributeProperty.new(
|
172
|
+
name,xpath,attribute
|
173
|
+
)
|
174
|
+
send(:attr_accessor, name)
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Adds to the class variable @embedded_properties in addition to
|
179
|
+
# creating a reader for this property
|
180
|
+
def _add_embedded_property(name,xpath,class_name,single)
|
181
|
+
@_embedded_properties << EmbeddedProperty.new(
|
182
|
+
name,xpath,class_name,single
|
183
|
+
)
|
184
|
+
send(:attr_accessor, name)
|
185
|
+
end
|
186
|
+
|
187
|
+
end # ClassMethods
|
188
|
+
|
189
|
+
module InstanceMethods
|
190
|
+
|
191
|
+
# def _something
|
192
|
+
# true
|
193
|
+
# end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
end # NokoParser
|
File without changes
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dusty-noko_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dusty Doris
|
@@ -30,10 +30,17 @@ extensions: []
|
|
30
30
|
|
31
31
|
extra_rdoc_files:
|
32
32
|
- README.txt
|
33
|
-
files:
|
34
|
-
|
33
|
+
files:
|
34
|
+
- README.txt
|
35
|
+
- examples/monkey.rb
|
36
|
+
- examples/monkeys.xml
|
37
|
+
- lib/noko_parser.rb
|
38
|
+
- lib/noko_parser/parser.rb
|
39
|
+
- lib/noko_parser/properties.rb
|
40
|
+
- test/test_noko_parser.rb
|
35
41
|
has_rdoc: true
|
36
42
|
homepage: http://code.dusty.name
|
43
|
+
licenses:
|
37
44
|
post_install_message:
|
38
45
|
rdoc_options: []
|
39
46
|
|
@@ -54,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
61
|
requirements: []
|
55
62
|
|
56
63
|
rubyforge_project: none
|
57
|
-
rubygems_version: 1.
|
64
|
+
rubygems_version: 1.3.5
|
58
65
|
signing_key:
|
59
66
|
specification_version: 2
|
60
67
|
summary: Wrapper around Nokogiri to easily parse xml files with xpath
|