Class: Scrapetor::Native::Element

Inherits:
Object
  • Object
show all
Defined in:
lib/scrapetor/native_dom.rb

Overview

Lightweight wrapper: two slots, the native doc + node id. Walks like a Dom::Element so the rest of Scrapetor can treat it the same.

Defined Under Namespace

Classes: AttrNode

Constant Summary collapse

DOM_TYPE_TEXT =

Direct text-node children only — handles the convention ‘parent > ::text` (and `> ::attr(x)`) where descendant text inside child elements must NOT be included.

3

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(doc, id, wrapper = nil) ⇒ Element

Returns a new instance of Element.



75
76
77
78
79
80
# File 'lib/scrapetor/native_dom.rb', line 75

def initialize(doc, id, wrapper = nil)
  @doc     = doc
  @id      = id
  @wrapper = wrapper
  @dom_node = nil
end

Instance Attribute Details

#docObject (readonly)

Returns the value of attribute doc.



73
74
75
# File 'lib/scrapetor/native_dom.rb', line 73

def doc
  @doc
end

#idObject (readonly)

Returns the value of attribute id.



73
74
75
# File 'lib/scrapetor/native_dom.rb', line 73

def id
  @id
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



513
514
515
516
517
518
519
520
521
522
523
# File 'lib/scrapetor/native_dom.rb', line 513

def ==(other)
  return true if equal?(other)
  return false unless other.is_a?(Element)
  if dom_node? && other.dom_backed?
    @dom_node.equal?(other.dom_node)
  elsif !dom_node? && !other.dom_backed?
    @doc.equal?(other.doc) && @id == other.id
  else
    false
  end
end

#[](key) ⇒ Object Also known as: get_attribute, attribute_value



103
104
105
# File 'lib/scrapetor/native_dom.rb', line 103

def [](key)
  dom_node? ? @dom_node[key.to_s] : @doc.node_attr(@id, key.to_s)
end

#[]=(key, value) ⇒ Object Also known as: set_attribute

—– mutation API —–

The native arena DOM is immutable by design (it gives us the zero-copy parse + 137x Lexbor lead). Mutations promote the document to a Ruby ‘Dom::Document` once, then operate on the equivalent Dom node. Reads continue to work on either side.



545
546
547
548
549
# File 'lib/scrapetor/native_dom.rb', line 545

def []=(key, value)
  ensure_dom!
  @dom_node[key.to_s] = value.nil? ? nil : value.to_s
  value
end

#add_child(node_or_html) ⇒ Object Also known as: <<



594
595
596
597
# File 'lib/scrapetor/native_dom.rb', line 594

def add_child(node_or_html)
  ensure_dom!
  @dom_node.add_child(unwrap_for_mutation(node_or_html))
end

#add_class(klass) ⇒ Object Also known as: append_class



559
560
561
562
563
# File 'lib/scrapetor/native_dom.rb', line 559

def add_class(klass)
  ensure_dom!
  @dom_node.add_class(klass.to_s)
  self
end

#add_next_sibling(node_or_html) ⇒ Object Also known as: after



606
607
608
609
# File 'lib/scrapetor/native_dom.rb', line 606

def add_next_sibling(node_or_html)
  ensure_dom!
  @dom_node.add_next_sibling(unwrap_for_mutation(node_or_html))
end

#add_previous_sibling(node_or_html) ⇒ Object Also known as: before



600
601
602
603
# File 'lib/scrapetor/native_dom.rb', line 600

def add_previous_sibling(node_or_html)
  ensure_dom!
  @dom_node.add_previous_sibling(unwrap_for_mutation(node_or_html))
end

#apply_pseudo_element(nodes, kind, arg) ⇒ Object



673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
# File 'lib/scrapetor/native_dom.rb', line 673

