Class: Dommy::Document
Overview
‘document` — the entry point for DOM construction and querying. Wrapper caching keeps DOM identity stable across repeated traversals (`body.children.parentElement`).
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 Attribute Summary collapse
Instance Method Summary
collapse
-
#[](key) ⇒ Object
-
#[]=(key, value) ⇒ Object
-
#__internal_event_parent__ ⇒ Object
-
#__internal_insert_at_doctype__(nodes, after:) ⇒ Object
Called by DocumentType#before/#after — insert ‘nodes` before the doctype (at the document start) or after it (just before the document element).
-
#__internal_notify_attribute_changed__(element, name, old_value, new_value) ⇒ Object
-
#__internal_notify_connected__(element) ⇒ Object
Lifecycle callback dispatchers.
-
#__internal_notify_connected_subtree__(nk) ⇒ Object
-
#__internal_notify_disconnected__(element) ⇒ Object
-
#__internal_notify_disconnected_subtree__(nk) ⇒ Object
-
#__internal_register_shadow_fragment__(fragment_node, shadow_root) ⇒ Object
ShadowRoot identity registry: map a Nokogiri DocumentFragment (the shadow tree’s backing node) to the wrapping ShadowRoot so slot assignment and event composition can walk from any inner node back to its shadow boundary.
-
#__internal_remove_doctype__(_doctype) ⇒ Object
Called by DocumentType#remove — the doctype is synthesized from the DTD, so remove the internal subset and mark it gone.
-
#__internal_reset_wrapper__(nokogiri_node) ⇒ Object
Clear the cached wrapper so the next ‘wrap_node` creates a new one.
-
#__internal_set_active_element__(el) ⇒ Object
-
#__internal_set_fullscreen_element__(element) ⇒ Object
-
#__internal_shadow_root_containing__(node) ⇒ Object
-
#__internal_shadow_root_for_fragment__(fragment_node) ⇒ Object
-
#__js_call__(method, args) ⇒ Object
-
#__js_get__(key) ⇒ Object
-
#__js_set__(key, value) ⇒ Object
-
#active_element ⇒ Object
Currently-focused element (or body if none).
-
#adopt_node(node) ⇒ Object
Move a node from another document into this one.
-
#append_child(node) ⇒ Object
Append a node as a child of the document itself (e.g. a comment alongside the document element).
-
#at_xpath(expression) ⇒ Object
XPath queries returning wrapped nodes (Element / TextNode / etc).
-
#attach_template_content(template_element, html) ⇒ Object
—– template content helpers (called from Element) —–.
-
#backend_node(node) ⇒ Object
-
#base_uri ⇒ Object
‘document.baseURI` — resolves the first `<base href>` (if any) relative to the document URL; otherwise just the document URL.
-
#body ⇒ Object
Resolve ‘body` fresh from the tree (not memoized) so it tracks a swapped `<body>` — e.g.
-
#child_element_count ⇒ Object
-
#child_nodes ⇒ Object
All child nodes of the document (doctype + document element, …), as a live, cached NodeList — unlike ‘children`, which is element-only.
-
#children ⇒ Object
ParentNode mixin (operates on the document’s element children — in practice the ‘<html>` root).
-
#clone_node(deep) ⇒ Object
‘document.cloneNode(deep)` → a fresh Document over a (deep) copy of the Nokogiri tree, preserving the content type.
-
#close ⇒ Object
-
#coerce_what_to_show(args, index) ⇒ Object
WebIDL ‘unsigned long whatToShow = 0xFFFFFFFF`: an omitted or `undefined` argument uses the default; `null` coerces to 0; otherwise ToUint32.
-
#compat_mode ⇒ Object
‘document.compatMode` — “CSS1Compat” in no-quirks mode, “BackCompat” in quirks mode.
-
#contains?(other) ⇒ Boolean
‘document.contains(node)` — true if `node` is the document itself or any node attached to its tree (per Node.contains, which all nodes including the document expose).
-
#cookie ⇒ Object
-
#cookie=(value) ⇒ Object
-
#create_attribute(name) ⇒ Object
-
#create_attribute_ns(namespace_uri, qualified_name) ⇒ Object
-
#create_cdata_section(text) ⇒ Object
-
#create_comment(text) ⇒ Object
-
#create_document_fragment ⇒ Object
-
#create_element(name) ⇒ Object
Delegate factory methods to NodeWrapperCache.
-
#create_element_ns(namespace_uri, qualified_name) ⇒ Object
-
#create_event(type_name) ⇒ Object
Legacy ‘document.createEvent(“EventName”)` factory.
-
#create_node_iterator(root, what_to_show = NodeFilter::SHOW_ALL, filter = nil) ⇒ Object
‘document.createNodeIterator(root, whatToShow?, filter?)` — flat depth-first iteration.
-
#create_processing_instruction(target, data) ⇒ Object
-
#create_range ⇒ Object
-
#create_text_node(text) ⇒ Object
-
#create_tree_walker(root, what_to_show = NodeFilter::SHOW_ALL, filter = nil) ⇒ Object
‘document.createTreeWalker(root, whatToShow?, filter?)` — stateful tree traversal with sibling/parent navigation.
-
#doctype ⇒ Object
Minimal DocumentType — represents the ‘<!doctype html>` line.
-
#document_element ⇒ Object
-
#document_insert(args, prepend:) ⇒ Object
ParentNode / Node mutation on the document’s direct children (the doctype and the document element).
-
#document_insert_before(node, ref) ⇒ Object
-
#document_remove_child(node) ⇒ Object
-
#document_replace_child(new_child, old_child) ⇒ Object
-
#document_replace_children(args) ⇒ Object
-
#domain ⇒ Object
‘document.domain` — host portion of the URL.
-
#element_from_point(_x, _y) ⇒ Object
-
#exit_fullscreen ⇒ Object
(also: #exitFullscreen)
-
#first_element_child ⇒ Object
-
#forms ⇒ Object
-
#get_element_by_id(id) ⇒ Object
-
#get_elements_by_class_name(name) ⇒ Object
-
#get_elements_by_name(name) ⇒ Object
-
#get_elements_by_tag_name(name) ⇒ Object
-
#get_elements_by_tag_name_ns(namespace, local_name) ⇒ Object
-
#get_selection ⇒ Object
-
#has_focus? ⇒ Boolean
(also: #has_focus)
Stubs for layout / focus / selection / execCommand APIs that don’t apply to a layout-less DOM.
-
#has_template_content?(nokogiri_node) ⇒ Boolean
-
#head ⇒ Object
-
#html_document? ⇒ Boolean
Whether this is an “HTML document” in the DOM sense (created by the HTML parser / ‘text/html`), as opposed to an XML document.
-
#images ⇒ Object
-
#implementation ⇒ Object
-
#import_node(node, deep = false) ⇒ Object
Copy a node from another document into this one.
-
#initialize(host = nil, nokogiri_doc: nil, default_view: nil) ⇒ Document
constructor
A new instance of Document.
-
#last_element_child ⇒ Object
-
#links ⇒ Object
Live HTMLCollection helpers — each call re-queries the document so post-mutation reads reflect the current state.
-
#migrate_template_descendants(root) ⇒ Object
-
#normalize_filter(value) ⇒ Object
A ‘null`/`undefined` filter argument means “no filter”.
-
#notify_attribute_mutation(target_node:, attribute_name:, old_value:, namespace: nil) ⇒ Object
-
#notify_character_data_mutation(target_node:, old_value:) ⇒ Object
-
#notify_child_list_mutation(target_node:, added_nodes:, removed_nodes:, previous_sibling: nil, next_sibling: nil) ⇒ Object
-
#open ⇒ Object
No-ops — real browsers reset the DOM on ‘open()` and flush pending writes on `close()`.
-
#origin ⇒ Object
‘document.origin` — serialized origin of the document URL, mirroring `window.location.origin`.
-
#query_command_supported(_command) ⇒ Object
-
#query_selector(selector) ⇒ Object
-
#query_selector_all(selector) ⇒ Object
-
#referrer ⇒ Object
‘document.referrer` — Dommy never has a referring page, so this is always empty.
-
#register_observer(observer) ⇒ Object
-
#remove_node_with_notify(node) ⇒ Object
Unlink a backend node from its parent and queue a childList removal record capturing the node’s position (previous/next sibling) BEFORE the unlink, so the record’s previousSibling/nextSibling are correct (the coordinator can’t recover them once the node is detached).
-
#run_node_iterator_pre_remove(backend_node) ⇒ Object
Run the “NodeIterator pre-removing steps” for every live iterator before ‘backend_node` is detached, so referenceNode/pointerBeforeReferenceNode stay valid.
-
#scripts ⇒ Object
-
#template_content_fragment(template_element) ⇒ Object
-
#template_content_inner_html(template_element) ⇒ Object
-
#title ⇒ Object
—– Public Ruby API (snake_case) —–.
-
#title=(value) ⇒ Object
-
#to_html ⇒ Object
Serialize the whole document to HTML (including the doctype).
-
#unregister_observer(observer) ⇒ Object
-
#url ⇒ Object
(also: #document_uri)
‘document.URL` / `documentURI` — both return location.href in real browsers (legacy aliases of the same field).
-
#wrap_node(node) ⇒ Object
Delegate node wrapping to NodeWrapperCache.
-
#write(*args) ⇒ Object
‘document.write(html)` — legacy API.
-
#xpath(expression) ⇒ Object
included
Methods included from Node
#compare_document_position, #get_root_node, #is_default_namespace, #is_equal_node, #is_same_node, #lookup_namespace_uri, #lookup_prefix
#__internal_deliver_event__, #add_event_listener, capture_flag, #deliver_at, #dispatch_event, js_truthy?, #remove_event_listener
Constructor Details
#initialize(host = nil, nokogiri_doc: nil, default_view: nil) ⇒ Document
Returns a new instance of Document.
Instance Attribute Details
#content_type ⇒ Object
content_type defaults to “text/html”; settable so an integration layer can reflect the response Content-Type. Read-only over the JS bridge.
311
312
313
|
# File 'lib/dommy/document.rb', line 311
def content_type
@content_type
end
|
#default_view ⇒ Object
Returns the value of attribute default_view.
308
309
310
|
# File 'lib/dommy/document.rb', line 308
def default_view
@default_view
end
|
#fullscreen_element ⇒ Object
Fullscreen API — no actual fullscreen mode, just track which element claimed it. ‘element.requestFullscreen()` sets it; this is the read side.
642
643
644
|
# File 'lib/dommy/document.rb', line 642
def fullscreen_element
@fullscreen_element
end
|
#nokogiri_doc ⇒ Object
Returns the value of attribute nokogiri_doc.
307
308
309
|
# File 'lib/dommy/document.rb', line 307
def nokogiri_doc
@nokogiri_doc
end
|
Instance Method Details
#[](key) ⇒ Object
846
847
848
|
# File 'lib/dommy/document.rb', line 846
def [](key)
__js_get__(key.to_s)
end
|
#[]=(key, value) ⇒ Object
850
851
852
|
# File 'lib/dommy/document.rb', line 850
def []=(key, value)
__js_set__(key.to_s, value)
end
|
#__internal_event_parent__ ⇒ Object
1128
1129
1130
|
# File 'lib/dommy/document.rb', line 1128
def __internal_event_parent__
@default_view
end
|
#__internal_insert_at_doctype__(nodes, after:) ⇒ Object
Called by DocumentType#before/#after — insert ‘nodes` before the doctype (at the document start) or after it (just before the document element).
772
773
774
775
776
777
778
779
780
781
782
|
# File 'lib/dommy/document.rb', line 772
def __internal_insert_at_doctype__(nodes, after:)
bns = nodes.filter_map { |n| backend_node(n) }
if after
root = @nokogiri_doc.root
root ? bns.each { |n| root.add_previous_sibling(n) } : bns.each { |n| @nokogiri_doc.add_child(n) }
else
first = @nokogiri_doc.children.first
first ? bns.reverse_each { |n| first.add_previous_sibling(n) } : bns.each { |n| @nokogiri_doc.add_child(n) }
end
nil
end
|
#__internal_notify_attribute_changed__(element, name, old_value, new_value) ⇒ Object
1183
1184
1185
|
# File 'lib/dommy/document.rb', line 1183
def __internal_notify_attribute_changed__(element, name, old_value, new_value)
@mutation_coordinator.notify_attribute_changed(element, name, old_value, new_value)
end
|
#__internal_notify_connected__(element) ⇒ Object
Lifecycle callback dispatchers. Errors raised inside user callbacks are swallowed so a single buggy custom element can’t break the whole mutation pipeline. Delegate to MutationCoordinator
1167
1168
1169
|
# File 'lib/dommy/document.rb', line 1167
def __internal_notify_connected__(element)
@mutation_coordinator.notify_connected(element)
end
|
#__internal_notify_connected_subtree__(nk) ⇒ Object
1175
1176
1177
|
# File 'lib/dommy/document.rb', line 1175
def __internal_notify_connected_subtree__(nk)
@mutation_coordinator.notify_connected_subtree(nk)
end
|
#__internal_notify_disconnected__(element) ⇒ Object
1171
1172
1173
|
# File 'lib/dommy/document.rb', line 1171
def __internal_notify_disconnected__(element)
@mutation_coordinator.notify_disconnected(element)
end
|
#__internal_notify_disconnected_subtree__(nk) ⇒ Object
1179
1180
1181
|
# File 'lib/dommy/document.rb', line 1179
def __internal_notify_disconnected_subtree__(nk)
@mutation_coordinator.notify_disconnected_subtree(nk)
end
|
#__internal_register_shadow_fragment__(fragment_node, shadow_root) ⇒ Object
ShadowRoot identity registry: map a Nokogiri DocumentFragment (the shadow tree’s backing node) to the wrapping ShadowRoot so slot assignment and event composition can walk from any inner node back to its shadow boundary. Delegate to ShadowRootRegistry
1150
1151
1152
|
# File 'lib/dommy/document.rb', line 1150
def __internal_register_shadow_fragment__(fragment_node, shadow_root)
@shadow_registry.register(fragment_node, shadow_root)
end
|
#__internal_remove_doctype__(_doctype) ⇒ Object
Called by DocumentType#remove — the doctype is synthesized from the DTD, so remove the internal subset and mark it gone.
764
765
766
767
768
|
# File 'lib/dommy/document.rb', line 764
def __internal_remove_doctype__(_doctype)
@doctype_removed = true
@nokogiri_doc.internal_subset&.unlink
nil
end
|
#__internal_reset_wrapper__(nokogiri_node) ⇒ Object
Clear the cached wrapper so the next ‘wrap_node` creates a new one. Used by `customElements.define` to upgrade nodes that were constructed before the registration landed.
1140
1141
1142
|
# File 'lib/dommy/document.rb', line 1140
def __internal_reset_wrapper__(nokogiri_node)
@node_wrapper_cache.reset_wrapper(nokogiri_node)
end
|
#__internal_set_active_element__(el) ⇒ Object
517
518
519
|
# File 'lib/dommy/document.rb', line 517
def __internal_set_active_element__(el)
@active_element = el
end
|
#__internal_set_fullscreen_element__(element) ⇒ Object
644
645
646
647
648
649
650
|
# File 'lib/dommy/document.rb', line 644
def __internal_set_fullscreen_element__(element)
previous = @fullscreen_element
@fullscreen_element = element
return if previous == element
dispatch_event(Event.new("fullscreenchange"))
end
|
#__internal_shadow_root_containing__(node) ⇒ Object
1158
1159
1160
|
# File 'lib/dommy/document.rb', line 1158
def __internal_shadow_root_containing__(node)
@shadow_registry.find_enclosing(node)
end
|
#__internal_shadow_root_for_fragment__(fragment_node) ⇒ Object
1154
1155
1156
|
# File 'lib/dommy/document.rb', line 1154
def __internal_shadow_root_for_fragment__(fragment_node)
@shadow_registry.find_for_fragment(fragment_node)
end
|
#__js_call__(method, args) ⇒ Object
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
|
# File 'lib/dommy/document.rb', line 1017
def __js_call__(method, args)
case method
when "hasChildNodes"
@nokogiri_doc.children.any?
when "contains"
contains?(args[0])
when "isEqualNode"
is_equal_node(args[0])
when "appendChild"
append_child(args[0])
when "append"
document_insert(args, prepend: false)
when "prepend"
document_insert(args, prepend: true)
when "replaceChildren"
document_replace_children(args)
when "removeChild"
document_remove_child(args[0])
when "insertBefore"
document_insert_before(args[0], args[1])
when "replaceChild"
document_replace_child(args[0], args[1])
when "cloneNode"
clone_node(args[0])
when "normalize"
nil when "writeln"
write(*(args + ["\n"]))
when "exitFullscreen"
exit_fullscreen
when "startViewTransition"
callback = args[0]
if callback.respond_to?(:__js_call__)
callback.__js_call__("call", [])
elsif callback.respond_to?(:call)
callback.call
end
ViewTransition.new(@default_view)
when "createElement"
create_element(args[0])
when "createElementNS"
create_element_ns(args[0], args[1])
when "createTextNode"
create_text_node(args[0])
when "createComment"
(args[0])
when "createCDATASection"
create_cdata_section(args[0])
when "createProcessingInstruction"
create_processing_instruction(args[0], args[1])
when "createDocumentFragment"
create_document_fragment
when "querySelector"
query_selector(Internal.css_query_arg!(args))
when "querySelectorAll"
query_selector_all(Internal.css_query_arg!(args))
when "getElementById"
get_element_by_id(args[0])
when "getElementsByClassName"
get_elements_by_class_name(args[0])
when "getElementsByTagNameNS"
get_elements_by_tag_name_ns(args[0], args[1])
when "getElementsByTagName"
get_elements_by_tag_name(args[0])
when "getElementsByName"
get_elements_by_name(args[0])
when "createAttribute"
create_attribute(args[0])
when "createAttributeNS"
create_attribute_ns(args[0], args[1])
when "createTreeWalker"
create_tree_walker(args[0], coerce_what_to_show(args, 1), normalize_filter(args[2]))
when "createNodeIterator"
create_node_iterator(args[0], coerce_what_to_show(args, 1), normalize_filter(args[2]))
when "createRange"
create_range
when "createEvent"
create_event(args[0])
when "importNode"
import_node(args[0], args[1])
when "adoptNode"
adopt_node(args[0])
when "hasFocus"
has_focus?
when "getSelection"
get_selection
when "elementFromPoint"
element_from_point(args[0], args[1])
when "queryCommandSupported"
query_command_supported(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 "write"
write(*args)
when "open"
open
when "close"
close
else
nil
end
end
|
#__js_get__(key) ⇒ Object
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
|
# File 'lib/dommy/document.rb', line 872
def __js_get__(key)
case key
when "body"
body
when "head"
head
when "doctype"
doctype
when "implementation"
implementation
when "defaultView"
@default_view
when "fullscreenElement"
@fullscreen_element
when "fullscreenEnabled"
true
when "scrollingElement"
wrap_node(@nokogiri_doc.at_css("html"))
when "documentElement"
wrap_node(@nokogiri_doc.root)
when "title"
read_title
when "cookie"
cookie
when "nodeType"
9
when "activeElement"
active_element
when "URL", "documentURI"
url
when "baseURI"
base_uri
when "domain"
domain
when "origin"
origin
when "contentType"
content_type
when "location"
@default_view&.__js_get__("location")
when "characterSet", "charset", "inputEncoding"
"UTF-8"
when "dir"
document_element&.get_attribute("dir") || ""
when "designMode"
@design_mode || "off"
when "lastModified"
@last_modified || "01/01/1970 00:00:00"
when "readyState"
"complete"
when "visibilityState"
"visible"
when "hidden"
false
when "compatMode"
compat_mode
when "referrer"
referrer
when "links"
links
when "forms"
forms
when "scripts"
scripts
when "images"
images
when "embeds", "plugins"
HTMLCollection.new { @nokogiri_doc.css("embed").map { |n| wrap_node(n) }.compact }
when "anchors"
HTMLCollection.new { @nokogiri_doc.css("a[name]").map { |n| wrap_node(n) }.compact }
when "styleSheets"
NodeList.new
when "children"
children
when "childNodes"
child_nodes
when "firstChild"
child_nodes.to_a.first
when "lastChild"
child_nodes.to_a.last
when "parentNode", "parentElement", "nextSibling", "previousSibling", "ownerDocument"
nil
when "childElementCount"
child_element_count
when "firstElementChild"
first_element_child
when "lastElementChild"
last_element_child
when "nodeName"
"#document"
else
nil
end
end
|
#__js_set__(key, value) ⇒ Object
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
|
# File 'lib/dommy/document.rb', line 983
def __js_set__(key, value)
case key
when "title"
write_title(value.to_s)
when "cookie"
self.cookie = value.to_s
when "dir"
document_element&.set_attribute("dir", value.to_s)
when "designMode"
v = value.to_s.downcase
@design_mode = v if %w[on off].include?(v)
when "location"
loc = @default_view&.__js_get__("location")
loc&.__js_set__("href", value)
else
return Bridge::UNHANDLED
end
nil
end
|
#active_element ⇒ Object
Currently-focused element (or body if none). Updated via ‘el.focus()` / `el.blur()`.
502
503
504
|
# File 'lib/dommy/document.rb', line 502
def active_element
@active_element || body
end
|
#adopt_node(node) ⇒ Object
Move a node from another document into this one. The source node is detached from its previous owner and its ownerDocument becomes this. Returns the (possibly re-wrapped) node.
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
|
# File 'lib/dommy/document.rb', line 572
def adopt_node(node)
return nil unless node.respond_to?(:__dommy_backend_node__)
src = node.__dommy_backend_node__
src.unlink if src.parent
return wrap_node(src) if src.document == @nokogiri_doc
src_doc_wrapper = node.instance_variable_get(:@document)
@nokogiri_doc.root.add_child(src)
src.unlink
node.instance_variable_set(:@document, self)
if src_doc_wrapper.respond_to?(:__internal_reset_wrapper__)
src_doc_wrapper.__internal_reset_wrapper__(src)
end
@node_wrapper_cache.register(src, node)
node
end
|
#append_child(node) ⇒ Object
Append a node as a child of the document itself (e.g. a comment alongside the document element). Adopts the node into this document.
700
701
702
703
704
705
|
# File 'lib/dommy/document.rb', line 700
def append_child(node)
return node unless node.respond_to?(:__dommy_backend_node__)
@nokogiri_doc.add_child(node.__dommy_backend_node__)
node
end
|
#at_xpath(expression) ⇒ Object
XPath queries returning wrapped nodes (Element / TextNode / etc).
383
384
385
386
|
# File 'lib/dommy/document.rb', line 383
def at_xpath(expression)
node = @nokogiri_doc.at_xpath(expression)
node && wrap_node(node)
end
|
#attach_template_content(template_element, html) ⇒ Object
—– template content helpers (called from Element) —–
1282
1283
1284
|
# File 'lib/dommy/document.rb', line 1282
def attach_template_content(template_element, html)
@template_content_registry.attach(template_element, html)
end
|
#backend_node(node) ⇒ Object
791
792
793
|
# File 'lib/dommy/document.rb', line 791
def backend_node(node)
node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__ : nil
end
|
#base_uri ⇒ Object
‘document.baseURI` — resolves the first `<base href>` (if any) relative to the document URL; otherwise just the document URL. When `<base href>` is itself absolute, that wins. Browsers also ignore subsequent <base> elements; we mirror that.
405
406
407
408
409
410
411
412
413
414
415
416
417
418
|
# File 'lib/dommy/document.rb', line 405
def base_uri
doc_url = url
base_el = @nokogiri_doc.at_css("base[href]")
return doc_url unless base_el
href = base_el["href"].to_s
return doc_url if href.empty?
begin
URI.join(doc_url.to_s.empty? ? "about:blank" : doc_url, href).to_s
rescue URI::InvalidURIError
doc_url
end
end
|
#body ⇒ Object
Resolve ‘body` fresh from the tree (not memoized) so it tracks a swapped `<body>` — e.g. Turbo’s page render does ‘documentElement.replaceChild(newBody, body)`, after which a stale cached wrapper would keep returning the detached old body. wrap_node caches by node, so identity (`document.body === document.body`) still holds.
373
374
375
|
# File 'lib/dommy/document.rb', line 373
def body
wrap_node(@nokogiri_doc.at_css("body"))
end
|
#child_element_count ⇒ Object
488
489
490
|
# File 'lib/dommy/document.rb', line 488
def child_element_count
children.size
end
|
#child_nodes ⇒ Object
All child nodes of the document (doctype + document element, …), as a live, cached NodeList — unlike ‘children`, which is element-only. Cached so `document.childNodes === document.childNodes` and mutations are reflected.
482
483
484
485
486
|
# File 'lib/dommy/document.rb', line 482
def child_nodes
@live_child_nodes ||= LiveNodeList.new do
@nokogiri_doc.children.map { |n| wrap_node(n) }.compact
end
end
|
#children ⇒ Object
ParentNode mixin (operates on the document’s element children —in practice the ‘<html>` root).
472
473
474
475
476
477
|
# File 'lib/dommy/document.rb', line 472
def children
HTMLCollection.new do
root = @nokogiri_doc.root
root ? [wrap_node(root)].compact : []
end
end
|
#clone_node(deep) ⇒ Object
‘document.cloneNode(deep)` → a fresh Document over a (deep) copy of the Nokogiri tree, preserving the content type.
786
787
788
789
|
# File 'lib/dommy/document.rb', line 786
def clone_node(deep)
copy = deep ? @nokogiri_doc.dup : Backend.document_class.new
Document.new(nil, nokogiri_doc: copy).tap { |d| d.content_type = @content_type }
end
|
#close ⇒ Object
842
843
844
|
# File 'lib/dommy/document.rb', line 842
def close
nil
end
|
#coerce_what_to_show(args, index) ⇒ Object
WebIDL ‘unsigned long whatToShow = 0xFFFFFFFF`: an omitted or `undefined` argument uses the default; `null` coerces to 0; otherwise ToUint32.
#compat_mode ⇒ Object
‘document.compatMode` — “CSS1Compat” in no-quirks mode, “BackCompat” in quirks mode. A missing doctype is quirks; a bare `<!DOCTYPE html>` (no public/system identifier) is no-quirks. (The full quirks algorithm keys off specific legacy public ids; this covers the common cases.)
341
342
343
344
345
346
347
|
# File 'lib/dommy/document.rb', line 341
def compat_mode
dt = @nokogiri_doc.internal_subset
return "BackCompat" unless dt
return "CSS1Compat" if dt.name.to_s.downcase == "html" && dt.external_id.nil?
"BackCompat"
end
|
#contains?(other) ⇒ Boolean
‘document.contains(node)` — true if `node` is the document itself or any node attached to its tree (per Node.contains, which all nodes including the document expose). Per spec, false for null / a non-Node.
509
510
511
512
513
514
515
|
# File 'lib/dommy/document.rb', line 509
def contains?(other)
return true if other.equal?(self)
return false unless other.respond_to?(:__dommy_backend_node__)
node = other.__dommy_backend_node__
node.document == @nokogiri_doc && node.ancestors.include?(@nokogiri_doc)
end
|
#cookie ⇒ Object
797
798
799
|
# File 'lib/dommy/document.rb', line 797
def cookie
@cookie_jar.to_cookie_string
end
|
#cookie=(value) ⇒ Object
801
802
803
804
|
# File 'lib/dommy/document.rb', line 801
def cookie=(value)
@cookie_jar.set_cookie(value)
nil
end
|
#create_attribute(name) ⇒ Object
Create a detached Attr. ‘setAttributeNode` attaches it to an element. Per spec, name must match the XML Name production —invalid names throw InvalidCharacterError.
524
525
526
|
# File 'lib/dommy/document.rb', line 524
def create_attribute(name)
@node_wrapper_cache.create_attribute(name)
end
|
#create_attribute_ns(namespace_uri, qualified_name) ⇒ Object
528
529
530
|
# File 'lib/dommy/document.rb', line 528
def create_attribute_ns(namespace_uri, qualified_name)
@node_wrapper_cache.create_attribute_ns(namespace_uri, qualified_name)
end
|
#create_cdata_section(text) ⇒ Object
860
861
862
|
# File 'lib/dommy/document.rb', line 860
def create_cdata_section(text)
@node_wrapper_cache.create_cdata_section(text)
end
|
Create a Comment node. Wraps the Nokogiri comment so it flows through the same wrap_node identity machinery as Element / TextNode.
856
857
858
|
# File 'lib/dommy/document.rb', line 856
def (text)
@node_wrapper_cache.(text)
end
|
#create_document_fragment ⇒ Object
864
865
866
|
# File 'lib/dommy/document.rb', line 864
def create_document_fragment
@node_wrapper_cache.create_document_fragment
end
|
#create_element(name) ⇒ Object
Delegate factory methods to NodeWrapperCache
1260
1261
1262
|
# File 'lib/dommy/document.rb', line 1260
def create_element(name)
@node_wrapper_cache.create_element(name)
end
|
#create_element_ns(namespace_uri, qualified_name) ⇒ Object
806
807
808
|
# File 'lib/dommy/document.rb', line 806
def create_element_ns(namespace_uri, qualified_name)
@node_wrapper_cache.create_element_ns(namespace_uri, qualified_name)
end
|
#create_event(type_name) ⇒ Object
Legacy ‘document.createEvent(“EventName”)` factory. Returns an Event subclass instance whose init still has to be called (`event.initEvent(type, bubbles, cancelable)`). Matches the mapping happy-dom and linkedom use.
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
|
# File 'lib/dommy/document.rb', line 605
def create_event(type_name)
name = type_name.to_s
case name
when "Event", "Events", "HTMLEvents"
Event.new("")
when "CustomEvent"
CustomEvent.new("")
when "MouseEvent", "MouseEvents"
MouseEvent.new("")
when "KeyboardEvent", "KeyboardEvents"
KeyboardEvent.new("")
else
Event.new("")
end
end
|
#create_node_iterator(root, what_to_show = NodeFilter::SHOW_ALL, filter = nil) ⇒ Object
‘document.createNodeIterator(root, whatToShow?, filter?)` —flat depth-first iteration.
672
673
674
675
676
677
678
|
# File 'lib/dommy/document.rb', line 672
def create_node_iterator(root, what_to_show = NodeFilter::SHOW_ALL, filter = nil)
iterator = NodeIterator.new(root, what_to_show, filter)
@node_iterators << iterator
iterator
end
|
#create_processing_instruction(target, data) ⇒ Object
694
695
696
|
# File 'lib/dommy/document.rb', line 694
def create_processing_instruction(target, data)
ProcessingInstruction.new(target, data)
end
|
#create_range ⇒ Object
635
636
637
|
# File 'lib/dommy/document.rb', line 635
def create_range
Range.new(self)
end
|
#create_text_node(text) ⇒ Object
1264
1265
1266
|
# File 'lib/dommy/document.rb', line 1264
def create_text_node(text)
@node_wrapper_cache.create_text_node(text)
end
|
#create_tree_walker(root, what_to_show = NodeFilter::SHOW_ALL, filter = nil) ⇒ Object
‘document.createTreeWalker(root, whatToShow?, filter?)` — stateful tree traversal with sibling/parent navigation. `filter` may be a Ruby Proc, a JS-bridge callable, or an object with `accept_node` / `acceptNode`.
#doctype ⇒ Object
Minimal DocumentType — represents the ‘<!doctype html>` line. Always present in HTML5 documents we parse, so we synthesize a stub object whose only useful field is `name`. Tests just need `nodeType == 10`.
684
685
686
687
688
|
# File 'lib/dommy/document.rb', line 684
def doctype
return nil if @doctype_removed
@doctype ||= DocumentType.new("html", owner_document: self)
end
|
#document_element ⇒ Object
359
360
361
362
|
# File 'lib/dommy/document.rb', line 359
def document_element
wrap_node(@nokogiri_doc.root)
end
|
#document_insert(args, prepend:) ⇒ Object
ParentNode / Node mutation on the document’s direct children (the doctype and the document element). Operate on the Nokogiri document node; string arguments (which would need a text child the document can’t hold) are ignored rather than raising.
711
712
713
714
715
716
717
718
719
|
# File 'lib/dommy/document.rb', line 711
def document_insert(args, prepend:)
nodes = args.filter_map { |a| backend_node(a) }
if prepend && (first = @nokogiri_doc.children.first)
nodes.reverse_each { |n| first.add_previous_sibling(n) }
else
nodes.each { |n| @nokogiri_doc.add_child(n) }
end
nil
end
|
#document_insert_before(node, ref) ⇒ Object
739
740
741
742
743
744
745
746
747
748
749
750
|
# File 'lib/dommy/document.rb', line 739
def document_insert_before(node, ref)
bn = backend_node(node)
return node unless bn
ref_node = ref && backend_node(ref)
if ref_node && ref_node.parent == @nokogiri_doc
ref_node.add_previous_sibling(bn)
else
@nokogiri_doc.add_child(bn)
end
node
end
|
#document_remove_child(node) ⇒ Object
727
728
729
730
731
732
733
734
735
736
737
|
# File 'lib/dommy/document.rb', line 727
def document_remove_child(node)
return __internal_remove_doctype__(node) if node.is_a?(DocumentType)
bn = backend_node(node)
raise DOMException::NotFoundError, "node is not a child of this document" unless bn && bn.parent == @nokogiri_doc
bn.unlink
node
end
|
#document_replace_child(new_child, old_child) ⇒ Object
752
753
754
755
756
757
758
759
760
|
# File 'lib/dommy/document.rb', line 752
def document_replace_child(new_child, old_child)
old_bn = backend_node(old_child)
raise DOMException::NotFoundError, "node is not a child of this document" unless old_bn && old_bn.parent == @nokogiri_doc
new_bn = backend_node(new_child)
old_bn.add_previous_sibling(new_bn) if new_bn
old_bn.unlink
old_child
end
|
#document_replace_children(args) ⇒ Object
721
722
723
724
725
|
# File 'lib/dommy/document.rb', line 721
def document_replace_children(args)
@nokogiri_doc.children.each(&:unlink)
args.filter_map { |a| backend_node(a) }.each { |n| @nokogiri_doc.add_child(n) }
nil
end
|
#domain ⇒ Object
‘document.domain` — host portion of the URL. Real browsers restrict cross-origin reads of this; we just return the bare host.
422
423
424
425
426
427
|
# File 'lib/dommy/document.rb', line 422
def domain
view = @default_view
return "" unless view&.location
view.location.__js_get__("hostname").to_s
end
|
#element_from_point(_x, _y) ⇒ Object
662
663
664
|
# File 'lib/dommy/document.rb', line 662
def element_from_point(_x, _y)
nil
end
|
#exit_fullscreen ⇒ Object
Also known as:
exitFullscreen
652
653
654
655
656
657
658
|
# File 'lib/dommy/document.rb', line 652
def exit_fullscreen
return PromiseValue.resolve(@default_view, nil) if @fullscreen_element.nil?
@fullscreen_element = nil
dispatch_event(Event.new("fullscreenchange"))
PromiseValue.resolve(@default_view, nil)
end
|
#first_element_child ⇒ Object
492
493
494
|
# File 'lib/dommy/document.rb', line 492
def first_element_child
wrap_node(@nokogiri_doc.root)
end
|
452
453
454
455
456
|
# File 'lib/dommy/document.rb', line 452
def forms
HTMLCollection.new do
@nokogiri_doc.css("form").map { |n| wrap_node(n) }.compact
end
end
|
#get_element_by_id(id) ⇒ Object
1276
1277
1278
|
# File 'lib/dommy/document.rb', line 1276
def get_element_by_id(id)
@node_wrapper_cache.get_element_by_id(id)
end
|
#get_elements_by_class_name(name) ⇒ Object
868
869
870
|
# File 'lib/dommy/document.rb', line 868
def get_elements_by_class_name(name)
@node_wrapper_cache.get_elements_by_class_name(name)
end
|
#get_elements_by_name(name) ⇒ Object
814
815
816
|
# File 'lib/dommy/document.rb', line 814
def get_elements_by_name(name)
@node_wrapper_cache.get_elements_by_name(name)
end
|
#get_elements_by_tag_name(name) ⇒ Object
810
811
812
|
# File 'lib/dommy/document.rb', line 810
def get_elements_by_tag_name(name)
@node_wrapper_cache.get_elements_by_tag_name(name)
end
|
#get_elements_by_tag_name_ns(namespace, local_name) ⇒ Object
818
819
820
|
# File 'lib/dommy/document.rb', line 818
def get_elements_by_tag_name_ns(namespace, local_name)
HTMLCollection.elements_by_tag_name_ns(@nokogiri_doc, self, namespace, local_name)
end
|
#get_selection ⇒ Object
631
632
633
|
# File 'lib/dommy/document.rb', line 631
def get_selection
@__selection ||= Selection.new(self)
end
|
#has_focus? ⇒ Boolean
Also known as:
has_focus
Stubs for layout / focus / selection / execCommand APIs that don’t apply to a layout-less DOM. They exist so callers don’t hit NoMethodError; semantics are documented as no-op.
625
626
627
|
# File 'lib/dommy/document.rb', line 625
def has_focus?
true
end
|
#has_template_content?(nokogiri_node) ⇒ Boolean
1298
1299
1300
|
# File 'lib/dommy/document.rb', line 1298
def has_template_content?(nokogiri_node)
@template_content_registry.has_content?(nokogiri_node)
end
|
#head ⇒ Object
364
365
366
|
# File 'lib/dommy/document.rb', line 364
def head
wrap_node(@nokogiri_doc.at_css("head"))
end
|
#html_document? ⇒ Boolean
Whether this is an “HTML document” in the DOM sense (created by the HTML parser / ‘text/html`), as opposed to an XML document. It drives the case-folding rules: `createElement` lowercases names and `Element#tagName` uppercases HTML-namespace names only in an HTML document. An XML or XHTML document (e.g. an `application/xhtml+xml` / `text/xml` resource) preserves case.
333
334
335
|
# File 'lib/dommy/document.rb', line 333
def html_document?
@content_type == "text/html"
end
|
#images ⇒ Object
464
465
466
467
468
|
# File 'lib/dommy/document.rb', line 464
def images
HTMLCollection.new do
@nokogiri_doc.css("img").map { |n| wrap_node(n) }.compact
end
end
|
#implementation ⇒ Object
690
691
692
|
# File 'lib/dommy/document.rb', line 690
def implementation
@implementation ||= DOMImplementation.new(self)
end
|
#import_node(node, deep = false) ⇒ Object
Copy a node from another document into this one. The returned wrapper is owned by ‘this`. Per spec, the source node is left in place. `deep: true` copies the entire subtree.
562
563
564
565
566
567
|
# File 'lib/dommy/document.rb', line 562
def import_node(node, deep = false)
return nil unless node.respond_to?(:__dommy_backend_node__)
copy = clone_into_doc(node.__dommy_backend_node__, deep)
wrap_node(copy)
end
|
#last_element_child ⇒ Object
496
497
498
|
# File 'lib/dommy/document.rb', line 496
def last_element_child
wrap_node(@nokogiri_doc.root)
end
|
#links ⇒ Object
Live HTMLCollection helpers — each call re-queries the document so post-mutation reads reflect the current state.
446
447
448
449
450
|
# File 'lib/dommy/document.rb', line 446
def links
HTMLCollection.new do
@nokogiri_doc.css("a[href], area[href]").map { |n| wrap_node(n) }.compact
end
end
|
#migrate_template_descendants(root) ⇒ Object
1294
1295
1296
|
# File 'lib/dommy/document.rb', line 1294
def migrate_template_descendants(root)
@template_content_registry.migrate_descendants(root)
end
|
#normalize_filter(value) ⇒ Object
A ‘null`/`undefined` filter argument means “no filter”.
552
553
554
555
556
557
|
# File 'lib/dommy/document.rb', line 552
def normalize_filter(value)
return nil if value.nil?
return nil if defined?(Bridge::UNDEFINED) && value.equal?(Bridge::UNDEFINED)
value
end
|
#notify_attribute_mutation(target_node:, attribute_name:, old_value:, namespace: nil) ⇒ Object
1242
1243
1244
1245
1246
1247
1248
1249
|
# File 'lib/dommy/document.rb', line 1242
def notify_attribute_mutation(target_node:, attribute_name:, old_value:, namespace: nil)
@mutation_coordinator.notify_attribute_mutation(
target_node: target_node,
attribute_name: attribute_name,
old_value: old_value,
namespace: namespace
)
end
|
#notify_character_data_mutation(target_node:, old_value:) ⇒ Object
1251
1252
1253
1254
1255
1256
|
# File 'lib/dommy/document.rb', line 1251
def notify_character_data_mutation(target_node:, old_value:)
@mutation_coordinator.notify_character_data_mutation(
target_node: target_node,
old_value: old_value
)
end
|
#notify_child_list_mutation(target_node:, added_nodes:, removed_nodes:, previous_sibling: nil, next_sibling: nil) ⇒ Object
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
|
# File 'lib/dommy/document.rb', line 1195
def notify_child_list_mutation(
target_node:,
added_nodes:,
removed_nodes:,
previous_sibling: nil,
next_sibling: nil
)
@mutation_coordinator.notify_child_list_mutation(
target_node: target_node,
added_nodes: added_nodes,
removed_nodes: removed_nodes,
previous_sibling: previous_sibling,
next_sibling: next_sibling
)
end
|
#open ⇒ Object
No-ops — real browsers reset the DOM on ‘open()` and flush pending writes on `close()`. We don’t model the parse pipeline.
838
839
840
|
# File 'lib/dommy/document.rb', line 838
def open
nil
end
|
#origin ⇒ Object
‘document.origin` — serialized origin of the document URL, mirroring `window.location.origin`. Empty when there is no associated window.
431
432
433
434
435
436
|
# File 'lib/dommy/document.rb', line 431
def origin
view = @default_view
return "" unless view&.location
view.location.__js_get__("origin").to_s
end
|
#query_command_supported(_command) ⇒ Object
666
667
668
|
# File 'lib/dommy/document.rb', line 666
def query_command_supported(_command)
false
end
|
#query_selector(selector) ⇒ Object
1268
1269
1270
|
# File 'lib/dommy/document.rb', line 1268
def query_selector(selector)
@node_wrapper_cache.query_selector(selector)
end
|
#query_selector_all(selector) ⇒ Object
1272
1273
1274
|
# File 'lib/dommy/document.rb', line 1272
def query_selector_all(selector)
@node_wrapper_cache.query_selector_all(selector)
end
|
#referrer ⇒ Object
‘document.referrer` — Dommy never has a referring page, so this is always empty.
440
441
442
|
# File 'lib/dommy/document.rb', line 440
def referrer
""
end
|
#register_observer(observer) ⇒ Object
1187
1188
1189
|
# File 'lib/dommy/document.rb', line 1187
def register_observer(observer)
@mutation_coordinator.register_observer(observer)
end
|
#remove_node_with_notify(node) ⇒ Object
Unlink a backend node from its parent and queue a childList removal record capturing the node’s position (previous/next sibling) BEFORE the unlink, so the record’s previousSibling/nextSibling are correct (the coordinator can’t recover them once the node is detached). Used by every remove path.
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
|
# File 'lib/dommy/document.rb', line 1215
def remove_node_with_notify(node)
parent = node.parent
return unless parent
prev_w = node.previous_sibling && wrap_node(node.previous_sibling)
next_w = node.next_sibling && wrap_node(node.next_sibling)
run_node_iterator_pre_remove(node)
node.unlink
notify_child_list_mutation(
target_node: parent,
added_nodes: [],
removed_nodes: [node],
previous_sibling: prev_w,
next_sibling: next_w
)
end
|
#run_node_iterator_pre_remove(backend_node) ⇒ Object
Run the “NodeIterator pre-removing steps” for every live iterator before ‘backend_node` is detached, so referenceNode/pointerBeforeReferenceNode stay valid. `backend_node` must still be attached (tree intact) here.
1235
1236
1237
1238
1239
1240
|
# File 'lib/dommy/document.rb', line 1235
def run_node_iterator_pre_remove(backend_node)
return if @node_iterators.empty?
removed = wrap_node(backend_node)
@node_iterators.each { |iter| iter.pre_remove(removed) }
end
|
#scripts ⇒ Object
458
459
460
461
462
|
# File 'lib/dommy/document.rb', line 458
def scripts
HTMLCollection.new do
@nokogiri_doc.css("script").map { |n| wrap_node(n) }.compact
end
end
|
#template_content_fragment(template_element) ⇒ Object
1286
1287
1288
|
# File 'lib/dommy/document.rb', line 1286
def template_content_fragment(template_element)
@template_content_registry.fragment_for(template_element)
end
|
#template_content_inner_html(template_element) ⇒ Object
1290
1291
1292
|
# File 'lib/dommy/document.rb', line 1290
def template_content_inner_html(template_element)
@template_content_registry.inner_html_of(template_element)
end
|
#title ⇒ Object
—– Public Ruby API (snake_case) —–
351
352
353
|
# File 'lib/dommy/document.rb', line 351
def title
read_title
end
|
#title=(value) ⇒ Object
355
356
357
|
# File 'lib/dommy/document.rb', line 355
def title=(value)
write_title(value.to_s)
end
|
#to_html ⇒ Object
Serialize the whole document to HTML (including the doctype).
378
379
380
|
# File 'lib/dommy/document.rb', line 378
def to_html
@nokogiri_doc.to_html
end
|
#unregister_observer(observer) ⇒ Object
1191
1192
1193
|
# File 'lib/dommy/document.rb', line 1191
def unregister_observer(observer)
@mutation_coordinator.unregister_observer(observer)
end
|
#url ⇒ Object
Also known as:
document_uri
‘document.URL` / `documentURI` — both return location.href in real browsers (legacy aliases of the same field).
394
395
396
397
|
# File 'lib/dommy/document.rb', line 394
def url
view = @default_view
view&.location ? view.location.href : ""
end
|
#wrap_node(node) ⇒ Object
Delegate node wrapping to NodeWrapperCache
1133
1134
1135
|
# File 'lib/dommy/document.rb', line 1133
def wrap_node(node)
@node_wrapper_cache.wrap(node)
end
|
#write(*args) ⇒ Object
‘document.write(html)` — legacy API. Appends parsed nodes to the body. Real browsers only re-stream the DOM during initial parse; this stub is enough for tests that fire write() during teardown.
825
826
827
828
829
830
831
832
833
834
|
# File 'lib/dommy/document.rb', line 825
def write(*args)
html = args.join
fragment = Parser.fragment(html, owner_doc: @nokogiri_doc)
removed = []
added = fragment.children.to_a
body_node = body.__dommy_backend_node__
added.each { |node| body_node.add_child(node) }
notify_child_list_mutation(target_node: body_node, added_nodes: added, removed_nodes: removed)
nil
end
|
#xpath(expression) ⇒ Object
388
389
390
|
# File 'lib/dommy/document.rb', line 388
def xpath(expression)
@nokogiri_doc.xpath(expression).map { |node| wrap_node(node) }
end
|