Class: Nokolexbor::Node

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/nokolexbor/node.rb

Direct Known Subclasses

Document, DocumentFragment, NodeSet

Constant Summary collapse

ELEMENT_NODE =
1
ATTRIBUTE_NODE =
2
TEXT_NODE =
3
CDATA_SECTION_NODE =
4
ENTITY_REF_NODE =
5
ENTITY_NODE =
6
PI_NODE =
7
COMMENT_NODE =
8
DOCUMENT_NODE =
9
DOCUMENT_TYPE_NODE =
10
DOCUMENT_FRAG_NODE =
11
NOTATION_NODE =
12
LOOKS_LIKE_XPATH =
%r{^(\./|/|\.\.|\.$)}

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#documentDocument (readonly)

Returns The associated Document of this node.

Returns:



21
22
23
# File 'lib/nokolexbor/node.rb', line 21

def document
  @document
end

Instance Method Details

#<<(node_or_tags) ⇒ Node

Add node_or_tags as a child of this Node.

Parameters:

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:

  • #add_child


208
209
210
211
# File 'lib/nokolexbor/node.rb', line 208

def <<(node_or_tags)
  add_child(node_or_tags)
  self
end

#add_class(names) ⇒ Node

Ensure CSS classes are present on self. Any CSS classes in names that already exist in the “class” attribute are not added. Note that any existing duplicates in the “class” attribute are not removed. Compare with #append_class.

This is a convenience function and is equivalent to:

node.kwattr_add("class", names)

Examples:

node.add_class("section") # => <div class="section"></div>
node.add_class("section") # => <div class="section"></div> # duplicate not added
node.add_class("section header") # => <div class="section header"></div>
node.add_class(["section", "header"]) # => <div class="section header"></div>

Parameters:

  • names (String, Array<String>)

    CSS class names to be added to the Node’s “class” attribute. May be a string containing whitespace-delimited names, or an Array of String names. Any class names already present will not be added. Any class names not present will be added. If no “class” attribute exists, one is created.

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:



481
482
483
# File 'lib/nokolexbor/node.rb', line 481

def add_class(names)
  kwattr_add("class", names)
end

#add_next_sibling(node_or_tags) ⇒ Node, NodeSet Also known as: next=

Insert node_or_tags after this Node (as a sibling).

Parameters:

Returns:

Raises:

  • (ArgumentError)

See Also:



165
166
167
168
169
170
# File 'lib/nokolexbor/node.rb', line 165

def add_next_sibling(node_or_tags)
  raise ArgumentError,
    "A document may not have multiple root nodes." if parent&.document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)

  add_sibling(:next, node_or_tags)
end

#add_previous_sibling(node_or_tags) ⇒ Node, NodeSet Also known as: previous=

Insert node_or_tags before this Node (as a sibling).

Parameters:

Returns:

Raises:

  • (ArgumentError)

See Also:



151
152
153
154
155
156
# File 'lib/nokolexbor/node.rb', line 151

def add_previous_sibling(node_or_tags)
  raise ArgumentError,
    "A document may not have multiple root nodes." if parent&.document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)

  add_sibling(:previous, node_or_tags)
end

#after(node_or_tags) ⇒ Node

Insert node_or_tags after this Node (as a sibling).

Parameters:

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:



191
192
193
194
# File 'lib/nokolexbor/node.rb', line 191

def after(node_or_tags)
  add_next_sibling(node_or_tags)
  self
end

#ancestors(selector = nil) ⇒ NodeSet

Get a list of ancestor Node of this Node

Parameters:

  • selector (String, nil) (defaults to: nil)

    The selector to match ancestors

Returns:

  • (NodeSet)

    A set of matched ancestor nodes



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/nokolexbor/node.rb', line 73

def ancestors(selector = nil)
  return NodeSet.new(@document) unless respond_to?(:parent)
  return NodeSet.new(@document) unless parent

  parents = [parent]

  while parents.last.respond_to?(:parent)
    break unless (ctx_parent = parents.last.parent)

    parents << ctx_parent
  end

  return NodeSet.new(@document, parents) unless selector

  root = parents.last
  search_results = root.search(selector)

  NodeSet.new(@document, parents.find_all do |parent|
    search_results.include?(parent)
  end)
end

#append_class(names) ⇒ Node