def apply_pseudo_element(nodes, kind, arg)
  case kind
  when nil then nodes
  when :text, :text_approx
    nodes.map do |n|
      t = Scrapetor::TextNode.new(n.respond_to?(:text) ? n.text.to_s : n.to_s)
      t.parent_node = n if n.respond_to?(:element?) && n.element?
      t
    end
  when :direct_text
    out = []
    nodes.each do |n|
      str = direct_text_of(n)
      tn = Scrapetor::TextNode.new(str)
      tn.parent_node = n if n.respond_to?(:element?) && n.element?
      out << tn
    end
    out
  when :attr
    nodes.map do |n|
      v = n.respond_to?(:[]) ? n[arg] : nil
      next nil if v.nil?
      t = Scrapetor::TextNode.new(v)
      t.parent_node = n if n.respond_to?(:element?) && n.element?
      t
    end
  when :direct_attr
    out = []
    nodes.each do |n|
      v = n.respond_to?(:[]) ? n[arg] : nil
      next if v.nil?
      tn = Scrapetor::TextNode.new(v)
      tn.parent_node = n if n.respond_to?(:element?) && n.element?
      out << tn
    end
    out
  end
end

#at_css_slow(selector) ⇒ Object



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/scrapetor/native_dom.rb', line 335

def at_css_slow(selector)
  str = selector.is_a?(String) ? selector : selector.to_s
  # Shared memo also covers the comma/pseudo-element slow path
  # — many SerpApi-style parsers call the same complex selector
  # repeatedly, and across identical-HTML iterations we can
  # short-circuit before even peeling.
  if !@dom_node && @id.is_a?(Integer)
    cached = @doc.cache_get(str, @id)
    if cached
      first = cached[0]
      return first.nil? ? nil : Element.new(@doc, first, @wrapper)
    end
  end
  if str.include?(",") && str.include?("::") &&
     Native.heterogeneous_pseudo_groups?(str)
    Native.split_selector_groups(str).each do |g|
      hit = at_css(g)
      return hit if hit
    end
    return nil
  end
  stripped, kind, arg = Native.peel_pseudo_element(str)
  stripped = "*" if stripped.empty?
  nodes = css_native_or_fallback(stripped, limit_one: true)
  return nil if nodes.empty?
  return nodes.first unless kind
  apply_pseudo_element(nodes, kind, arg).first
end

#at_xpath(_expr) ⇒ Object



365
# File 'lib/scrapetor/native_dom.rb', line 365

def at_xpath(_expr); nil; end

#attribute(name) ⇒ Object



138
139
140
141
142
143
144
145
# File 'lib/scrapetor/native_dom.rb', line 138

def attribute(name)
  if dom_node?
    @dom_node.attribute(name)
  else
    v = self[name]
    v && AttrNode.new(name.to_s, v)
  end
end

#attribute_nodesObject



130
131
132
133
134
135
136
# File 'lib/scrapetor/native_dom.rb', line 130

def attribute_nodes
  if dom_node?
    @dom_node.attribute_nodes
  else
    attributes.map { |k, v| AttrNode.new(k, v) }
  end
end

#attributesObject



109
110
111
112
113
114
115
# File 'lib/scrapetor/native_dom.rb', line 109

def attributes
  if dom_node?
    @dom_node.attributes
  else
    @doc.node_attributes(@id)
  end
end

#batch_css(selectors) ⇒ Object

