Class: Dommy::CharacterDataNode

Inherits:
Object
  • Object
show all
Includes:
Bridge::Methods, EventTarget, Node
Defined in:
lib/dommy/element.rb

Overview

CharacterData base — TextNode and CommentNode share the data / nodeValue / textContent API and ‘remove` / `cloneNode` semantics.

Direct Known Subclasses

CommentNode, TextNode

Constant Summary

Constants included from Node

Node::ATTRIBUTE_NODE, Node::CDATA_SECTION_NODE, Node::COMMENT_NODE, Node::DOCUMENT_FRAGMENT_NODE, Node::DOCUMENT_NODE, Node::DOCUMENT_POSITION_CONTAINED_BY, Node::DOCUMENT_POSITION_CONTAINS, Node::DOCUMENT_POSITION_DISCONNECTED, Node::DOCUMENT_POSITION_FOLLOWING, Node::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, Node::DOCUMENT_POSITION_PRECEDING, Node::DOCUMENT_TYPE_NODE, Node::ELEMENT_NODE, Node::HTML_NAMESPACE, Node::PROCESSING_INSTRUCTION_NODE, Node::TEXT_NODE

Instance Method Summary collapse

Methods included from Bridge::Methods

included

Methods included from EventTarget

#__internal_deliver_event__, #add_event_listener, capture_flag, #deliver_at, #dispatch_event, js_truthy?, #remove_event_listener

Methods included from Node

#compare_document_position, #get_root_node, #is_default_namespace, #is_equal_node, #is_same_node, #lookup_namespace_uri, #lookup_prefix

Constructor Details

#initialize(document, nokogiri_node) ⇒ CharacterDataNode

Returns a new instance of CharacterDataNode.



262
263
264
265
# File 'lib/dommy/element.rb', line 262

def initialize(document, nokogiri_node)
  @document = document
  @__node__ = nokogiri_node
end

Instance Method Details

#[](key) ⇒ Object



310
311
312
# File 'lib/dommy/element.rb', line 310

def [](key)
  __js_get__(key.to_s)
end

#[]=(key, value) ⇒ Object



314
315
316
# File 'lib/dommy/element.rb', line 314

def []=(key, value)
  __js_set__(key.to_s, value)
end

#__dommy_backend_node__Object



240
# File 'lib/dommy/element.rb', line 240

def __dommy_backend_node__ = @__node__

#__internal_event_parent__Object

EventTarget needs a parent for event propagation; a character-data node bubbles to its parent element.



244
245
246
# File 'lib/dommy/element.rb', line 244

def __internal_event_parent__
  @__node__.parent && @document.wrap_node(@__node__.parent)
end

#__js_call__(method, args) ⇒ Object



414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/dommy/element.rb', line 414

def __js_call__(method, args)
  case method
  when "hasChildNodes"
    false
  when "contains"
    # A leaf node contains only itself (no descendants).
    args[0].respond_to?(:__dommy_backend_node__) &&
      args[0].__dommy_backend_node__ == @__node__
  when "appendChild", "insertBefore"
    # CharacterData is a leaf — it cannot be a parent.
    raise DOMException::HierarchyRequestError, "this node type does not support children"
  when "removeChild", "replaceChild"
    raise DOMException::NotFoundError, "the node to be removed is not a child of this node"
  when "compareDocumentPosition"
    compare_document_position(args[0])
  when "isSameNode"
    is_same_node(args[0])
  when "getRootNode"
    get_root_node(args[0])
  when "lookupNamespaceURI"
    lookup_namespace_uri(args[0])
  when "lookupPrefix"
    lookup_prefix(args[0])
  when "isDefaultNamespace"
    is_default_namespace(args[0])
  when "normalize"
    nil # a leaf has no child text runs to merge
  when "splitText"
    split_text(args[0])
  when "addEventListener"
    add_event_listener(args[0], args[1], args[2])
  when "removeEventListener"
    remove_event_listener(args[0], args[1], args[2])
  when "dispatchEvent"
    dispatch_event(args[0])
  when "appendData"
    append_data(args[0])
  when "insertData"
    insert_data(args[0], args[1])
  when "deleteData"
    delete_data(args[0], args[1])
  when "replaceData"
    replace_data(args[0], args[1], args[2])
  when "substringData"
    substring_data(args[0], args[1])
  when "remove"
    remove
  when "before"
    before(*args)
  when "after"
    after(*args)
  when "replaceWith"
    replace_with(*args)
  when "isEqualNode"
    is_equal_node(args[0])
  end
end

#__js_get__(key) ⇒ Object



365
366
367
368
369
370
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
# File 'lib/dommy/element.rb', line 365

def __js_get__(key)
  case key
  when "nodeType"
    node_type
  when "nodeName"
    node_name
  when "textContent"
    @__node__.content
  when "data"
    @__node__.content
  when "nodeValue"
    @__node__.content
  when "length"
    length
  when "parentNode"
    parent_node
  when "ownerDocument"
    @document
  when "nextSibling"
    next_sibling
  when "previousSibling"
    previous_sibling
  when "childNodes"
    # CharacterData is a leaf node: childNodes is always an empty (but
    # present and iterable) NodeList, and firstChild/lastChild are null.
    # DOM-walking code (e.g. idiomorph's morphChildren) iterates
    # `node.childNodes` on every node, so a missing one crashes it.
    NodeList.new
  when "firstChild", "lastChild"
    nil
  end
end

#__js_set__(key, value) ⇒ Object



398
399
400
401
402
403
404
405
# File 'lib/dommy/element.rb', line 398

def __js_set__(key, value)
  case key
  when "textContent", "data", "nodeValue"
    write_data(value)
  end

  nil
end

#after(*args) ⇒ Object



488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/dommy/element.rb', line 488

def after(*args)
  parent = @__node__.parent
  return nil unless parent

  added = args.map { |arg| coerce_node(arg) }.compact
  anchor = @__node__.next_sibling
  if anchor
    added.reverse_each { |node| anchor.add_previous_sibling(node) }
  else
    added.each { |node| parent.add_child(node) }
  end
  notify_child_list_added(parent, added)
  nil
end

#append_data(value) ⇒ Object



344
345
346
# File 'lib/dommy/element.rb', line 344

def append_data(value)
  write_data(@__node__.content + value.to_s)
end

#before(*args) ⇒ Object

ChildNode mixin: WHATWG DOM defines ‘before`, `after`, `replaceWith` on all child nodes, including Text and Comment. Implementations operate on the Nokogiri layer and notify the MutationObserver with the underlying nodes (mirroring Element#remove_child / replace_child).



478
479
480
481
482
483
484
485
486
# File 'lib/dommy/element.rb', line 478

def before(*args)
  parent = @__node__.parent
  return nil unless parent

  added = args.map { |arg| coerce_node(arg) }.compact
  added.reverse_each { |node| @__node__.add_previous_sibling(node) }
  notify_child_list_added(parent, added)
  nil
end

#dataObject

Snake_case facade (CRuby idiomatic)



269
270
271
# File 'lib/dommy/element.rb', line 269

def data
  @__node__.content
end

#data=(value) ⇒ Object



273
274
275
# File 'lib/dommy/element.rb', line 273

def data=(value)
  write_data(value)
end

#delete_data(offset, count) ⇒ Object



352
353
354
# File 'lib/dommy/element.rb', line 352

def delete_data(offset, count)
  replace_data(offset, count, "")
end

#insert_data(offset, value) ⇒ Object



348
349
350
# File 'lib/dommy/element.rb', line 348

def insert_data(offset, value)
  replace_data(offset, 0, value)
end

#lengthObject

CharacterData length / mutation methods. Offsets and counts are UTF-16 code units per spec; for BMP text (the common case) Ruby char indices match. Each mutating op routes through write_data, which fires the characterData MutationObserver record.



333
334
335
# File 'lib/dommy/element.rb', line 333

def length
  @__node__.content.length
end

#next_siblingObject



302
303
304
# File 'lib/dommy/element.rb', line 302

def next_sibling
  @__node__.next && @document.wrap_node(@__node__.next)
end

#node_nameObject

WHATWG nodeName for character-data nodes is a per-type constant (“#text” / “#comment” / “#cdata-section”), not the element name.



320
321
322
323
324
325
326
# File 'lib/dommy/element.rb', line 320

def node_name
  case node_type
  when 3 then "#text"
  when 4 then "#cdata-section"
  when 8 then "#comment"
  end
end

#node_valueObject



277
278
279
# File 'lib/dommy/element.rb', line 277

def node_value
  @__node__.content
end

#node_value=(value) ⇒ Object



281
282
283
# File 'lib/dommy/element.rb', line 281

def node_value=(value)
  write_data(value)
end

#parent_nodeObject



298
299
300
# File 'lib/dommy/element.rb', line 298

def parent_node
  @__node__.parent && @document.wrap_node(@__node__.parent)
end

#previous_siblingObject



306
307
308
# File 'lib/dommy/element.rb', line 306

def previous_sibling
  @__node__.previous && @document.wrap_node(@__node__.previous)
end

#removeObject



293
294
295
296
# File 'lib/dommy/element.rb', line 293

def remove
  @document.remove_node_with_notify(@__node__)
  nil
end

#replace_data(offset, count, value) ⇒ Object



356
357
358
359
360
361
362
363
# File 'lib/dommy/element.rb', line 356

def replace_data(offset, count, value)
  s = @__node__.content
  o = offset.to_i
  raise DOMException::IndexSizeError, "offset out of bounds" if o.negative? || o > s.length

  c = [[count.to_i, 0].max, s.length - o].min
  write_data(s[0, o].to_s + value.to_s + s[(o + c)..].to_s)
end

#replace_with(*args) ⇒ Object



503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'lib/dommy/element.rb', line 503

def replace_with(*args)
  parent = @__node__.parent
  return nil unless parent

  added = args.map { |arg| coerce_node(arg) }.compact
  removed = @__node__
  anchor = @__node__.next_sibling
  @__node__.unlink
  if anchor
    added.reverse_each { |node| anchor.add_previous_sibling(node) }
  else
    added.each { |node| parent.add_child(node) }
  end
  @document.notify_child_list_mutation(
    target_node: parent,
    added_nodes: added,
    removed_nodes: [removed]
  )
  nil
end

#split_text(offset) ⇒ Object

Text.splitText / CharacterData split: break the node at ‘offset`, keeping [0, offset) here and returning a new sibling node with the remainder.



250
251
252
253
254
255
256
257
258
259
260
# File 'lib/dommy/element.rb', line 250

def split_text(offset)
  off = offset.to_i
  full = @__node__.content
  raise DOMException::IndexSizeError, "offset #{off} is out of bounds" if off.negative? || off > full.length

  rest = full[off..] || ""
  write_data(full[0, off])
  new_node = @document.create_text_node(rest)
  @__node__.add_next_sibling(new_node.__dommy_backend_node__) if @__node__.parent
  new_node
end

#substring_data(offset, count) ⇒ Object



337
338
339
340
341
342
# File 'lib/dommy/element.rb', line 337

def substring_data(offset, count)
  s = @__node__.content
  raise DOMException::IndexSizeError, "offset out of bounds" if offset.to_i.negative? || offset.to_i > s.length

  s[offset.to_i, [count.to_i, 0].max].to_s
end

#text_contentObject



285
286
287
# File 'lib/dommy/element.rb', line 285

def text_content
  @__node__.content
end

#text_content=(value) ⇒ Object



289
290
291
# File 'lib/dommy/element.rb', line 289

def text_content=(value)
  write_data(value)
end