nokolexbor 0.6.2 → 0.7.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: 5095dc8ed38a170bab48cc78e7a7e4475657b3066cb114a46eab1d9f4ed25c36
4
- data.tar.gz: c2a00a4ebc644dda81e0b010166063a5ad975ad7e2410739c69af2246cce560f
3
+ metadata.gz: fb6fea4008f68e39df01fbc3920634bd747f6c6e58605876b4600b6037745d4a
4
+ data.tar.gz: 38f67c6e320e95d35a82db68f34b08e7103fa8d56e52fe6e59ebdeb266701c16
5
5
  SHA512:
6
- metadata.gz: 07df8df17e08dbe9293b6f0e1f878ee70d787f3370c33a00bcf0ca71dee007a708ecf4a7b069039cb05d95e93cd741654a3bdb8a11833804f2b9f63dd5d9fb4b
7
- data.tar.gz: 31addbc3877af1b1d814cb720c17e8130d98280d35831794e135e259b790de0c719236004c202649092366323a7e82a9175003be0116ac0267639d15fd186081
6
+ metadata.gz: a44ba9da124b17e58ae2abc8c6e99cfab22edbdcdff5b07e9636838f28098a4716aa6f50cf7282c1f15fcbb0630b7bd5ef814b54cb7886dd75b16a864e496405
7
+ data.tar.gz: 40fcdc69521d2f985be5fa62addf3f4fd03ac577eeb66878f496dbc684fc3925ceddb9fa2f74a870188431c672b16394ccc6c58303caae6c6718af8e978bc967
@@ -23,12 +23,16 @@ nl_attribute_new(int argc, VALUE *argv, VALUE klass)
23
23
 
24
24
  rb_scan_args(argc, argv, "2*", &rb_document, &rb_name, &rest);
25
25
 