Batch API at the Element level. Pass an array of selector strings; receive parallel results in one C round trip. Selectors ending in ‘::text` / `::attr(…)` come back as Arrays of strings; everything else as a NodeSet.



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/scrapetor/native_dom.rb', line 371

def batch_css(selectors)
  return [] if selectors.nil? || selectors.empty?
  w = @wrapper
  return selectors.map { |s| css(s) } if w.nil? || @dom_node
  plans  = Array.new(selectors.size)
  kinds  = Array.new(selectors.size)
  args   = Array.new(selectors.size)
  stripped = Array.new(selectors.size)
  fallback = []
  selectors.each_with_index do |sel, i|
    str = sel.is_a?(String) ? sel : sel.to_s
    s2, k, a = Native.peel_pseudo_element(str)
    s2 = "*" if s2.empty?
    kinds[i] = k
    args[i]  = a
    stripped[i] = s2
    if !s2.include?(",")
      plan = w.compiled_plan(s2)
      if plan
        plans[i] = plan
        next
      end
    end
    fallback << i
  end
  id_lists = @doc.batch_chain(plans.map { |p| p || [] }, @id)
  out = Array.new(selectors.size)
  id_lists.each_with_index do |ids, i|
    next if fallback.include?(i)
    kind = kinds[i]
    arg  = args[i]
    out[i] =
      case kind
      when :text, :text_approx
        wire_text_parents!(@doc.bulk_text(ids), ids, w)
      when :attr
        wire_text_parents!(@doc.bulk_attr(ids, arg), ids, w)
      else
        # Plain selector — wrap ids as Elements. For consistency
        # with css() return shape, expose as an Array (caller can
        # wrap in NodeSet at the boundary).
        ids.map { |nid| Element.new(@doc, nid, w) }
      end
  end
  # Fall back per-selector for the few that didn't compile.
  fallback.each { |i| out[i] = css(selectors[i]) }
  out
end

#cdata?Boolean

Returns:

  • (Boolean)


193
# File 'lib/scrapetor/native_dom.rb', line 193

def cdata?;    false; end

#childrenObject



214
215
216
217
218
219
220
# File 'lib/scrapetor/native_dom.rb', line 214

def children
  if dom_node?
    @dom_node.children.map { |c| wrap_dom(c) }
  else
    @doc.node_children(@id).map { |cid| Element.new(@doc, cid, wrapper) }
  end
end

#classesObject



297
298
299
# File 'lib/scrapetor/native_dom.rb', line 297

def classes
  dom_node? ? @dom_node.classes : @doc.node_classes(@id)
end

#comment?Boolean

Returns:

  • (Boolean)


94
# File 'lib/scrapetor/native_dom.rb', line 94

def comment?; @dom_node ? @dom_node.comment? : @doc.node_type(@id) == 8; end

#content=(text) ⇒ Object Also known as: text=



572
573
574
575
576
# File 'lib/scrapetor/native_dom.rb', line 572

def content=(text)
  ensure_dom!
  @dom_node.content = text.to_s
  text
end

#css_slow(selector) ⇒ Object

Slow path for css(). Native fast path is installed as a C method (‘native_css`) at module load and aliased to `css`, so the heavy Ruby dispatch only runs for shapes that the C path can’t handle directly (heterogeneous pseudo groups, post-peel attr/text transforms, dom-mode mutated trees, etc.).



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/scrapetor/native_dom.rb', line 310

def css_slow(selector)
  str = selector.is_a?(String) ? selector : selector.to_s
  if str.include?(",") && str.include?("::") &&
     Native.heterogeneous_pseudo_groups?(str)
    return Native.split_selector_groups(str).flat_map { |g| css(g).to_a }
  end
  stripped, kind, arg = Native.peel_pseudo_element(str)
  stripped = "*" if stripped.empty?
  if kind && %i[text text_approx attr].include?(kind) && !dom_node?
    w = wrapper
    plan = w ? w.compiled_plan(stripped) : Native.compile_selector_chain(stripped)
    if plan && !stripped.include?(",")
      ids = @doc.run_chain(plan, @id)
      return case kind
             when :text, :text_approx
               wire_text_parents!(@doc.bulk_text(ids), ids, w)
             when :attr
               wire_text_parents!(@doc.bulk_attr(ids, arg), ids, w)
             end
    end
  end
  nodes = css_native_or_fallback(stripped)
  apply_pseudo_element(nodes, kind, arg)
end

#direct_text_of(n) ⇒ Object



