nokolexbor 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb6fea4008f68e39df01fbc3920634bd747f6c6e58605876b4600b6037745d4a
4
- data.tar.gz: 38f67c6e320e95d35a82db68f34b08e7103fa8d56e52fe6e59ebdeb266701c16
3
+ metadata.gz: f479dae3741e343ff292ef23b69fb31697f149b1e799b5eed26c2626ff5b9909
4
+ data.tar.gz: 35e0a468a1c9c94283271e66abd98663ce4ad9699613c4dea70df82f4d91e99b
5
5
  SHA512:
6
- metadata.gz: a44ba9da124b17e58ae2abc8c6e99cfab22edbdcdff5b07e9636838f28098a4716aa6f50cf7282c1f15fcbb0630b7bd5ef814b54cb7886dd75b16a864e496405
7
- data.tar.gz: 40fcdc69521d2f985be5fa62addf3f4fd03ac577eeb66878f496dbc684fc3925ceddb9fa2f74a870188431c672b16394ccc6c58303caae6c6718af8e978bc967
6
+ metadata.gz: 1b7c6cb9015d9b4540fb79e3fb04afb484a072f914f2480caa65f81fb66a077c099b8cbc81026aa9c8f1b4fc960ce57d9d2c17e1b1982de0f85a70c35e77cff9
7
+ data.tar.gz: 6bc107899aae62a756a84944579442fe482cece4375b49bd59a1061525d5e9a590e74308a08758b0effb6190c0f6f9920db5f6bceef43bf1caa4e8c413e68aca
@@ -237,9 +237,23 @@ nl_node_content_set(VALUE self, VALUE content)
237
237
 
238
238
  const char *c_content = StringValuePtr(content);
239
239
  size_t content_len = RSTRING_LEN(content);
240
- lxb_status_t status = lxb_dom_node_text_content_set(node, (const lxb_char_t *)c_content, content_len);
241
- if (status != LXB_STATUS_OK) {
242
- nl_raise_lexbor_error(status);
240
+
241
+ if (node->type == LXB_DOM_NODE_TYPE_DOCUMENT_FRAGMENT || node->type == LXB_DOM_NODE_TYPE_ELEMENT) {
242
+ lxb_dom_text_t *text = lxb_dom_document_create_text_node(node->owner_document, (const lxb_char_t *)c_content, content_len);
243
+ if (text == NULL) {
244
+ nl_raise_lexbor_error(LXB_STATUS_ERROR_MEMORY_ALLOCATION);
245
+ }
246
+
247
+ // Ruby wrappers may still reference replaced nodes, so detach rather than destroy them.
248
+ while (node->first_child != NULL) {
249
+ lxb_dom_node_remove(node->first_child);
250
+ }
251
+ lxb_dom_node_insert_child(node, lxb_dom_interface_node(text));
252
+ } else {
253
+ lxb_status_t status = lxb_dom_node_text_content_set(node, (const lxb_char_t *)c_content, content_len);
254
+ if (status != LXB_STATUS_OK) {
255
+ nl_raise_lexbor_error(status);
256
+ }
243
257
  }
244
258
  return content;
245
259
  }
@@ -561,12 +575,19 @@ static VALUE
561
575
  nl_node_inner_html(int argc, VALUE *argv, VALUE self)
