Class: Hexp::Node

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Attributes, Children
Defined in:
lib/hexp/node.rb,
lib/hexp/node/pp.rb,
lib/hexp/node/domize.rb,
lib/hexp/node/selector.rb,
lib/hexp/node/children.rb,
lib/hexp/node/rewriter.rb,
lib/hexp/node/normalize.rb,
lib/hexp/node/attributes.rb,
lib/hexp/node/css_selection.rb

Overview

A Hexp::Node represents a single element in a HTML syntax tree. It consists of three parts : the #tag, the #attributes and the #children.

Instances of Hexp::Node are immutable. Because of this all methods that "alter" the node actually return a new instance, leaving the old one untouched.

The Hexp::Node constructor takes a Symbol, a Hash and an Array for the #tag, #attributes and #children respectively. However the attributes and children can each be ommitted if they are empty.

If the node contains a single child node, then it is not necessary to wrap that child node in an array. Just put the single node in place of the list of children.

One can use H[tag, attrs, children] as a shorthand syntax. Finally one can use Hexp.build to construct nodes using Ruby blocks, not unlike the Builder or Nokogiri gems.

Methods that read or alter the attributes Hash are defined in Attributes. Methods that read or alter the list of child nodes are defined in Children

CSS selectors

When working with large trees of Node objects, it is convenient to be able to target specific nodes using CSS selector syntax. Hexp supports a subset of the CSS 3 selector syntax, see CssSelection for info on the supported syntax.

For changing, replacing or removing specific nodes based on a CSS selector string, see #replace. To iterate over nodes, see #select.

Examples:

Immutable nodes : the old one is untouched

node = Hexp::Node.new(:div)
node2 = node.add_class('items')
p node  # => H[:div]
p node2 # => H[:div, 'class' => 'items']

Creating Hexp : syntax alternatives and optional parameters

Hexp::Node.new(:div, class: 'foo')
Hexp::Node.new(:div, {class: 'foo'}, "A text node")
Hexp::Node.new(:div, {class: 'foo'}, ["A text node"])
H[:div, {class: 'foo'}, H[:span, {class: 'big'}, "good stuff"]]
H[:div, {class: 'foo'}, [
    H[:span, {class: 'big'}, "good stuff"],
    H[:a, {href: '/index'}, "go home"]
  ]
]
Hexp.build { div.strong { "Hello, world!" } }

Defined Under Namespace

Modules: Attributes, Children Classes: CssSelection, Domize, Normalize, PP, Rewriter, Selector

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Methods included from Children

#add_child, #empty?, #map_children, #set_children, #text

Methods included from Attributes

#[], #add_class, #attr, #class?, #class_list, #has_attr?, #merge_attrs, #remove_attr, #remove_class, #set_attrs

Constructor Details

- (Hexp::Node) initialize(*args)

Normalize the arguments

Examples:

Hexp::Node.new(:p, {'class' => 'foo'}, [[:b, "Hello, World!"]])

Parameters:

  • args (Array)

    args a Hexp node components



123
124
125
# File 'lib/hexp/node.rb', line 123

def initialize(*args)
  @tag, @attributes, @children = Normalize.new(args).call
end

Instance Attribute Details

- (Hash<String, String>) attributes (readonly)

The attributes of this node

Examples:

H[:p, class: 'foo'].attributes #=> {'class' => 'foo'}

Returns:

  • (Hash<String, String>)


79
80
81
# File 'lib/hexp/node.rb', line 79

def attributes
  @attributes
end

- (Hexp::List) children (readonly)

The child nodes of this node

Examples:

H[:p, [ H[:span], 'hello' ]].children
#=> Hexp::List[ H[:span], Hexp::TextNode["hello"] ]

Returns:



90
91
92
# File 'lib/hexp/node.rb', line 90

def children
  @children
end

- (Symbol) tag (readonly)

The HTML tag of this node

Examples:

H[:p].tag #=> :p

Returns:

  • (Symbol)


69
70
71
# File 'lib/hexp/node.rb', line 69

def tag
  @tag
end

Class Method Details

+ (Hexp::Node) [](*args)

Main entry point for creating literal hexps

At the moment this just redirects to #new, and since Hexp::Node is aliased to H this provides a shorthand for the contructor,

Note that while the H[] form is part of the public API and expected to remain, it's implementation might change. In particular H might become a class or module in its own right, so it is recommended to only use this method in its H[] form.

Examples:

H[:span, {attr: 'value'}].

Parameters:

  • args (Array)

    args a Hexp node components

Returns:



109
110
111
# File 'lib/hexp/node.rb', line 109

def self.[](*args)
  new(*args)
end