716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
# File 'lib/scrapetor/native_dom.rb', line 716

def direct_text_of(n)
  buf = +""
  if n.is_a?(Element) && !n.send(:dom_node?)
    doc = @doc
    cid = doc.node_first_child(n.id)
    while cid
      if doc.node_type(cid) == DOM_TYPE_TEXT
        buf << doc.node_text(cid).to_s
      end
      cid = doc.node_next_sibling(cid)
    end
  elsif n.respond_to?(:children)
    n.children.each do |c|
      if c.respond_to?(:text?) && c.text?
        buf << (c.respond_to?(:text) ? c.text.to_s : c.to_s)
      elsif !c.respond_to?(:element?) || !c.element?
        buf << c.to_s
      end
    end
  end
  buf
end

#document?Boolean

Returns:

  • (Boolean)


95
# File 'lib/scrapetor/native_dom.rb', line 95

def document?; @dom_node ? @dom_node.document? : @doc.node_type(@id) == 9; end

#dom_backed?Boolean

Internal: was this Element already promoted to a Dom::Element?

Returns:

  • (Boolean)


656
657
658
# File 'lib/scrapetor/native_dom.rb', line 656

def dom_backed?
  dom_node?
end

#dom_nodeObject



660
661
662
# File 'lib/scrapetor/native_dom.rb', line 660

def dom_node
  @dom_node
end

#element?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/scrapetor/native_dom.rb', line 89

def element?
  @dom_node ? @dom_node.element? : @doc.node_is_element(@id)
end

#element_childrenObject Also known as: elements



222
223
224
225
226
227
228
# File 'lib/scrapetor/native_dom.rb', line 222

def element_children
  if dom_node?
    @dom_node.element_children.map { |c| wrap_dom(c) }
  else
    @doc.node_element_children(@id).map { |cid| Element.new(@doc, cid, wrapper) }
  end
end

#extract(map) ⇒ Object

Single-result extract — one C call, fields compiled in C, field iteration in C, result hash assembled in C. Falls back to the per-field Ruby loop only when a selector can’t be compiled natively.



434
435
436
437
438
439
# File 'lib/scrapetor/native_dom.rb', line 434

def extract(map)
  return slow_extract(map) if @dom_node || @wrapper.nil?
  r = @doc.extract_one_h(@id, map, @wrapper)
  return slow_extract(map) if r.equal?(true)
  r
end

#extract_css(map) ⇒ Object

Hash-form batch: map of => selector → => result. The classic scrape pattern shaped as a single declarative call.



422
423
424
425
426
427
428
# File 'lib/scrapetor/native_dom.rb', line 422

def extract_css(map)
  keys = map.keys
  results = batch_css(map.values)
  out = {}
  keys.each_with_index { |k, i| out[k] = results[i] }
  out
end

#extract_each(outer_selector, fields) ⇒ Object

extract_each — one C call covers compile + outer plan run + every (match × field) tuple resolution. Outer selector is peeled inside C. Falls back to Ruby per-row only when any selector can’t compile natively.



445
446
447
448
449
450
451
# File 'lib/scrapetor/native_dom.rb', line 445

def extract_each(outer_selector, fields)
  return slow_extract_each(outer_selector, fields) if @dom_node || @wrapper.nil?
  outer_str = outer_selector.is_a?(String) ? outer_selector : outer_selector.to_s
  r = @doc.extract_each_h(outer_str, @id, fields, @wrapper)
  return slow_extract_each(outer_selector, fields) if r.equal?(true)
  r
end

#fingerprintObject



534
535
536
# File 'lib/scrapetor/native_dom.rb', line 534

def fingerprint
  Scrapetor::Fingerprint.structural(self)
end

#first_element_childObject



231
232
233
234
235
236
237
238
239
# File 'lib/scrapetor/native_dom.rb', line 231

