Skip to content
Browse files

Merge branch 'master' of http://github.com/jnicklas/xpath

  • Loading branch information...
2 parents 97904ef + 546dbf6 commit b5b30da7a87ae7865ea1cd0e630a02a9ce83eb1b @jnicklas jnicklas committed
Showing with 79 additions and 27 deletions.
  1. +2 −1 Gemfile.lock
  2. +9 −0 lib/xpath.rb
  3. +15 −6 lib/xpath/expression.rb
  4. +0 −12 lib/xpath/html.rb
  5. +1 −1 lib/xpath/version.rb
  6. +6 −0 spec/fixtures/simple.html
  7. +44 −6 spec/xpath_spec.rb
  8. +2 −1 xpath.gemspec
View
3 Gemfile.lock
@@ -2,6 +2,7 @@ PATH
remote: .
specs:
xpath (0.1.0)
+ nokogiri (~> 1.3)
GEM
remote: http://rubygems.org/
@@ -14,7 +15,7 @@ PLATFORMS
ruby
DEPENDENCIES
- nokogiri (>= 1.3.3)
+ nokogiri (~> 1.3)
rspec (>= 1.2.9)
xpath!
yard (>= 0.5.8)
View
9 lib/xpath.rb
@@ -1,3 +1,5 @@
+require 'nokogiri'
+
module XPath
autoload :Expression, 'xpath/expression'
autoload :Union, 'xpath/union'
@@ -49,6 +51,13 @@ def string
Expression::StringFunction.new(current)
end
+ def css(selector)
+ paths = Nokogiri::CSS.xpath_for(selector).map do |selector|
+ Expression::CSS.new(current, Expression::Literal.new(selector))
+ end
+ Union.new(*paths)
+ end
+
def varstring(name)
var(name).string_literal
end
View
21 lib/xpath/expression.rb
@@ -25,9 +25,6 @@ class Multiple < Expression
def initialize(left, expressions)
@left = wrap_xpath(left)
@expressions = expressions.map { |e| wrap_xpath(e) }
- if @expressions.empty?
- raise ArgumentError, "must specify at least one expression"
- end
end
end
@@ -45,8 +42,10 @@ class Child < Multiple
def to_xpath(predicate=nil)
if @expressions.length == 1
"#{@left.to_xpath(predicate)}/#{@expressions.first.to_xpath(predicate)}"
- else
+ elsif @expressions.length > 1
"#{@left.to_xpath(predicate)}/*[#{@expressions.map { |e| "self::#{e.to_xpath(predicate)}" }.join(" | ")}]"
+ else
+ "#{@left.to_xpath(predicate)}/*"
end
end
end
@@ -55,8 +54,10 @@ class Descendant < Multiple
def to_xpath(predicate=nil)
if @expressions.length == 1
"#{@left.to_xpath(predicate)}//#{@expressions.first.to_xpath(predicate)}"
- else
+ elsif @expressions.length > 1
"#{@left.to_xpath(predicate)}//*[#{@expressions.map { |e| "self::#{e.to_xpath(predicate)}" }.join(" | ")}]"
+ else
+ "#{@left.to_xpath(predicate)}//*"
end
end
end
@@ -65,8 +66,10 @@ class NextSibling < Multiple
def to_xpath(predicate=nil)
if @expressions.length == 1
"#{@left.to_xpath(predicate)}/following-sibling::*[1]/self::#{@expressions.first.to_xpath(predicate)}"
- else
+ elsif @expressions.length > 1
"#{@left.to_xpath(predicate)}/following-sibling::*[1]/self::*[#{@expressions.map { |e| "self::#{e.to_xpath(predicate)}" }.join(" | ")}]"
+ else
+ "#{@left.to_xpath(predicate)}/following-sibling::*[1]/self::*"
end
end
end
@@ -211,6 +214,12 @@ def to_xpath(predicate=nil)
end
end
+ class CSS < Binary
+ def to_xpath(predicate=nil)
+ "#{@left.to_xpath}#{@right.to_xpath}"
+ end
+ end
+
def current
self
end
View
12 lib/xpath/html.rb
@@ -3,10 +3,6 @@ module HTML
include XPath
extend self
- def from_css(css)
- XPath::Union.new(*Nokogiri::CSS.xpath_for(css).map { |selector| ::XPath::Expression::Literal.new(:".#{selector}") }.flatten)
- end
-
def link(locator)
link = descendant(:a)[attr(:href)]
link[attr(:id).equals(locator) | string.n.is(locator) | attr(:title).is(locator) | descendant(:img)[attr(:alt).is(locator)]]
@@ -101,14 +97,6 @@ def table_row(cells)
cell_conditions
end
- def wrap(path)
- if path.respond_to?(:to_xpaths)
- path.to_xpaths
- else
- [path.to_s].flatten
- end
- end
-
protected
def locate_field(xpath, locator)
View
2 lib/xpath/version.rb
@@ -1,3 +1,3 @@
module XPath
- VERSION = '0.1.0'
+ VERSION = '0.1.1'
end
View
6 spec/fixtures/simple.html
@@ -24,3 +24,9 @@
of
whitespace
</p>
+
+<div id="moar">
+ <p id="impchay">chimp</p>
+ <p id="amingoflay">flamingo</p>
+ <div id="elephantay">elephant</div>
+</div>
View
50 spec/xpath_spec.rb
@@ -43,6 +43,12 @@ def xpath(predicate=nil, &block)
@results[0].text.should == 'Blah'
@results[3].text.should == 'A list'
end
+
+ it "should find all nodes when no arguments given" do
+ @results = xpath { |x| x.descendant[x.attr(:id) == 'foo'].descendant }
+ @results[0].text.should == 'Blah'
+ @results[4].text.should == 'A list'
+ end
end
describe '#child' do
@@ -62,6 +68,12 @@ def xpath(predicate=nil, &block)
@results[0].text.should == 'Blah'
@results[3].text.should == 'A list'
end
+
+ it "should find all nodes when no arguments given" do
+ @results = xpath { |x| x.descendant[x.attr(:id) == 'foo'].child }
+ @results[0].text.should == 'Blah'
+ @results[3].text.should == 'A list'
+ end
end
describe '#next_sibling' do
@@ -70,6 +82,7 @@ def xpath(predicate=nil, &block)
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:ul, :p) }.first.text.should == 'Bax'
xpath { |x| x.descendant(:p)[x.attr(:title) == 'monkey'].next_sibling(:ul, :p) }.first.text.should == 'A list'
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:ul, :li) }.first.should be_nil
+ xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling }.first.text.should == 'Bax'
end
end
@@ -221,7 +234,7 @@ def xpath(predicate=nil, &block)
@results[0][:title].should == "barDiv"
@results[1][:title].should == "fooDiv"
end
-
+
it "should be closed" do
@results = xpath do |x|
foo_div = x.anywhere(:div).where(x.attr(:id) == 'foo')
@@ -231,6 +244,27 @@ def xpath(predicate=nil, &block)
end
end
+ describe '#css' do
+ it "should find nodes by the given CSS selector" do
+ @results = xpath { |x| x.css('#preference p') }
+ @results[0].text.should == 'allamas'
+ @results[1].text.should == 'llama'
+ end
+
+ it "should respect previous expression" do
+ @results = xpath { |x| x.descendant[x.attr(:id) == 'moar'].css('p') }
+ @results[0].text.should == 'chimp'
+ @results[1].text.should == 'flamingo'
+ end
+
+ it "should allow comma separated selectors" do
+ @results = xpath { |x| x.descendant[x.attr(:id) == 'moar'].css('div, p') }
+ @results[0].text.should == 'chimp'
+ @results[1].text.should == 'flamingo'
+ @results[2].text.should == 'elephant'
+ end
+ end
+
describe '#name' do
it "should match the node's name" do
xpath { |x| x.descendant(:*).where(x.name == 'ul') }.first.text.should == "A list"
@@ -250,7 +284,11 @@ def xpath(predicate=nil, &block)
it "should raise an argument error if the interpolation key is not given" do
@xpath = XPath.generate { |x| x.descendant(:*).where(x.attr(:id) == x.var(:id).string_literal) }
- lambda { @xpath.apply.to_xpath }.should raise_error(ArgumentError)
+ if defined?(KeyError)
+ lambda { @xpath.apply.to_xpath }.should raise_error(KeyError)
+ else
+ lambda { @xpath.apply.to_xpath }.should raise_error(ArgumentError)
+ end
end
end
@@ -274,8 +312,8 @@ def xpath(predicate=nil, &block)
describe '#union' do
it "should create a union expression" do
- @expr1 = XPath.generate { |x| x.descendant(:p) }
- @expr2 = XPath.generate { |x| x.descendant(:div) }
+ @expr1 = XPath.generate { |x| x.descendant(:p) }
+ @expr2 = XPath.generate { |x| x.descendant(:div) }
@collection = @expr1.union(@expr2)
@xpath1 = @collection.where(XPath.attr(:id) == 'foo').to_xpath
@xpath2 = @collection.where(XPath.attr(:id) == XPath.varstring(:id)).apply(:id => 'fooDiv').to_xpath
@@ -286,8 +324,8 @@ def xpath(predicate=nil, &block)
end
it "should be aliased as +" do
- @expr1 = XPath.generate { |x| x.descendant(:p) }
- @expr2 = XPath.generate { |x| x.descendant(:div) }
+ @expr1 = XPath.generate { |x| x.descendant(:p) }
+ @expr2 = XPath.generate { |x| x.descendant(:div) }
@collection = @expr1 + @expr2
@xpath1 = @collection.where(XPath.attr(:id) == 'foo').to_xpath
@xpath2 = @collection.where(XPath.attr(:id) == XPath.varstring(:id)).apply(:id => 'fooDiv').to_xpath
View
3 xpath.gemspec
@@ -22,7 +22,8 @@ Gem::Specification.new do |s|
s.rubygems_version = "1.3.6"
s.summary = "Generate XPath expressions from Ruby"
+ s.add_dependency("nokogiri", ["~> 1.3"])
+
s.add_development_dependency("rspec", [">= 1.2.9"])
- s.add_development_dependency("nokogiri", [">= 1.3.3"])
s.add_development_dependency("yard", [">= 0.5.8"])
end

0 comments on commit b5b30da

Please sign in to comment.
Something went wrong with that request. Please try again.