+ (String) inspect_name

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the class name for use in creating inspection strings

This will return "H" if H == Hexp::Node, or "Hexp::Node" otherwise.

Returns:

  • (String)


308
309
310
311
312
313
314
# File 'lib/hexp/node.rb', line 308

def inspect_name
  if defined?(H) && H == self
    'H'
  else
    self.name
  end
end

Instance Method Details

- (String) inspect

Return a string representation that is close to the literal form

Examples:

H[:p, {class: 'foo'}].inspect #=> "H[:p, {\"class\"=>\"foo\"}]"

Returns:

  • (String)


175
176
177
# File 'lib/hexp/node.rb', line 175

def inspect
  self.class.inspect_name + [tag, attributes, children].compact.reject(&:empty?).inspect
end

- (String) pp

Pretty print, a multiline representation with indentation

Examples:

H[:p, [[:span], [:div]]].pp # => "H[:p, [\n  H[:span],\n  H[:div]]]"

Returns:

  • (String)


187
188
189
# File 'lib/hexp/node.rb', line 187

def pp
  self.class::PP.new(self).call
end

- (Hexp::Node) process(*processors)

Run a number of processors on this node

This is pure convenience, but it helps to conceptualize the "processor" idea of a component (be it a lambda or other object), that responds to call, and transform a Hexp::Node tree.

Examples:

hexp.process(
  ->(node) { node.replace('.section') {|node| H[:p, class: 'big', node]} },
  ->(node) { node.add_class 'foo' },
  InlineAssets.new
)

Parameters:

  • processors (Array<#call>)

Returns:



271
272
273
# File 'lib/hexp/node.rb', line 271

def process(*processors)
  processors.empty? ? self : processors.first.(self).process(*processors.drop(1))
end

- (Hexp::Node) rewrite(css_selector = nil, &block) Also known as: replace

Replace nodes in a tree

With a CSS selector string like "form.checkout" you specify the nodes you want to operate on. These will be passed one by one into the block. The block returns the Hexp::Node that will replace the old node, or it can replace an Array of nodes to fill the place of the old node.

Because of this you can add one or more nodes, or remove nodes by returning an empty array.

Examples:

Remove all script tags

tree.replace('script') {|_| [] }

Wrap each <input> tag into a <p> tag

tree.replace('input') do |input|
  H[:p, input]
end

Parameters:

  • block (Proc)

    The rewrite action

Returns:



238
239
240
241
# File 'lib/hexp/node.rb', line 238

def rewrite(css_selector = nil, &block)
  return Rewriter.new(self, block) if css_selector.nil?
  CssSelection.new(self, css_selector).rewrite(&block)
end

- (Object) select(css_selector = nil, &block)

Select nodes based on a css selector



246
247
248
249
250
251
252
# File 'lib/hexp/node.rb', line 246

def select(css_selector = nil, &block)
  if css_selector
    CssSelection.new(self, css_selector).each(&block)
  else
    Selector.new(self, block)
  end
end

- (Hexp::Node) set_tag(tag)

Return a new node, with a different tag

Examples:

H[:div, class: 'foo'].set_tag(:bar)
# => H[:bar, class: 'foo']

Parameters:

  • tag (#to_sym)

    The new tag

Returns:



212
213
214
# File 'lib/hexp/node.rb', line 212

def set_tag(tag)
  H[tag.to_sym, attributes, children]
end

- (FalseClass) text?

Is this a text node? Returns false

Examples:

H[:p].text? #=> false

Returns:

  • (FalseClass)


199
200
201
# File 'lib/hexp/node.rb', line 199

def text?
  false
end

- (Nokogiri::HTML::Document) to_dom(options = {})

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Convert this node into a Nokogiri Document

Examples:

H[:p].to_dom
#=> #<Nokogiri::HTML::Document name="document"
      children=[#<Nokogiri::XML::DTD name="html">,
      #<Nokogiri::XML::Element name="p">]>

Returns:

  • (Nokogiri::HTML::Document)


163
164
165
# File 'lib/hexp/node.rb', line 163

def to_dom(options = {})
  Domize.new(self, options).call
end

- (Hexp::Node) to_hexp

Standard hexp coercion protocol, return self

Examples:

H[:p].to_hexp #=> H[:p]

Returns:



135
136
137
# File 'lib/hexp/node.rb', line 135

def to_hexp
  self
end

- (String) to_html(options = {})

Serialize this node to HTML

Examples:

H[:html, [ H[:body, ["hello, world"] ] ]] .to_html
# => "<html><body>hello, world</body></html>"

Returns:

  • (String)


148
149
150
# File 'lib/hexp/node.rb', line 148

def to_html(options = {})
  to_dom(options).to_html
end