Module: Leptris::XML::Searchable

Included in:
Document, DocumentFragment, Node, NodeSet
Defined in:
lib/leptris/xml/searchable.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.wrap_xpath_first_result(document, result_ptr) ⇒ Object

Single-node seam beside wrap_xpath_result: a nodeset answers via result_get_node(0) + wrap + free — no NodeSet container, no AutoPointer, one fewer FFI than xpath().first; scalars keep the full-wrapper semantics.



121
122
123
124
125
126
127
128
129
130
131
# File 'lib/leptris/xml/searchable.rb', line 121

def self.wrap_xpath_first_result(document, result_ptr)
  type = Leptris::XML::FFI.leptris_xpath_result_type(result_ptr)
  if type == Leptris::XML::FFI::XPATH_NODESET
    ptr = Leptris::XML::FFI.leptris_xpath_result_get_node(result_ptr, 0)
    node = ptr.null? ? nil : Leptris::XML::Node.wrap(ptr, document)
    Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
    node
  else
    wrap_xpath_result(document, result_ptr)
  end
end

.wrap_xpath_result(document, result_ptr) ⇒ Object

Shared by Searchable#xpath and Leptris::XML::XPath (compiled expressions): wraps a raw XPathResult pointer into the Ruby-typed result and frees the C handle.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/leptris/xml/searchable.rb', line 136

def self.wrap_xpath_result(document, result_ptr)
  type = Leptris::XML::FFI.leptris_xpath_result_type(result_ptr)
  case type
  when Leptris::XML::FFI::XPATH_NODESET
    Leptris::XML::NodeSet.from_result(document, result_ptr)
  when Leptris::XML::FFI::XPATH_BOOLEAN
    v = Leptris::XML::FFI.leptris_xpath_result_boolean(result_ptr) != 0
    Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
    v
  when Leptris::XML::FFI::XPATH_NUMBER
    v = Leptris::XML::FFI.leptris_xpath_result_number(result_ptr)
    Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
    v
  when Leptris::XML::FFI::XPATH_STRING
    str_ptr = Leptris::XML::FFI.leptris_xpath_result_string(result_ptr)
    v = Leptris::XML::FFI.read_owned_string(str_ptr)
    Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
    v
  else
    Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
    raise Leptris::XML::XPathError, "unknown xpath result type #{type}"
  end
end

Instance Method Details

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

Single-node seam (round XIV): dispatch on syntax like #search, then call the at_* fast paths directly — no NodeSet container, no result handle, one fewer dispatch than search().first.



56
57
58
59
60
61
62
63
# File 'lib/leptris/xml/searchable.rb', line 56

def at(*args)
  paths = args.first.is_a?(Array) ? args.first : [args.first]
  if paths.map(&:to_s).all? { |p| looks_like_xpath?(p) }
    at_xpath(*paths)
  else
    at_css(*args)
  end
end

#at_css(*args) ⇒ Object

Raises:

  • (ArgumentError)


78
79
80
81
82
83
84
85
86
# File 'lib/leptris/xml/searchable.rb', line 78

def at_css(*args)
  handler, ns, _ = parse_search_args(args)
  raise ArgumentError, "namespace bindings not supported in css" if ns && !ns.empty?
  raise ArgumentError, "custom CSS handlers not supported" if handler
  prefix = is_a?(Leptris::XML::Document) ? "//" : ".//"
  expr = args.map { |r| Leptris::XML::CssToXPath.convert(r, prefix: prefix) }
    .join(" | ")
  at_xpath(expr)
end

#at_xpath(*paths) ⇒ Object

Raises:

  • (ArgumentError)


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/leptris/xml/searchable.rb', line 26

def at_xpath(*paths)
  handler, ns, _vars = parse_search_args(paths)
  raise ArgumentError, "custom XPath handlers not supported" if handler
  expr = paths.join(" | ")

  doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
  context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr

  result_ptr =
    if ns && !ns.empty?
      xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
    else
      Leptris::XML::FFI.leptris_xpath_eval(doc_ptr, context_ptr, expr)
    end
  if result_ptr.null?
    raise Leptris::XML::XPathError,
      Leptris::XML::FFI.leptris_last_error.to_s
  end
  Leptris::XML::Searchable.wrap_xpath_first_result(document, result_ptr)
end

#css(*args) ⇒ Object

Raises:

  • (ArgumentError)


66
67
68
69
70
71
72
73
74
75
76
# File 'lib/leptris/xml/searchable.rb', line 66

def css(*args)
  handler, ns, _ = parse_search_args(args)
  raise ArgumentError, "namespace bindings not supported in css" if ns && !ns.empty?
  raise ArgumentError, "custom CSS handlers not supported" if handler
  # Nokogiri semantics: css is receiver-relative — absolute "//"
  # from a Document, descendant ".//" from elements and fragments.
  prefix = is_a?(Leptris::XML::Document) ? "//" : ".//"
  expr = args.map { |r| Leptris::XML::CssToXPath.convert(r, prefix: prefix) }
    .join(" | ")
  xpath(expr)
end

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



47
48
49
50
# File 'lib/leptris/xml/searchable.rb', line 47

def search(*args)
  paths = args.first.is_a?(Array) ? args.first : [args.first]
  paths.map(&:to_s).all? { |p| looks_like_xpath?(p) } ? xpath(*paths) : css(*paths)
end

#xpath(*paths) ⇒ Object

Raises:

  • (ArgumentError)


4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/leptris/xml/searchable.rb', line 4

def xpath(*paths)
  handler, ns, _vars = parse_search_args(paths)
  raise ArgumentError, "custom XPath handlers not supported" if handler
  expr = paths.join(" | ")

  doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
  context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr

  result_ptr =
    if ns && !ns.empty?
      xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
    else
      Leptris::XML::FFI.leptris_xpath_eval(doc_ptr, context_ptr, expr)
    end
  if result_ptr.null?
    raise Leptris::XML::XPathError,
      Leptris::XML::FFI.leptris_last_error.to_s
  end

  Leptris::XML::Searchable.wrap_xpath_result(document, result_ptr)
end