jam 0.2
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/HISTORY +21 -0
- data/MANIFEST +52 -0
- data/README +39 -0
- data/TODO +22 -0
- data/bin/jam +0 -0
- data/demo/array.rb +16 -0
- data/demo/attribute.rb +24 -0
- data/demo/demi/fixtures/ex1.html +10 -0
- data/demo/demi/fixtures/samples.yaml +19 -0
- data/demo/demi/integration/01_jam.rd +41 -0
- data/demo/demi/integration/02_xpath.rd +41 -0
- data/demo/demi/integration/samples.rd +28 -0
- data/demo/demi/unit/hpricot.rd +150 -0
- data/demo/demi/unit/libxml.rd +75 -0
- data/demo/demi/unit/nokogiri.rd +148 -0
- data/demo/demi/unit/rexml.rd +63 -0
- data/demo/hash.rb +20 -0
- data/demo/helloworld.rb +12 -0
- data/demo/object.rb +19 -0
- data/demo/table.rb +24 -0
- data/js/jquery.jam.js +230 -0
- data/lib/jam.rb +2 -0
- data/lib/jam/cherry.rb +1 -0
- data/lib/jam/css2xpath.rb +104 -0
- data/lib/jam/css_to_xpath.rb +132 -0
- data/lib/jam/engine.rb +169 -0
- data/lib/jam/hpricot.rb +137 -0
- data/lib/jam/libxml.rb +151 -0
- data/lib/jam/nokogiri.rb +167 -0
- data/lib/jam/rexml.rb +135 -0
- data/lib/jam/template.rb +224 -0
- data/meta/abstract +2 -0
- data/meta/author +1 -0
- data/meta/created +1 -0
- data/meta/homepage +1 -0
- data/meta/package +1 -0
- data/meta/project +1 -0
- data/meta/status +1 -0
- data/meta/summary +1 -0
- data/meta/timestamp +1 -0
- data/meta/title +1 -0
- data/meta/version +1 -0
- metadata +103 -0
data/lib/jam.rb
ADDED
data/lib/jam/cherry.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Cherry Adaptor
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Jam
|
2
|
+
|
3
|
+
def self.css_to_xpath(rule)
|
4
|
+
regElement = /^([#.]?)([a-z0-9\\*_-]*)((\|)([a-z0-9\\*_-]*))?/i
|
5
|
+
regAttr1 = /^\[([^\]]*)\]/i
|
6
|
+
regAttr2 = /^\[\s*([^~=\s]+)\s*(~?=)\s*"([^"]+)"\s*\]/i
|
7
|
+
regPseudo = /^:([a-z_-])+/i
|
8
|
+
regCombinator = /^(\s*[>+\s])?/i
|
9
|
+
regComma = /^\s*,/i
|
10
|
+
|
11
|
+
index = 1;
|
12
|
+
parts = ["//", "*"]
|
13
|
+
lastRule = nil
|
14
|
+
|
15
|
+
while rule.length && rule != lastRule
|
16
|
+
lastRule = rule
|
17
|
+
|
18
|
+
# Trim leading whitespace
|
19
|
+
rule = rule.gsub(/^\s*|\s*$/, "") #.replace
|
20
|
+
break if rule.length == 0
|
21
|
+
|
22
|
+
# Match the element identifier
|
23
|
+
m = regElement.match(rule)
|
24
|
+
if m
|
25
|
+
if !m[1]
|
26
|
+
# XXXjoe Namespace ignored for now
|
27
|
+
if m[5]
|
28
|
+
parts[index] = m[5]
|
29
|
+
else
|
30
|
+
parts[index] = m[2]
|
31
|
+
end
|
32
|
+
elsif (m[1] == '#')
|
33
|
+
parts.push("[@id='" + m[2] + "']")
|
34
|
+
elsif (m[1] == '.')
|
35
|
+
parts.push("[contains(@class, '" + m[2] + "')]")
|
36
|
+
end
|
37
|
+
rule = rule.substr(m[0].length)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Match attribute selectors
|
41
|
+
m = regAttr2.match(rule)
|
42
|
+
if m
|
43
|
+
if m[2] == "~="
|
44
|
+
parts.push("[contains(@" + m[1] + ", '" + m[3] + "')]")
|
45
|
+
else
|
46
|
+
parts.push("[@" + m[1] + "='" + m[3] + "']")
|
47
|
+
end
|
48
|
+
rule = rule.substr(m[0].length)
|
49
|
+
else
|
50
|
+
m = regAttr1.match(rule)
|
51
|
+
if m
|
52
|
+
parts.push("[@" + m[1] + "]")
|
53
|
+
rule = rule.substr(m[0].length)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Skip over pseudo-classes and pseudo-elements, which are of no use to us
|
58
|
+
m = regPseudo.match(rule)
|
59
|
+
while m
|
60
|
+
rule = rule.substr(m[0].length)
|
61
|
+
m = regPseudo.match(rule)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Match combinators
|
65
|
+
m = regCombinator.match(rule)
|
66
|
+
if m && m[0].length
|
67
|
+
if m[0].index(">")
|
68
|
+
parts.push("/")
|
69
|
+
elsif m[0].index("+")
|
70
|
+
parts.push("/following-sibling::")
|
71
|
+
else
|
72
|
+
parts.push("//")
|
73
|
+
end
|
74
|
+
index = parts.length
|
75
|
+
parts.push("*")
|
76
|
+
rule = rule.substr(m[0].length)
|
77
|
+
end
|
78
|
+
|
79
|
+
m = regComma.match(rule)
|
80
|
+
if m
|
81
|
+
parts.push(" | ", "//", "*")
|
82
|
+
index = parts.length - 1
|
83
|
+
rule = rule.substr(m[0].length)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
xpath = parts.join("")
|
88
|
+
return xpath
|
89
|
+
end
|
90
|
+
|
91
|
+
end #module Jam
|
92
|
+
|
93
|
+
|
94
|
+
class String
|
95
|
+
|
96
|
+
#
|
97
|
+
def substr(start, ending=-1)
|
98
|
+
str = self[start..ending]
|
99
|
+
self[start..ending] = ''
|
100
|
+
str
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
@@ -0,0 +1,132 @@
|
|
1
|
+
|
2
|
+
# css_to_xpath - generic CSS to XPath selector transformer
|
3
|
+
#
|
4
|
+
# * @author Andrea Giammarchi
|
5
|
+
# * @license Mit Style License
|
6
|
+
# * @blog http://webreflection.blogspot.com/
|
7
|
+
# * @project http://code.google.com/p/css2xpath/
|
8
|
+
# * @version 0.1 - Converts correctly every SlickSpeed CSS selector [http://mootools.net/slickspeed/]
|
9
|
+
# * @note stand alone vice-versa subproject [http://code.google.com/p/css2xpath/]
|
10
|
+
# * @info http://www.w3.org/TR/CSS2/selector.html
|
11
|
+
# * @credits some tips from Aristotle Pagaltzis [http://plasmasturm.org/log/444/]
|
12
|
+
#
|
13
|
+
|
14
|
+
module Jam
|
15
|
+
|
16
|
+
def self.css_to_xpath(css)
|
17
|
+
CSStoXPath.css_to_xpath(css)
|
18
|
+
end
|
19
|
+
|
20
|
+
module CSStoXPath
|
21
|
+
extend self
|
22
|
+
|
23
|
+
RULES = []
|
24
|
+
|
25
|
+
def rule(re_from, re_to=nil, &block)
|
26
|
+
RULES << [re_from, re_to || block]
|
27
|
+
end
|
28
|
+
|
29
|
+
# add @ for attribs
|
30
|
+
rule /\[([^\]~\$\*\^\|\!]+)(=[^\]]+)?\]/, '[@\1\2]'
|
31
|
+
|
32
|
+
# multiple queries
|
33
|
+
rule /\s*,\s*/, "|"
|
34
|
+
|
35
|
+
# , + ~ >
|
36
|
+
rule /\s*(\+|~|>)\s*/, '\1'
|
37
|
+
|
38
|
+
# * ~ + >
|
39
|
+
rule /([a-zA-Z0-9\_\-\*])~([a-zA-Z0-9\_\-\*])/ , '\1/following-sibling::\2'
|
40
|
+
rule /([a-zA-Z0-9\_\-\*])\+([a-zA-Z0-9\_\-\*])/, '\1/following-sibling::*[1]/self::\2'
|
41
|
+
rule /([a-zA-Z0-9\_\-\*])>([a-zA-Z0-9\_\-\*])/ , '\1/\2'
|
42
|
+
|
43
|
+
# all unescaped stuff escaped
|
44
|
+
rule /\[([^=]+)=([^'|"][^\]]*)\]/, '[\1=\'\2\']'
|
45
|
+
|
46
|
+
# all descendant or self to //
|
47
|
+
rule /(^|[^a-zA-Z0-9\_\-\*])(#|\.)([a-zA-Z0-9\_\-]+)/, '\1*\2\3'
|
48
|
+
rule /([\>\+\|\~\,\s])([a-zA-Z\*]+)/, '\1//\2'
|
49
|
+
rule /\s+\/\//, '//'
|
50
|
+
|
51
|
+
# :first-child
|
52
|
+
rule /([a-zA-Z0-9\_\-\*]+):first-child/, '*[1]/self::\1'
|
53
|
+
|
54
|
+
# :last-child
|
55
|
+
rule /([a-zA-Z0-9\_\-\*]+):last-child/, '\1[not(following-sibling::*)]'
|
56
|
+
|
57
|
+
# :only-child
|
58
|
+
rule /([a-zA-Z0-9\_\-\*]+):only-child/, '*[last()=1]/self::\1'
|
59
|
+
|
60
|
+
# :empty
|
61
|
+
rule /([a-zA-Z0-9\_\-\*]+):empty/, '\1[not(*) and not(normalize-space())]'
|
62
|
+
|
63
|
+
# :not
|
64
|
+
rule /([a-zA-Z0-9\_\-\*]+):not\(([^\)]*)\)/ do |s, a, b|
|
65
|
+
return a.concat("[not(", css_to_xpath(b).gsub(/^[^\[]+\[([^\]]*)\].*$/, '\1'), ")]")
|
66
|
+
end
|
67
|
+
|
68
|
+
# :nth-child
|
69
|
+
rule /([a-zA-Z0-9\_\-\*]+):nth-child\(([^\)]*)\)/ do |s, a, b|
|
70
|
+
case b
|
71
|
+
when "n"
|
72
|
+
return a
|
73
|
+
when "even"
|
74
|
+
return "*[position() mod 2=0 and position()>=0]/self::" + a
|
75
|
+
when "odd"
|
76
|
+
return a + "[(count(preceding-sibling::*) + 1) mod 2=1]"
|
77
|
+
else
|
78
|
+
b = (b || "0").gsub(/^([0-9]*)n.*?([0-9]*)$/, '\1+\2').split("+")
|
79
|
+
b[1] = b[1] || "0"
|
80
|
+
return "*[(position()-".concat(b[1], ") mod ", b[0], "=0 and position()>=", b[1], "]/self::", a)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# :contains(selectors)
|
85
|
+
rule /:contains\(([^\)]*)\)/ do |s, a|
|
86
|
+
# return "[contains(css:lower-case(string(.)),'" + a.toLowerCase() + "')]"; // it does not work in firefox 3*/
|
87
|
+
return "[contains(string(.),'" + a + "')]"
|
88
|
+
end
|
89
|
+
|
90
|
+
# |= attrib
|
91
|
+
rule /\[([a-zA-Z0-9\_\-]+)\|=([^\]]+)\]/, '[@\1=\2 or starts-with(@\1,concat(\2,\'-\'))]'
|
92
|
+
|
93
|
+
# *= attrib
|
94
|
+
rule /\[([a-zA-Z0-9\_\-]+)\*=([^\]]+)\]/, '[contains(@\1,\2)]'
|
95
|
+
|
96
|
+
# ~= attrib
|
97
|
+
rule /\[([a-zA-Z0-9\_\-]+)~=([^\]]+)\]/, '[contains(concat(' ',normalize-space(@\1),' '),concat(' ',\2,' '))]'
|
98
|
+
|
99
|
+
# ^= attrib
|
100
|
+
rule /\[([a-zA-Z0-9\_\-]+)\^=([^\]]+)\]/, '[starts-with(@\1,\2)]'
|
101
|
+
|
102
|
+
# $= attrib
|
103
|
+
rule /\[([a-zA-Z0-9\_\-]+)\$=([^\]]+)\]/ do |s, a, b|
|
104
|
+
return '[substring(@'.concat(a, ',string-length(@', a, ')-', b.length - 3, ')=', b, ']')
|
105
|
+
end
|
106
|
+
|
107
|
+
# != attrib
|
108
|
+
rule /\[([a-zA-Z0-9\_\-]+)\!=([^\]]+)\]/, '[not(@\1) or @\1!=\2]'
|
109
|
+
|
110
|
+
# ids and classes
|
111
|
+
rule /#([a-zA-Z0-9\_\-]+)/ , '[@id=\'\1\']'
|
112
|
+
rule /\.([a-zA-Z0-9\_\-]+)/, '[contains(concat(\' \',normalize-space(@class),\' \'),\' \1 \')]'
|
113
|
+
|
114
|
+
# normalize multiple filters
|
115
|
+
rule /\]\[([^\]]+)/, ' and (\1)'
|
116
|
+
|
117
|
+
def css_to_xpath(css)
|
118
|
+
RULES.each do |f, t|
|
119
|
+
case t
|
120
|
+
when Proc
|
121
|
+
css = css.gsub(f, &t)
|
122
|
+
else
|
123
|
+
css = css.gsub(f, t)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
return ".//" + css;
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
data/lib/jam/engine.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
#require 'facets/hash/to_h'
|
2
|
+
|
3
|
+
module Jam
|
4
|
+
|
5
|
+
# = Jam Engine
|
6
|
+
#
|
7
|
+
# The Engine class serves as the base class for the various Jam adapters.
|
8
|
+
#
|
9
|
+
#--
|
10
|
+
# TODO: Can we normalize parser options across all backends. REXML seems a limiting factor.
|
11
|
+
#++
|
12
|
+
|
13
|
+
class Engine
|
14
|
+
|
15
|
+
# Interpolate data into document, returning the document object.
|
16
|
+
#
|
17
|
+
def interpolate(node, data)
|
18
|
+
interpolate_node(node, data)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Converts plain keys to css selectors.
|
24
|
+
#
|
25
|
+
# TODO: Change to use special attribute instead of 'id'?
|
26
|
+
#
|
27
|
+
def prepare_data(data)
|
28
|
+
d = {}
|
29
|
+
data.each do |key, val|
|
30
|
+
case key.to_s
|
31
|
+
when /^<(.*?)>(.*)$/
|
32
|
+
d["#{$1}#{$2}"] = val
|
33
|
+
when /^\w/
|
34
|
+
d["##{key}"] = val
|
35
|
+
else
|
36
|
+
d[key.to_s] = val
|
37
|
+
end
|
38
|
+
end
|
39
|
+
return d
|
40
|
+
end
|
41
|
+
|
42
|
+
# Interpolate data.
|
43
|
+
#
|
44
|
+
def interpolate_node(node, data)
|
45
|
+
case data
|
46
|
+
when nil
|
47
|
+
remove(node)
|
48
|
+
when Array
|
49
|
+
interpolate_sequence(node, data)
|
50
|
+
when String, Numeric
|
51
|
+
interpolate_scalar(node, data)
|
52
|
+
when Hash
|
53
|
+
interpolate_mapping(node, data)
|
54
|
+
else
|
55
|
+
interpolate_object(node, data)
|
56
|
+
end
|
57
|
+
return node
|
58
|
+
end
|
59
|
+
|
60
|
+
# Interpolate mapping.
|
61
|
+
#
|
62
|
+
def interpolate_mapping(node, data)
|
63
|
+
data = prepare_data(data)
|
64
|
+
|
65
|
+
data.each do |id, val|
|
66
|
+
att = false
|
67
|
+
qry = id.to_s
|
68
|
+
|
69
|
+
result = qry.match(/^((.*?)\/)?([@](.*?))$/);
|
70
|
+
if result
|
71
|
+
att = result[4]
|
72
|
+
qry = result[2] #+ '[@' + att + ']'
|
73
|
+
else
|
74
|
+
att = false
|
75
|
+
end
|
76
|
+
|
77
|
+
if att
|
78
|
+
#qry = qry + '[@' + att + ']';
|
79
|
+
#search(node,qry).attr(att,val);
|
80
|
+
if qry
|
81
|
+
#if tag == false
|
82
|
+
# qry = '#' + qry
|
83
|
+
#end
|
84
|
+
nodeset = search(node, qry)
|
85
|
+
attribute(nodeset, att, val)
|
86
|
+
else
|
87
|
+
attribute(node, att, val)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
nodeset = search(node, qry)
|
91
|
+
#if nodeset.size > 0
|
92
|
+
interpolate_node(nodeset, val)
|
93
|
+
#end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Interpolate object.
|
99
|
+
#
|
100
|
+
def interpolate_object(node, data)
|
101
|
+
data = Hash[data.instance_variables.map{ |iv| [iv[1..-1], data.instance_variable_get(iv)] }]
|
102
|
+
interpolate_mapping(node, data)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Interpolate attribute.
|
106
|
+
#
|
107
|
+
#def interpolate_attribute(nodes, data)
|
108
|
+
#
|
109
|
+
#end
|
110
|
+
|
111
|
+
# Interpolate array sequence.
|
112
|
+
#
|
113
|
+
def interpolate_sequence(nodes, data)
|
114
|
+
each_node(nodes) do |node|
|
115
|
+
parent = node.parent
|
116
|
+
data.each do |new_data|
|
117
|
+
new_node = interpolate_node(node.dup, new_data)
|
118
|
+
append(parent, new_node)
|
119
|
+
remove(node)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Interpolate value.
|
125
|
+
#
|
126
|
+
# TODO This should have some special HTML features ?
|
127
|
+
#
|
128
|
+
# TODO Should we have two modes --one with and one without the extra HTML features?
|
129
|
+
#
|
130
|
+
def interpolate_scalar(nodes, data)
|
131
|
+
#var all_special = new Array;
|
132
|
+
|
133
|
+
# text inputs
|
134
|
+
#var special = this.find('input[@type=text]');
|
135
|
+
#special.val(data.toString());
|
136
|
+
#all_special.concat(special);
|
137
|
+
# textarea
|
138
|
+
# TODO
|
139
|
+
|
140
|
+
#this.not(special).empty();
|
141
|
+
#this.not(special).append(data.toString());
|
142
|
+
#alert(data);
|
143
|
+
#empty(node)
|
144
|
+
replace_content_with_text(nodes, data.to_s)
|
145
|
+
end
|
146
|
+
|
147
|
+
end #class Engine
|
148
|
+
|
149
|
+
end #module Jam
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
=begin
|
154
|
+
# Interpolate list.
|
155
|
+
|
156
|
+
function interpolate_list(node, data) {
|
157
|
+
temp = node.copy(true)
|
158
|
+
node.empty!
|
159
|
+
for (var d in data) {
|
160
|
+
nc = temp.copy(true);
|
161
|
+
interpolate_children( nc, d )
|
162
|
+
for (var c in nc.children) {
|
163
|
+
node.add(c);
|
164
|
+
};
|
165
|
+
};
|
166
|
+
};
|
167
|
+
=end
|
168
|
+
|
169
|
+
|
data/lib/jam/hpricot.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'hpricot'
|
2
|
+
require 'jam/engine'
|
3
|
+
|
4
|
+
module Hpricot
|
5
|
+
|
6
|
+
class Doc
|
7
|
+
def jam(data, opts={})
|
8
|
+
engine = ::Jam::Hpricot.new()
|
9
|
+
engine.interpolate(self, data)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Elem
|
14
|
+
# This turns the Elem into an Elements object containing it.
|
15
|
+
def jam(data, opts={})
|
16
|
+
engine = ::Jam::Hpricot.new()
|
17
|
+
engine.interpolate(::Hpricot::Elements[self], data)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Elements
|
22
|
+
def jam(data, opts={})
|
23
|
+
engine = ::Jam::Hpricot.new()
|
24
|
+
engine.interpolate(self, data)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
module Jam
|
31
|
+
|
32
|
+
# Hpricot Adaptor
|
33
|
+
#
|
34
|
+
class Hpricot < Engine
|
35
|
+
|
36
|
+
#
|
37
|
+
def initialize(*options)
|
38
|
+
@options = options
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
def document(source)
|
43
|
+
::Hpricot.parse(source) #, *@options)
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
def search(node, qry)
|
48
|
+
node.search(qry)
|
49
|
+
end
|
50
|
+
|
51
|
+
# deep copy # TODO: works?
|
52
|
+
def copy(node)
|
53
|
+
node.dup
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
def remove(node)
|
58
|
+
node.remove
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
def empty(ns)
|
63
|
+
ns.empty
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
def append(ns, child)
|
68
|
+
ns.append(child.to_s)
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
def replace(ns, child)
|
73
|
+
empty(ns)
|
74
|
+
append(ns, child)
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
def remove(node)
|
79
|
+
node.remove
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
def replace_content_with_text(node, text)
|
84
|
+
case node
|
85
|
+
when ::Hpricot::Elements
|
86
|
+
node.inner_html = text
|
87
|
+
when ::Array
|
88
|
+
node.each do |n|
|
89
|
+
n.inner_html = text
|
90
|
+
end
|
91
|
+
else
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
def attribute(ns, att, val)
|
98
|
+
ns.set(att, val)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Remove jam nodes that ask for it, and all jam attributes.
|
102
|
+
#
|
103
|
+
def cleanup(node)
|
104
|
+
#node = node.root if ::Nokogiri::XML::Document === node
|
105
|
+
# remove unwanted tags
|
106
|
+
node.search("//*[@jam='erase']").each do |n|
|
107
|
+
unwrap(n)
|
108
|
+
end
|
109
|
+
# remove jam attributes
|
110
|
+
|
111
|
+
#
|
112
|
+
node
|
113
|
+
end
|
114
|
+
|
115
|
+
# Unwrap a node, such that the outer tag is
|
116
|
+
# removed, leaving only it's own children.
|
117
|
+
#
|
118
|
+
def unwrap(node)
|
119
|
+
node.each_child{ |n| node.parent.insert_before(n, node) }
|
120
|
+
::Hpricot::Elements[node].remove
|
121
|
+
end
|
122
|
+
|
123
|
+
# Iterate over each node.
|
124
|
+
#
|
125
|
+
def each_node(nodes)
|
126
|
+
unless ::Hpricot::Elements === nodes
|
127
|
+
nodes = [nodes]
|
128
|
+
end
|
129
|
+
nodes.each do |node|
|
130
|
+
yield(node)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|