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.
@@ -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>
@@ -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.9
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.2.0
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