Add CSS classes to self, regardless of duplication. Compare with #add_class.

This is a convenience function and is equivalent to:

node.kwattr_append("class", names)

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:



497
498
499
# File 'lib/nokolexbor/node.rb', line 497

def append_class(names)
  kwattr_append("class", names)
end

#at(*args) ⇒ Node? Also known as: %

Like #search, but returns the first match.

Returns:

  • (Node, nil)

    The first matched Node.

See Also:



422
423
424
425
426
427
428
429
430
# File 'lib/nokolexbor/node.rb', line 422

def at(*args)
  paths, handler, ns, binds = extract_params(args)

  if paths.size == 1 && !LOOKS_LIKE_XPATH.match?(paths.first)
    return at_css(paths.first)
  end

  at_xpath(*(paths + [ns, handler, binds].compact))
end

#at_css(*args) ⇒ Node?

Like #css, but returns the first match.

This method uses Lexbor as the selector engine. Its performance is much higher than #at_xpath or #nokogiri_at_css.

Returns:

  • (Node, nil)

    The first matched Node.

See Also:



346
347
348
# File 'lib/nokolexbor/node.rb', line 346

def at_css(*args)
  at_css_impl(args.join(', '))
end

#at_xpath(*args) ⇒ Node?

Like #xpath, but returns the first match.

It works the same way as Nokogiri::Node#at_xpath.

Returns:

  • (Node, nil)

    The first matched Node.

See Also:



398
399
400
# File 'lib/nokolexbor/node.rb', line 398

def at_xpath(*args)
  xpath(*args).first
end

#attributesHash{String => Attribute}

Fetch this node’s attributes.

Returns:

  • (Hash{String => Attribute})

    Hash containing attributes belonging to self. The hash keys are String attribute names, and the hash values are Attribute.



248
249
250
251
252
# File 'lib/nokolexbor/node.rb', line 248

def attributes
  attribute_nodes.each_with_object({}) do |node, hash|
    hash[node.name] = node
  end
end

#before(node_or_tags) ⇒ Node

Insert node_or_tags before this Node (as a sibling).

Parameters:

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:



179
180
181
182
# File 'lib/nokolexbor/node.rb', line 179

def before(node_or_tags)
  add_previous_sibling(node_or_tags)
  self
end

#cdata?Boolean

Returns true if this is a CDATA.

Returns:

  • (Boolean)

    true if this is a CDATA



31
32
33
# File 'lib/nokolexbor/node.rb', line 31

def cdata?
  type == CDATA_SECTION_NODE
end

#children=(node) ⇒ Object Also known as: inner_html=

Set the content of this Node.

Parameters:

See Also:



284
285
286
287
# File 'lib/nokolexbor/node.rb', line 284

def children=(node)
  children.remove
  add_child(node)
end

#classesArray

Fetch CSS class names of a Node.

This is a convenience function and is equivalent to:

node.kwattr_values("class")

Examples:

node.classes # => ["section", "title", "header"]

Returns:

  • (Array)

    The CSS classes present in the Node’s “class” attribute. If the attribute is empty or non-existent, the return value is an empty array.

See Also:



451
452
453
# File 'lib/nokolexbor/node.rb', line 451

def classes
  kwattr_values("class")
end

#comment?Boolean

Returns true if this is a Comment.

Returns:

  • (Boolean)

    true if this is a Comment



26
27
28
# File 'lib/nokolexbor/node.rb', line 26

def comment?
  type == COMMENT_NODE
end

#css(*args) ⇒ NodeSet

Search this object for CSS rules. rules must be one or more CSS selectors.

This method uses Lexbor as the selector engine. Its performance is much higher than #xpath or #nokogiri_css.

Examples:

node.css('title')
node.css('body h1.bold')
node.css('div + p.green', 'div#one')

Returns:

  • (NodeSet)

    The matched set of Nodes.

See Also:



334
335
336
# File 'lib/nokolexbor/node.rb', line 334

def css(*args)
  css_impl(args.join(', '))
end

#css_pathObject

Get the path to this node as a CSS expression



62
63
64
65
66
# File 'lib/nokolexbor/node.rb', line 62

def css_path
  path.split(%r{/}).filter_map do |part|
    part.empty? ? nil : part.gsub(/\[(\d+)\]/, ':nth-of-type(\1)')
  end.join(" > ")