def first_element_child
  if dom_node?
    c = @dom_node.first_element_child
    c && wrap_dom(c)
  else
    ids = @doc.node_element_children(@id)
    ids.empty? ? nil : Element.new(@doc, ids.first, wrapper)
  end
end

#fragment?Boolean

Returns:

  • (Boolean)


192
# File 'lib/scrapetor/native_dom.rb', line 192

def fragment?; false; end

#has_attribute?(k) ⇒ Boolean Also known as: key?

Returns:

  • (Boolean)


149
150
151
152
153
154
155
# File 'lib/scrapetor/native_dom.rb', line 149

def has_attribute?(k)
  if dom_node?
    @dom_node.has_attribute?(k)
  else
    !@doc.node_attr(@id, k.to_s).nil?
  end
end

#has_class?(klass) ⇒ Boolean

Returns:

  • (Boolean)


301
# File 'lib/scrapetor/native_dom.rb', line 301

def has_class?(klass); classes.include?(klass.to_s); end

#hashObject



526
527
528
529
530
531
532
# File 'lib/scrapetor/native_dom.rb', line 526

def hash
  if dom_node?
    @dom_node.object_id
  else
    [@doc.object_id, @id].hash
  end
end

#inner_htmlObject

—– serialization —–



484
485
486
487
488
489
490
# File 'lib/scrapetor/native_dom.rb', line 484

def inner_html
  if dom_node?
    @dom_node.inner_html
  else
    element_children.map(&:to_html).join + text_only_children
  end
end

#inner_html=(html) ⇒ Object



579
580
581
582
583
584
585
586
587
588
589
590
591
592
# File 'lib/scrapetor/native_dom.rb', line 579

def inner_html=(html)
  # Native fast path: parse the fragment in C and graft it
  # directly into the arena, no Ruby Dom round-trip. The
  # selector engine continues to query the native arena on
  # subsequent reads (with a parent-walk descendant fallback
  # for the now-non-contiguous fragment subtree).
  if !@dom_node && @wrapper && !@wrapper.dom_mode?
    ok = @doc.node_set_inner_html(@id, html.to_s)
    return html if ok == true
  end
  ensure_dom!
  @dom_node.inner_html = html.to_s
  html
end

#keysObject



147
# File 'lib/scrapetor/native_dom.rb', line 147

def keys;    dom_node? ? @dom_node.keys : attributes.keys; end

#last_element_childObject



241
242
243
244
245
246
247
248
249
# File 'lib/scrapetor/native_dom.rb', line 241

def last_element_child
  if dom_node?
    c = @dom_node.last_element_child
    c && wrap_dom(c)
  else
    ids = @doc.node_element_children(@id)
    ids.empty? ? nil : Element.new(@doc, ids.last, wrapper)
  end
end

#matches?(selector) ⇒ Boolean

Returns:

  • (Boolean)


470
471
472
473
474
475
476
477
478
479
480
# File 'lib/scrapetor/native_dom.rb', line 470

def matches?(selector)
  # Walk up self's ancestor-or-self set; cheap version of
  # checking whether *this* node matches the selector.
  doc = wrapper ? wrapper : nil
  if doc
    doc.css(selector).any? { |n| n == self }
  else
    # No wrapper available — fall back to checking via parent.
    false
  end
end

#nameObject Also known as: node_name, tag_name



97
98
99
# File 'lib/scrapetor/native_dom.rb', line 97

def name
  dom_node? ? @dom_node.name : @doc.node_name(@id)
end

#next_element_siblingObject



271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/scrapetor/native_dom.rb', line 271

def next_element_sibling
  if dom_node?
    n = @dom_node.next_element_sibling
    n && wrap_dom(n)
  else
    cur = @doc.node_next_sibling(@id)
    while cur && !@doc.node_is_element(cur)
      cur = @doc.node_next_sibling(cur)
    end
    cur ? Element.new(@doc, cur, wrapper) : nil
  end
