hexp 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +4 -0
- data/.travis.yml +2 -3
- data/.yardopts +1 -0
- data/Gemfile.devtools +17 -24
- data/Gemfile.lock +77 -82
- data/{LICENSE.md → LICENSE} +0 -0
- data/README.md +20 -10
- data/Rakefile +17 -1
- data/bench/node/rewrite_bench.rb +23 -0
- data/config/flay.yml +1 -1
- data/config/flog.yml +1 -1
- data/config/reek.yml +6 -0
- data/config/yardstick.yml +7 -2
- data/hexp.gemspec +6 -5
- data/lib/hexp.rb +26 -18
- data/lib/hexp/builder.rb +54 -35
- data/lib/hexp/css_selector.rb +124 -25
- data/lib/hexp/css_selector/parser.rb +45 -2
- data/lib/hexp/css_selector/sass_parser.rb +16 -0
- data/lib/hexp/dom.rb +2 -0
- data/lib/hexp/dsl.rb +20 -21
- data/lib/hexp/errors.rb +2 -2
- data/lib/hexp/list.rb +14 -11
- data/lib/hexp/node.rb +89 -38
- data/lib/hexp/node/attributes.rb +43 -26
- data/lib/hexp/node/children.rb +59 -4
- data/lib/hexp/node/css_selection.rb +113 -7
- data/lib/hexp/node/domize.rb +22 -13
- data/lib/hexp/node/normalize.rb +3 -9
- data/lib/hexp/node/pp.rb +13 -9
- data/lib/hexp/node/rewriter.rb +28 -3
- data/lib/hexp/node/{selector.rb → selection.rb} +48 -2
- data/lib/hexp/nokogiri/reader.rb +2 -2
- data/lib/hexp/text_node.rb +1 -1
- data/lib/hexp/version.rb +1 -1
- data/spec/unit/hexp/css_selector/universal_spec.rb +7 -0
- data/spec/unit/hexp/node/domize_spec.rb +12 -0
- data/spec/unit/hexp/node/{selector_spec.rb → selection_spec.rb} +9 -9
- data/spec/unit/hexp/parse_spec.rb +10 -0
- metadata +40 -44
- data/SPEC.md +0 -53
- data/notes +0 -34
@@ -10,35 +10,75 @@ module Hexp
|
|
10
10
|
#
|
11
11
|
# The classes that make up the parse tree largely mimic the ones from SASS,
|
12
12
|
# like CommaSequence, SimpleSequence, Class, Id, etc. By having them in our
|
13
|
-
# own namespace however we can easily add Hexp-specific
|
13
|
+
# own namespace however we can easily add Hexp-specific functionality to them.
|
14
14
|
#
|
15
15
|
class Parser
|
16
|
+
# Initialize the parser with the selector to parse
|
17
|
+
#
|
18
|
+
# @param [String] selector
|
19
|
+
#
|
20
|
+
# @api private
|
16
21
|
def initialize(selector)
|
17
22
|
@selector = selector.freeze
|
18
23
|
end
|
19
24
|
|
25
|
+
# Parse the selector
|
26
|
+
#
|
27
|
+
# @return [Hexp::CssSelector::CommaSequence]
|
28
|
+
#
|
29
|
+
# @api private
|
20
30
|
def parse
|
21
31
|
rewrite_comma_sequence(SassParser.call(@selector))
|
22
32
|
end
|
23
33
|
|
34
|
+
# Parse a CSS selector in one go
|
35
|
+
#
|
36
|
+
# @param [String] selector
|
37
|
+
# @return [Hexp::CssSelector::CommaSequence]
|
38
|
+
#
|
39
|
+
# @api private
|
24
40
|
def self.call(selector)
|
25
41
|
new(selector).parse
|
26
42
|
end
|
27
43
|
|
28
44
|
private
|
29
45
|
|
46
|
+
# Map CommaSequence from the SASS namespace to our own
|
47
|
+
#
|
48
|
+
# @param [Sass::Selector::CommaSequence] comma_sequence
|
49
|
+
# @return [Hexp::CssSelector::CommaSequence]
|
50
|
+
#
|
51
|
+
# @api private
|
30
52
|
def rewrite_comma_sequence(comma_sequence)
|
31
53
|
CommaSequence.new(comma_sequence.members.map{|sequence| rewrite_sequence(sequence)})
|
32
54
|
end
|
33
55
|
|
56
|
+
# Map Sequence from the SASS namespace to our own
|
57
|
+
#
|
58
|
+
# @param [Sass::Selector::Sequence] comma_sequence
|
59
|
+
# @return [Hexp::CssSelector::Sequence]
|
60
|
+
#
|
61
|
+
# @api private
|
34
62
|
def rewrite_sequence(sequence)
|
35
63
|
Sequence.new(sequence.members.map{|simple_sequence| rewrite_simple_sequence(simple_sequence)})
|
36
64
|
end
|
37
65
|
|
66
|
+
# Map SimpleSequence from the SASS namespace to our own
|
67
|
+
#
|
68
|
+
# @param [Sass::Selector::SimpleSequence] comma_sequence
|
69
|
+
# @return [Hexp::CssSelector::SimpleSequence]
|
70
|
+
#
|
71
|
+
# @api private
|
38
72
|
def rewrite_simple_sequence(simple_sequence)
|
39
73
|
SimpleSequence.new(simple_sequence.members.map{|simple| rewrite_simple(simple)})
|
40
74
|
end
|
41
75
|
|
76
|
+
# Map Simple from the SASS namespace to our own
|
77
|
+
#
|
78
|
+
# @param [Sass::Selector::Simple] comma_sequence
|
79
|
+
# @return [Hexp::CssSelector::Simple]
|
80
|
+
#
|
81
|
+
# @api private
|
42
82
|
def rewrite_simple(simple)
|
43
83
|
case simple
|
44
84
|
when ::Sass::Selector::Element # span
|
@@ -50,7 +90,7 @@ module Hexp
|
|
50
90
|
when ::Sass::Selector::Attribute # [href^="http://"]
|
51
91
|
raise "CSS attribute selector flags are curently ignored by Hexp (not implemented)" unless simple.flags.nil?
|
52
92
|
raise "CSS attribute namespaces are curently ignored by Hexp (not implemented)" unless simple.namespace.nil?
|
53
|
-
raise "CSS attribute operator #{simple.operator} not understood by Hexp" unless %w[= ~= ^=].include?(simple.operator) || simple.operator.nil?
|
93
|
+
raise "CSS attribute operator #{simple.operator} not understood by Hexp" unless %w[= ~= ^= |= $= *=].include?(simple.operator) || simple.operator.nil?
|
54
94
|
Attribute.new(
|
55
95
|
simple.name.first,
|
56
96
|
simple.namespace,
|
@@ -58,10 +98,13 @@ module Hexp
|
|
58
98
|
simple.value ? simple.value.first : nil,
|
59
99
|
simple.flags
|
60
100
|
)
|
101
|
+
when ::Sass::Selector::Universal # *
|
102
|
+
Universal.new
|
61
103
|
else
|
62
104
|
raise "CSS selectors containing #{simple.class} are not implemented in Hexp"
|
63
105
|
end
|
64
106
|
|
107
|
+
# As of yet unimplemented
|
65
108
|
# when ::Sass::Selector::Universal # *
|
66
109
|
# when ::Sass::Selector::Parent # & in Sass
|
67
110
|
# when ::Sass::Selector::Interpolation # #{} in Sass
|
@@ -3,10 +3,20 @@ module Hexp
|
|
3
3
|
# A CSS Parser that only knows how to parse CSS selectors
|
4
4
|
#
|
5
5
|
class SassParser < ::Sass::SCSS::CssParser
|
6
|
+
# Initialize the parser with the selector to parse
|
7
|
+
#
|
8
|
+
# @param [String] selector
|
9
|
+
#
|
10
|
+
# @api private
|
6
11
|
def initialize(selector)
|
7
12
|
super(selector, '')
|
8
13
|
end
|
9
14
|
|
15
|
+
# Parse the selector
|
16
|
+
#
|
17
|
+
# @return [Sass::Selector::CommaSequence]
|
18
|
+
#
|
19
|
+
# @api private
|
10
20
|
def parse
|
11
21
|
init_scanner!
|
12
22
|
result = selector_comma_sequence
|
@@ -14,6 +24,12 @@ module Hexp
|
|
14
24
|
result
|
15
25
|
end
|
16
26
|
|
27
|
+
# Parse a CSS selector in one go
|
28
|
+
#
|
29
|
+
# @param [String] selector
|
30
|
+
# @return [Sass::Selector::CommaSequence]
|
31
|
+
#
|
32
|
+
# @api private
|
17
33
|
def self.call(selector)
|
18
34
|
self.new(selector).parse
|
19
35
|
end
|
data/lib/hexp/dom.rb
CHANGED
data/lib/hexp/dsl.rb
CHANGED
@@ -1,26 +1,25 @@
|
|
1
1
|
module Hexp
|
2
|
+
# Make the Hexp::Node DSL available to objects that implement `to_hexp`
|
3
|
+
#
|
4
|
+
# Mixing in {Hexp} has the same effect as mixing in {Hexp::DSL}, and is the
|
5
|
+
# recommended way.
|
6
|
+
#
|
2
7
|
module DSL
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
:
|
12
|
-
:
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
:text,
|
19
|
-
:remove_attr,
|
20
|
-
:set_attributes,
|
21
|
-
].each do |meth|
|
22
|
-
define_method meth do |*args, &blk|
|
23
|
-
to_hexp.public_send(meth, *args, &blk)
|
8
|
+
# The names of methods related to manipulating the list of children of a node
|
9
|
+
CHILDREN_METHODS = Hexp::Node::Children.public_instance_methods.freeze
|
10
|
+
|
11
|
+
# The names of methods related to a node's attributes
|
12
|
+
ATTRIBUTES_METHODS = Hexp::Node::Attributes.public_instance_methods.freeze
|
13
|
+
|
14
|
+
# Methods that are defined directly in {Hexp::Node}
|
15
|
+
NODE_METHODS = Hexp::Node.public_instance_methods(false) - [
|
16
|
+
:to_hexp,
|
17
|
+
:inspect
|
18
|
+
]
|
19
|
+
|
20
|
+
[CHILDREN_METHODS, ATTRIBUTES_METHODS, NODE_METHODS].flatten.each do |method|
|
21
|
+
define_method method do |*args, &blk|
|
22
|
+
to_hexp.public_send(method, *args, &blk)
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|
data/lib/hexp/errors.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module Hexp
|
2
|
+
# Base class for exceptions raised by Hexp
|
3
|
+
#
|
2
4
|
Error = Class.new(StandardError)
|
3
5
|
|
4
6
|
# Raised when trying to stick things inside a Hexp where they don't belong
|
@@ -7,7 +9,6 @@ module Hexp
|
|
7
9
|
# Create a new FormatError
|
8
10
|
#
|
9
11
|
# @api private
|
10
|
-
#
|
11
12
|
def initialize(msg = 'You have illegal contents in your Hexp')
|
12
13
|
super
|
13
14
|
end
|
@@ -17,5 +18,4 @@ module Hexp
|
|
17
18
|
#
|
18
19
|
class ParseError < Error
|
19
20
|
end
|
20
|
-
|
21
21
|
end
|
data/lib/hexp/list.rb
CHANGED
@@ -8,10 +8,10 @@ module Hexp
|
|
8
8
|
# @example
|
9
9
|
# Hexp::List.new([H[:p], H[:div]])
|
10
10
|
#
|
11
|
-
# @param
|
11
|
+
# @param [#to_ary] nodes
|
12
|
+
# List of nodes
|
12
13
|
#
|
13
14
|
# @api public
|
14
|
-
#
|
15
15
|
def initialize(nodes)
|
16
16
|
super nodes.to_ary.freeze
|
17
17
|
end
|
@@ -24,11 +24,12 @@ module Hexp
|
|
24
24
|
# Hexp::Node[:hr],
|
25
25
|
# ]
|
26
26
|
#
|
27
|
-
# @param
|
27
|
+
# @param [Array] args
|
28
|
+
# individual nodes
|
28
29
|
#
|
29
30
|
# @return [Hexp::List]
|
30
|
-
# @api public
|
31
31
|
#
|
32
|
+
# @api public
|
32
33
|
def self.[](*args)
|
33
34
|
new(args)
|
34
35
|
end
|
@@ -39,21 +40,21 @@ module Hexp
|
|
39
40
|
# that this is a wrapping class. This is convenient when inspecting nested
|
40
41
|
# hexps, but probably something we want to solve differently.
|
41
42
|
#
|
42
|
-
# @
|
43
|
-
# @return string
|
43
|
+
# @return [String]
|
44
44
|
#
|
45
|
+
# @api private
|
45
46
|
def inspect
|
46
47
|
__getobj__.inspect
|
47
48
|
end
|
48
49
|
|
49
|
-
#
|
50
|
+
# Implicit conversion to Array
|
50
51
|
#
|
51
52
|
# @example
|
52
53
|
# Hexp::List[ H[:p], H[:span] ].to_ary #=> [H[:p], H[:span]]
|
53
54
|
#
|
54
55
|
# @return [Array<Hexp::Node>]
|
55
|
-
# @api public
|
56
56
|
#
|
57
|
+
# @api public
|
57
58
|
def to_ary
|
58
59
|
__getobj__
|
59
60
|
end
|
@@ -72,10 +73,12 @@ module Hexp
|
|
72
73
|
# H[:div, [[:span]]].children.eql? [H[:span]] #=> false
|
73
74
|
# H[:div, [[:span]]].children.eql? Hexp::List[H[:span]] #=> true
|
74
75
|
#
|
75
|
-
# @param
|
76
|
-
#
|
77
|
-
# @return [Boolean]
|
76
|
+
# @param [Object] other
|
77
|
+
# Object to compare with
|
78
78
|
#
|
79
|
+
# @return [true,false]
|
80
|
+
#
|
81
|
+
# @api public
|
79
82
|
def eql?(other)
|
80
83
|
self == other && self.class == other.class
|
81
84
|
end
|
data/lib/hexp/node.rb
CHANGED
@@ -1,5 +1,56 @@
|
|
1
1
|
module Hexp
|
2
|
-
# A Hexp
|
2
|
+
# A +Hexp::Node+ represents a single element in a HTML syntax tree. It
|
3
|
+
# consists of three parts : the {#tag}, the {#attributes} and the {#children}.
|
4
|
+
#
|
5
|
+
# Instances of +Hexp::Node+ are immutable. Because of this all methods that
|
6
|
+
# "alter" the node actually return a new instance, leaving the old one
|
7
|
+
# untouched.
|
8
|
+
#
|
9
|
+
# @example Immutable nodes : the old one is untouched
|
10
|
+
# node = Hexp::Node.new(:div)
|
11
|
+
# node2 = node.add_class('items')
|
12
|
+
# p node # => H[:div]
|
13
|
+
# p node2 # => H[:div, 'class' => 'items']
|
14
|
+
#
|
15
|
+
# The +Hexp::Node+ constructor takes a +Symbol+, a +Hash+ and an +Array+ for
|
16
|
+
# the {#tag}, {#attributes} and {#children} respectively. However the
|
17
|
+
# attributes and children can each be ommitted if they are empty.
|
18
|
+
#
|
19
|
+
# If the node contains a single child node, then it is not necessary to wrap
|
20
|
+
# that child node in an array. Just put the single node in place of the list
|
21
|
+
# of children.
|
22
|
+
#
|
23
|
+
# One can use +H[tag, attrs, children]+ as a
|
24
|
+
# shorthand syntax. Finally one can use {Hexp.build Hexp.build} to construct nodes using
|
25
|
+
# Ruby blocks, not unlike the Builder or Nokogiri gems.
|
26
|
+
#
|
27
|
+
# @example Creating Hexp : syntax alternatives and optional parameters
|
28
|
+
# Hexp::Node.new(:div, class: 'foo')
|
29
|
+
# Hexp::Node.new(:div, {class: 'foo'}, "A text node")
|
30
|
+
# Hexp::Node.new(:div, {class: 'foo'}, ["A text node"])
|
31
|
+
# H[:div, {class: 'foo'}, H[:span, {class: 'big'}, "good stuff"]]
|
32
|
+
# H[:div, {class: 'foo'}, [
|
33
|
+
# H[:span, {class: 'big'}, "good stuff"],
|
34
|
+
# H[:a, {href: '/index'}, "go home"]
|
35
|
+
# ]
|
36
|
+
# ]
|
37
|
+
# Hexp.build { div.strong { "Hello, world!" } }
|
38
|
+
#
|
39
|
+
# Methods that read or alter the attributes Hash are defined in
|
40
|
+
# {Hexp::Node::Attributes}. Methods that read or alter the list of child nodes
|
41
|
+
# are defined in {Hexp::Node::Children}
|
42
|
+
#
|
43
|
+
# == CSS selectors
|
44
|
+
#
|
45
|
+
# When working with large trees of {Hexp::Node} objects, it is convenient to
|
46
|
+
# be able to target specific nodes using CSS selector syntax. Hexp supports a
|
47
|
+
# subset of the CSS 3 selector syntax, see {Hexp::Node::CssSelection} for info
|
48
|
+
# on the supported syntax.
|
49
|
+
#
|
50
|
+
# For changing, replacing or removing specific nodes based on a CSS selector
|
51
|
+
# string, see {Hexp::Node#replace}. To iterate over nodes, see
|
52
|
+
# {Hexp::Node#select}.
|
53
|
+
#
|
3
54
|
class Node
|
4
55
|
include Equalizer.new(:tag, :attributes, :children)
|
5
56
|
extend Forwardable
|
@@ -149,66 +200,66 @@ module Hexp
|
|
149
200
|
false
|
150
201
|
end
|
151
202
|
|
203
|
+
# Return a new node, with a different tag
|
204
|
+
#
|
205
|
+
# @example
|
206
|
+
# H[:div, class: 'foo'].set_tag(:bar)
|
207
|
+
# # => H[:bar, class: 'foo']
|
208
|
+
#
|
209
|
+
# @param tag [#to_sym] The new tag
|
210
|
+
# @return [Hexp::Node]
|
211
|
+
#
|
212
|
+
# @api public
|
152
213
|
def set_tag(tag)
|
153
214
|
H[tag.to_sym, attributes, children]
|
154
215
|
end
|
155
216
|
|
156
|
-
#
|
157
|
-
#
|
158
|
-
# Since nodes are immutable, this is the main entry point for deriving nodes
|
159
|
-
# from others.
|
217
|
+
# Replace nodes in a tree
|
160
218
|
#
|
161
|
-
#
|
162
|
-
#
|
219
|
+
# With a CSS selector string like +"form.checkout"+ you specify the nodes
|
220
|
+
# you want to operate on. These will be passed one by one into the block.
|
221
|
+
# The block returns the {Hexp::Node} that will replace the old node, or it
|
222
|
+
# can replace an +Array+ of nodes to fill the place of the old node.
|
163
223
|
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
# nodes.
|
224
|
+
# Because of this you can add one or more nodes, or remove nodes by
|
225
|
+
# returning an empty array.
|
167
226
|
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
# needs to return one of these
|
227
|
+
# @example Remove all script tags
|
228
|
+
# tree.replace('script') {|_| [] }
|
171
229
|
#
|
172
|
-
#
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
230
|
+
# @example Wrap each +<input>+ tag into a +<p>+ tag
|
231
|
+
# tree.replace('input') do |input|
|
232
|
+
# H[:p, input]
|
233
|
+
# end
|
176
234
|
#
|
177
|
-
#
|
178
|
-
# currently referenced node where it is. This is very handy when you only want
|
179
|
-
# to act on certain nodes, just return nothing if you want to do nothing.
|
235
|
+
# @param [String] css_selector
|
180
236
|
#
|
181
|
-
#
|
237
|
+
# @yieldparam [Hexp::Node]
|
238
|
+
# The matching nodes
|
182
239
|
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
# @example
|
186
|
-
# tree.rewrite{|node| [] if node.tag == :script }
|
187
|
-
#
|
188
|
-
# Wrap each <input> tag into a <p> tag
|
240
|
+
# @return [Hexp::Node]
|
241
|
+
# The rewritten tree
|
189
242
|
#
|
190
|
-
# @example
|
191
|
-
# tree.rewrite do |node|
|
192
|
-
# if node.tag == :input
|
193
|
-
# [ H[:p, [ child ] ]
|
194
|
-
# end
|
195
|
-
# end
|
196
|
-
#
|
197
|
-
# @param blk [Proc] The rewrite action
|
198
|
-
# @return [Hexp::Node] The rewritten tree
|
199
243
|
# @api public
|
200
|
-
#
|
201
244
|
def rewrite(css_selector = nil, &block)
|
202
245
|
return Rewriter.new(self, block) if css_selector.nil?
|
203
246
|
CssSelection.new(self, css_selector).rewrite(&block)
|
204
247
|
end
|
205
248
|
alias :replace :rewrite
|
206
249
|
|
250
|
+
# Select nodes based on a css selector
|
251
|
+
#
|
252
|
+
# @param [String] css_selector
|
253
|
+
# @yieldparam [Hexp::Node]
|
254
|
+
#
|
255
|
+
# @return [Hexp::Selector,Hexp::CssSelector]
|
256
|
+
#
|
257
|
+
# @api public
|
207
258
|
def select(css_selector = nil, &block)
|
208
259
|
if css_selector
|
209
260
|
CssSelection.new(self, css_selector).each(&block)
|
210
261
|
else
|
211
|
-
|
262
|
+
Selection.new(self, block)
|
212
263
|
end
|
213
264
|
end
|
214
265
|
|
data/lib/hexp/node/attributes.rb
CHANGED
@@ -14,9 +14,10 @@ module Hexp
|
|
14
14
|
# H[:p, class: 'hello'].attr('id', 'para1') # => H[:p, {"class"=>"hello", "id"=>"para1"}]
|
15
15
|
# H[:p, class: 'hello'].attr('class', nil) # => H[:p]
|
16
16
|
#
|
17
|
+
# @param [Array<#to_s>] args
|
17
18
|
# @return [String|Hexp::Node]
|
18
|
-
# @api private
|
19
19
|
#
|
20
|
+
# @api private
|
20
21
|
def attr(*args)
|
21
22
|
arity = args.count
|
22
23
|
attr_name = args[0].to_s
|
@@ -38,10 +39,12 @@ module Hexp
|
|
38
39
|
# @example
|
39
40
|
# H[:option].has_attr?('selected') #=> false
|
40
41
|
#
|
41
|
-
# @param
|
42
|
-
#
|
43
|
-
# @api public
|
42
|
+
# @param [String|Symbol] name
|
43
|
+
# The name of the attribute
|
44
44
|
#
|
45
|
+
# @return [true,false]
|
46
|
+
#
|
47
|
+
# @api public
|
45
48
|
def has_attr?(name)
|
46
49
|
attributes.has_key? name.to_s
|
47
50
|
end
|
@@ -51,10 +54,13 @@ module Hexp
|
|
51
54
|
# @example
|
52
55
|
# H[:span, class: "banner strong"].class?("strong") #=> true
|
53
56
|
#
|
54
|
-
# @param
|
55
|
-
#
|
56
|
-
#
|
57
|
+
# @param [String] klass
|
58
|
+
# The name of the class to check for
|
59
|
+
#
|
60
|
+
# @return [Boolean]
|
61
|
+
# True if the class is present, false otherwise
|
57
62
|
#
|
63
|
+
# @api public
|
58
64
|
def class?(klass)
|
59
65
|
attr('class') && attr('class').split(' ').include?(klass.to_s)
|
60
66
|
end
|
@@ -64,10 +70,12 @@ module Hexp
|
|
64
70
|
# @example
|
65
71
|
# H[:div].add_class('foo') #=> H[:div, class: 'foo']
|
66
72
|
#
|
67
|
-
# @param
|
73
|
+
# @param [#to_s] klass
|
74
|
+
# The class to add
|
75
|
+
#
|
68
76
|
# @return [Hexp::Node]
|
69
|
-
# @api public
|
70
77
|
#
|
78
|
+
# @api public
|
71
79
|
def add_class(klass)
|
72
80
|
attr('class', [attr('class'), klass].compact.join(' '))
|
73
81
|
end
|
@@ -77,8 +85,8 @@ module Hexp
|
|
77
85
|
# Convenience method so you don't have to split the class list yourself.
|
78
86
|
#
|
79
87
|
# @return [Array<String>]
|
80
|
-
# @api public
|
81
88
|
#
|
89
|
+
# @api public
|
82
90
|
def class_list
|
83
91
|
@class_list ||= (attr('class') || '').split(' ').freeze
|
84
92
|
end
|
@@ -93,11 +101,12 @@ module Hexp
|
|
93
101
|
# Calling this on a node with a class attribute that is equal to an
|
94
102
|
# empty string will result in the class attribute being removed.
|
95
103
|
#
|
96
|
-
# @param
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
104
|
+
# @param [#to_s] klass
|
105
|
+
# The class to be removed
|
106
|
+
# @return [Hexp::Node]
|
107
|
+
# A node that is identical to this one, but with the given class removed
|
100
108
|
#
|
109
|
+
# @api public
|
101
110
|
def remove_class(klass)
|
102
111
|
return self unless has_attr?('class')
|
103
112
|
new_list = class_list - [klass.to_s]
|
@@ -107,10 +116,11 @@ module Hexp
|
|
107
116
|
|
108
117
|
# Set or override multiple attributes using a hash syntax
|
109
118
|
#
|
110
|
-
# @param
|
119
|
+
# @param [Hash<#to_s,#to_s>] attrs
|
120
|
+
#
|
111
121
|
# @return [Hexp::Node]
|
112
|
-
# @api public
|
113
122
|
#
|
123
|
+
# @api public
|
114
124
|
def set_attrs(attrs)
|
115
125
|
H[
|
116
126
|
self.tag,
|
@@ -123,10 +133,13 @@ module Hexp
|
|
123
133
|
|
124
134
|
# Remove an attribute by name
|
125
135
|
#
|
126
|
-
# @param
|
127
|
-
#
|
128
|
-
#
|
136
|
+
# @param [#to_s] name
|
137
|
+
# The attribute to be removed
|
138
|
+
#
|
139
|
+
# @return [Hexp::Node]
|
140
|
+
# A new node with the attribute removed
|
129
141
|
#
|
142
|
+
# @api public
|
130
143
|
def remove_attr(name)
|
131
144
|
H[
|
132
145
|
self.tag,
|
@@ -137,10 +150,13 @@ module Hexp
|
|
137
150
|
|
138
151
|
# Attribute accessor
|
139
152
|
#
|
140
|
-
# @
|
141
|
-
#
|
142
|
-
# @api public
|
153
|
+
# @param_name [#to_s] attr
|
154
|
+
# The name of the attribute
|
143
155
|
#
|
156
|
+
# @return [String]
|
157
|
+
# The value of the attribute
|
158
|
+
#
|
159
|
+
# @api public
|
144
160
|
def [](attr_name)
|
145
161
|
self.attributes[attr_name.to_s]
|
146
162
|
end
|
@@ -148,16 +164,17 @@ module Hexp
|
|
148
164
|
# Merge attributes into this Hexp
|
149
165
|
#
|
150
166
|
# Class attributes are treated special : the class lists are merged, rather
|
151
|
-
# than being overwritten. See {set_attrs} for a more basic version.
|
167
|
+
# than being overwritten. See {#set_attrs} for a more basic version.
|
152
168
|
#
|
153
|
-
# This method is analoguous with
|
169
|
+
# This method is analoguous with `Hash#merge`. As argument it can take a
|
154
170
|
# Hash, or another Hexp element, in which case that element's attributes
|
155
171
|
# are used.
|
156
172
|
#
|
157
|
-
# @
|
173
|
+
# @param_or_hash [#to_hexp|Hash] node
|
174
|
+
#
|
158
175
|
# @return [Hexp::Node]
|
159
|
-
# @api public
|
160
176
|
#
|
177
|
+
# @api public
|
161
178
|
def merge_attrs(node_or_hash)
|
162
179
|
hash = node_or_hash.respond_to?(:to_hexp) ?
|
163
180
|
node_or_hash.to_hexp.attributes : node_or_hash
|