Custom function for class check in Nokogiri - ruby

How can I add a custom XPath function for class check to Nokogiri?
I.e. something like https://gist.github.com/knu/087b7f89bb31de4f419c, but performing
contains(concat(' ',normalize-space(#class),' '),' #{class_name} ')
Thanks :)

The following seems to work:
require 'nokogiri'
require 'singleton'
class CustomFunctions
include Singleton
# Implements: contains(concat(' ',normalize-space(#class),' '),' #{class_name} ')
def contains_class(nodeset, class_name)
nodeset.any? do |node|
attribute_value = node['class']
!attribute_value.nil? && attribute_value.split(' ').include?(class_name)
end
end
alias_method :'contains-class', :contains_class
module SetDefaultHandler
private
def extract_params(args)
params = super
params[1] ||= CustomFunctions.instance
params
end
Nokogiri::XML::Node.prepend self
Nokogiri::XML::NodeSet.prepend self
end
end
xml = Nokogiri::XML('<html><body><div class="a1">ascabsc</div><div class="b1 a2">zjtzj</div></body></html>')
xml.root.add_namespace('fn', 'http://www.w3.org/2005/xpath-functions')
%w[a2 a3].each do |class_name|
puts "Test #{class_name}: " + xml.xpath(".//div[fn:contains-class(., \"#{class_name}\")]").to_s
end

Related

Ruby: Performing additional commands on delegated method

I'd like to use delegate to pass map from a string on to chars, then join back to a string result.
require 'forwardable'
module Map
module String
extend Forwardable
def self.included(base)
base.send :extend, Forwardable
end
# Map for String
delegate map: :chars
end
end
class String
include Map::String
end
As it's originally a string I'd like to perform join after the delegated method has performed its duties. How do I modify the delegate line to include the additional change? The closest thing I've seen online is SimpleDelegator with __setobj__. But that's not well documented and I'm not able to ascertain how to use it for this.
I'm strictly looking for an answer in regards to delegate or SimpleDelegate
The equivalent behavior I'm looking for is this:
module Map
module String
def map(*args, &block)
if (!args.compact.empty? || !block.nil?)
self.chars.map(*args,&block).join
else
self.chars.map(*args,&block)
end
end
end
end
class String
include Map::String
end
I'm looking to understand how to do this with delegate.
The Fowardable docs are hilarious--as if that first example will run without a hundred errors. Your pseudo code tells ruby to forward the method call String#map, which doesn't exist, to String#chars, and you want to join() the result of that? Skip all the method calls and just write puts "some_string". So your question doesn't seem to make a lot of sense. In any case, Forwardable#delegate() does not allow you to map one name to another name.
With regards to SimpleDelegat**or**, you can do this:
module Map
require 'delegate'
class MyStringDecorator < SimpleDelegator
def map
chars.shuffle.join('|')
end
end
end
d = Map::MyStringDecorator.new 'hello'
puts d.map
--output:--
h|o|l|l|e
Response to edit: The equivalent behavior I'm looking for..
The problem is ruby won't let you do this:
class String < SomeClass
end
which is what include does, and you need to be able to do that in order to use delegate to forward all the method calls sent to one class to another class. This is the best you can do:
require 'delegate'
class MyString < DelegateClass(String)
def map(*args, &block)
if (!args.compact.empty? || !block.nil?)
self.chars.map(*args,&block).join
else
self.chars.map(*args,&block)
end
end
end
s = MyString.new 'hello'
puts s.upcase
puts s.map {|letter| letter.succ }
--output:--
HELLO
ifmmp
Or:
require 'forwardable'
class MyString
extend Forwardable
def initialize(str)
#str = str
end
def_delegators :#str, :upcase, :capitalize, :[], :chars #etc., etc., etc.
#Or: delegate({[:upcase, :capitalize, :[], :chars] => :#str})
#Or: instance_delegate({[:upcase, :capitalize, :[], :chars] => :#str})
def map(*args, &block)
if (!args.compact.empty? || !block.nil?)
self.chars.map(*args,&block).join
else
self.chars.map(*args,&block)
end
end
end
s = MyString.new('hello')
puts s.upcase
puts s.map {|letter| letter.succ }
--output:--
HELLO
ifmmp
Of course, you could always override String#method_missing() to do what you want. What is it that you read about delegate that made you think it could replace include?

how do I override a class method so I can access it in parent class

I have a class method which I use to get class' alternative name.
The idea is to have an abstract Figure class that has a universal method for printing it's children.I want it to print that alternative name which gets overridden in every child.
Here is my code:
class Figure
def to_s
s = self.name + ' '
#edges.each do |e|
s += e.to_s
end
s
end
def self.name
puts 'override me'
end
end
class Line < Figure
def initialize(points)
#edges = [Edge.new(points[0], points[1])]
end
def self.name
'line'
end
end
...Which doesn't work:
'to_s': undefined method 'name' for #<Line:0x00000001f12880> (NoMethodError)
If I replace class methods with instances it works perfectly, so... why does this happen? What is the difference?
If you make name an attribute stored at the Figure class, this will be a lot simpler:
class Figure
def initialize
#name = 'DefaultFigure'
end
def to_s
s = #name + ' '
#edges.each do |e|
s += e.to_s
end
s
end
end
class Line < Figure
def initialize(points)
#name = 'Line'
#edges = [Edge.new(points[0], points[1])]
end
end
Oops... I just figured out that you have to use self.class.name to access a class method INSIDE an instance. Like this:
def to_s
s = self.class.name + ' '
#edges.each do |e|
s += e.to_s
end
s
end
it works now.

OOP Confusion in Ruby

I want to do something like this. Here is one class:
module MyModule
class ClassOne
def initialize
#chain = []
end
def add_type_one(thing1, thing2)
#chain << thing1 + thing2
end
def add_type_two(thing3, thing4)
#chain << thing3 + thing4
end
def sanitize
#chain.join(" ").gsub("this", "that")
end
end
end
Here is another class:
module MyModule
class ClassTwo
def initialize
#other_chain = []
end
def add_more(thingA, thingB)
#other_chain << thingA + thingB
end
def add_even_more(thingC, thingD)
#other_chain << thingC + thingD
end
def run
system('program #{#chain} #{#other_chain}')
end
end
end
Then I'd like to call these methods like so:
a = ClassOne.new
a.add_type_one("I", "Want")
a.add_type_two("These", "Methods")
a.sanitize
b = ClassTwo.new
b.add_more("And", "Variables")
b.add_even_more("To", "Work together")
b.run
What must be done to get a final output of
system('program I Want These MethodsAndVariablesToWork together')
The point of this example is simply that I do not have access to ClassOne methods or variables within ClassTwo. The
b.run
needs to take in some message or output from ClassOne. I know that instance variables aren't accessible outside of the instance of the class, and I know that I could use a global variable or a constant and that could work - but this is not the best practice. I don't know why this still isn't clear to me. I'm missing a small piece of this puzzle. Please advise.
Imagine you had multiple ClassOne instances. How would ClassTwo even know which instance to use?
You could approach this problem by injecting an instance of ClassOne into ClassTwo. Like so
a = ClassOne.new
a.add_type_one("I", "Want")
a.add_type_two("These", "Methods")
a.sanitize
b = ClassTwo.new(a)
b.add_more("And", "Variables")
b.add_even_more("To", "Work together")
b.run
And then access the instance variables of ClassOne from within ClassTwo.
Have it in the following way so that the ClassOne is accessible by the ClassTwo,
module MyModule
class ClassOne
attr_reader :chain
def initialize
#chain = []
end
def add_type_one(thing1, thing2)
#chain << thing1 + thing2
end
def add_type_two(thing3, thing4)
#chain << thing3 + thing4
end
def sanitize
#chain = #chain.join(" ").gsub("this", "that")
end
end
class ClassTwo
def initialize(obj)
#classOne = obj
#other_chain = []
end
def add_more(thingA, thingB)
#other_chain << thingA + thingB
end
def add_even_more(thingC, thingD)
#other_chain << thingC + thingD
end
def run
system('program #{#classOne.chain} #{#other_chain.join}')
end
end
end

Ruby metaprogramming with method_missing to make a HTML DSL

I'm learning metaprogramming and am trying to make a little DSL to generate HTML. The #result instance variable is not generating the correct answer because when the h1 method is called, the #result instance variable is reset. Is there an elegant way to deal with these 'nested' method calls (I know Ruby doesn't technically have nested methods). Here's my code:
class HtmlDsl
attr_reader :result
def initialize(&block)
instance_eval(&block)
end
private
def method_missing(name, *args, &block)
tag = name.to_s
content = args.first
#result = "<#{tag}>#{block_given? ? instance_eval(&block) : content}</#{tag}>"
end
end
html = HtmlDsl.new do
html do
head do
title 'yoyo'
end
body do
h1 'hey'
end
end
end
p html.result # => "<html><body><h1>hey</h1></body></html>"
# desired result # => "<html><head><title>yoyo</title></head><body><h1>hey</h1></body></html>"
Your problem is not that #result is reset, only that you add into the #result the return value of instance_eval(&block), which is the last line in the block, and not the aggregated block. This should work better (although not perfectly):
class HtmlDsl
attr_reader :result
def initialize(&block)
instance_eval(&block)
end
private
def method_missing(name, *args, &block)
tag = name.to_s
content = args.first
(#result ||= '') << "<#{tag}>"
if block_given?
instance_eval(&block)
else
#result << content
end
#result << "</#{tag}>"
end
end
So now:
html = HtmlDsl.new do
html do
head do
title 'yoyo'
end
body do
h1 'hey'
end
end
end
p html.result
#=> "<html><head><title>yoyo</title></head><body><h1>hey</h1></body></html>"
What I've done is that each call actually renders a fragment to the #result, so inner calls render inner fragments, each wrapping its own inner fragments with tags.

Creating class methods from a module

Given the simple example here:
class Base
#tag = nil
def self.tag(v = nil)
return #tag unless v
#tag = v
end
end
class A < Base
tag :A
end
class B < Base
tag :B
end
class C < Base; end
puts "A: #{A.tag}"
puts "B: #{B.tag}"
puts "A: #{A.tag}"
puts "C: #{C.tag}"
which works as expected
A: A
B: B
A: A
C:
I want to create a module that base will extend to give the same functionality but with all the tag information specified by the class. Eg.
module Tester
def add_ident(v); ....; end
end
class Base
extend Tester
add_ident :tag
end
I've found i can do it with a straight eval, so:
def add_ident(v)
v = v.to_s
eval "def self.#{v}(t = nil); return ##{v} unless t; ##{v} = t; end"
end
but i really dislike using eval string in any language.
Is there a way that i can get this functionality without using eval? I've gone through every combination of define_method and instance_variable_get/set i can think of and i can't get it to work.
Ruby 1.9 without Rails.
You want to define a dynamic method on the singleton class of the class you're extending. The singleton class of a class can be accessed with expression like this: class << self; self end. To open the scope of a class's class, you can use class_eval. Putting all this together, you can write:
module Identification
def add_identifier(identifier)
(class << self; self end).class_eval do
define_method(identifier) do |*args|
value = args.first
if value
instance_variable_set("##{identifier}", value)
else
instance_variable_get("##{identifier}")
end
end
end
end
end
class A
extend Identification
add_identifier :tag
end
If you're using recent versions of Ruby, this approach can be replaced with Module#define_singleton_method:
module Identification
def add_identifier(identifier)
define_singleton_method(identifier) do |value = nil|
if value
instance_variable_set("##{identifier}", value)
else
instance_variable_get("##{identifier}")
end
end
end
end
I don't believe you want to use self.class.send(:define_method), as shown in another answer here; this has the unintended side effect of adding the dynamic method to all child classes of self.class, which in the case of A in my example is Class.
module Tester
def add_ident(var)
self.class.send(:define_method, var) do |val=nil|
return instance_variable_get("##{var}") unless val
instance_variable_set "##{var}", val
end
end
end
My favourite ruby book Metaprogramming Ruby solved these questions like the following way:
module AddIdent
def self.included(base)
base.extend ClassMethods # hook method
end
module ClassMethods
def add_ident(tag)
define_method "#{tag}=" do |value=nil|
instance_variable_set("##{tag}", value)
end
define_method tag do
instance_variable_get "##{tag}"
end
end
end
end
# And use it like this
class Base
include AddIdent
add_ident :tag
end
Bah isn't it always the way that once you get frustrated enough to post you then find the answer :)
The trick seems to be in (class << self; self; end) to give you the class instance without destroying the local scope. Referencing: How do I use define_method to create class methods?
def add_ident(v)
var_name = ('#' + v.to_s).to_sym
(class << self; self; end).send(:define_method, v) do |t = nil|
return instance_variable_get(var_name) unless t
instance_variable_set(var_name, t)
end
end
I'll accept better answers if them come along though.

Resources