562
576
  {
563
577
  lxb_dom_node_t *node = nl_rb_node_unwrap(self);
578
+ lxb_dom_node_t *serialization_root = node;
564
579
  lexbor_str_t str = {0};
565
580
  VALUE options;
566
581
  lxb_status_t status;
567
582
  size_t indent = 0;
568
583
  rb_scan_args(argc, argv, "01", &options);
569
584
 
585
+ if (node->type == LXB_DOM_NODE_TYPE_ELEMENT
586
+ && node->local_name == LXB_TAG_TEMPLATE
587
+ && node->ns == LXB_NS_HTML) {
588
+ serialization_root = &lxb_html_interface_template(node)->content->node;
589
+ }
590
+
570
591
  if (TYPE(options) == T_HASH) {
571
592
  VALUE rb_indent = rb_hash_aref(options, ID2SYM(rb_intern("indent")));
572
593
  if (!NIL_P(rb_indent)) {
@@ -574,9 +595,9 @@ nl_node_inner_html(int argc, VALUE *argv, VALUE self)
574
595
  }
575
596
  }
576
597
  if (indent > 0) {
577
- status = lxb_html_serialize_pretty_deep_str(node, 0, 0, &str);
598
+ status = lxb_html_serialize_pretty_deep_str(serialization_root, 0, 0, &str);
578
599
  } else {
579
- status = lxb_html_serialize_deep_str(node, &str);
600
+ status = lxb_html_serialize_deep_str(serialization_root, &str);
580
601
  }
581
602
  if (status != LXB_STATUS_OK) {
582
603
  if (str.data != NULL) {
@@ -921,6 +942,13 @@ nl_node_equals(VALUE self, VALUE other)
921
942
  return node1 == node2 ? Qtrue : Qfalse;
922
943
  }
923
944
 
945
+ static VALUE
946
+ nl_node_pointer_id(VALUE self)
947
+ {
948
+ lxb_dom_node_t *node = nl_rb_node_unwrap(self);
949
+ return ULL2NUM((unsigned long long)(uintptr_t)node);
950
+ }
951
+
924
952
  const lxb_char_t *
925
953
  lxb_dom_node_name_qualified(lxb_dom_node_t *node, size_t *len)
926
954
  {
@@ -951,7 +979,7 @@ nl_node_parse_fragment(lxb_dom_document_t *doc, lxb_dom_element_t *element, lxb_
951
979
  size_t tag_name_len;
952
980
  lxb_html_document_t *html_doc = lxb_html_interface_document(doc);
953
981
  if (element == NULL) {
954
- const lxb_char_t *tag_name = lxb_tag_name_by_id(lxb_html_document_tags(html_doc), LXB_TAG__UNDEF, &tag_name_len);
982
+ const lxb_char_t *tag_name = lxb_tag_name_by_id(lxb_html_document_tags(html_doc), LXB_TAG_TEMPLATE, &tag_name_len);
955
983
  if (tag_name == NULL) {
956
984
  rb_raise(rb_eRuntimeError, "Error getting tag name");
957
985
  }
@@ -1241,6 +1269,7 @@ void Init_nl_node(void)
1241
1269
  rb_define_method(cNokolexborNode, "[]=", nl_node_set_attr, 2);
1242
1270
  rb_define_method(cNokolexborNode, "remove_attr", nl_node_remove_attr, 1);
1243
1271
  rb_define_method(cNokolexborNode, "==", nl_node_equals, 1);
1272
+ rb_define_method(cNokolexborNode, "pointer_id", nl_node_pointer_id, 0);
1244
1273
  rb_define_method(cNokolexborNode, "css_impl", nl_node_css, 1);
1245
1274
  rb_define_method(cNokolexborNode, "at_css_impl", nl_node_at_css, 1);
1246
1275
  rb_define_method(cNokolexborNode, "inner_html", nl_node_inner_html, -1);
@@ -1289,4 +1318,6 @@ void Init_nl_node(void)
1289
1318
  rb_define_alias(cNokolexborNode, "unlink", "remove");
1290
1319
  rb_define_alias(cNokolexborNode, "type", "node_type");
1291
1320
  rb_define_alias(cNokolexborNode, "dup", "clone");
1321
+ rb_define_alias(cNokolexborNode, "eql?", "==");
1322
+ rb_define_alias(cNokolexborNode, "hash", "pointer_id");
1292
1323
  }
@@ -7998,7 +7998,7 @@ nl_xmlXPathNextParent(xmlXPathParserContextPtr ctxt, lxb_dom_node_t_ptr cur) {
7998
7998
  case LXB_DOM_NODE_TYPE_ATTRIBUTE: {
7999
7999
  lxb_dom_attr_t_ptr att = (lxb_dom_attr_t_ptr) ctxt->context->node;
8000
8000
 
8001
- return(att->node.parent);
8001
+ return((lxb_dom_node_t_ptr) att->owner);
8002
8002
  }
8003
8003
  case LXB_DOM_NODE_TYPE_DOCUMENT:
8004
8004
  case LXB_DOM_NODE_TYPE_DOCUMENT_TYPE:
@@ -8068,7 +8068,7 @@ nl_xmlXPathNextAncestor(xmlXPathParserContextPtr ctxt, lxb_dom_node_t_ptr cur) {
8068
8068
  case LXB_DOM_NODE_TYPE_ATTRIBUTE: {
8069
8069
  lxb_dom_attr_t_ptr tmp = (lxb_dom_attr_t_ptr) ctxt->context->node;
8070
8070
 
8071
- return(tmp->node.parent);
8071
+ return((lxb_dom_node_t_ptr) tmp->owner);
8072
8072
  }
8073
8073
  case LXB_DOM_NODE_TYPE_DOCUMENT:
8074
8074
  case LXB_DOM_NODE_TYPE_DOCUMENT_TYPE:
@@ -8235,7 +8235,7 @@ nl_xmlXPathNextFollowing(xmlXPathParserContextPtr ctxt, lxb_dom_node_t_ptr cur)
8235
8235
  if (cur == NULL) {
8236
8236
  cur = ctxt->context->node;
8237
8237
  if (cur->type == LXB_DOM_NODE_TYPE_ATTRIBUTE) {
8238
- cur = cur->parent;
8238
+ cur = (lxb_dom_node_t_ptr)((lxb_dom_attr_t *)cur)->owner;
8239
8239
  } else if (cur->type == XML_NAMESPACE_DECL) {
8240
8240
  xmlNsPtr ns = (xmlNsPtr) cur;
8241
8241
 
@@ -8305,7 +8305,7 @@ nl_xmlXPathNextPreceding(xmlXPathParserContextPtr ctxt, lxb_dom_node_t_ptr cur)
8305
8305
  if (cur == NULL) {
8306
8306
  cur = ctxt->context->node;
8307
8307
  if (cur->type == LXB_DOM_NODE_TYPE_ATTRIBUTE) {
8308
- cur = cur->parent;
8308
+ cur = (lxb_dom_node_t_ptr)((lxb_dom_attr_t *)cur)->owner;
8309
8309
  } else if (cur->type == XML_NAMESPACE_DECL) {
8310
8310
  xmlNsPtr ns = (xmlNsPtr) cur;
8311
8311
 
@@ -8359,7 +8359,7 @@ xmlXPathNextPrecedingInternal(xmlXPathParserContextPtr ctxt,
8359
8359
  if (cur == NULL)
8360
8360
  return (NULL);
8361
8361
  if (cur->type == LXB_DOM_NODE_TYPE_ATTRIBUTE) {
8362
- cur = cur->parent;
8362
+ cur = (lxb_dom_node_t_ptr)((lxb_dom_attr_t *)cur)->owner;
8363
8363
  } else if (cur->type == XML_NAMESPACE_DECL) {
8364
8364
  xmlNsPtr ns = (xmlNsPtr) cur;
8365
8365
 
@@ -8452,9 +8452,22 @@ nl_xmlXPathNextAttribute(xmlXPathParserContextPtr ctxt, lxb_dom_node_t_ptr cur)
8452
8452
  if (cur == NULL) {
8453
8453
  if (ctxt->context->node == (lxb_dom_node_t_ptr) ctxt->context->doc)
8454
8454
  return(NULL);
8455
- return (lxb_dom_node_t_ptr)lxb_dom_element_first_attribute((lxb_dom_element_t *)ctxt->context->node);
8455
+ cur = (lxb_dom_node_t_ptr)lxb_dom_element_first_attribute((lxb_dom_element_t *)ctxt->context->node);
8456
+ } else {
8457
+ cur = (lxb_dom_node_t_ptr)(((lxb_dom_attr_t *)cur)->next);
8458
+ }
8459
+ /*
8460
+ * In Lexbor, attr->owner is not set during tree construction.
8461
+ * Set it here so axis traversal functions that need to find the
8462
+ * owning element (preceding::, following::, ancestor::, parent::)
8463
+ * can use attr->owner when attributes are context nodes.
8464
+ */
8465
+ if (cur != NULL) {
8466
+ lxb_dom_attr_t *attr = (lxb_dom_attr_t *)cur;
8467
+ if (attr->owner == NULL)
8468
+ attr->owner = (lxb_dom_element_t *)ctxt->context->node;
8456
8469
  }
8457
- return (lxb_dom_node_t_ptr)(((lxb_dom_attr_t *)cur)->next);
8470
+ return cur;
8458
8471
  }
8459
8472
 
8460
8473
  /************************************************************************
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nokolexbor
4
- VERSION = '0.7.0'
4
+ VERSION = '0.8.0'
5
5
  end
@@ -0,0 +1,174 @@
1
+ diff --git a/source/lexbor/selectors/selectors.c b/source/lexbor/selectors/selectors.c
2
+ index 37b825e..3fec327 100644
3
+ --- a/source/lexbor/selectors/selectors.c
4
+ +++ b/source/lexbor/selectors/selectors.c
5
+ @@ -87,6 +87,16 @@ static lxb_status_t
6
+ lxb_selectors_first_match(lxb_dom_node_t *node,
7
+ lxb_css_selector_specificity_t *spec, void *ctx);
8
+
9
+ +static bool
10
+ +lxb_selectors_match_chain_rtl(lxb_selectors_t *selectors,
11
+ + lxb_css_selector_t *selector,
12
+ + lxb_dom_node_t *node);
13
+ +
14
+ +static bool
15
+ +lxb_selectors_match_node_rtl(lxb_selectors_t *selectors,
16
+ + lxb_dom_node_t *node,
17
+ + lxb_css_selector_list_t *list);
18
+ +
19
+
20
+ lxb_selectors_t *
21
+ lxb_selectors_create(void)
22
+ @@ -1254,6 +1264,106 @@ lxb_selectors_pseudo_class(lxb_css_selector_t *selector, lxb_dom_node_t *node)
23
+ return false;
24
+ }
25
+
26
+ +/*
27
+ + * Recursively match a selector chain (right-to-left) anchored at `node`,
28
+ + * with backtracking: when a combinator has multiple candidate elements
29
+ + * (descendant, following sibling), each candidate is tried until the rest
30
+ + * of the chain matches.
31
+ + */
32
+ +static bool
33
+ +lxb_selectors_match_chain_rtl(lxb_selectors_t *selectors,
34
+ + lxb_css_selector_t *selector,
35
+ + lxb_dom_node_t *node)
36
+ +{
37
+ + lxb_css_selector_t *prev;
38
+ + lxb_dom_node_t *cur;
39
+ + lxb_selectors_entry_t tmp_entry;
40
+ +
41
+ + memset(&tmp_entry, 0, sizeof(tmp_entry));
42
+ + tmp_entry.selector = selector;
43
+ +
44
+ + if (!lxb_selectors_match(selectors, &tmp_entry, selector, node)) {
45
+ + return false;
46
+ + }
47
+ +
48
+ + prev = selector->prev;
49
+ + if (prev == NULL) {
50
+ + return true;
51
+ + }
52
+ +
53
+ + switch (selector->combinator) {
54
+ + case LXB_CSS_SELECTOR_COMBINATOR_CLOSE:
55
+ + /* Same node - check prev selector on same element. */
56
+ + return lxb_selectors_match_chain_rtl(selectors, prev, node);
57
+ +
58
+ + case LXB_CSS_SELECTOR_COMBINATOR_DESCENDANT:
59
+ + /* node is a descendant of prev - try each ancestor. */
60
+ + for (cur = node->parent; cur != NULL; cur = cur->parent) {
61
+ + if (cur->type == LXB_DOM_NODE_TYPE_ELEMENT
62
+ + && lxb_selectors_match_chain_rtl(selectors, prev, cur))
63
+ + {
64
+ + return true;
65
+ + }
66
+ + }
67
+ + return false;
68
+ +
69
+ + case LXB_CSS_SELECTOR_COMBINATOR_CHILD:
70
+ + /* node is a child of prev - check parent. */
71
+ + cur = node->parent;
72
+ + return cur != NULL
73
+ + && cur->type == LXB_DOM_NODE_TYPE_ELEMENT
74
+ + && lxb_selectors_match_chain_rtl(selectors, prev, cur);
75
+ +
76
+ + case LXB_CSS_SELECTOR_COMBINATOR_SIBLING:
77
+ + /* node is adjacent to prev (+) - preceding element. */
78
+ + cur = node->prev;
79
+ + while (cur != NULL && cur->type != LXB_DOM_NODE_TYPE_ELEMENT) {
80
+ + cur = cur->prev;
81
+ + }
82
+ + return cur != NULL
83
+ + && lxb_selectors_match_chain_rtl(selectors, prev, cur);
84
+ +
85
+ + case LXB_CSS_SELECTOR_COMBINATOR_FOLLOWING:
86
+ + /* node follows prev (~) - try each preceding sibling. */
87
+ + for (cur = node->prev; cur != NULL; cur = cur->prev) {
88
+ + if (cur->type == LXB_DOM_NODE_TYPE_ELEMENT
89
+ + && lxb_selectors_match_chain_rtl(selectors, prev, cur))
90
+ + {
91
+ + return true;
92
+ + }
93
+ + }
94
+ + return false;
95
+ +
96
+ + default:
97
+ + return false;
98
+ + }
99
+ +}
100
+ +
101
+ +/*
102
+ + * Match a node against a selector list using right-to-left evaluation.
103
+ + * This is needed for :not(), :is(), :where() etc. where we test a specific
104
+ + * node against a selector that may contain sibling/following combinators.
105
+ + *
106
+ + * The left-to-right algorithm (lxb_selectors_next) can't correctly test
107
+ + * whether a node matches "A ~ B" because it searches forward from the node
108
+ + * for A, but A should be BEFORE the node.
109
+ + */
110
+ +static bool
111
+ +lxb_selectors_match_node_rtl(lxb_selectors_t *selectors,
112
+ + lxb_dom_node_t *node,
113
+ + lxb_css_selector_list_t *list)
114
+ +{
115
+ + while (list != NULL) {
116
+ + if (lxb_selectors_match_chain_rtl(selectors, list->last, node)) {
117
+ + return true;
118
+ + }
119
+ +
120
+ + list = list->next;
121
+ + }
122
+ +
123
+ + return false;
124
+ +}
125
+ +
126
+ static bool
127
+ lxb_selectors_pseudo_class_function(lxb_selectors_t *selectors,
128
+ lxb_css_selector_t *selector,
129
+ @@ -1268,14 +1378,7 @@ lxb_selectors_pseudo_class_function(lxb_selectors_t *selectors,
130
+
131
+ switch (pseudo->type) {
132
+ case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_CURRENT:
133
+ - entry->child = lxb_selectors_next(selectors, node, entry->child,
134
+ - pseudo->data,
135
+ - lxb_selectors_first_match, &found);
136
+ - if (entry->child == NULL) {
137
+ - return false;
138
+ - }
139
+ -
140
+ - return found;
141
+ + return lxb_selectors_match_node_rtl(selectors, node, pseudo->data);
142
+
143
+ case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_DIR:
144
+ break;
145
+ @@ -1291,27 +1394,13 @@ lxb_selectors_pseudo_class_function(lxb_selectors_t *selectors,
146
+ return found;
147
+
148
+ case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_IS:
149
+ - entry->child = lxb_selectors_next(selectors, node, entry->child,
150
+ - pseudo->data,
151
+ - lxb_selectors_first_match, &found);
152
+ - if (entry->child == NULL) {
153
+ - return false;
154
+ - }
155
+ -
156
+ - return found;
157
+ + return lxb_selectors_match_node_rtl(selectors, node, pseudo->data);
158
+
159
+ case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_LANG:
160
+ break;
161
+
162
+ case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NOT:
163
+ - entry->child = lxb_selectors_next(selectors, node, entry->child,
164
+ - pseudo->data,
165
+ - lxb_selectors_first_match, &found);
166
+ - if (entry->child == NULL) {
167
+ - return false;
168
+ - }
169
+ -
170
+ - return !found;
171
+ + return !lxb_selectors_match_node_rtl(selectors, node, pseudo->data);
172
+
173
+ case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_CHILD:
174
+ index = 0;
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nokolexbor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yicheng Zhou
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-03-25 00:00:00.000000000 Z
10
+ date: 2026-07-24 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rake-compiler
@@ -132,6 +132,7 @@ files:
132
132
  - patches/0003-lexbor-attach-template-content-to-self.patch
133
133
  - patches/0004-lexbor-fix-template-clone.patch
134
134
  - patches/0005-lexbor-add-source-location-to-node.patch
135
+ - patches/0006-lexbor-fix-sibling-combinator-in-pseudo-class-functions.patch
135
136
  - vendor/lexbor/CMakeLists.txt
136
137
  - vendor/lexbor/config.cmake
137
138
  - vendor/lexbor/feature.cmake
@@ -537,7 +538,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
537
538
  - !ruby/object:Gem::Version
538
539
  version: '0'
539
540
  requirements: []
540
- rubygems_version: 4.0.6
541
+ rubygems_version: 4.0.16
541
542
  specification_version: 4
542
543
  summary: High-performance HTML5 parser, with support for both CSS selectors and XPath.
543
544
  test_files: []