tiny_xpath_helper 0.1.8

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.rdoc ADDED
@@ -0,0 +1,116 @@
1
+ = TinyXPathHelper
2
+
3
+ == Description
4
+
5
+ Very light syntax for using XPaths on XML documents or REXML nodes
6
+
7
+ == Examples
8
+
9
+ TinyXPathHelper.new takes one parameter: an XML document (as a string or IO) or a REXML element
10
+ >> xpath = TinyXPathHelper.new( File.open( 'test/xml/sample.xml' ) )
11
+
12
+ Then, to find the string values of matching nodes, there's a super-light syntax:
13
+ >> xpath[ 'node/' ]
14
+ => ["one", "two", "three\n "]
15
+
16
+ It works on attributes, too
17
+ >> xpath[ 'node/@style' ]
18
+ => ["first"]
19
+
20
+ If you prefer, you can use #all instead of square brackets
21
+ >> xpath.all( 'node/@style' )
22
+ => ["first"]
23
+
24
+ If you just want the first match, use #first
25
+ >> xpath.first( 'node/' )
26
+ => "one"
27
+
28
+ A slightly more elaborate API is available as #find_xpath
29
+ >> xpath.find_xpath( 'node/@style', :format => :rexml, :find => :first )
30
+ => style='first'
31
+
32
+ Where :format can be :text (returns a string), or :rexml (returns a REXML element),
33
+ and :find can be :first or :all
34
+ >> xpath.find_xpath( 'node/@style', :format => :rexml, :find => :all )
35
+ => [style='first']
36
+
37
+ If you want to filter the output, you can pass something that can be to_proc'd as :format
38
+ (Note that this acts on the string value of the element, not the REXML node)
39
+ >> xpath[ 'node/@style', :to_i ]
40
+ => [0]
41
+
42
+ >> xpath[ 'node/@style', :length ]
43
+ => [5]
44
+
45
+ >> xpath[ 'node', lambda{|x| x.strip.split(//) } ]
46
+ => [["o", "n", "e"], ["t", "w", "o"], ["t", "h", "r", "e", "e"]]
47
+
48
+ If you only need to set :format, you may pass just the symbol instead of an options hash
49
+ This is especially convenient using the [] syntax since a bug in ruby1.8
50
+ prevents using => inside of []
51
+ >> xpath[ 'node/@style', :rexml ]
52
+ => [style='first']
53
+
54
+ Actually, formats :rexml and :text are aliases for the :auto_text parameter, which takes a bool,
55
+ and defaults to true.
56
+ >> xpath.all( 'node/@style', :auto_text => false )
57
+ => [style='first']
58
+
59
+ If you find yourself writing iterative xpaths, you may want to use :format => :tiny_xpath_helper
60
+ >> node_xpath = xpath.first( 'node', :format => :tiny_xpath_helper)
61
+ >> node_xpath.first('@style')
62
+ => style='first'
63
+
64
+ Option defaults can be passed to TinyXPathHelper#new
65
+ >> xpath_with_options = TinyXPathHelper.new( File.open( 'test/xml/sample.xml' ), :format => :to_i, :find => :all )
66
+ >> xpath_with_options.find_xpath( 'node' )
67
+ => [0,0,0]
68
+
69
+ You can access the default options for the TinyXMLHelper Class or any instance with #default_options
70
+ >> TinyXPathHelper.default_options
71
+ => {:format => nil, :auto_text => true, :find => :first}
72
+ >> xpath_with_options.default_options
73
+ => {:format => :to_i, :auto_text => true, :find => :all}
74
+
75
+ You can also pass a block, which will get called on the entire return value, but
76
+ only if at least one element is found
77
+ >> xpath.all( 'node' ){ |nodes| nodes.count } # nodes is an array of strings
78
+ => 3
79
+
80
+ >> xpath.first( 'node' ){ |node| node.reverse } # node is a string
81
+ => "eno"
82
+
83
+ >> xpath.all( 'node', &:count )
84
+ => 3
85
+
86
+ >> xpath.all( 'nothing' ){ |nodes| raise "this will not run" }
87
+ => []
88
+
89
+ If you really want shortcut for the case where you want to run something
90
+ over the array of matching REXML nodes, set :auto_text to false when calling TinyXpathHelper#new
91
+ >> rexml_xpath = TinyXPathHelper.new( File.open( 'test/xml/sample.xml' ), :auto_text => false )
92
+ >> rexml_xpath[ 'node', :name ]
93
+ => ['node', 'node', 'node']
94
+
95
+ The output from :format => :rexml is suitable for passing to another TinyXPathHelper
96
+ >> node_tree = xpath.find_xpath( 'node/three', :format => :rexml )
97
+ >> deeper_xpath = TinyXPathHelper.new( node_tree )
98
+ >> deeper_xpath[ 'b/@beta' ]
99
+ => ["true"]
100
+
101
+ If you want empty documents to not raise an error
102
+ >> empty_xpath = TinyXPathHelper.new( String.new, :allow_empty_document => true )
103
+ >> empty_xpath[ 'b/@beta' ]
104
+ => []
105
+
106
+ If you want to call to_s on the xpath argument
107
+ >> xpath_with_indifferent_access = TinyXPathHelper.new( File.open( 'test/xml/sample.xml' ), :with_indifferent_access => true )
108
+ >> xpath_with_indifferent_access[ :node ]
109
+ => ["one", "two", "three\n "]
110
+
111
+
112
+ --
113
+ doctest_require: 'lib/tiny_xpath_helper.rb'
114
+ ++
115
+
116
+ Copyright (c) 2009 raSANTIAGO + Associates LLC
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :rubydoctest
7
+
8
+ desc 'Test the tiny_xpath_helper plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'ruby rubydoctest'
17
+ task :rubydoctest do
18
+ results = `rubydoctest README.rdoc`
19
+ raise results if results =~ /FAIL/
20
+ puts results
21
+ end
22
+
23
+ desc 'Generate documentation for the tiny_xpath_helper plugin.'
24
+ Rake::RDocTask.new(:rdoc) do |rdoc|
25
+ rdoc.rdoc_dir = 'rdoc'
26
+ rdoc.title = 'TinyXPathHelper'
27
+ rdoc.options << '--line-numbers' << '--inline-source'
28
+ rdoc.rdoc_files.include('README.rdoc')
29
+ rdoc.rdoc_files.include('lib/**/*.rb')
30
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "lib", "tiny_xpath_helper")
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,139 @@
1
+ require 'rexml/document'
2
+
3
+ class TinyXPathHelper
4
+ attr :node
5
+
6
+ def initialize(xml, options = {})
7
+ @xml = xml
8
+ @node = self.class.xml_node_for_xmlish(xml, options)
9
+ @options = options.freeze
10
+ end
11
+
12
+ def default_options
13
+ self.class.default_options.dup.update(@options)
14
+ end
15
+
16
+ def with_options(*options)
17
+ r = default_options.dup
18
+
19
+ options.reverse.each do |option|
20
+ if not option.is_a?( Hash )
21
+ option = {:format => option}
22
+ end
23
+ r.update(option)
24
+ end
25
+
26
+ return r
27
+ end
28
+
29
+ def find_xpath(xpath_expr, options = {}, &blk)
30
+ self.class.find_xpath_from( node, xpath_expr, with_options(options), &blk)
31
+ end
32
+
33
+ def first(xpath_expr, options = {}, &blk)
34
+ self.find_xpath( xpath_expr, with_options(options, :find => :first), &blk)
35
+ end
36
+ alias at first
37
+
38
+ def all(xpath_expr, options = {}, &blk)
39
+ self.find_xpath( xpath_expr, with_options(options, :find => :all), &blk)
40
+ end
41
+ alias [] all
42
+
43
+ def self.io_stream_classes
44
+ [ IOStream ] rescue [ IO, StringIO ] # thoughtbot-paperclip fixes the ducktype mess in StringIO
45
+ end
46
+
47
+ def self.classes_that_are_xmlish
48
+ io_stream_classes + [ String, REXML::Document ]
49
+ end
50
+
51
+ def self.xml_node_for_xmlish( xml, options = {} )
52
+ if io_stream_classes.any?{|k| xml.is_a?(k) }
53
+ xml = xml.read
54
+ end
55
+ if xml.is_a?(String)
56
+ xml = REXML::Document.new(xml)
57
+ end
58
+ if xml.is_a?(REXML::Document)
59
+ if options[:allow_empty_document] and ! xml.root
60
+ xml = xml
61
+ else
62
+ xml = xml.root
63
+ end
64
+ end
65
+ if not xml.is_a?(REXML::Element)
66
+ raise TypeError.new("Expected REXML::Element, got #{xml.class}")
67
+ end
68
+ return xml
69
+ end
70
+
71
+ def self.xml_node_to_text(node)
72
+ if(node.respond_to? :text)
73
+ # XML::Elements don't to_s in the way we want
74
+ val = node.text
75
+ else
76
+ val = node.to_s
77
+ end
78
+ end
79
+
80
+ def self.default_options
81
+ {:format => nil, :auto_text => true, :find => :first}.freeze
82
+ end
83
+
84
+ def self.find_xpath_from(element, path, options = {}, &blk)
85
+ if options[:with_indifferent_access]
86
+ path = path.to_s
87
+ end
88
+
89
+ options = self.default_options.dup.update(options)
90
+ format = options[:format]
91
+ auto_text = options[:auto_text]
92
+ count = options[:find]
93
+
94
+ if format == :array
95
+ count = :all
96
+ format = nil
97
+ elsif format == :text
98
+ auto_text = true
99
+ format = nil
100
+ elsif format == :xml or format == :rexml
101
+ auto_text = false
102
+ format = nil
103
+ elsif format == :xpath_helper or format == :tiny_xpath_helper
104
+ auto_text = false
105
+ format = self.method(:new)
106
+ end
107
+
108
+ filter1 = auto_text ? self.method(:xml_node_to_text) : nil
109
+ filter2 = format
110
+
111
+ if count == :all
112
+ elements = REXML::XPath.match(element, path)
113
+
114
+ elsif count == :first
115
+ elements = [ REXML::XPath.first(element, path) ].compact # Haskell hacker wishing for the Maybe monad
116
+
117
+ else
118
+ raise "I don't know how to find #{count.inspect}"
119
+ end
120
+
121
+ elements = elements.map(&filter1).map(&filter2)
122
+
123
+ if count == :all
124
+ r = elements
125
+ elsif count == :first
126
+ r = elements.first
127
+ end
128
+
129
+ if(blk and elements.length > 0)
130
+ return blk.call( r )
131
+ end
132
+
133
+ return r
134
+
135
+ end
136
+
137
+ end
138
+
139
+ TinyXpathHelper = TinyXPathHelper
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :tiny_xpath_helper do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'active_support/test_case'
4
+ require 'test/unit'
5
+ require 'tiny_xpath_helper.rb'
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+
3
+ class TinyXPathHelperTest < ActiveSupport::TestCase
4
+ test "find first returns nil if there is no match" do
5
+ xpath = TinyXPathHelper.new("<xml/>")
6
+ assert xpath.first('*').nil?
7
+ end
8
+
9
+ test "find_xpath with :format => :array doesn't crash" do
10
+ xpath = TinyXPathHelper.new("<xml><a/></xml>")
11
+ assert xpath.find_xpath('*', :format => :array)
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ <xml>
2
+ <node style="first">one</node>
3
+ <node>two</node>
4
+ <node>three
5
+ <three>
6
+ <a/><b beta="true"/><c/>
7
+ </three>
8
+ </node>
9
+ </xml>
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{tiny_xpath_helper}
5
+ s.version = "0.1.8"
6
+
7
+ s.authors = ["Jesse Wolfe"]
8
+ s.date = %q{2009-06-01}
9
+ s.description = %q{Very light syntax for using XPaths on XML documents or REXML nodes}
10
+ s.email = %q{jesse@rasantiago.com}
11
+ s.files = %w[ uninstall.rb init.rb Rakefile lib/tiny_xpath_helper.rb tiny_xpath_helper.gemspec README.rdoc tasks/tiny_xpath_helper_tasks.rake install.rb test/tiny_xpath_helper_test.rb test/xml test/xml/sample.xml test/test_helper.rb ]
12
+ s.has_rdoc = false
13
+ s.homepage = %q{http://github.com/rasantiago/tiny_xpath_helper}
14
+ s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
15
+ s.require_paths = ["lib"]
16
+ s.rubygems_version = %q{1.3.1}
17
+ s.summary = %q{Very light syntax for using XPaths on XML documents or REXML nodes}
18
+ end
19
+
data/uninstall.rb ADDED
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tiny_xpath_helper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.8
5
+ platform: ruby
6
+ authors:
7
+ - Jesse Wolfe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-01 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Very light syntax for using XPaths on XML documents or REXML nodes
17
+ email: jesse@rasantiago.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - uninstall.rb
26
+ - init.rb
27
+ - Rakefile
28
+ - lib/tiny_xpath_helper.rb
29
+ - tiny_xpath_helper.gemspec
30
+ - README.rdoc
31
+ - tasks/tiny_xpath_helper_tasks.rake
32
+ - install.rb
33
+ - test/tiny_xpath_helper_test.rb
34
+ - test/xml/sample.xml
35
+ - test/test_helper.rb
36
+ has_rdoc: true
37
+ homepage: http://github.com/rasantiago/tiny_xpath_helper
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --inline-source
43
+ - --charset=UTF-8
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.3.5
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Very light syntax for using XPaths on XML documents or REXML nodes
65
+ test_files: []
66
+