end

#next_siblingObject



251
252
253
254
255
256
257
258
259
# File 'lib/scrapetor/native_dom.rb', line 251

def next_sibling
  if dom_node?
    n = @dom_node.next_sibling
    n && wrap_dom(n)
  else
    nid = @doc.node_next_sibling(@id)
    nid ? Element.new(@doc, nid, wrapper) : nil
  end
end

#node_typeObject Also known as: type



508
509
510
# File 'lib/scrapetor/native_dom.rb', line 508

def node_type
  dom_node? ? @dom_node.node_type : @doc.node_type(@id)
end

#outer_htmlObject Also known as: to_html, to_xml, to_s



492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/scrapetor/native_dom.rb', line 492

def outer_html
  if dom_node?
    @dom_node.outer_html
  else
    attr_str = attributes.map { |k, v| %( #{k}="#{Dom.escape_attr(v)}") }.join
    if Dom::VOID.include?(name) && @doc.node_children(@id).empty?
      "<#{name}#{attr_str}>"
    else
      "<#{name}#{attr_str}>#{inner_html}</#{name}>"
    end
  end
end

#parentObject



202
203
204
205
206
207
208
209
210
211
212
# File 'lib/scrapetor/native_dom.rb', line 202

def parent
  if dom_node?
    p = @dom_node.parent
    return nil if p.nil?
    return nil unless p.respond_to?(:element?) && p.element?
    wrap_dom(p)
  else
    pid = @doc.node_parent(@id)
    pid ? Element.new(@doc, pid, wrapper) : nil
  end
end

#pathObject

Stable identity used to relocate this node inside a lazy Dom view after the document switches to dom-mode. Builds the same ‘/tag/…/tag` shape we already exposed publicly. Memoized per-id on the document wrapper so a fallback-heavy parser doesn’t pay the O(depth*siblings) walk per at_css call.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/scrapetor/native_dom.rb', line 163

def path
  w = wrapper
  if w && (cached = w.cached_path(@id))
    return cached
  end
  parts = []
  cur = self
  while cur && cur.element?
    id = cur["id"]
    if id && !id.empty?
      parts.unshift("#{cur.name}[@id='#{id}']")
      break
    end
    idx = 1
    sib = cur.previous_sibling
    while sib
      if sib.element? && sib.name == cur.name
        idx += 1
      end
      sib = sib.previous_sibling
    end
    parts.unshift("#{cur.name}[#{idx}]")
    cur = cur.parent
  end
  str = "/" + parts.join("/")
  w.store_path(@id, str) if w
  str
end

#previous_element_siblingObject



284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/scrapetor/native_dom.rb', line 284

def previous_element_sibling
  if dom_node?
    n = @dom_node.previous_element_sibling
    n && wrap_dom(n)
  else
    cur = @doc.node_prev_sibling(@id)
    while cur && !@doc.node_is_element(cur)
      cur = @doc.node_prev_sibling(cur)
    end
    cur ? Element.new(@doc, cur, wrapper) : nil
  end
end

#previous_siblingObject



261
262
263
264
265
266
267
268
269
# File 'lib/scrapetor/native_dom.rb', line 261

def previous_sibling
  if dom_node?
    n = @dom_node.previous_sibling
    n && wrap_dom(n)
  else
    nid = @doc.node_prev_sibling(@id)
    nid ? Element.new(@doc, nid, wrapper) : nil
  end
end

#processing_instruction?Boolean

Returns:

  • (Boolean)


194
# File 'lib/scrapetor/native_dom.rb', line 194

def processing_instruction?; false; end

#promote_to_dom!Object

Public version of the lazy dom-promotion step. NodeSet#remove uses it to resolve every node to its Dom equivalent BEFORE the first mutation, so subsequent removals don’t shift the path index under their feet.



668
669
670
671
# File 'lib/scrapetor/native_dom.rb', line 668

def promote_to_dom!
  ensure_dom!
  @dom_node
end

#removeObject Also known as: unlink, delete

Detach this element from its parent. When we’re still on the native arena, mutate it in place — that avoids the cross-DOM path lookup (which can’t always pin down a node on HTML where the native vs Ruby SAX parsers disagree about whitespace or implicit close tags). Once the document has been promoted to Ruby Dom by some other mutation, delegate to that side.



625
626
627
628
629
630
631
632
# File 'lib/scrapetor/native_dom.rb', line 625

def remove
  if @dom_node
    @dom_node.remove
  else
    @doc.node_remove(@id)
  end
  self
end

#remove_attribute(key) ⇒ Object Also known as: delete_attribute



552
553
554
555
556
# File 'lib/scrapetor/native_dom.rb', line 552

def remove_attribute(key)
  ensure_dom!
  @dom_node.remove_attribute(key.to_s)
  self
end

#remove_class(klass = nil) ⇒ Object



566
567
568
569
570
# File 'lib/scrapetor/native_dom.rb', line 566

def remove_class(klass = nil)
  ensure_dom!
  @dom_node.remove_class(klass && klass.to_s)
  self
end

#replace(node_or_html) ⇒ Object Also known as: swap, replace_with



612
613
614
615
# File 'lib/scrapetor/native_dom.rb', line 612

def replace(node_or_html)
  ensure_dom!
  @dom_node.replace(unwrap_for_mutation(node_or_html))
end

#textObject Also known as: content, inner_text



196
197
198
# File 'lib/scrapetor/native_dom.rb', line 196

def text
  dom_node? ? @dom_node.text : @doc.node_text(@id)
end

#text?Boolean

Returns:

  • (Boolean)


93
# File 'lib/scrapetor/native_dom.rb', line 93

def text?;    @dom_node ? @dom_node.text?    : @doc.node_type(@id) == 3; end

#traverse(&block) ⇒ Object



645
646
647
648
649
650
651
652
653
# File 'lib/scrapetor/native_dom.rb', line 645

def traverse(&block)
  if block_given?
    yield self
    element_children.each { |c| c.traverse(&block) }
    self
  else
    enum_for(:traverse)
  end
end

#valuesObject



148
# File 'lib/scrapetor/native_dom.rb', line 148

def values;  dom_node? ? @dom_node.values : attributes.values; end

#wire_text_parents!(values, ids, w) ⇒ Object

Helper for Element#css: take a bulk_text / bulk_attr result and wire each TextNode’s parent to the matching Element wrapper.



741
742
743
744
745
746
747
748
749
750
751
752
# File 'lib/scrapetor/native_dom.rb', line 741

def wire_text_parents!(values, ids, w)
  i = 0
  n = values.length
  while i < n
    v = values[i]
    if v.is_a?(Scrapetor::TextNode)
      v.parent_node = Element.new(@doc, ids[i], w)
    end
    i += 1
  end
  values
end

#wrap(html_or_node) ⇒ Object

Wrap this element in a parsed HTML fragment whose deepest descendant becomes the new parent. Matches Nokogiri’s Node#wrap semantics.



639
640
641
642
643
# File 'lib/scrapetor/native_dom.rb', line 639

def wrap(html_or_node)
  ensure_dom!
  @dom_node.wrap(html_or_node) if @dom_node.respond_to?(:wrap)
  self
end

#wrapperObject

The DocumentWrapper governs the native arena and any lazy Dom view used for mutations / fallback selectors. Surface it so subclasses and nav helpers can stay coherent.



85
86
87
# File 'lib/scrapetor/native_dom.rb', line 85

def wrapper
  @wrapper ||= @doc.instance_variable_get(:@__scrapetor_wrapper)
end

#xpath(_expr) ⇒ Object



364
# File 'lib/scrapetor/native_dom.rb', line 364

def xpath(_expr); []; end