selma 0.5.0 → 0.5.1
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 +4 -4
- data/Cargo.lock +10 -10
- data/README.md +1 -0
- data/ext/selma/Cargo.toml +1 -1
- data/ext/selma/src/html/element.rs +45 -0
- data/ext/selma/src/rewriter.rs +61 -63
- data/ext/selma/src/selector.rs +6 -6
- data/lib/selma/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed5dccbff7a289d57157e0ce627e31ed233ba316d6989ab14bfc6a9f28d241ec
|
|
4
|
+
data.tar.gz: de7de41bb7cd201f866515e3ef730491d435c90610e771dac5f928e1ca2e8688
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 488c6240447f302b3479a20b76e17552648399dc82a465ddd0bf3d59cb058ec095a5ceae57a4f5cfef1a3865f6e765d2fdc4a94d79796f5d654d115982c53755
|
|
7
|
+
data.tar.gz: 371014f6ed8074097f1d7fd0c0c9600bbe7a5284248c7e7fdca6db9e4a5e26c4e565e3f20fa2c61612ad3a2ef199a8e8d87dc2103dbb987aa393e53f1f95e00d
|
data/Cargo.lock
CHANGED
|
@@ -202,9 +202,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
|
|
202
202
|
|
|
203
203
|
[[package]]
|
|
204
204
|
name = "hashbrown"
|
|
205
|
-
version = "0.
|
|
205
|
+
version = "0.17.1"
|
|
206
206
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
207
|
-
checksum = "
|
|
207
|
+
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
|
208
208
|
dependencies = [
|
|
209
209
|
"allocator-api2",
|
|
210
210
|
"equivalent",
|
|
@@ -256,9 +256,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
|
256
256
|
|
|
257
257
|
[[package]]
|
|
258
258
|
name = "lol_html"
|
|
259
|
-
version = "
|
|
259
|
+
version = "3.0.0"
|
|
260
260
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
261
|
-
checksum = "
|
|
261
|
+
checksum = "2ae574a677ef0443a0bd3c291f0cab9d1e61a3ad85a1515e3cfc0bc720d5a48e"
|
|
262
262
|
dependencies = [
|
|
263
263
|
"bitflags",
|
|
264
264
|
"cfg-if",
|
|
@@ -409,18 +409,18 @@ dependencies = [
|
|
|
409
409
|
|
|
410
410
|
[[package]]
|
|
411
411
|
name = "rb-sys"
|
|
412
|
-
version = "0.9.
|
|
412
|
+
version = "0.9.128"
|
|
413
413
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
414
|
-
checksum = "
|
|
414
|
+
checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
|
|
415
415
|
dependencies = [
|
|
416
416
|
"rb-sys-build",
|
|
417
417
|
]
|
|
418
418
|
|
|
419
419
|
[[package]]
|
|
420
420
|
name = "rb-sys-build"
|
|
421
|
-
version = "0.9.
|
|
421
|
+
version = "0.9.128"
|
|
422
422
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
423
|
-
checksum = "
|
|
423
|
+
checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
|
|
424
424
|
dependencies = [
|
|
425
425
|
"bindgen",
|
|
426
426
|
"lazy_static",
|
|
@@ -483,9 +483,9 @@ dependencies = [
|
|
|
483
483
|
|
|
484
484
|
[[package]]
|
|
485
485
|
name = "selectors"
|
|
486
|
-
version = "0.
|
|
486
|
+
version = "0.37.0"
|
|
487
487
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
488
|
-
checksum = "
|
|
488
|
+
checksum = "2cfaaa6035167f0e604e42723c7650d59ee269ef220d7bbe0565602c8a0173b9"
|
|
489
489
|
dependencies = [
|
|
490
490
|
"bitflags",
|
|
491
491
|
"cssparser",
|
data/README.md
CHANGED
|
@@ -161,6 +161,7 @@ The `element` argument in `handle_element` has the following methods:
|
|
|
161
161
|
- `remove_attribute`: Remove an attribute
|
|
162
162
|
- `has_attribute?`: A bool which identifies whether or not the element has an attribute
|
|
163
163
|
- `attributes`: List all the attributes
|
|
164
|
+
- `attribute_source_location(name)`: Returns the byte ranges of an attribute's name and value within the original input as `{ name: Range, value: Range | nil }`, or `nil` if the attribute is missing or was added/modified during the rewrite. Pure boolean attributes written without `=` (e.g. `<input disabled>`) return `nil` because lol_html does not record their position.
|
|
164
165
|
- `ancestors`: List all of an element's ancestors as an array of strings
|
|
165
166
|
- `before(content, as: content_type)`: Inserts `content` before the element. `content_type` is either `:text` or `:html` and determines how the content will be applied.
|
|
166
167
|
- `after(content, as: content_type)`: Inserts `content` after the element. `content_type` is either `:text` or `:html` and determines how the content will be applied.
|
data/ext/selma/Cargo.toml
CHANGED
|
@@ -114,6 +114,47 @@ impl SelmaHTMLElement {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
fn get_attribute_source_location(&self, attr: String) -> Result<Option<RHash>, Error> {
|
|
118
|
+
let binding = self.0.borrow();
|
|
119
|
+
let ruby = Ruby::get().unwrap();
|
|
120
|
+
|
|
121
|
+
let Ok(e) = binding.element.get() else {
|
|
122
|
+
return Ok(None);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
let lowered = attr.to_lowercase();
|
|
126
|
+
let attrs = e.attributes();
|
|
127
|
+
let Some(attribute) = attrs.iter().find(|a| a.name() == lowered) else {
|
|
128
|
+
return Ok(None);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
let Some(name_loc) = attribute.name_source_location() else {
|
|
132
|
+
return Ok(None);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
let hash = ruby.hash_new();
|
|
136
|
+
let name_range = name_loc.bytes();
|
|
137
|
+
hash.aset(
|
|
138
|
+
ruby.to_symbol("name"),
|
|
139
|
+
ruby.range_new(name_range.start, name_range.end, true)?,
|
|
140
|
+
)?;
|
|
141
|
+
|
|
142
|
+
match attribute.value_source_location() {
|
|
143
|
+
Some(loc) => {
|
|
144
|
+
let r = loc.bytes();
|
|
145
|
+
hash.aset(
|
|
146
|
+
ruby.to_symbol("value"),
|
|
147
|
+
ruby.range_new(r.start, r.end, true)?,
|
|
148
|
+
)?;
|
|
149
|
+
}
|
|
150
|
+
None => {
|
|
151
|
+
hash.aset(ruby.to_symbol("value"), ruby.qnil())?;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Ok(Some(hash))
|
|
156
|
+
}
|
|
157
|
+
|
|
117
158
|
fn get_attributes(&self) -> Result<RHash, Error> {
|
|
118
159
|
let binding = self.0.borrow();
|
|
119
160
|
let ruby = Ruby::get().unwrap();
|
|
@@ -281,6 +322,10 @@ pub fn init(c_html: RClass) -> Result<(), Error> {
|
|
|
281
322
|
method!(SelmaHTMLElement::has_attribute, 1),
|
|
282
323
|
)?;
|
|
283
324
|
c_element.define_method("attributes", method!(SelmaHTMLElement::get_attributes, 0))?;
|
|
325
|
+
c_element.define_method(
|
|
326
|
+
"attribute_source_location",
|
|
327
|
+
method!(SelmaHTMLElement::get_attribute_source_location, 1),
|
|
328
|
+
)?;
|
|
284
329
|
c_element.define_method("ancestors", method!(SelmaHTMLElement::get_ancestors, 0))?;
|
|
285
330
|
|
|
286
331
|
c_element.define_method("before", method!(SelmaHTMLElement::before, -1))?;
|
data/ext/selma/src/rewriter.rs
CHANGED
|
@@ -34,7 +34,11 @@ use crate::{
|
|
|
34
34
|
#[derive(Clone)]
|
|
35
35
|
pub struct Handler {
|
|
36
36
|
rb_handler: Opaque<Value>,
|
|
37
|
-
|
|
37
|
+
// Store the selector's data by value (Rust-owned). The `#selector` method may return a
|
|
38
|
+
// freshly-built `Selma::Selector` that nothing else on the Ruby side references; keeping a
|
|
39
|
+
// `Obj`/`Opaque` handle to it would make its lifetime depend on GC (use-after-free if it is
|
|
40
|
+
// collected while the Rewriter is alive). We only ever read Rust data off it, so clone it.
|
|
41
|
+
selector: SelmaSelector,
|
|
38
42
|
// total_element_handler_calls: usize,
|
|
39
43
|
// total_elapsed_element_handlers: f64,
|
|
40
44
|
|
|
@@ -43,7 +47,8 @@ pub struct Handler {
|
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
struct RewriterOptions {
|
|
46
|
-
|
|
50
|
+
max_allowed_memory_usage: usize,
|
|
51
|
+
preallocated_parsing_buffer_size: usize,
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
pub struct Rewriter {
|
|
@@ -126,7 +131,9 @@ impl SelmaRewriter {
|
|
|
126
131
|
};
|
|
127
132
|
let handler = Handler {
|
|
128
133
|
rb_handler: Opaque::from(rb_handler),
|
|
129
|
-
|
|
134
|
+
// clone the selector's data out of the Ruby object right away so the
|
|
135
|
+
// Handler no longer depends on that object surviving GC (see struct docs)
|
|
136
|
+
selector: (*rb_selector).clone(),
|
|
130
137
|
// total_element_handler_calls: 0,
|
|
131
138
|
// total_elapsed_element_handlers: 0.0,
|
|
132
139
|
|
|
@@ -162,7 +169,7 @@ impl SelmaRewriter {
|
|
|
162
169
|
if let Some(max_allowed_memory_usage) = max_allowed_memory_usage {
|
|
163
170
|
match max_allowed_memory_usage.to_u64() {
|
|
164
171
|
Ok(max_allowed_memory_usage) => {
|
|
165
|
-
rewriter_options.
|
|
172
|
+
rewriter_options.max_allowed_memory_usage =
|
|
166
173
|
max_allowed_memory_usage as usize;
|
|
167
174
|
}
|
|
168
175
|
Err(_e) => {
|
|
@@ -173,7 +180,7 @@ impl SelmaRewriter {
|
|
|
173
180
|
}
|
|
174
181
|
}
|
|
175
182
|
} else {
|
|
176
|
-
rewriter_options.
|
|
183
|
+
rewriter_options.max_allowed_memory_usage = RewriterOptions::DEFAULT_MAX_ALLOWED_MEMORY_USAGE;
|
|
177
184
|
}
|
|
178
185
|
}
|
|
179
186
|
|
|
@@ -183,7 +190,7 @@ impl SelmaRewriter {
|
|
|
183
190
|
if let Some(preallocated_parsing_buffer_size) = preallocated_parsing_buffer_size {
|
|
184
191
|
match preallocated_parsing_buffer_size.to_u64() {
|
|
185
192
|
Ok(preallocated_parsing_buffer_size) => {
|
|
186
|
-
rewriter_options.
|
|
193
|
+
rewriter_options.preallocated_parsing_buffer_size =
|
|
187
194
|
preallocated_parsing_buffer_size as usize;
|
|
188
195
|
}
|
|
189
196
|
Err(_e) => {
|
|
@@ -194,7 +201,7 @@ impl SelmaRewriter {
|
|
|
194
201
|
}
|
|
195
202
|
}
|
|
196
203
|
} else {
|
|
197
|
-
rewriter_options.
|
|
204
|
+
rewriter_options.preallocated_parsing_buffer_size = RewriterOptions::DEFAULT_PREALLOCATED_PARSING_BUFFER_SIZE;
|
|
198
205
|
}
|
|
199
206
|
}
|
|
200
207
|
}
|
|
@@ -210,10 +217,8 @@ impl SelmaRewriter {
|
|
|
210
217
|
}
|
|
211
218
|
}
|
|
212
219
|
|
|
213
|
-
if rewriter_options
|
|
214
|
-
.
|
|
215
|
-
.preallocated_parsing_buffer_size
|
|
216
|
-
> rewriter_options.memory_options.max_allowed_memory_usage
|
|
220
|
+
if rewriter_options.preallocated_parsing_buffer_size
|
|
221
|
+
> rewriter_options.max_allowed_memory_usage
|
|
217
222
|
{
|
|
218
223
|
return Err(magnus::Error::new(
|
|
219
224
|
ruby.exception_arg_error(),
|
|
@@ -364,55 +369,42 @@ impl SelmaRewriter {
|
|
|
364
369
|
handlers.iter().for_each(|handler| {
|
|
365
370
|
let element_stack: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(vec![]));
|
|
366
371
|
|
|
367
|
-
let
|
|
368
|
-
|
|
369
|
-
let selector = ruby.get_inner(handler.rb_selector);
|
|
372
|
+
let selector = &handler.selector;
|
|
370
373
|
|
|
371
374
|
// TODO: test final raise by simulating errors
|
|
372
|
-
if selector.match_element()
|
|
375
|
+
if let Some(match_element) = selector.match_element() {
|
|
373
376
|
let closure_element_stack = element_stack.clone();
|
|
374
377
|
|
|
375
|
-
element_content_handlers.push(element!(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
)
|
|
383
|
-
Ok(_) => Ok(()),
|
|
384
|
-
Err(err) => Err(err.to_string().into()),
|
|
385
|
-
}
|
|
378
|
+
element_content_handlers.push(element!(match_element, move |el| {
|
|
379
|
+
match Self::process_element_handlers(
|
|
380
|
+
handler,
|
|
381
|
+
el,
|
|
382
|
+
&closure_element_stack.borrow(),
|
|
383
|
+
) {
|
|
384
|
+
Ok(_) => Ok(()),
|
|
385
|
+
Err(err) => Err(err.to_string().into()),
|
|
386
386
|
}
|
|
387
|
-
));
|
|
387
|
+
}));
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
if selector.match_text_within()
|
|
390
|
+
if let Some(match_text_within) = selector.match_text_within() {
|
|
391
391
|
let closure_element_stack = element_stack.clone();
|
|
392
392
|
|
|
393
|
-
element_content_handlers.push(text!(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if selector
|
|
401
|
-
.ignore_text_within()
|
|
402
|
-
.unwrap()
|
|
403
|
-
.iter()
|
|
404
|
-
.any(|t| element_stack.contains(t))
|
|
405
|
-
{
|
|
406
|
-
return Ok(());
|
|
407
|
-
}
|
|
393
|
+
element_content_handlers.push(text!(match_text_within, move |text| {
|
|
394
|
+
let element_stack = closure_element_stack.as_ref().borrow();
|
|
395
|
+
// check if current tag is a tag we should be ignoring text within;
|
|
396
|
+
// also checks if tag is within an ancestery of ignored tags
|
|
397
|
+
if let Some(ignore_text_within) = selector.ignore_text_within() {
|
|
398
|
+
if ignore_text_within.iter().any(|t| element_stack.contains(t)) {
|
|
399
|
+
return Ok(());
|
|
408
400
|
}
|
|
401
|
+
}
|
|
409
402
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
403
|
+
match Self::process_text_handlers(handler, text) {
|
|
404
|
+
Ok(_) => Ok(()),
|
|
405
|
+
Err(err) => Err(err.to_string().into()),
|
|
414
406
|
}
|
|
415
|
-
));
|
|
407
|
+
}));
|
|
416
408
|
}
|
|
417
409
|
|
|
418
410
|
// we need to check *every* element we iterate over, to create a stack of elements
|
|
@@ -456,15 +448,16 @@ impl SelmaRewriter {
|
|
|
456
448
|
let binding = &self.0.borrow();
|
|
457
449
|
let mut output = vec![];
|
|
458
450
|
{
|
|
459
|
-
let mut
|
|
460
|
-
Settings
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
451
|
+
let mut settings =
|
|
452
|
+
Settings::new().with_memory_settings(Self::get_memory_options(binding));
|
|
453
|
+
for handler in document_content_handlers {
|
|
454
|
+
settings = settings.append_document_content_handler(handler);
|
|
455
|
+
}
|
|
456
|
+
for handler in element_content_handlers {
|
|
457
|
+
settings = settings.append_element_content_handler(handler);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
let mut rewriter = HtmlRewriter::new(settings, |c: &[u8]| output.extend_from_slice(c));
|
|
468
461
|
match rewriter.write(html) {
|
|
469
462
|
Ok(_) => {}
|
|
470
463
|
Err(err) => {
|
|
@@ -561,18 +554,23 @@ impl SelmaRewriter {
|
|
|
561
554
|
}
|
|
562
555
|
|
|
563
556
|
fn get_memory_options(binding: &Ref<Rewriter>) -> MemorySettings {
|
|
564
|
-
let options = &binding.options
|
|
565
|
-
MemorySettings
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
557
|
+
let options = &binding.options;
|
|
558
|
+
MemorySettings::new()
|
|
559
|
+
.with_max_allowed_memory_usage(options.max_allowed_memory_usage)
|
|
560
|
+
.with_preallocated_parsing_buffer_size(options.preallocated_parsing_buffer_size)
|
|
569
561
|
}
|
|
570
562
|
}
|
|
571
563
|
|
|
572
564
|
impl RewriterOptions {
|
|
565
|
+
// Mirrors lol_html's `MemorySettings` defaults, which are no longer exposed as
|
|
566
|
+
// public fields as of lol_html 3.0 (settings are now consuming builders).
|
|
567
|
+
const DEFAULT_MAX_ALLOWED_MEMORY_USAGE: usize = usize::MAX;
|
|
568
|
+
const DEFAULT_PREALLOCATED_PARSING_BUFFER_SIZE: usize = 1024;
|
|
569
|
+
|
|
573
570
|
pub fn new() -> Self {
|
|
574
571
|
Self {
|
|
575
|
-
|
|
572
|
+
max_allowed_memory_usage: Self::DEFAULT_MAX_ALLOWED_MEMORY_USAGE,
|
|
573
|
+
preallocated_parsing_buffer_size: Self::DEFAULT_PREALLOCATED_PARSING_BUFFER_SIZE,
|
|
576
574
|
}
|
|
577
575
|
}
|
|
578
576
|
}
|
data/ext/selma/src/selector.rs
CHANGED
|
@@ -87,16 +87,16 @@ impl SelmaSelector {
|
|
|
87
87
|
Ok((match_element, match_text_within, rb_ignore_text_within))
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
pub fn match_element(&self) -> Option
|
|
91
|
-
self.match_element.
|
|
90
|
+
pub fn match_element(&self) -> Option<&str> {
|
|
91
|
+
self.match_element.as_deref()
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
pub fn match_text_within(&self) -> Option
|
|
95
|
-
self.match_text_within.
|
|
94
|
+
pub fn match_text_within(&self) -> Option<&str> {
|
|
95
|
+
self.match_text_within.as_deref()
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
pub fn ignore_text_within(&self) -> Option
|
|
99
|
-
self.ignore_text_within.
|
|
98
|
+
pub fn ignore_text_within(&self) -> Option<&[String]> {
|
|
99
|
+
self.ignore_text_within.as_deref()
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
data/lib/selma/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: selma
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Garen J. Torikian
|
|
@@ -112,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
112
112
|
- !ruby/object:Gem::Version
|
|
113
113
|
version: '3.4'
|
|
114
114
|
requirements: []
|
|
115
|
-
rubygems_version: 4.0.
|
|
115
|
+
rubygems_version: 4.0.10
|
|
116
116
|
specification_version: 4
|
|
117
117
|
summary: Selma selects and matches HTML nodes using CSS rules. Backed by Rust's lol_html
|
|
118
118
|
parser.
|