26
- if (!rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
27
- rb_raise(rb_eArgError, "Document must be a Nokolexbor::Document");
26
+ if (rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
27
+ document = nl_rb_document_unwrap(rb_document);
28
+ } else if (rb_obj_is_kind_of(rb_document, cNokolexborNode)) {
29
+ lxb_dom_node_t *node = nl_rb_node_unwrap(rb_document);
30
+ document = node->owner_document;
31
+ rb_document = nl_rb_document_get(rb_document);
32
+ } else {
33
+ rb_raise(rb_eArgError, "Expected a Document or Node, got %s", rb_class2name(CLASS_OF(rb_document)));
28
34
  }
29
35
 
30
- document = nl_rb_document_unwrap(rb_document);
31
-
32
36
  const char *c_name = StringValuePtr(rb_name);
33
37
  size_t name_len = RSTRING_LEN(rb_name);
34
38
  lxb_dom_attr_t *attr = lxb_dom_attr_interface_create(document);
@@ -1,6 +1,7 @@
1
1
  #include "nokolexbor.h"
2
2
 
3
3
  VALUE cNokolexborCData;
4
+ extern VALUE cNokolexborNode;
4
5
  extern VALUE cNokolexborText;
5
6
  extern VALUE mNokolexbor;
6
7
 
@@ -22,12 +23,16 @@ nl_cdata_new(int argc, VALUE *argv, VALUE klass)
22
23
 
23
24
  rb_scan_args(argc, argv, "2*", &rb_content, &rb_document, &rest);
24
25
 
25
- if (!rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
26
- rb_raise(rb_eArgError, "Document must be a Nokolexbor::Document");
26
+ if (rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
27
+ document = nl_rb_document_unwrap(rb_document);
28
+ } else if (rb_obj_is_kind_of(rb_document, cNokolexborNode)) {
29
+ lxb_dom_node_t *node = nl_rb_node_unwrap(rb_document);
30
+ document = node->owner_document;
31
+ rb_document = nl_rb_document_get(rb_document);
32
+ } else {
33
+ rb_raise(rb_eArgError, "Expected a Document or Node, got %s", rb_class2name(CLASS_OF(rb_document)));
27
34
  }
28
35
 
29
- document = nl_rb_document_unwrap(rb_document);
30
-
31
36
  const char *c_content = StringValuePtr(rb_content);
32
37
  size_t content_len = RSTRING_LEN(rb_content);
33
38
  lxb_dom_cdata_section_t *element = lxb_dom_document_create_cdata_section(document, (const lxb_char_t *)c_content, content_len);
@@ -1,6 +1,7 @@
1
1
  #include "nokolexbor.h"
2
2
 
3
3
  VALUE cNokolexborComment;
4
+ extern VALUE cNokolexborNode;
4
5
  extern VALUE cNokolexborCharacterData;
5
6
  extern VALUE mNokolexbor;
6
7
 
@@ -20,12 +21,16 @@ nl_comment_new(int argc, VALUE *argv, VALUE klass)
20
21
 
21
22
  rb_scan_args(argc, argv, "2*", &rb_content, &rb_document, &rest);
22
23
 
23
- if (!rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
24
- rb_raise(rb_eArgError, "Document must be a Nokolexbor::Document");
24
+ if (rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
25
+ document = nl_rb_document_unwrap(rb_document);
26
+ } else if (rb_obj_is_kind_of(rb_document, cNokolexborNode)) {
27
+ lxb_dom_node_t *node = nl_rb_node_unwrap(rb_document);
28
+ document = node->owner_document;
29
+ rb_document = nl_rb_document_get(rb_document);
30
+ } else {
31
+ rb_raise(rb_eArgError, "Expected a Document or Node, got %s", rb_class2name(CLASS_OF(rb_document)));
25
32
  }
26
33
 
27
- document = nl_rb_document_unwrap(rb_document);
28
-
29
34
  const char *c_content = StringValuePtr(rb_content);
30
35
  size_t content_len = RSTRING_LEN(rb_content);
31
36
  lxb_dom_comment_t *element = lxb_dom_document_create_comment(document, (const lxb_char_t *)c_content, content_len);
@@ -5,6 +5,11 @@ extern VALUE mNokolexbor;
5
5
  extern VALUE cNokolexborNode;
6
6
  VALUE cNokolexborDocument;
7
7
 
8
+ #ifdef HAVE_PTHREAD_H
9
+ #include <pthread.h>
10
+ pthread_key_t p_key_html_parser;
11
+ #endif
12
+
8
13
  static void
9
14
  free_nl_document(lxb_html_document_t *document)
10
15
  {
@@ -28,7 +33,11 @@ nl_document_parse_native(VALUE self, VALUE rb_html)
28
33
  const char *html_c = StringValuePtr(rb_html);
29
34
  size_t html_len = RSTRING_LEN(rb_html);
30
35
 
31
- static lxb_html_parser_t *html_parser = NULL;
36
+ #ifdef HAVE_PTHREAD_H
37
+ lxb_html_parser_t *html_parser = (lxb_html_parser_t *)pthread_getspecific(p_key_html_parser);
38
+ #else
39
+ lxb_html_parser_t *html_parser = NULL;
40
+ #endif
32
41
  if (html_parser == NULL) {
33
42
  html_parser = lxb_html_parser_create();
34
43
  lxb_status_t status = lxb_html_parser_init(html_parser);
@@ -38,10 +47,17 @@ nl_document_parse_native(VALUE self, VALUE rb_html)
38
47
  nl_raise_lexbor_error(status);
39
48
  }
40
49
  html_parser->tree->scripting = true;
50
+ #ifdef HAVE_PTHREAD_H
51
+ pthread_setspecific(p_key_html_parser, html_parser);
52
+ #endif
41
53
  }
42
54
 
43
55
  lxb_html_document_t *document = lxb_html_parse(html_parser, (const lxb_char_t *)html_c, html_len);
44
56
 
57
+ #ifndef HAVE_PTHREAD_H
58
+ lxb_html_parser_destroy(html_parser);
59
+ #endif
60
+
45
61
  if (document == NULL) {
46
62
  rb_raise(rb_eRuntimeError, "Error parsing document");
47
63
  }
@@ -115,8 +131,21 @@ nl_document_root(VALUE self)
115
131
  return nl_rb_node_create(lxb_dom_document_root(doc), self);
116
132
  }
117
133
 
134
+ static void
135
+ free_html_parser(void *data)
136
+ {
137
+ lxb_html_parser_t *html_parser = (lxb_html_parser_t *)data;
138
+ if (html_parser != NULL) {
139
+ html_parser = lxb_html_parser_destroy(html_parser);
140
+ }
141
+ }
142
+
118
143
  void Init_nl_document(void)
119
144
  {
145
+ #ifdef HAVE_PTHREAD_H
146
+ pthread_key_create(&p_key_html_parser, free_html_parser);
147
+ #endif
148
+
120
149
  cNokolexborDocument = rb_define_class_under(mNokolexbor, "Document", cNokolexborNode);
121
150
  rb_define_singleton_method(cNokolexborDocument, "new", nl_document_new, 0);
122
151
  rb_define_singleton_method(cNokolexborDocument, "parse_native", nl_document_parse_native, 1);
@@ -22,12 +22,16 @@ nl_document_fragment_new(int argc, VALUE *argv, VALUE klass)
22
22
 
23
23
  rb_scan_args(argc, argv, "1*", &rb_document, &rest);
24
24
 
25
- if (!rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
26
- rb_raise(rb_eArgError, "Document must be a Nokolexbor::Document");
25
+ if (rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
26
+ document = nl_rb_document_unwrap(rb_document);
27
+ } else if (rb_obj_is_kind_of(rb_document, cNokolexborNode)) {
28
+ lxb_dom_node_t *node = nl_rb_node_unwrap(rb_document);
29
+ document = node->owner_document;
30
+ rb_document = nl_rb_document_get(rb_document);
31
+ } else {
32
+ rb_raise(rb_eArgError, "Expected a Document or Node, got %s", rb_class2name(CLASS_OF(rb_document)));
27
33
  }
28
34
 
29
- document = nl_rb_document_unwrap(rb_document);
30
-
31
35
  lxb_dom_document_fragment_t *node = lxb_dom_document_create_document_fragment(document);
32
36
  if (node == NULL) {
33
37
  rb_raise(rb_eRuntimeError, "Error creating document fragment");
@@ -1,6 +1,13 @@
1
1
  #include "nokolexbor.h"
2
+ #include "config.h"
2
3
  #include "libxml/tree.h"
3
4
 
5
+ #ifdef HAVE_PTHREAD_H
6
+ #include <pthread.h>
7
+ pthread_key_t p_key_css_parser;
8
+ pthread_key_t p_key_selectors;
9
+ #endif
10
+
4
11
  #define SORT_NAME nl_css_result
5
12
  #define SORT_TYPE lxb_dom_node_t *
6
13
  #define SORT_CMP(x, y) (x->user >= y->user ? (x->user == y->user ? 0 : 1) : -1)
@@ -113,12 +120,16 @@ nl_node_new(int argc, VALUE *argv, VALUE klass)
113
120
 
114
121
  rb_scan_args(argc, argv, "2*", &rb_name, &rb_document, &rest);
115
122
 
116
- if (!rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
117
- rb_raise(rb_eArgError, "Document must be a Nokolexbor::Document");
123
+ if (rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
124
+ document = nl_rb_document_unwrap(rb_document);
125
+ } else if (rb_obj_is_kind_of(rb_document, cNokolexborNode)) {
126
+ lxb_dom_node_t *node = nl_rb_node_unwrap(rb_document);
127
+ document = node->owner_document;
128
+ rb_document = nl_rb_document_get(rb_document);
129
+ } else {
130
+ rb_raise(rb_eArgError, "Expected a Document or Node, got %s", rb_class2name(CLASS_OF(rb_document)));
118
131
  }
119
132
 
120
- document = nl_rb_document_unwrap(rb_document);
121
-
122
133
  const char *c_name = StringValuePtr(rb_name);
123
134
  size_t name_len = RSTRING_LEN(rb_name);
124
135
  lxb_dom_element_t *element = lxb_dom_document_create_element(document, (const lxb_char_t *)c_name, name_len, NULL);
@@ -366,8 +377,13 @@ nl_node_find(VALUE self, VALUE selector, lxb_selectors_cb_f cb, void *ctx)
366
377
  lxb_dom_node_t *node = nl_rb_node_unwrap(self);
367
378
 
368
379
  lxb_status_t status;
369
- static lxb_css_parser_t *css_parser = NULL;
370
- static lxb_selectors_t *selectors = NULL;
380
+ #ifdef HAVE_PTHREAD_H
381
+ lxb_css_parser_t *css_parser = (lxb_css_parser_t *)pthread_getspecific(p_key_css_parser);
382
+ lxb_selectors_t *selectors = (lxb_selectors_t *)pthread_getspecific(p_key_selectors);
383
+ #else
384
+ lxb_css_parser_t *css_parser = NULL;
385
+ lxb_selectors_t *selectors = NULL;
386
+ #endif
371
387
  lxb_css_selector_list_t *list = NULL;
372
388
 
373
389
  /* CSS parser. */
@@ -377,6 +393,9 @@ nl_node_find(VALUE self, VALUE selector, lxb_selectors_cb_f cb, void *ctx)
377
393
  if (status != LXB_STATUS_OK) {
378
394
  goto init_error;
379
395
  }
396
+ #ifdef HAVE_PTHREAD_H
397
+ pthread_setspecific(p_key_css_parser, css_parser);
398
+ #endif
380
399
  }
381
400
 
382
401
  /* Selectors. */
@@ -386,6 +405,9 @@ nl_node_find(VALUE self, VALUE selector, lxb_selectors_cb_f cb, void *ctx)
386
405
  if (status != LXB_STATUS_OK) {
387
406
  goto init_error;
388
407
  }
408
+ #ifdef HAVE_PTHREAD_H
409
+ pthread_setspecific(p_key_selectors, selectors);
410
+ #endif
389
411
  }
390
412
 
391
413
  /* Parse and get the log. */
@@ -397,11 +419,16 @@ nl_node_find(VALUE self, VALUE selector, lxb_selectors_cb_f cb, void *ctx)
397
419
 
398
420
  /* Find HTML nodes by CSS Selectors. */
399
421
  status = lxb_selectors_find(selectors, node, list, cb, ctx);
400
- if (status != LXB_STATUS_OK) {
401
- goto cleanup;
402
- }
403
422
 
404
423
  cleanup:
424
+ #ifndef HAVE_PTHREAD_H
425
+ /* Destroy Selectors object. */
426
+ (void)lxb_selectors_destroy(selectors, true);
427
+
428
+ /* Destroy resources for CSS Parser. */
429
+ (void)lxb_css_parser_destroy(css_parser, true);
430
+ #endif
431
+
405
432
  /* Destroy all object for all CSS Selector List. */
406
433
  lxb_css_selector_list_destroy_memory(list);
407
434
 
@@ -416,8 +443,10 @@ init_error:
416
443
  lxb_css_parser_destroy(css_parser, true);
417
444
  css_parser = NULL;
418
445
 
419
- /* Destroy all object for all CSS Selector List. */
420
- lxb_css_selector_list_destroy_memory(list);
446
+ #ifdef HAVE_PTHREAD_H
447
+ pthread_setspecific(p_key_css_parser, NULL);
448
+ pthread_setspecific(p_key_selectors, NULL);
449
+ #endif
421
450
 
422
451
  return status;
423
452
  }
@@ -1173,8 +1202,30 @@ nl_node_path(VALUE self)
1173
1202
  return ret;
1174
1203
  }
1175
1204
 
1205
+ static void
1206
+ free_css_parser(void *data)
1207
+ {
1208
+ lxb_css_parser_t *css_parser = (lxb_css_parser_t *)data;
1209
+ if (css_parser != NULL) {
1210
+ lxb_css_parser_destroy(css_parser, true);
1211
+ }
1212
+ }
1213
+
1214
+ static void
1215
+ free_selectors(void *data)
1216
+ {
1217
+ lxb_selectors_t *selectors = (lxb_selectors_t *)data;
1218
+ if (selectors != NULL) {
1219
+ lxb_selectors_destroy(selectors, true);
1220
+ }
1221
+ }
1222
+
1176
1223
  void Init_nl_node(void)
1177
1224
  {
1225
+ #ifdef HAVE_PTHREAD_H
1226
+ pthread_key_create(&p_key_css_parser, free_css_parser);
1227
+ pthread_key_create(&p_key_selectors, free_selectors);
1228
+ #endif
1178
1229
  cNokolexborNode = rb_define_class_under(mNokolexbor, "Node", rb_cObject);
1179
1230
  rb_undef_alloc_func(cNokolexborNode);
1180
1231
 
@@ -21,12 +21,16 @@ nl_processing_instruction_new(int argc, VALUE *argv, VALUE klass)
21
21
 
22
22
  rb_scan_args(argc, argv, "3*", &rb_name, &rb_content, &rb_document, &rest);
23
23
 
24
- if (!rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
25
- rb_raise(rb_eArgError, "Document must be a Nokolexbor::Document");
24
+ if (rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
25
+ document = nl_rb_document_unwrap(rb_document);
26
+ } else if (rb_obj_is_kind_of(rb_document, cNokolexborNode)) {
27
+ lxb_dom_node_t *node = nl_rb_node_unwrap(rb_document);
28
+ document = node->owner_document;
29
+ rb_document = nl_rb_document_get(rb_document);
30
+ } else {
31
+ rb_raise(rb_eArgError, "Expected a Document or Node, got %s", rb_class2name(CLASS_OF(rb_document)));
26
32
  }
27
33
 
28
- document = nl_rb_document_unwrap(rb_document);
29
-
30
34
  const char *c_name = StringValuePtr(rb_name);
31
35
  size_t name_len = RSTRING_LEN(rb_name);
32
36
  const char *c_content = StringValuePtr(rb_content);
@@ -1,6 +1,7 @@
1
1
  #include "nokolexbor.h"
2
2
 
3
3
  VALUE cNokolexborText;
4
+ extern VALUE cNokolexborNode;
4
5
  extern VALUE cNokolexborCharacterData;
5
6
  extern VALUE mNokolexbor;
6
7
 
@@ -20,12 +21,16 @@ nl_text_new(int argc, VALUE *argv, VALUE klass)
20
21
 
21
22
  rb_scan_args(argc, argv, "2*", &rb_text, &rb_document, &rest);
22
23
 
23
- if (!rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
24
- rb_raise(rb_eArgError, "Document must be a Nokolexbor::Document");
24
+ if (rb_obj_is_kind_of(rb_document, cNokolexborDocument)) {
25
+ document = nl_rb_document_unwrap(rb_document);
26
+ } else if (rb_obj_is_kind_of(rb_document, cNokolexborNode)) {
27
+ lxb_dom_node_t *node = nl_rb_node_unwrap(rb_document);
28
+ document = node->owner_document;
29
+ rb_document = nl_rb_document_get(rb_document);
30
+ } else {
31
+ rb_raise(rb_eArgError, "Expected a Document or Node, got %s", rb_class2name(CLASS_OF(rb_document)));
25
32
  }
26
33
 
27
- document = nl_rb_document_unwrap(rb_document);
28
-
29
34
  const char *c_text = StringValuePtr(rb_text);
30
35
  size_t text_len = RSTRING_LEN(rb_text);
31
36
  lxb_dom_text_t *element = lxb_dom_document_create_text_node(document, (const lxb_char_t *)c_text, text_len);
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Nokolexbor
6
+ # A DSL for programmatically building HTML documents.
7
+ #
8
+ # == No-arg block (instance_eval style) – tag names are called bare:
9
+ #
10
+ # Nokolexbor do
11
+ # body do
12
+ # h1 'Hello world'
13
+ # p 'This little p'
14
+ # ul do
15
+ # li 'Go to market'
16
+ # li 'Go to bed'
17
+ # end
18
+ # end
19
+ # end
20
+ #
21
+ # == Block-parameter style – tag names are called on the builder argument:
22
+ #
23
+ # Nokolexbor::Builder.new do |b|
24
+ # b.body do
25
+ # b.h1 'Hello world'
26
+ # end
27
+ # end
28
+ #
29
+ # == Building into an existing node:
30
+ #
31
+ # Nokolexbor::Builder.with(existing_node) do
32
+ # span 'injected'
33
+ # end
34
+ #
35
+ class Builder
36
+ # The Document used to create new nodes
37
+ attr_accessor :doc
38
+
39
+ # The node that currently receives new children
40
+ attr_accessor :parent
41
+
42
+ # Arity of the outermost block (drives instance_eval vs yield dispatch)
43
+ attr_accessor :arity
44
+
45
+ # A context object for use when the block has no arguments
46
+ attr_accessor :context
47
+
48
+ # Build into an existing +root+ node.
49
+ def self.with(root, &block)
50
+ new(root, &block)
51
+ end
52
+
53
+ # @param root [Node, nil]
54
+ # When given, new nodes are appended under +root+ and +root.document+
55
+ # is used as the owning document. When omitted a fresh {Document} and
56
+ # a {DocumentFragment} container are created.
57
+ def initialize(root = nil, &block)
58
+ if root
59
+ @doc = root.document
60
+ @parent = root
61
+ else
62
+ @doc = Document.new
63
+ @parent = DocumentFragment.new(@doc)
64
+ end
65
+
66
+ @context = nil
67
+ @arity = nil
68
+
69
+ return unless block
70
+
71
+ @arity = block.arity
72
+ if @arity <= 0
73
+ # Capture outer self so that outer methods / ivars remain accessible
74
+ # via method_missing delegation while the builder acts as self.
75
+ @context = eval("self", block.binding)
76
+ instance_eval(&block)
77
+ else
78
+ yield self
79
+ end
80
+ end
81
+
82
+ # Insert a Text node containing +string+.
83
+ def text(string)
84
+ insert(@doc.create_text_node(string))
85
+ end
86
+
87
+ # Insert a CDATA node containing +string+.
88
+ def cdata(string)
89
+ insert(@doc.create_cdata(string))
90
+ end
91
+
92
+ # Insert a Comment node containing +string+.
93
+ def comment(string)
94
+ insert(@doc.create_comment(string))
95
+ end
96
+
97
+ # Append raw HTML (parsed into nodes) under the current parent.
98
+ def <<(string)
99
+ @doc.fragment(string).children.each { |x| insert(x) }
100
+ end
101
+
102
+ # Serialize the built content to an HTML string.
103
+ def to_html
104
+ @parent.to_html
105
+ end
106
+ alias_method :to_s, :to_html
107
+
108
+ # Ruby's Kernel#p, Kernel#pp, etc. are defined on Object and therefore on
109
+ # Builder itself. They must be overridden so they fall through to
110
+ # method_missing and create the corresponding HTML element instead of
111
+ # printing values to stdout.
112
+ def p(*args, &block) # :nodoc:
113
+ method_missing(:p, *args, &block)
114
+ end
115
+
116
+ def method_missing(method, *args, &block) # :nodoc:
117
+ # Only delegate to the outer context for methods the user defined there.
118
+ # Standard Object / Kernel methods (like :p, :pp, :puts …) must not be
119
+ # forwarded to @context because all objects respond to them, which would
120
+ # bypass element creation entirely.
121
+ if @context && !OBJECT_INSTANCE_METHODS.include?(method) && @context.respond_to?(method)
122
+ @context.send(method, *args, &block)
123
+ else
124
+ node = @doc.create_element(method.to_s.sub(/[_!]$/, ""), *args)
125
+ insert(node, &block)
126
+ end
127
+ end
128
+
129
+ def respond_to_missing?(method, include_private = false) # :nodoc:
130
+ (@context && !OBJECT_INSTANCE_METHODS.include?(method) && @context.respond_to?(method)) || super
131
+ end
132
+
133
+ # Methods defined on plain Object that should never be forwarded to @context
134
+ # as HTML element creation fallbacks.
135
+ OBJECT_INSTANCE_METHODS = Object.instance_methods.to_set.freeze
136
+
137
+ private
138
+
139
+ # Add +node+ as a child of @parent; if a block is given, push @parent
140
+ # to +node+ for the duration of that block, then restore it.
141
+ # Returns a {NodeBuilder} for the inserted node so attributes / classes
142
+ # can be chained: <tt>div.container.hero!</tt>
143
+ def insert(node, &block)
144
+ node = @parent.add_child(node)
145
+ if block
146
+ old_parent = @parent
147
+ @parent = node
148
+ @arity ||= block.arity
149
+ begin
150
+ if @arity <= 0
151
+ instance_eval(&block)
152
+ else
153
+ yield(self)
154
+ end
155
+ ensure
156
+ @parent = old_parent
157
+ end
158
+ end
159
+ NodeBuilder.new(node, self)
160
+ end
161
+
162
+ # Wraps a built node and exposes a fluent API for setting classes and ids:
163
+ #
164
+ # div.container # => <div class="container">
165
+ # div.box.highlight # => <div class="box highlight">
166
+ # div.thing! # => <div id="thing">
167
+ # div.container.hero! # => <div class="container" id="hero">
168
+ #
169
+ class NodeBuilder # :nodoc:
170
+ def initialize(node, doc_builder)
171
+ @node = node
172
+ @doc_builder = doc_builder
173
+ end
174
+
175
+ def []=(k, v)
176
+ @node[k] = v
177
+ end
178
+
179
+ def [](k)
180
+ @node[k]
181
+ end
182
+
183
+ def method_missing(method, *args, &block)
184
+ opts = args.last.is_a?(Hash) ? args.pop : {}
185
+
186
+ case method.to_s
187
+ when /^(.*)!$/
188
+ @node["id"] = Regexp.last_match(1)
189
+ @node.content = args.first if args.first
190
+ when /^(.*)=$/
191
+ @node[Regexp.last_match(1)] = args.first
192
+ else
193
+ @node["class"] =
194
+ ((@node["class"] || "").split(/\s/) + [method.to_s]).join(" ")
195
+ @node.content = args.first if args.first
196
+ end
197
+
198
+ opts.each do |k, v|
199
+ @node[k.to_s] = ((@node[k.to_s] || "").split(/\s/) + [v.to_s]).join(" ")
200
+ end
201
+
202
+ if block
203
+ old_parent = @doc_builder.parent
204
+ @doc_builder.parent = @node
205
+ arity = @doc_builder.arity || block.arity
206
+ value = if arity <= 0
207
+ @doc_builder.instance_eval(&block)
208
+ else
209
+ yield(@doc_builder)
210
+ end
211
+ @doc_builder.parent = old_parent
212
+ return value
213
+ end
214
+
215
+ self
216
+ end
217
+
218
+ def respond_to_missing?(_method, _include_private = false) # :nodoc:
219
+ true
220
+ end
221
+ end
222
+ end
223
+ end
@@ -11,7 +11,13 @@ module Nokolexbor
11
11
  # @return [Document]
12
12
  def self.new(document, list = [])
13
13
  obj = allocate
14
- obj.instance_variable_set(:@document, document)
14
+ if document.is_a?(Document) || document.nil?
15
+ obj.instance_variable_set(:@document, document)
16
+ elsif document.is_a?(Node)
17
+ obj.instance_variable_set(:@document, document.document)
18
+ else
19
+ raise ArgumentError, "Expected a Document or Node, got #{document.class}"
20
+ end
15
21
  list.each { |x| obj << x }
16
22
  yield obj if block_given?
17
23
  obj
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nokolexbor
4
- VERSION = '0.6.2'
4
+ VERSION = '0.7.0'
5
5
  end
data/lib/nokolexbor.rb CHANGED
@@ -28,11 +28,23 @@ require 'nokolexbor/node_set'
28
28
  require 'nokolexbor/document_fragment'
29
29
  require 'nokolexbor/xpath'
30
30
  require 'nokolexbor/xpath_context'
31
+ require 'nokolexbor/builder'
31
32
 
32
33
  module Nokolexbor
33
34
  class << self
34
- def HTML(*args)
35
+ def parse(*args)
35
36
  Document.parse(*args)
36
37
  end
38
+
39
+ alias_method :HTML, :parse
40
+ end
41
+ end
42
+
43
+ # Parse an HTML document, or build one with the DSL when a block is given.
44
+ def Nokolexbor(string_or_io = nil, &block)
45
+ if block
46
+ Nokolexbor::Builder.new(&block).parent
47
+ else
48
+ Nokolexbor.parse(string_or_io)
37
49
  end
38
50
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nokolexbor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yicheng Zhou
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-05-13 00:00:00.000000000 Z
10
+ date: 2026-03-25 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake-compiler
@@ -120,6 +119,7 @@ files:
120
119
  - ext/nokolexbor/xml_tree.c
121
120
  - ext/nokolexbor/xml_xpath.c
122
121
  - lib/nokolexbor.rb
122
+ - lib/nokolexbor/builder.rb
123
123
  - lib/nokolexbor/document.rb
124
124
  - lib/nokolexbor/document_fragment.rb
125
125
  - lib/nokolexbor/node.rb
@@ -523,7 +523,6 @@ licenses:
523
523
  - MIT
524
524
  metadata:
525
525
  msys2_mingw_dependencies: cmake
526
- post_install_message:
527
526
  rdoc_options: []
528
527
  require_paths:
529
528
  - lib
@@ -538,8 +537,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
538
537
  - !ruby/object:Gem::Version
539
538
  version: '0'
540
539
  requirements: []
541
- rubygems_version: 3.0.3.1
542
- signing_key:
540
+ rubygems_version: 4.0.6
543
541
  specification_version: 4
544
542
  summary: High-performance HTML5 parser, with support for both CSS selectors and XPath.
545
543
  test_files: []