end

#document?Boolean

Returns true if this is a Document.

Returns:

  • (Boolean)

    true if this is a Document



57
58
59
# File 'lib/nokolexbor/node.rb', line 57

def document?
  is_a?(Nokolexbor::Document)
end

#each {|String, String| ... } ⇒ Object

Iterate over each attribute name and value pair of this Node.

Yields:

  • (String, String)

    The name and value of the current attribute.



304
305
306
307
308
# File 'lib/nokolexbor/node.rb', line 304

def each
  attributes.each do |name, node|
    yield [name, node.value]
  end
end

#element?Boolean Also known as: elem?

Returns true if this is an Element.

Returns:

  • (Boolean)

    true if this is an Element



51
52
53
# File 'lib/nokolexbor/node.rb', line 51

def element?
  type == ELEMENT_NODE
end

#fragment(tags) ⇒ DocumentFragment

Create a DocumentFragment containing tags that is relative to this context node.

Returns:



314
315
316
# File 'lib/nokolexbor/node.rb', line 314

def fragment(tags)
  Nokolexbor::DocumentFragment.new(document, tags, self)
end

#fragment?Boolean

Returns true if this is a DocumentFragment.

Returns:



46
47
48
# File 'lib/nokolexbor/node.rb', line 46

def fragment?
  type == DOCUMENT_FRAG_NODE
end

#kwattr_add(attribute_name, keywords) ⇒ Node

Ensure that values are present in a keyword attribute.

Any values in keywords that already exist in the Node’s attribute values are not added. Note that any existing duplicates in the attribute values are not removed. Compare with #kwattr_append.

A “keyword attribute” is a node attribute that contains a set of space-delimited values. Perhaps the most familiar example of this is the HTML “class” attribute used to contain CSS classes. But other keyword attributes exist, for instance the “rel” attribute.

Parameters:

  • attribute_name (String)

    The name of the keyword attribute to be modified.

  • keywords (String, Array<String>)

    Keywords to be added to the attribute named attribute_name. May be a string containing whitespace-delimited values, or an Array of String values. Any values already present will not be added. Any values not present will be added. If the named attribute does not exist, it is created.

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:



576
577
578
579
580
581
582
# File 'lib/nokolexbor/node.rb', line 576

def kwattr_add(attribute_name, keywords)
  keywords = keywordify(keywords)
  current_kws = kwattr_values(attribute_name)
  new_kws = (current_kws + (keywords - current_kws)).join(" ")
  set_attr(attribute_name, new_kws)
  self
end

#kwattr_append(attribute_name, keywords) ⇒ Node

Add keywords to a Node’s keyword attribute, regardless of duplication. Compare with #kwattr_add.

A “keyword attribute” is a node attribute that contains a set of space-delimited values. Perhaps the most familiar example of this is the HTML “class” attribute used to contain CSS classes. But other keyword attributes exist, for instance the “rel” attribute.

Parameters:

  • attribute_name (String)

    The name of the keyword attribute to be modified.

  • keywords (String, Array<String>)

    Keywords to be added to the attribute named attribute_name. May be a string containing whitespace-delimited values, or an Array of String values. Any values already present will not be added. Any values not present will be added. If the named attribute does not exist, it is created.

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:



605
606
607
608
609
610
611
# File 'lib/nokolexbor/node.rb', line 605

def kwattr_append(attribute_name, keywords)
  keywords = keywordify(keywords)
  current_kws = kwattr_values(attribute_name)
  new_kws = (current_kws + keywords).join(" ")
  set_attr(attribute_name, new_kws)
  self
end

#kwattr_remove(attribute_name, keywords) ⇒ Node

Remove keywords from a keyword attribute. Any matching keywords that exist in the named attribute are removed, including any multiple entries.

If no keywords remain after this operation, or if keywords is nil, the attribute is deleted from the node.

A “keyword attribute” is a node attribute that contains a set of space-delimited values. Perhaps the most familiar example of this is the HTML “class” attribute used to contain CSS classes. But other keyword attributes exist, for instance the “rel” attribute.

Parameters:

  • attribute_name (String)

    The name of the keyword attribute to be modified.

  • keywords (String, Array<String>)

    Keywords to be added to the attribute named attribute_name. May be a string containing whitespace-delimited values, or an Array of String values. Any values already present will not be added. Any values not present will be added. If the named attribute does not exist, it is created.

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:



637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
# File 'lib/nokolexbor/node.rb', line 637

def kwattr_remove(attribute_name, keywords)
  if keywords.nil?
    remove_attr(attribute_name)
    return self
  end

  keywords = keywordify(keywords)
  current_kws = kwattr_values(attribute_name)
  new_kws = current_kws - keywords
  if new_kws.empty?
    remove_attr(attribute_name)
  else
    set_attr(attribute_name, new_kws.join(" "))
  end
  self
end

#kwattr_values(attribute_name) ⇒ Array<String>

Fetch values from a keyword attribute of a Node.

A “keyword attribute” is a node attribute that contains a set of space-delimited values. Perhaps the most familiar example of this is the HTML “class” attribute used to contain CSS classes. But other keyword attributes exist, for instance the “rel” attribute.

@#kwattr_append @#kwattr_remove

Parameters:

  • attribute_name (String)

    The name of the keyword attribute to be inspected.

Returns:

  • (Array<String>)

    The values present in the Node’s attribute_name attribute. If the attribute is empty or non-existent, the return value is an empty array.

See Also:



548
549
550
# File 'lib/nokolexbor/node.rb', line 548

def kwattr_values(attribute_name)
  keywordify(attr(attribute_name) || [])
end

#matches?(selector) ⇒ Boolean

Returns true if this Node matches selector.

Parameters:

  • selector (String)

    The selector to match

Returns:

  • (Boolean)

    true if this Node matches selector



241
242
243
# File 'lib/nokolexbor/node.rb', line 241

def matches?(selector)
  ancestors.last.css(selector).any? { |node| node == self }
end

#nokogiri_at_css(*args) ⇒ Node?

Like #nokogiri_css, but returns the first match.

This method uses libxml2 as the selector engine. It works the same way as Nokogiri::Node#at_css.

Returns:

  • (Node, nil)

    The first matched Node.

See Also:



372
373
374
# File 'lib/nokolexbor/node.rb', line 372

def nokogiri_at_css(*args)
  nokogiri_css(*args).first
end

#nokogiri_css(*args) ⇒ NodeSet

Search this object for CSS rules. rules must be one or more CSS selectors. It supports a mixed syntax of CSS selectors and XPath.

This method uses libxml2 as the selector engine. It works the same way as Nokogiri::Node#css.

Returns:

  • (NodeSet)

    The matched set of Nodes.

See Also:



358
359
360
361
362
# File 'lib/nokolexbor/node.rb', line 358

def nokogiri_css(*args)
  rules, handler, ns, _ = extract_params(args)

  nokogiri_css_internal(self, rules, handler, ns)
end

#parent=(parent_node) ⇒ Object

Set the parent Node of this Node.

Parameters:

  • parent_node (Node)

    The parent node.



292
293
294
# File 'lib/nokolexbor/node.rb', line 292

def parent=(parent_node)
  parent_node.add_child(self)
end

#prepend_child(node) ⇒ Node, NodeSet

Add node as the first child of this Node.

Parameters:

Returns:

See Also:

  • #add_child


220
221
222
223
224
225
226
227
228
229
# File 'lib/nokolexbor/node.rb', line 220

def prepend_child(node)
  if (first = children.first)
    # Mimic the error add_child would raise.
    raise "Document already has a root node" if document? && !(node.comment? || node.processing_instruction?)

    first.add_sibling(:previous, node)
  else
    add_child(node)
  end
end

#processing_instruction?Boolean

Returns true if this is a ProcessingInstruction.

Returns:

  • (Boolean)

    true if this is a ProcessingInstruction



36
37
38
# File 'lib/nokolexbor/node.rb', line 36

def processing_instruction?
  type == PI_NODE
end

#remove_class(names = nil) ⇒ Node

Remove CSS classes from this node. Any CSS class names in css_classes that exist in this node’s “class” attribute are removed, including any multiple entries.

If no CSS classes remain after this operation, or if css_classes is nil, the “class” attribute is deleted from the node.

This is a convenience function and is equivalent to:

node.kwattr_remove("class", css_classes)

Examples:

node.remove_class("section")
node.remove_class(["section", "float"])

