1use crate::value::StrykeValue;
7use parking_lot::RwLock;
8use std::sync::Arc;
9
10fn arg_str(args: &[StrykeValue]) -> String {
11 args.first().map(|v| v.to_string()).unwrap_or_default()
12}
13
14fn arr(vs: Vec<StrykeValue>) -> StrykeValue {
15 StrykeValue::array_ref(Arc::new(RwLock::new(vs)))
16}
17
18fn parse_json(s: &str) -> Option<serde_json::Value> {
23 serde_json::from_str(s).ok()
24}
25
26fn json_to_stryke(v: &serde_json::Value) -> StrykeValue {
27 use indexmap::IndexMap;
28 match v {
29 serde_json::Value::Null => StrykeValue::UNDEF,
30 serde_json::Value::Bool(b) => StrykeValue::integer(if *b { 1 } else { 0 }),
31 serde_json::Value::Number(n) => {
32 if let Some(i) = n.as_i64() {
33 StrykeValue::integer(i)
34 } else if let Some(f) = n.as_f64() {
35 StrykeValue::float(f)
36 } else {
37 StrykeValue::string(n.to_string())
38 }
39 }
40 serde_json::Value::String(s) => StrykeValue::string(s.clone()),
41 serde_json::Value::Array(items) => {
42 let elems: Vec<StrykeValue> = items.iter().map(json_to_stryke).collect();
43 arr(elems)
44 }
45 serde_json::Value::Object(m) => {
46 let mut h: IndexMap<String, StrykeValue> = IndexMap::new();
47 for (k, v) in m {
48 h.insert(k.clone(), json_to_stryke(v));
49 }
50 StrykeValue::hash_ref(Arc::new(RwLock::new(h)))
51 }
52 }
53}
54
55fn split_path(path: &str) -> Vec<&str> {
56 path.split('.').filter(|s| !s.is_empty()).collect()
57}
58
59pub fn jq_get(args: &[StrykeValue]) -> StrykeValue {
62 let s = arg_str(args);
63 let path = args.get(1).map(|v| v.to_string()).unwrap_or_default();
64 let Some(mut v) = parse_json(&s) else {
65 return StrykeValue::UNDEF;
66 };
67 for seg in split_path(&path) {
68 if let Ok(idx) = seg.parse::<usize>() {
69 v = match v {
70 serde_json::Value::Array(arr) => {
71 arr.into_iter().nth(idx).unwrap_or(serde_json::Value::Null)
72 }
73 _ => serde_json::Value::Null,
74 };
75 } else {
76 v = match v {
77 serde_json::Value::Object(mut m) => {
78 m.remove(seg).unwrap_or(serde_json::Value::Null)
79 }
80 _ => serde_json::Value::Null,
81 };
82 }
83 }
84 json_to_stryke(&v)
85}
86
87pub fn jq_set(args: &[StrykeValue]) -> StrykeValue {
88 let s = arg_str(args);
89 let path = args.get(1).map(|v| v.to_string()).unwrap_or_default();
90 let new_val = args.get(2).map(|v| v.to_string()).unwrap_or_default();
91 let Some(mut v) = parse_json(&s) else {
92 return StrykeValue::UNDEF;
93 };
94 let new_v = parse_json(&new_val).unwrap_or(serde_json::Value::String(new_val.clone()));
95 let segs = split_path(&path);
96 fn set_path(v: &mut serde_json::Value, segs: &[&str], new_v: serde_json::Value) {
97 if segs.is_empty() {
98 *v = new_v;
99 return;
100 }
101 let seg = segs[0];
102 let rest = &segs[1..];
103 if let Ok(idx) = seg.parse::<usize>() {
104 if let serde_json::Value::Array(arr) = v {
105 while arr.len() <= idx {
106 arr.push(serde_json::Value::Null);
107 }
108 set_path(&mut arr[idx], rest, new_v);
109 }
110 } else {
111 if !v.is_object() {
112 *v = serde_json::Value::Object(serde_json::Map::new());
113 }
114 if let serde_json::Value::Object(m) = v {
115 let entry = m.entry(seg.to_string()).or_insert(serde_json::Value::Null);
116 set_path(entry, rest, new_v);
117 }
118 }
119 }
120 set_path(&mut v, &segs, new_v);
121 StrykeValue::string(serde_json::to_string(&v).unwrap_or_default())
122}
123
124pub fn jq_delete(args: &[StrykeValue]) -> StrykeValue {
125 let s = arg_str(args);
126 let path = args.get(1).map(|v| v.to_string()).unwrap_or_default();
127 let Some(mut v) = parse_json(&s) else {
128 return StrykeValue::UNDEF;
129 };
130 let segs = split_path(&path);
131 if segs.is_empty() {
132 return StrykeValue::string("null".to_string());
133 }
134 let last = segs[segs.len() - 1];
135 let parents = &segs[..segs.len() - 1];
136 fn descend<'a>(
137 v: &'a mut serde_json::Value,
138 segs: &[&str],
139 ) -> Option<&'a mut serde_json::Value> {
140 let mut cur = v;
141 for seg in segs {
142 if let Ok(idx) = seg.parse::<usize>() {
143 if let serde_json::Value::Array(arr) = cur {
144 cur = arr.get_mut(idx)?;
145 } else {
146 return None;
147 }
148 } else if let serde_json::Value::Object(m) = cur {
149 cur = m.get_mut(*seg)?;
150 } else {
151 return None;
152 }
153 }
154 Some(cur)
155 }
156 if let Some(target) = descend(&mut v, parents) {
157 if let Ok(idx) = last.parse::<usize>() {
158 if let serde_json::Value::Array(arr) = target {
159 if idx < arr.len() {
160 arr.remove(idx);
161 }
162 }
163 } else if let serde_json::Value::Object(m) = target {
164 m.remove(last);
165 }
166 }
167 StrykeValue::string(serde_json::to_string(&v).unwrap_or_default())
168}
169
170pub fn jq_select(args: &[StrykeValue]) -> StrykeValue {
171 jq_get(args)
172}
173
174pub fn jq_keys_at(args: &[StrykeValue]) -> StrykeValue {
175 let v = jq_get(args);
176 if let Some(h) = v.as_hash_ref() {
177 let g = h.read();
178 return arr(g.keys().map(|k| StrykeValue::string(k.clone())).collect());
179 }
180 StrykeValue::UNDEF
181}
182
183pub fn jq_values_at(args: &[StrykeValue]) -> StrykeValue {
184 let v = jq_get(args);
185 if let Some(h) = v.as_hash_ref() {
186 let g = h.read();
187 return arr(g.values().cloned().collect());
188 }
189 if let Some(a) = v.as_array_ref() {
190 return StrykeValue::array_ref(a);
191 }
192 StrykeValue::UNDEF
193}
194
195pub fn jq_length_at(args: &[StrykeValue]) -> StrykeValue {
196 let v = jq_get(args);
197 if let Some(h) = v.as_hash_ref() {
198 return StrykeValue::integer(h.read().len() as i64);
199 }
200 if let Some(a) = v.as_array_ref() {
201 return StrykeValue::integer(a.read().len() as i64);
202 }
203 if let Some(s) = v.as_str() {
204 return StrykeValue::integer(s.chars().count() as i64);
205 }
206 StrykeValue::integer(0)
207}
208
209pub fn jq_type(args: &[StrykeValue]) -> StrykeValue {
210 let s = arg_str(args);
211 let Some(v) = parse_json(&s) else {
212 return StrykeValue::string("invalid".to_string());
213 };
214 let t = match v {
215 serde_json::Value::Null => "null",
216 serde_json::Value::Bool(_) => "boolean",
217 serde_json::Value::Number(_) => "number",
218 serde_json::Value::String(_) => "string",
219 serde_json::Value::Array(_) => "array",
220 serde_json::Value::Object(_) => "object",
221 };
222 StrykeValue::string(t.to_string())
223}
224
225pub fn jq_has(args: &[StrykeValue]) -> StrykeValue {
226 let v = jq_get(args);
227 StrykeValue::integer(if v.is_undef() { 0 } else { 1 })
228}
229
230pub fn jq_paths(args: &[StrykeValue]) -> StrykeValue {
231 let s = arg_str(args);
232 let Some(v) = parse_json(&s) else {
233 return StrykeValue::UNDEF;
234 };
235 fn walk(v: &serde_json::Value, prefix: String, out: &mut Vec<String>) {
236 match v {
237 serde_json::Value::Object(m) => {
238 for (k, vv) in m {
239 let p = if prefix.is_empty() {
240 k.clone()
241 } else {
242 format!("{}.{}", prefix, k)
243 };
244 out.push(p.clone());
245 walk(vv, p, out);
246 }
247 }
248 serde_json::Value::Array(a) => {
249 for (i, vv) in a.iter().enumerate() {
250 let p = if prefix.is_empty() {
251 i.to_string()
252 } else {
253 format!("{}.{}", prefix, i)
254 };
255 out.push(p.clone());
256 walk(vv, p, out);
257 }
258 }
259 _ => {}
260 }
261 }
262 let mut paths: Vec<String> = Vec::new();
263 walk(&v, String::new(), &mut paths);
264 arr(paths.into_iter().map(StrykeValue::string).collect())
265}
266
267pub fn jq_leaf_paths(args: &[StrykeValue]) -> StrykeValue {
268 let s = arg_str(args);
269 let Some(v) = parse_json(&s) else {
270 return StrykeValue::UNDEF;
271 };
272 fn walk(v: &serde_json::Value, prefix: String, out: &mut Vec<String>) {
273 match v {
274 serde_json::Value::Object(m) => {
275 for (k, vv) in m {
276 let p = if prefix.is_empty() {
277 k.clone()
278 } else {
279 format!("{}.{}", prefix, k)
280 };
281 walk(vv, p, out);
282 }
283 }
284 serde_json::Value::Array(a) => {
285 for (i, vv) in a.iter().enumerate() {
286 let p = if prefix.is_empty() {
287 i.to_string()
288 } else {
289 format!("{}.{}", prefix, i)
290 };
291 walk(vv, p, out);
292 }
293 }
294 _ => out.push(prefix),
295 }
296 }
297 let mut paths: Vec<String> = Vec::new();
298 walk(&v, String::new(), &mut paths);
299 arr(paths.into_iter().map(StrykeValue::string).collect())
300}
301
302pub fn jq_walk(args: &[StrykeValue]) -> StrykeValue {
303 let s = arg_str(args);
304 let Some(v) = parse_json(&s) else {
305 return StrykeValue::UNDEF;
306 };
307 json_to_stryke(&v)
308}
309
310pub fn jq_map_values(args: &[StrykeValue]) -> StrykeValue {
311 let s = arg_str(args);
312 let Some(v) = parse_json(&s) else {
313 return StrykeValue::UNDEF;
314 };
315 json_to_stryke(&v)
316}
317
318pub fn jq_filter(args: &[StrykeValue]) -> StrykeValue {
319 jq_get(args)
320}
321
322pub fn jq_to_entries(args: &[StrykeValue]) -> StrykeValue {
323 let s = arg_str(args);
324 let Some(v) = parse_json(&s) else {
325 return StrykeValue::UNDEF;
326 };
327 if let serde_json::Value::Object(m) = v {
328 use indexmap::IndexMap;
329 let entries: Vec<StrykeValue> = m
330 .into_iter()
331 .map(|(k, v)| {
332 let mut h: IndexMap<String, StrykeValue> = IndexMap::new();
333 h.insert("key".to_string(), StrykeValue::string(k));
334 h.insert("value".to_string(), json_to_stryke(&v));
335 StrykeValue::hash_ref(Arc::new(RwLock::new(h)))
336 })
337 .collect();
338 return arr(entries);
339 }
340 StrykeValue::UNDEF
341}
342
343pub fn jq_from_entries(args: &[StrykeValue]) -> StrykeValue {
344 use indexmap::IndexMap;
345 let Some(arr_ref) = args.first().and_then(|v| v.as_array_ref()) else {
346 return StrykeValue::UNDEF;
347 };
348 let g = arr_ref.read();
349 let mut h: IndexMap<String, StrykeValue> = IndexMap::new();
350 for entry in g.iter() {
351 if let Some(eh) = entry.as_hash_ref() {
352 let eg = eh.read();
353 let key = eg.get("key").map(|v| v.to_string()).unwrap_or_default();
354 let val = eg.get("value").cloned().unwrap_or(StrykeValue::UNDEF);
355 h.insert(key, val);
356 }
357 }
358 StrykeValue::hash_ref(Arc::new(RwLock::new(h)))
359}
360
361pub fn jq_with_entries(args: &[StrykeValue]) -> StrykeValue {
362 jq_to_entries(args)
363}
364
365pub fn jq_recurse(args: &[StrykeValue]) -> StrykeValue {
366 jq_paths(args)
367}
368
369pub fn jq_min_by(args: &[StrykeValue]) -> StrykeValue {
370 let v = jq_get(args);
371 if let Some(arr_ref) = v.as_array_ref() {
372 let g = arr_ref.read();
373 return g
374 .iter()
375 .min_by(|a, b| {
376 a.to_number()
377 .partial_cmp(&b.to_number())
378 .unwrap_or(std::cmp::Ordering::Equal)
379 })
380 .cloned()
381 .unwrap_or(StrykeValue::UNDEF);
382 }
383 StrykeValue::UNDEF
384}
385
386pub fn jq_max_by(args: &[StrykeValue]) -> StrykeValue {
387 let v = jq_get(args);
388 if let Some(arr_ref) = v.as_array_ref() {
389 let g = arr_ref.read();
390 return g
391 .iter()
392 .max_by(|a, b| {
393 a.to_number()
394 .partial_cmp(&b.to_number())
395 .unwrap_or(std::cmp::Ordering::Equal)
396 })
397 .cloned()
398 .unwrap_or(StrykeValue::UNDEF);
399 }
400 StrykeValue::UNDEF
401}
402
403pub fn jq_sort_by(args: &[StrykeValue]) -> StrykeValue {
404 let v = jq_get(args);
405 if let Some(arr_ref) = v.as_array_ref() {
406 let mut g: Vec<StrykeValue> = arr_ref.read().clone();
407 g.sort_by(|a, b| {
408 a.to_number()
409 .partial_cmp(&b.to_number())
410 .unwrap_or(std::cmp::Ordering::Equal)
411 });
412 return arr(g);
413 }
414 StrykeValue::UNDEF
415}
416
417pub fn jq_group_by(args: &[StrykeValue]) -> StrykeValue {
418 use indexmap::IndexMap;
419 let v = jq_get(args);
420 if let Some(arr_ref) = v.as_array_ref() {
421 let mut m: IndexMap<String, Vec<StrykeValue>> = IndexMap::new();
422 for x in arr_ref.read().iter() {
423 m.entry(x.to_string()).or_default().push(x.clone());
424 }
425 let groups: Vec<StrykeValue> = m.into_values().map(arr).collect();
426 return arr(groups);
427 }
428 StrykeValue::UNDEF
429}
430
431pub fn jq_unique_by(args: &[StrykeValue]) -> StrykeValue {
432 use std::collections::HashSet;
433 let v = jq_get(args);
434 if let Some(arr_ref) = v.as_array_ref() {
435 let mut seen: HashSet<String> = HashSet::new();
436 let out: Vec<StrykeValue> = arr_ref
437 .read()
438 .iter()
439 .filter(|v| seen.insert(v.to_string()))
440 .cloned()
441 .collect();
442 return arr(out);
443 }
444 StrykeValue::UNDEF
445}
446
447pub fn jq_any(args: &[StrykeValue]) -> StrykeValue {
448 let v = jq_get(args);
449 if let Some(arr_ref) = v.as_array_ref() {
450 return StrykeValue::integer(if arr_ref.read().iter().any(|v| v.is_true()) {
451 1
452 } else {
453 0
454 });
455 }
456 StrykeValue::integer(0)
457}
458
459pub fn jq_all(args: &[StrykeValue]) -> StrykeValue {
460 let v = jq_get(args);
461 if let Some(arr_ref) = v.as_array_ref() {
462 let g = arr_ref.read();
463 if g.is_empty() {
464 return StrykeValue::integer(1);
465 }
466 return StrykeValue::integer(if g.iter().all(|v| v.is_true()) { 1 } else { 0 });
467 }
468 StrykeValue::integer(0)
469}
470
471pub fn jq_flatten(args: &[StrykeValue]) -> StrykeValue {
472 let v = jq_get(args);
473 if let Some(arr_ref) = v.as_array_ref() {
474 let mut out: Vec<StrykeValue> = Vec::new();
475 fn walk(v: &StrykeValue, out: &mut Vec<StrykeValue>) {
476 if let Some(a) = v.as_array_ref() {
477 for x in a.read().iter() {
478 walk(x, out);
479 }
480 } else {
481 out.push(v.clone());
482 }
483 }
484 for x in arr_ref.read().iter() {
485 walk(x, &mut out);
486 }
487 return arr(out);
488 }
489 v
490}
491
492pub fn jq_index(args: &[StrykeValue]) -> StrykeValue {
493 let v = jq_get(args);
494 let needle = args.get(2).map(|v| v.to_string()).unwrap_or_default();
495 if let Some(arr_ref) = v.as_array_ref() {
496 let g = arr_ref.read();
497 for (i, x) in g.iter().enumerate() {
498 if x.to_string() == needle {
499 return StrykeValue::integer(i as i64);
500 }
501 }
502 return StrykeValue::integer(-1);
503 }
504 StrykeValue::integer(-1)
505}
506
507pub fn jq_indices(args: &[StrykeValue]) -> StrykeValue {
508 let v = jq_get(args);
509 let needle = args.get(2).map(|v| v.to_string()).unwrap_or_default();
510 if let Some(arr_ref) = v.as_array_ref() {
511 let g = arr_ref.read();
512 let mut out: Vec<StrykeValue> = Vec::new();
513 for (i, x) in g.iter().enumerate() {
514 if x.to_string() == needle {
515 out.push(StrykeValue::integer(i as i64));
516 }
517 }
518 return arr(out);
519 }
520 StrykeValue::UNDEF
521}
522
523pub fn jq_first(args: &[StrykeValue]) -> StrykeValue {
524 let v = jq_get(args);
525 if let Some(arr_ref) = v.as_array_ref() {
526 return arr_ref
527 .read()
528 .first()
529 .cloned()
530 .unwrap_or(StrykeValue::UNDEF);
531 }
532 v
533}
534
535pub fn jq_last(args: &[StrykeValue]) -> StrykeValue {
536 let v = jq_get(args);
537 if let Some(arr_ref) = v.as_array_ref() {
538 return arr_ref.read().last().cloned().unwrap_or(StrykeValue::UNDEF);
539 }
540 v
541}
542
543pub fn jq_split_at(args: &[StrykeValue]) -> StrykeValue {
544 let v = jq_get(args);
545 let n = args.get(2).map(|v| v.to_int() as usize).unwrap_or(0);
546 if let Some(arr_ref) = v.as_array_ref() {
547 let g = arr_ref.read();
548 let (l, r): (Vec<_>, Vec<_>) = g.iter().enumerate().partition(|(i, _)| *i < n);
549 let left: Vec<StrykeValue> = l.into_iter().map(|(_, v)| v.clone()).collect();
550 let right: Vec<StrykeValue> = r.into_iter().map(|(_, v)| v.clone()).collect();
551 return arr(vec![arr(left), arr(right)]);
552 }
553 StrykeValue::UNDEF
554}
555
556pub fn jq_chunks(args: &[StrykeValue]) -> StrykeValue {
557 let v = jq_get(args);
558 let n = args.get(2).map(|v| v.to_int().max(1) as usize).unwrap_or(1);
559 if let Some(arr_ref) = v.as_array_ref() {
560 let g = arr_ref.read();
561 let out: Vec<StrykeValue> = g.chunks(n).map(|c| arr(c.to_vec())).collect();
562 return arr(out);
563 }
564 StrykeValue::UNDEF
565}
566
567pub fn jq_zip(args: &[StrykeValue]) -> StrykeValue {
568 let a = jq_get(args);
569 let b = jq_get(&[
570 args.get(2).cloned().unwrap_or(StrykeValue::UNDEF),
571 args.get(3).cloned().unwrap_or(StrykeValue::UNDEF),
572 ]);
573 let (Some(a_arr), Some(b_arr)) = (a.as_array_ref(), b.as_array_ref()) else {
574 return StrykeValue::UNDEF;
575 };
576 let ag = a_arr.read();
577 let bg = b_arr.read();
578 let n = ag.len().min(bg.len());
579 let out: Vec<StrykeValue> = (0..n)
580 .map(|i| arr(vec![ag[i].clone(), bg[i].clone()]))
581 .collect();
582 arr(out)
583}
584
585pub fn jq_combinations(args: &[StrykeValue]) -> StrykeValue {
586 let v = jq_get(args);
587 let k = args.get(2).map(|v| v.to_int() as usize).unwrap_or(2);
588 let Some(arr_ref) = v.as_array_ref() else {
589 return StrykeValue::UNDEF;
590 };
591 let g = arr_ref.read();
592 let n = g.len();
593 if k > n {
594 return arr(vec![]);
595 }
596 let mut indices: Vec<usize> = (0..k).collect();
597 let mut out: Vec<StrykeValue> = Vec::new();
598 loop {
599 out.push(arr(indices.iter().map(|i| g[*i].clone()).collect()));
600 let mut i = k;
601 while i > 0 {
602 i -= 1;
603 if indices[i] < n - k + i {
604 indices[i] += 1;
605 for j in i + 1..k {
606 indices[j] = indices[j - 1] + 1;
607 }
608 break;
609 }
610 if i == 0 {
611 return arr(out);
612 }
613 }
614 }
615}
616
617pub fn json_diff(args: &[StrykeValue]) -> StrykeValue {
618 let a = arg_str(args);
619 let b = args.get(1).map(|v| v.to_string()).unwrap_or_default();
620 let (Some(va), Some(vb)) = (parse_json(&a), parse_json(&b)) else {
621 return StrykeValue::UNDEF;
622 };
623 if va == vb {
624 StrykeValue::string("[]".to_string())
625 } else {
626 StrykeValue::string(format!(
628 "[{{\"old\":{}}},{{\"new\":{}}}]",
629 serde_json::to_string(&va).unwrap_or_default(),
630 serde_json::to_string(&vb).unwrap_or_default()
631 ))
632 }
633}
634
635pub fn json_patch(args: &[StrykeValue]) -> StrykeValue {
636 let s = arg_str(args);
638 let patches_s = args.get(1).map(|v| v.to_string()).unwrap_or_default();
639 let Some(mut v) = parse_json(&s) else {
640 return StrykeValue::UNDEF;
641 };
642 let Some(patches) = parse_json(&patches_s) else {
643 return StrykeValue::UNDEF;
644 };
645 let serde_json::Value::Array(ops) = patches else {
646 return StrykeValue::UNDEF;
647 };
648 for op in ops {
649 let serde_json::Value::Object(m) = op else {
650 continue;
651 };
652 let op_kind = m.get("op").and_then(|v| v.as_str()).unwrap_or("");
653 let path = m.get("path").and_then(|v| v.as_str()).unwrap_or("");
654 let value = m.get("value").cloned();
655 let segs: Vec<&str> = path.split('/').skip(1).collect();
656 match op_kind {
657 "replace" | "add" => {
658 if let Some(nv) = value {
659 if segs.is_empty() {
660 v = nv;
661 } else {
662 let path_str = segs.join(".");
663 let new_s = jq_set(&[
664 StrykeValue::string(serde_json::to_string(&v).unwrap_or_default()),
665 StrykeValue::string(path_str),
666 StrykeValue::string(serde_json::to_string(&nv).unwrap_or_default()),
667 ])
668 .to_string();
669 if let Some(parsed) = parse_json(&new_s) {
670 v = parsed;
671 }
672 }
673 }
674 }
675 "remove" => {
676 let path_str = segs.join(".");
677 let new_s = jq_delete(&[
678 StrykeValue::string(serde_json::to_string(&v).unwrap_or_default()),
679 StrykeValue::string(path_str),
680 ])
681 .to_string();
682 if let Some(parsed) = parse_json(&new_s) {
683 v = parsed;
684 }
685 }
686 _ => {}
687 }
688 }
689 StrykeValue::string(serde_json::to_string(&v).unwrap_or_default())
690}
691
692pub fn json_merge_patch(args: &[StrykeValue]) -> StrykeValue {
693 let s = arg_str(args);
695 let patch_s = args.get(1).map(|v| v.to_string()).unwrap_or_default();
696 let (Some(mut v), Some(p)) = (parse_json(&s), parse_json(&patch_s)) else {
697 return StrykeValue::UNDEF;
698 };
699 fn merge(v: &mut serde_json::Value, p: serde_json::Value) {
700 match (v, p) {
701 (serde_json::Value::Object(target), serde_json::Value::Object(patch)) => {
702 for (k, pv) in patch {
703 if pv.is_null() {
704 target.remove(&k);
705 } else {
706 let entry = target.entry(k).or_insert(serde_json::Value::Null);
707 merge(entry, pv);
708 }
709 }
710 }
711 (v, p) => *v = p,
712 }
713 }
714 merge(&mut v, p);
715 StrykeValue::string(serde_json::to_string(&v).unwrap_or_default())
716}
717
718pub fn json_pointer_resolve(args: &[StrykeValue]) -> StrykeValue {
719 let s = arg_str(args);
720 let ptr = args.get(1).map(|v| v.to_string()).unwrap_or_default();
721 let Some(v) = parse_json(&s) else {
722 return StrykeValue::UNDEF;
723 };
724 let resolved = v.pointer(&ptr).cloned().unwrap_or(serde_json::Value::Null);
725 json_to_stryke(&resolved)
726}
727
728pub fn json_pointer_set(args: &[StrykeValue]) -> StrykeValue {
729 let s = arg_str(args);
730 let ptr = args.get(1).map(|v| v.to_string()).unwrap_or_default();
731 let new_val = args.get(2).map(|v| v.to_string()).unwrap_or_default();
732 let Some(mut v) = parse_json(&s) else {
733 return StrykeValue::UNDEF;
734 };
735 let new_v = parse_json(&new_val).unwrap_or(serde_json::Value::String(new_val.clone()));
736 if let Some(target) = v.pointer_mut(&ptr) {
737 *target = new_v;
738 }
739 StrykeValue::string(serde_json::to_string(&v).unwrap_or_default())
740}
741
742pub fn html_parse(args: &[StrykeValue]) -> StrykeValue {
747 let s = arg_str(args);
748 let _doc = scraper::Html::parse_document(&s);
749 StrykeValue::string(s)
751}
752
753pub fn html_to_text(args: &[StrykeValue]) -> StrykeValue {
754 let s = arg_str(args);
755 let doc = scraper::Html::parse_document(&s);
756 let body_sel = scraper::Selector::parse("body").unwrap();
757 let text: String = doc
758 .select(&body_sel)
759 .flat_map(|n| n.text())
760 .collect::<Vec<_>>()
761 .join(" ");
762 StrykeValue::string(text.split_whitespace().collect::<Vec<_>>().join(" "))
763}
764
765pub fn html_pretty(args: &[StrykeValue]) -> StrykeValue {
766 StrykeValue::string(arg_str(args))
768}
769
770pub fn html_minify(args: &[StrykeValue]) -> StrykeValue {
771 let s = arg_str(args);
772 let re = regex::Regex::new(r">\s+<").unwrap();
773 let collapsed = re.replace_all(&s, "><").to_string();
774 StrykeValue::string(collapsed.split_whitespace().collect::<Vec<_>>().join(" "))
775}
776
777pub fn html_sanitize(args: &[StrykeValue]) -> StrykeValue {
778 let s = arg_str(args);
780 let s = regex::Regex::new(r"(?is)<script[^>]*>.*?</script>")
781 .unwrap()
782 .replace_all(&s, "")
783 .to_string();
784 let s = regex::Regex::new(r"(?is)<style[^>]*>.*?</style>")
785 .unwrap()
786 .replace_all(&s, "")
787 .to_string();
788 let s = regex::Regex::new(r#"(?i)\son\w+\s*=\s*"[^"]*""#)
789 .unwrap()
790 .replace_all(&s, "")
791 .to_string();
792 let s = regex::Regex::new(r#"(?i)javascript:[^"'\s>]*"#)
793 .unwrap()
794 .replace_all(&s, "")
795 .to_string();
796 StrykeValue::string(s)
797}
798
799pub fn html_strip_tags(args: &[StrykeValue]) -> StrykeValue {
800 let s = arg_str(args);
801 let re = regex::Regex::new(r"<[^>]+>").unwrap();
802 StrykeValue::string(re.replace_all(&s, "").to_string())
803}
804
805pub fn html_strip_scripts(args: &[StrykeValue]) -> StrykeValue {
806 let s = arg_str(args);
807 let re = regex::Regex::new(r"(?is)<script[^>]*>.*?</script>").unwrap();
808 StrykeValue::string(re.replace_all(&s, "").to_string())
809}
810
811pub fn html_strip_styles(args: &[StrykeValue]) -> StrykeValue {
812 let s = arg_str(args);
813 let re = regex::Regex::new(r"(?is)<style[^>]*>.*?</style>").unwrap();
814 StrykeValue::string(re.replace_all(&s, "").to_string())
815}
816
817fn extract_attrs(html: &str, selector: &str, attr: &str) -> Vec<String> {
818 let doc = scraper::Html::parse_document(html);
819 let Ok(sel) = scraper::Selector::parse(selector) else {
820 return Vec::new();
821 };
822 doc.select(&sel)
823 .filter_map(|n| n.value().attr(attr).map(|s| s.to_string()))
824 .collect()
825}
826
827pub fn html_extract_links(args: &[StrykeValue]) -> StrykeValue {
828 let urls = extract_attrs(&arg_str(args), "a[href]", "href");
829 arr(urls.into_iter().map(StrykeValue::string).collect())
830}
831
832pub fn html_extract_images(args: &[StrykeValue]) -> StrykeValue {
833 let urls = extract_attrs(&arg_str(args), "img[src]", "src");
834 arr(urls.into_iter().map(StrykeValue::string).collect())
835}
836
837pub fn html_extract_text(args: &[StrykeValue]) -> StrykeValue {
838 html_to_text(args)
839}
840
841pub fn html_extract_meta(args: &[StrykeValue]) -> StrykeValue {
842 use indexmap::IndexMap;
843 let doc = scraper::Html::parse_document(&arg_str(args));
844 let sel = scraper::Selector::parse("meta").unwrap();
845 let mut h: IndexMap<String, StrykeValue> = IndexMap::new();
846 for n in doc.select(&sel) {
847 let v = n.value();
848 let name = v.attr("name").or_else(|| v.attr("property"));
849 let content = v.attr("content");
850 if let (Some(name), Some(content)) = (name, content) {
851 h.insert(name.to_string(), StrykeValue::string(content.to_string()));
852 }
853 }
854 StrykeValue::hash_ref(Arc::new(RwLock::new(h)))
855}
856
857pub fn html_extract_title(args: &[StrykeValue]) -> StrykeValue {
858 let doc = scraper::Html::parse_document(&arg_str(args));
859 let sel = scraper::Selector::parse("title").unwrap();
860 let t: String = doc
861 .select(&sel)
862 .next()
863 .map(|n| n.text().collect::<Vec<_>>().join(""))
864 .unwrap_or_default();
865 StrykeValue::string(t.trim().to_string())
866}
867
868pub fn html_extract_headings(args: &[StrykeValue]) -> StrykeValue {
869 let doc = scraper::Html::parse_document(&arg_str(args));
870 let sel = scraper::Selector::parse("h1, h2, h3, h4, h5, h6").unwrap();
871 let out: Vec<StrykeValue> = doc
872 .select(&sel)
873 .map(|n| StrykeValue::string(n.text().collect::<Vec<_>>().join("").trim().to_string()))
874 .collect();
875 arr(out)
876}
877
878pub fn html_extract_tables(args: &[StrykeValue]) -> StrykeValue {
879 let doc = scraper::Html::parse_document(&arg_str(args));
880 let table_sel = scraper::Selector::parse("table").unwrap();
881 let row_sel = scraper::Selector::parse("tr").unwrap();
882 let cell_sel = scraper::Selector::parse("td, th").unwrap();
883 let mut tables: Vec<StrykeValue> = Vec::new();
884 for t in doc.select(&table_sel) {
885 let mut rows: Vec<StrykeValue> = Vec::new();
886 for r in t.select(&row_sel) {
887 let cells: Vec<StrykeValue> = r
888 .select(&cell_sel)
889 .map(|c| {
890 StrykeValue::string(c.text().collect::<Vec<_>>().join("").trim().to_string())
891 })
892 .collect();
893 rows.push(arr(cells));
894 }
895 tables.push(arr(rows));
896 }
897 arr(tables)
898}
899
900pub fn html_inner_text(args: &[StrykeValue]) -> StrykeValue {
901 html_to_text(args)
902}
903
904pub fn html_canonical_url(args: &[StrykeValue]) -> StrykeValue {
905 let doc = scraper::Html::parse_document(&arg_str(args));
906 let sel = scraper::Selector::parse("link[rel='canonical']").unwrap();
907 let url = doc
908 .select(&sel)
909 .next()
910 .and_then(|n| n.value().attr("href"))
911 .unwrap_or_default()
912 .to_string();
913 StrykeValue::string(url)
914}
915
916pub fn html_meta_charset(args: &[StrykeValue]) -> StrykeValue {
917 let doc = scraper::Html::parse_document(&arg_str(args));
918 let sel = scraper::Selector::parse("meta[charset]").unwrap();
919 let cs = doc
920 .select(&sel)
921 .next()
922 .and_then(|n| n.value().attr("charset"))
923 .unwrap_or("utf-8")
924 .to_string();
925 StrykeValue::string(cs)
926}
927
928pub fn html_meta_keywords(args: &[StrykeValue]) -> StrykeValue {
929 let s = arg_str(args);
930 let doc = scraper::Html::parse_document(&s);
931 let sel = scraper::Selector::parse("meta[name='keywords']").unwrap();
932 let kw = doc
933 .select(&sel)
934 .next()
935 .and_then(|n| n.value().attr("content"))
936 .unwrap_or("")
937 .to_string();
938 StrykeValue::string(kw)
939}
940
941pub fn html_meta_description(args: &[StrykeValue]) -> StrykeValue {
942 let doc = scraper::Html::parse_document(&arg_str(args));
943 let sel = scraper::Selector::parse("meta[name='description']").unwrap();
944 let d = doc
945 .select(&sel)
946 .next()
947 .and_then(|n| n.value().attr("content"))
948 .unwrap_or("")
949 .to_string();
950 StrykeValue::string(d)
951}
952
953pub fn html_meta_og(args: &[StrykeValue]) -> StrykeValue {
954 use indexmap::IndexMap;
955 let doc = scraper::Html::parse_document(&arg_str(args));
956 let sel = scraper::Selector::parse("meta[property]").unwrap();
957 let mut h: IndexMap<String, StrykeValue> = IndexMap::new();
958 for n in doc.select(&sel) {
959 let v = n.value();
960 if let (Some(p), Some(c)) = (v.attr("property"), v.attr("content")) {
961 if p.starts_with("og:") {
962 h.insert(p.to_string(), StrykeValue::string(c.to_string()));
963 }
964 }
965 }
966 StrykeValue::hash_ref(Arc::new(RwLock::new(h)))
967}
968
969pub fn html_meta_twitter(args: &[StrykeValue]) -> StrykeValue {
970 use indexmap::IndexMap;
971 let doc = scraper::Html::parse_document(&arg_str(args));
972 let sel = scraper::Selector::parse("meta[name]").unwrap();
973 let mut h: IndexMap<String, StrykeValue> = IndexMap::new();
974 for n in doc.select(&sel) {
975 let v = n.value();
976 if let (Some(name), Some(c)) = (v.attr("name"), v.attr("content")) {
977 if name.starts_with("twitter:") {
978 h.insert(name.to_string(), StrykeValue::string(c.to_string()));
979 }
980 }
981 }
982 StrykeValue::hash_ref(Arc::new(RwLock::new(h)))
983}
984
985pub fn html_to_markdown(args: &[StrykeValue]) -> StrykeValue {
986 let s = arg_str(args);
988 let s = s.replace("<h1>", "# ").replace("</h1>", "\n");
989 let s = s.replace("<h2>", "## ").replace("</h2>", "\n");
990 let s = s.replace("<h3>", "### ").replace("</h3>", "\n");
991 let s = s.replace("<strong>", "**").replace("</strong>", "**");
992 let s = s.replace("<em>", "_").replace("</em>", "_");
993 let s = s.replace("<br>", "\n").replace("<br/>", "\n");
994 let s = s.replace("<p>", "").replace("</p>", "\n\n");
995 let re = regex::Regex::new(r"<[^>]+>").unwrap();
996 StrykeValue::string(re.replace_all(&s, "").to_string())
997}
998
999pub fn markdown_to_html(args: &[StrykeValue]) -> StrykeValue {
1000 let s = arg_str(args);
1002 let mut out = String::new();
1003 for line in s.lines() {
1004 if let Some(rest) = line.strip_prefix("### ") {
1005 out.push_str(&format!("<h3>{rest}</h3>\n"));
1006 } else if let Some(rest) = line.strip_prefix("## ") {
1007 out.push_str(&format!("<h2>{rest}</h2>\n"));
1008 } else if let Some(rest) = line.strip_prefix("# ") {
1009 out.push_str(&format!("<h1>{rest}</h1>\n"));
1010 } else if line.is_empty() {
1011 out.push('\n');
1012 } else {
1013 out.push_str(&format!("<p>{line}</p>\n"));
1014 }
1015 }
1016 StrykeValue::string(out)
1017}
1018
1019pub fn markdown_render(args: &[StrykeValue]) -> StrykeValue {
1020 markdown_to_html(args)
1021}
1022
1023pub fn xml_parse(args: &[StrykeValue]) -> StrykeValue {
1028 StrykeValue::string(arg_str(args))
1029}
1030
1031pub fn xml_pretty(args: &[StrykeValue]) -> StrykeValue {
1032 let s = arg_str(args);
1033 let mut out = String::new();
1034 let mut depth = 0i32;
1035 let mut i = 0;
1036 let bytes = s.as_bytes();
1037 while i < bytes.len() {
1038 if bytes[i] == b'<' {
1039 let start = i;
1041 while i < bytes.len() && bytes[i] != b'>' {
1042 i += 1;
1043 }
1044 i += 1;
1045 let tag = &s[start..i];
1046 if tag.starts_with("</") {
1047 depth -= 1;
1048 }
1049 for _ in 0..depth.max(0) {
1050 out.push_str(" ");
1051 }
1052 out.push_str(tag);
1053 out.push('\n');
1054 if !tag.starts_with("</") && !tag.ends_with("/>") && !tag.starts_with("<?") {
1055 depth += 1;
1056 }
1057 } else {
1058 let start = i;
1060 while i < bytes.len() && bytes[i] != b'<' {
1061 i += 1;
1062 }
1063 let text = s[start..i].trim();
1064 if !text.is_empty() {
1065 for _ in 0..depth.max(0) {
1066 out.push_str(" ");
1067 }
1068 out.push_str(text);
1069 out.push('\n');
1070 }
1071 }
1072 }
1073 StrykeValue::string(out)
1074}
1075
1076pub fn xml_minify(args: &[StrykeValue]) -> StrykeValue {
1077 let s = arg_str(args);
1078 let re = regex::Regex::new(r">\s+<").unwrap();
1079 StrykeValue::string(re.replace_all(&s, "><").to_string())
1080}
1081
1082pub fn xml_namespace(args: &[StrykeValue]) -> StrykeValue {
1083 let s = arg_str(args);
1084 let re = regex::Regex::new(r#"xmlns(?::\w+)?\s*=\s*"([^"]+)""#).unwrap();
1085 if let Some(c) = re.captures(&s) {
1086 return StrykeValue::string(c[1].to_string());
1087 }
1088 StrykeValue::UNDEF
1089}
1090
1091pub fn xml_text(args: &[StrykeValue]) -> StrykeValue {
1092 let s = arg_str(args);
1093 let re = regex::Regex::new(r"<[^>]+>").unwrap();
1094 let stripped = re.replace_all(&s, " ").to_string();
1095 StrykeValue::string(stripped.split_whitespace().collect::<Vec<_>>().join(" "))
1096}
1097
1098pub fn xml_attrs(args: &[StrykeValue]) -> StrykeValue {
1099 use indexmap::IndexMap;
1100 let s = arg_str(args);
1101 let re = regex::Regex::new(r#"(\w+)\s*=\s*"([^"]*)""#).unwrap();
1102 let mut h: IndexMap<String, StrykeValue> = IndexMap::new();
1103 for cap in re.captures_iter(&s) {
1104 h.insert(cap[1].to_string(), StrykeValue::string(cap[2].to_string()));
1105 }
1106 StrykeValue::hash_ref(Arc::new(RwLock::new(h)))
1107}
1108
1109pub fn xml_children_by_tag(args: &[StrykeValue]) -> StrykeValue {
1110 let s = arg_str(args);
1111 let tag = args.get(1).map(|v| v.to_string()).unwrap_or_default();
1112 let re = regex::Regex::new(&format!(
1113 r"<{}\b[^>]*>(.*?)</{}>",
1114 regex::escape(&tag),
1115 regex::escape(&tag)
1116 ))
1117 .unwrap();
1118 let out: Vec<StrykeValue> = re
1119 .captures_iter(&s)
1120 .map(|c| StrykeValue::string(c[0].to_string()))
1121 .collect();
1122 arr(out)
1123}
1124
1125pub fn xml_root(args: &[StrykeValue]) -> StrykeValue {
1126 let s = arg_str(args);
1127 let re = regex::Regex::new(r"<(\w+)").unwrap();
1128 if let Some(c) = re.captures(s.trim_start_matches(|c: char| {
1129 c == '<' && {
1130 let i = s.find('<').unwrap();
1131 s[i..].starts_with("<?")
1132 }
1133 })) {
1134 return StrykeValue::string(c[1].to_string());
1135 }
1136 StrykeValue::UNDEF
1137}
1138
1139pub fn xpath_select_one(args: &[StrykeValue]) -> StrykeValue {
1140 let s = arg_str(args);
1142 let xp = args.get(1).map(|v| v.to_string()).unwrap_or_default();
1143 if let Some(tag) = xp.strip_prefix("//") {
1144 let re = regex::Regex::new(&format!(
1145 r"<{}\b[^>]*>(.*?)</{}>",
1146 regex::escape(tag),
1147 regex::escape(tag)
1148 ))
1149 .unwrap();
1150 if let Some(c) = re.captures(&s) {
1151 return StrykeValue::string(c[0].to_string());
1152 }
1153 }
1154 StrykeValue::UNDEF
1155}
1156
1157pub fn xpath_attribute(args: &[StrykeValue]) -> StrykeValue {
1158 let s = arg_str(args);
1159 let attr = args.get(1).map(|v| v.to_string()).unwrap_or_default();
1160 let re = regex::Regex::new(&format!(r#"\b{}\s*=\s*"([^"]*)""#, regex::escape(&attr))).unwrap();
1161 if let Some(c) = re.captures(&s) {
1162 return StrykeValue::string(c[1].to_string());
1163 }
1164 StrykeValue::UNDEF
1165}
1166
1167pub fn xpath_text(args: &[StrykeValue]) -> StrykeValue {
1168 xml_text(args)
1169}
1170
1171pub fn xml_to_json(args: &[StrykeValue]) -> StrykeValue {
1172 let attrs = xml_attrs(args);
1174 if let Some(h) = attrs.as_hash_ref() {
1175 let g = h.read();
1176 let s: Vec<String> = g
1177 .iter()
1178 .map(|(k, v)| format!("\"{}\":\"{}\"", k, v.to_string().replace('"', "\\\"")))
1179 .collect();
1180 return StrykeValue::string(format!("{{{}}}", s.join(",")));
1181 }
1182 StrykeValue::string("{}".to_string())
1183}
1184
1185pub fn json_to_xml(args: &[StrykeValue]) -> StrykeValue {
1186 let s = arg_str(args);
1187 let Some(v) = parse_json(&s) else {
1188 return StrykeValue::UNDEF;
1189 };
1190 fn render(v: &serde_json::Value, tag: &str) -> String {
1191 match v {
1192 serde_json::Value::Object(m) => {
1193 let inner: String = m.iter().map(|(k, v)| render(v, k)).collect();
1194 format!("<{}>{}</{}>", tag, inner, tag)
1195 }
1196 serde_json::Value::Array(a) => a.iter().map(|v| render(v, tag)).collect(),
1197 _ => format!("<{}>{}</{}>", tag, v.to_string().trim_matches('"'), tag),
1198 }
1199 }
1200 StrykeValue::string(render(&v, "root"))
1201}
1202
1203pub fn xml_canonicalize(args: &[StrykeValue]) -> StrykeValue {
1204 xml_minify(args)
1205}
1206
1207pub fn css_parse(args: &[StrykeValue]) -> StrykeValue {
1212 StrykeValue::string(arg_str(args))
1213}
1214
1215pub fn css_minify(args: &[StrykeValue]) -> StrykeValue {
1216 let s = arg_str(args);
1217 let re = regex::Regex::new(r"/\*.*?\*/").unwrap();
1218 let s = re.replace_all(&s, "").to_string();
1219 let s = s.split_whitespace().collect::<Vec<_>>().join(" ");
1220 let s = s
1221 .replace(" {", "{")
1222 .replace("{ ", "{")
1223 .replace(" }", "}")
1224 .replace("; ", ";")
1225 .replace(": ", ":");
1226 StrykeValue::string(s)
1227}
1228
1229pub fn css_pretty(args: &[StrykeValue]) -> StrykeValue {
1230 let s = arg_str(args);
1231 let s = s
1232 .replace("{", " {\n ")
1233 .replace("}", "\n}\n")
1234 .replace(";", ";\n ");
1235 StrykeValue::string(s)
1236}
1237
1238pub fn css_selector_parse(args: &[StrykeValue]) -> StrykeValue {
1239 let s = arg_str(args);
1240 let parts: Vec<StrykeValue> = s
1241 .split(',')
1242 .map(|p| StrykeValue::string(p.trim().to_string()))
1243 .collect();
1244 arr(parts)
1245}
1246
1247pub fn css_rule_extract(args: &[StrykeValue]) -> StrykeValue {
1248 let s = arg_str(args);
1249 let re = regex::Regex::new(r"([^{}]+)\{([^}]*)\}").unwrap();
1250 let mut rules: Vec<StrykeValue> = Vec::new();
1251 for cap in re.captures_iter(&s) {
1252 rules.push(arr(vec![
1253 StrykeValue::string(cap[1].trim().to_string()),
1254 StrykeValue::string(cap[2].trim().to_string()),
1255 ]));
1256 }
1257 arr(rules)
1258}
1259
1260pub fn css_specificity(args: &[StrykeValue]) -> StrykeValue {
1261 let s = arg_str(args);
1262 let mut id = 0i64;
1263 let mut class = 0i64;
1264 let mut tag = 0i64;
1265 for part in s.split(|c: char| c.is_whitespace() || c == '>' || c == '+' || c == '~') {
1266 let p = part.trim();
1267 if p.is_empty() {
1268 continue;
1269 }
1270 id += p.matches('#').count() as i64;
1272 class += (p.matches('.').count() + p.matches(':').count() - p.matches("::").count()) as i64;
1273 if p.chars().next().map(|c| c.is_alphabetic()).unwrap_or(false) {
1274 tag += 1;
1275 }
1276 tag += p.matches("::").count() as i64;
1277 }
1278 arr(vec![
1279 StrykeValue::integer(id),
1280 StrykeValue::integer(class),
1281 StrykeValue::integer(tag),
1282 ])
1283}
1284
1285pub fn css_var_resolve(args: &[StrykeValue]) -> StrykeValue {
1286 let s = arg_str(args);
1287 let var_re = regex::Regex::new(r"--([\w-]+)\s*:\s*([^;}]+)").unwrap();
1288 use indexmap::IndexMap;
1289 let mut vars: IndexMap<String, String> = IndexMap::new();
1290 for cap in var_re.captures_iter(&s) {
1291 vars.insert(cap[1].to_string(), cap[2].trim().to_string());
1292 }
1293 let use_re = regex::Regex::new(r"var\(\s*--([\w-]+)(?:\s*,\s*([^)]*))?\)").unwrap();
1294 let out = use_re.replace_all(&s, |cap: ®ex::Captures| {
1295 vars.get(&cap[1])
1296 .cloned()
1297 .or_else(|| cap.get(2).map(|m| m.as_str().to_string()))
1298 .unwrap_or_default()
1299 });
1300 StrykeValue::string(out.to_string())
1301}
1302
1303pub fn css_property_set(args: &[StrykeValue]) -> StrykeValue {
1304 let s = arg_str(args);
1305 let prop = args.get(1).map(|v| v.to_string()).unwrap_or_default();
1306 let value = args.get(2).map(|v| v.to_string()).unwrap_or_default();
1307 let re = regex::Regex::new(&format!(r"{}\s*:\s*[^;}}]+", regex::escape(&prop))).unwrap();
1308 let new = format!("{}: {}", prop, value);
1309 if re.is_match(&s) {
1310 StrykeValue::string(re.replace(&s, new.as_str()).to_string())
1311 } else {
1312 StrykeValue::string(format!("{}; {}", s, new))
1313 }
1314}
1315
1316pub fn css_property_get(args: &[StrykeValue]) -> StrykeValue {
1317 let s = arg_str(args);
1318 let prop = args.get(1).map(|v| v.to_string()).unwrap_or_default();
1319 let re = regex::Regex::new(&format!(r"{}\s*:\s*([^;}}]+)", regex::escape(&prop))).unwrap();
1320 if let Some(c) = re.captures(&s) {
1321 return StrykeValue::string(c[1].trim().to_string());
1322 }
1323 StrykeValue::UNDEF
1324}
1325
1326pub fn css_url_extract(args: &[StrykeValue]) -> StrykeValue {
1327 let s = arg_str(args);
1328 let re = regex::Regex::new(r#"url\(\s*['"]?([^'")]+)['"]?\s*\)"#).unwrap();
1329 let urls: Vec<StrykeValue> = re
1330 .captures_iter(&s)
1331 .map(|c| StrykeValue::string(c[1].to_string()))
1332 .collect();
1333 arr(urls)
1334}
1335
1336pub fn css_import_extract(args: &[StrykeValue]) -> StrykeValue {
1337 let s = arg_str(args);
1338 let re = regex::Regex::new(r#"@import\s+(?:url\()?['"]([^'"]+)['"]"#).unwrap();
1339 let urls: Vec<StrykeValue> = re
1340 .captures_iter(&s)
1341 .map(|c| StrykeValue::string(c[1].to_string()))
1342 .collect();
1343 arr(urls)
1344}
1345
1346pub fn css_font_extract(args: &[StrykeValue]) -> StrykeValue {
1347 let s = arg_str(args);
1348 let re = regex::Regex::new(r"font-family\s*:\s*([^;}]+)").unwrap();
1349 let fonts: Vec<StrykeValue> = re
1350 .captures_iter(&s)
1351 .map(|c| StrykeValue::string(c[1].trim().to_string()))
1352 .collect();
1353 arr(fonts)
1354}
1355
1356pub fn selector_to_xpath(args: &[StrykeValue]) -> StrykeValue {
1357 let s = arg_str(args);
1358 let s = regex::Regex::new(r"^(\w+)\.([\w-]+)")
1360 .unwrap()
1361 .replace(&s, "//$1[@class='$2']");
1362 let s = regex::Regex::new(r"^#([\w-]+)")
1363 .unwrap()
1364 .replace(&s, "//*[@id='$1']");
1365 let s = regex::Regex::new(r"^(\w+)$").unwrap().replace(&s, "//$1");
1366 StrykeValue::string(s.to_string())
1367}
1368
1369pub fn xpath_to_selector(args: &[StrykeValue]) -> StrykeValue {
1370 let s = arg_str(args);
1371 let s = regex::Regex::new(r#"//(\w+)\[@class='([\w-]+)'\]"#)
1372 .unwrap()
1373 .replace(&s, "$1.$2");
1374 let s = regex::Regex::new(r#"//\*\[@id='([\w-]+)'\]"#)
1375 .unwrap()
1376 .replace(&s, "#$1");
1377 let s = regex::Regex::new(r"^//(\w+)$").unwrap().replace(&s, "$1");
1378 StrykeValue::string(s.to_string())
1379}