Parameters:

  • names (String, Array<String>) (defaults to: nil)

    CSS class names to be removed from the Node’s “class” attribute. May be a string containing whitespace-delimited names, or an Array of String names. Any class names already present will be removed. If no CSS classes remain, the “class” attribute is deleted.

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:



527
528
529
# File 'lib/nokolexbor/node.rb', line 527

def remove_class(names = nil)
  kwattr_remove("class", names)
end

#replace(node) ⇒ Node, NodeSet

Replace this Node with node.

Parameters:

Returns:

See Also:



261
262
263
264
265
# File 'lib/nokolexbor/node.rb', line 261

def replace(node)
  ret = add_sibling(:previous, node)
  remove
  ret
end

#search(*args) ⇒ NodeSet Also known as: /

Search this object for paths. paths must be one or more XPath or CSS selectors.

Returns:

  • (NodeSet)

    The matched set of Nodes.



405
406
407
408
409
410
411
412
413
# File 'lib/nokolexbor/node.rb', line 405

def search(*args)
  paths, handler, ns, binds = extract_params(args)

  if paths.size == 1 && !LOOKS_LIKE_XPATH.match?(paths.first)
    return css(paths.first)
  end

  xpath(*(paths + [ns, handler, binds].compact))
end

#swap(node) ⇒ Node

Swap this Node for node.

Parameters:

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:



274
275
276
277
# File 'lib/nokolexbor/node.rb', line 274

def swap(node)
  replace(node)
  self
end

#text?Boolean

Returns true if this is a Text.

Returns:

  • (Boolean)

    true if this is a Text



41
42
43
# File 'lib/nokolexbor/node.rb', line 41

def text?
  type == TEXT_NODE
end

#traverse { ... } ⇒ Object

Traverse self and all children.

Yields:

  • self and all children to block recursively.



233
234
235
236
# File 'lib/nokolexbor/node.rb', line 233

def traverse(&block)
  children.each { |j| j.traverse(&block) }
  yield(self)
end

#value?(value) ⇒ Boolean

Returns true if this Node’s attributes include <value>.

Returns:

  • (Boolean)

    true if this Node’s attributes include <value>



297
298
299
# File 'lib/nokolexbor/node.rb', line 297

def value?(value)
  values.include?(value)
end

#wrap(node) ⇒ Node

Wrap this Node with another node.

Examples:

with a String argument:


doc = Nokolexbor::HTML('<body><a>123</a></body>')
doc.at_css('a').wrap('<div></div>')
doc.at_css('body').inner_html
# => "<div><a>123</a></div>"

with a Nokolexbor::Node argument:


doc = Nokolexbor::HTML('<body><a>123</a></body>')
doc.at_css('a').wrap(doc.create_element('div'))
doc.at_css('body').inner_html
# => "<div><a>123</a></div>"

Parameters:

  • node (String, Node)

    A string or a node

    • when String: The markup that is parsed and used as the wrapper. If the parsed fragment has multiple roots, the first root node is used as the wrapper.

    • when Nokolexbor::Node: An element that is cloned and used as the wrapper.

Returns:

  • (Node)

    self, to support chaining of calls.

See Also:



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/nokolexbor/node.rb', line 122

def wrap(node)
  case node
  when String
    new_parent = fragment(node).child
  when DocumentFragment
    new_parent = node.child
  when Node
    new_parent = node.dup
  else
    raise ArgumentError, "Requires a String or Node argument, and cannot accept a #{node.class}"
  end

  if parent
    add_sibling(:next, new_parent)
  else
    new_parent.remove
  end
  new_parent.add_child(self)

  self
end

#write_to(io, *options) ⇒ Object Also known as: write_html_to

Serialize Node and write to io.



655
656
657
# File 'lib/nokolexbor/node.rb', line 655

def write_to(io, *options)
  io.write(to_html(*options))
end

#xpath(*args) ⇒ NodeSet

Search this node for XPath paths. paths must be one or more XPath queries.

It works the same way as Nokogiri::Node#xpath.

Examples:

node.xpath('.//title')

Returns:

  • (NodeSet)

    The matched set of Nodes.



385
386
387
388
389
# File 'lib/nokolexbor/node.rb', line 385

def xpath(*args)
  paths, handler, ns, binds = extract_params(args)

  xpath_internal(self, paths, handler, ns, binds)
end