1#![allow(
66 missing_docs,
67 non_snake_case,
68 non_camel_case_types,
69 non_upper_case_globals
70)]
71
72use core::ffi::c_void;
73use core::ptr;
74use std::os::raw::{c_char, c_int};
75
76use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl};
77use crate::abi::types::xmlChar;
78
79#[allow(dead_code)]
85const MAX_NFA_STATES: usize = 1024;
86
87#[allow(dead_code)]
89const MAX_PARSE_DEPTH: usize = 256;
90
91const REGEXP_MATCH: c_int = 1;
93
94const REGEXP_NOMATCH: c_int = 0;
96
97const REGEXP_ERROR: c_int = -1;
99
100#[derive(Debug, Clone, Copy, PartialEq)]
106enum AnchorType {
107 Start,
109 End,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq)]
115enum PredefinedClass {
116 Digit,
118 NotDigit,
120 Space,
122 NotSpace,
124 Word,
126 NotWord,
128}
129
130#[derive(Debug, Clone, PartialEq)]
132enum Transition {
133 Char(u8),
135 Range(u8, u8),
137 #[allow(dead_code)]
139 Set(Vec<u8>),
140 NotRange(u8, u8),
142 NotSet(Vec<u8>),
144 Wildcard,
146 Predefined(PredefinedClass),
148 Epsilon,
150 Anchor(AnchorType),
152}
153
154#[derive(Debug, Clone)]
160struct NfaState {
161 transitions: Vec<(Transition, usize)>,
163 is_accept: bool,
165}
166
167impl NfaState {
168 const fn new() -> Self {
169 NfaState {
170 transitions: Vec::new(),
171 is_accept: false,
172 }
173 }
174}
175
176#[derive(Debug, Clone)]
178struct Nfa {
179 states: Vec<NfaState>,
181 start: usize,
183}
184
185impl Nfa {
186 fn new() -> Self {
187 let start = 0;
188 Nfa {
189 states: vec![NfaState::new()],
190 start,
191 }
192 }
193
194 fn add_state(&mut self) -> usize {
196 let index = self.states.len();
197 self.states.push(NfaState::new());
198 index
199 }
200
201 fn add_transition(&mut self, from: usize, to: usize, trans: Transition) {
203 if from < self.states.len() && to < self.states.len() {
204 self.states[from].transitions.push((trans, to));
205 }
206 }
207
208 fn set_accept(&mut self, state: usize) {
210 if state < self.states.len() {
211 self.states[state].is_accept = true;
212 }
213 }
214}
215
216struct NfaFragment {
221 nfa: Nfa,
222 start: usize,
224 out: Vec<usize>,
229}
230
231impl NfaFragment {
232 const fn new(nfa: Nfa, start: usize, out: Vec<usize>) -> Self {
233 NfaFragment { nfa, start, out }
234 }
235}
236
237impl Clone for NfaFragment {
238 fn clone(&self) -> Self {
239 NfaFragment {
240 nfa: self.nfa.clone(),
241 start: self.start,
242 out: self.out.clone(),
243 }
244 }
245}
246
247fn nfa_char(c: u8) -> NfaFragment {
253 let mut nfa = Nfa::new();
254 let start = nfa.start;
255 let accept = nfa.add_state();
256 nfa.set_accept(accept);
257 nfa.add_transition(start, accept, Transition::Char(c));
258 NfaFragment::new(nfa, start, vec![accept])
259}
260
261fn nfa_epsilon() -> NfaFragment {
263 let mut nfa = Nfa::new();
264 let start = nfa.start;
265 nfa.set_accept(start);
266 NfaFragment::new(nfa, start, vec![start])
267}
268
269fn nfa_range(lo: u8, hi: u8) -> NfaFragment {
271 let mut nfa = Nfa::new();
272 let start = nfa.start;
273 let accept = nfa.add_state();
274 nfa.set_accept(accept);
275 nfa.add_transition(start, accept, Transition::Range(lo, hi));
276 NfaFragment::new(nfa, start, vec![accept])
277}
278
279#[allow(dead_code)]
281fn nfa_set(chars: Vec<u8>) -> NfaFragment {
282 let mut nfa = Nfa::new();
283 let start = nfa.start;
284 let accept = nfa.add_state();
285 nfa.set_accept(accept);
286 nfa.add_transition(start, accept, Transition::Set(chars));
287 NfaFragment::new(nfa, start, vec![accept])
288}
289
290fn nfa_not_range(lo: u8, hi: u8) -> NfaFragment {
292 let mut nfa = Nfa::new();
293 let start = nfa.start;
294 let accept = nfa.add_state();
295 nfa.set_accept(accept);
296 nfa.add_transition(start, accept, Transition::NotRange(lo, hi));
297 NfaFragment::new(nfa, start, vec![accept])
298}
299
300#[allow(dead_code)]
302fn nfa_not_set(chars: Vec<u8>) -> NfaFragment {
303 let mut nfa = Nfa::new();
304 let start = nfa.start;
305 let accept = nfa.add_state();
306 nfa.set_accept(accept);
307 nfa.add_transition(start, accept, Transition::NotSet(chars));
308 NfaFragment::new(nfa, start, vec![accept])
309}
310
311fn nfa_wildcard() -> NfaFragment {
313 let mut nfa = Nfa::new();
314 let start = nfa.start;
315 let accept = nfa.add_state();
316 nfa.set_accept(accept);
317 nfa.add_transition(start, accept, Transition::Wildcard);
318 NfaFragment::new(nfa, start, vec![accept])
319}
320
321fn nfa_predefined(class: PredefinedClass) -> NfaFragment {
323 let mut nfa = Nfa::new();
324 let start = nfa.start;
325 let accept = nfa.add_state();
326 nfa.set_accept(accept);
327 nfa.add_transition(start, accept, Transition::Predefined(class));
328 NfaFragment::new(nfa, start, vec![accept])
329}
330
331fn nfa_start_anchor() -> NfaFragment {
333 let mut nfa = Nfa::new();
334 let start = nfa.start;
335 let accept = nfa.add_state();
336 nfa.set_accept(accept);
337 nfa.add_transition(start, accept, Transition::Anchor(AnchorType::Start));
338 NfaFragment::new(nfa, start, vec![accept])
339}
340
341fn nfa_end_anchor() -> NfaFragment {
343 let mut nfa = Nfa::new();
344 let start = nfa.start;
345 let accept = nfa.add_state();
346 nfa.set_accept(accept);
347 nfa.add_transition(start, accept, Transition::Anchor(AnchorType::End));
348 NfaFragment::new(nfa, start, vec![accept])
349}
350
351fn concat(a: NfaFragment, b: NfaFragment) -> NfaFragment {
356 let a_out = a.out.clone();
357 let a_start = a.start;
358 let a_size = a.nfa.states.len();
359
360 let mut nfa = a.nfa;
361 let b_start = b.nfa.start + a_size;
362
363 for mut state in b.nfa.states {
365 for (_, target) in &mut state.transitions {
366 *target += a_size;
367 }
368 nfa.states.push(state);
369 }
370
371 for &out_state in &a_out {
373 nfa.add_transition(out_state, b_start, Transition::Epsilon);
374 }
375
376 let b_out: Vec<usize> = b.out.iter().map(|&s| s + a_size).collect();
378
379 NfaFragment::new(nfa, a_start, b_out)
380}
381
382fn union(a: NfaFragment, b: NfaFragment) -> NfaFragment {
386 let mut nfa = Nfa::new();
387 let new_start = nfa.start;
388
389 let a_start = nfa.states.len();
391 let _a_size = a.nfa.states.len();
392 for mut state in a.nfa.states {
393 for (_, target) in &mut state.transitions {
394 *target += a_start;
395 }
396 nfa.states.push(state);
397 }
398
399 let b_start = nfa.states.len();
401 for mut state in b.nfa.states {
402 for (_, target) in &mut state.transitions {
403 *target += b_start;
404 }
405 nfa.states.push(state);
406 }
407
408 nfa.add_transition(new_start, a_start, Transition::Epsilon);
410 nfa.add_transition(new_start, b_start, Transition::Epsilon);
411
412 let mut out = Vec::new();
414 for &s in &a.out {
415 out.push(s + a_start);
416 }
417 for &s in &b.out {
418 out.push(s + b_start);
419 }
420
421 NfaFragment::new(nfa, new_start, out)
422}
423
424fn kleene_star(frag: NfaFragment) -> NfaFragment {
426 let mut nfa = Nfa::new();
427 let new_start = nfa.start;
428 let new_accept = nfa.add_state();
429
430 let frag_start = nfa.states.len();
432 let _frag_size = frag.nfa.states.len();
433 for mut state in frag.nfa.states {
434 for (_, target) in &mut state.transitions {
435 *target += frag_start;
436 }
437 nfa.states.push(state);
438 }
439
440 nfa.add_transition(new_start, new_accept, Transition::Epsilon);
442 nfa.add_transition(new_start, frag_start, Transition::Epsilon);
443
444 for &s in &frag.out {
446 nfa.add_transition(s + frag_start, frag_start, Transition::Epsilon);
447 nfa.add_transition(s + frag_start, new_accept, Transition::Epsilon);
448 }
449
450 nfa.set_accept(new_accept);
451 NfaFragment::new(nfa, new_start, vec![new_accept])
452}
453
454fn plus(frag: NfaFragment) -> NfaFragment {
456 let out_orig = frag.out.clone();
457 let _frag_start_orig = frag.start;
458
459 let mut nfa = Nfa::new();
460 let new_start = nfa.start;
461
462 let frag_start = nfa.states.len();
464 for mut state in frag.nfa.states {
465 for (_, target) in &mut state.transitions {
466 *target += frag_start;
467 }
468 nfa.states.push(state);
469 }
470
471 nfa.add_transition(new_start, frag_start, Transition::Epsilon);
473
474 for &s in &out_orig {
476 let adjusted = s + frag_start;
477 nfa.add_transition(adjusted, frag_start, Transition::Epsilon);
478 }
479
480 let out: Vec<usize> = out_orig.iter().map(|&s| s + frag_start).collect();
482
483 NfaFragment::new(nfa, new_start, out)
484}
485
486fn optional(frag: NfaFragment) -> NfaFragment {
488 let mut nfa = Nfa::new();
489 let new_start = nfa.start;
490 let new_accept = nfa.add_state();
491
492 let frag_start = nfa.states.len();
493 for mut state in frag.nfa.states {
494 for (_, target) in &mut state.transitions {
495 *target += frag_start;
496 }
497 nfa.states.push(state);
498 }
499
500 nfa.add_transition(new_start, new_accept, Transition::Epsilon);
502 nfa.add_transition(new_start, frag_start, Transition::Epsilon);
503
504 for &s in &frag.out {
506 nfa.add_transition(s + frag_start, new_accept, Transition::Epsilon);
507 }
508
509 nfa.set_accept(new_accept);
510 NfaFragment::new(nfa, new_start, vec![new_accept])
511}
512
513#[derive(Debug, Clone, PartialEq)]
519#[allow(dead_code)]
520enum RegexToken {
521 Char(u8),
523 Dot,
525 AnchorStart,
527 AnchorEnd,
529 LParen,
531 RParen,
533 Pipe,
535 Star,
537 Plus,
539 Question,
541 LBrace,
543 RBrace,
545 Comma,
547 #[allow(dead_code)]
549 Number(u32),
550 Escape(u8),
552 ClassStart,
554 ClassEnd,
556 ClassNegate,
558 ClassRange,
560}
561
562struct RegexParser<'a> {
564 input: &'a [u8],
566 pos: usize,
568 lookahead: Option<RegexToken>,
570}
571
572impl<'a> RegexParser<'a> {
573 const fn new(input: &'a [u8]) -> Self {
574 RegexParser {
575 input,
576 pos: 0,
577 lookahead: None,
578 }
579 }
580
581 fn peek(&self) -> Option<u8> {
583 self.input.get(self.pos).copied()
584 }
585
586 fn advance(&mut self) -> Option<u8> {
588 let ch = self.input.get(self.pos).copied();
589 if ch.is_some() {
590 self.pos += 1;
591 }
592 ch
593 }
594
595 #[allow(dead_code)]
597 fn skip_whitespace(&mut self) {
598 while let Some(ch) = self.peek() {
599 if ch == b' ' || ch == b'\t' || ch == b'\n' || ch == b'\r' {
600 self.advance();
601 } else {
602 break;
603 }
604 }
605 }
606
607 fn scan_token(&mut self) -> Option<RegexToken> {
609 let ch = self.advance()?;
610 match ch {
611 b'.' => Some(RegexToken::Dot),
612 b'^' => Some(RegexToken::AnchorStart),
613 b'$' => Some(RegexToken::AnchorEnd),
614 b'(' => Some(RegexToken::LParen),
615 b')' => Some(RegexToken::RParen),
616 b'|' => Some(RegexToken::Pipe),
617 b'*' => Some(RegexToken::Star),
618 b'+' => Some(RegexToken::Plus),
619 b'?' => Some(RegexToken::Question),
620 b'{' => Some(RegexToken::LBrace),
621 b'}' => Some(RegexToken::RBrace),
622 b',' => Some(RegexToken::Comma),
623 b'[' => Some(RegexToken::ClassStart),
624 b']' => Some(RegexToken::ClassEnd),
625 b'\\' => {
626 let next = self.advance()?;
628 Some(RegexToken::Escape(next))
629 }
630 _ => Some(RegexToken::Char(ch)),
631 }
632 }
633
634 fn next_token(&mut self) -> Option<RegexToken> {
636 if let Some(token) = self.lookahead.take() {
637 Some(token)
638 } else {
639 self.scan_token()
640 }
641 }
642
643 const fn unscan(&mut self, token: RegexToken) {
645 self.lookahead = Some(token);
646 }
647
648 fn parse(&mut self) -> Result<NfaFragment, String> {
652 let frag = self.parse_alternation()?;
653 Ok(frag)
654 }
655
656 fn parse_alternation(&mut self) -> Result<NfaFragment, String> {
658 let mut frag = self.parse_sequence()?;
659
660 loop {
661 match self.next_token() {
662 Some(RegexToken::Pipe) => {
663 let rhs = self.parse_sequence()?;
664 frag = union(frag, rhs);
665 }
666 Some(other) => {
667 self.unscan(other);
668 break;
669 }
670 None => break,
671 }
672 }
673
674 Ok(frag)
675 }
676
677 fn parse_sequence(&mut self) -> Result<NfaFragment, String> {
679 let mut fragments: Vec<NfaFragment> = Vec::new();
680
681 loop {
682 match self.peek() {
683 None => break,
684 Some(b'|') | Some(b')') => break,
685 _ => {}
686 }
687
688 let atom = self.parse_atom()?;
689 fragments.push(atom);
690 }
691
692 if fragments.is_empty() {
693 return Ok(nfa_epsilon());
694 }
695
696 let mut result = fragments.remove(0);
697 for frag in fragments {
698 result = concat(result, frag);
699 }
700
701 Ok(result)
702 }
703
704 fn parse_atom(&mut self) -> Result<NfaFragment, String> {
706 let token = self
707 .next_token()
708 .ok_or_else(|| "Unexpected end of pattern".to_string())?;
709
710 let base = match token {
711 RegexToken::Char(c) => nfa_char(c),
712 RegexToken::Dot => nfa_wildcard(),
713 RegexToken::AnchorStart => nfa_start_anchor(),
714 RegexToken::AnchorEnd => nfa_end_anchor(),
715 RegexToken::LParen => {
716 let inner = self.parse_alternation()?;
717 match self.next_token() {
718 Some(RegexToken::RParen) => inner,
719 Some(t) => return Err(format!("Expected ')', got {:?}", t)),
720 None => return Err("Unterminated group".to_string()),
721 }
722 }
723 RegexToken::Escape(c) => {
724 match c {
725 b'd' => nfa_predefined(PredefinedClass::Digit),
726 b'D' => nfa_predefined(PredefinedClass::NotDigit),
727 b's' => nfa_predefined(PredefinedClass::Space),
728 b'S' => nfa_predefined(PredefinedClass::NotSpace),
729 b'w' => nfa_predefined(PredefinedClass::Word),
730 b'W' => nfa_predefined(PredefinedClass::NotWord),
731 b'n' => nfa_char(b'\n'),
732 b'r' => nfa_char(b'\r'),
733 b't' => nfa_char(b'\t'),
734 b'\\' => nfa_char(b'\\'),
735 b'.' => nfa_char(b'.'),
736 b'^' => nfa_char(b'^'),
737 b'$' => nfa_char(b'$'),
738 b'|' => nfa_char(b'|'),
739 b'*' => nfa_char(b'*'),
740 b'+' => nfa_char(b'+'),
741 b'?' => nfa_char(b'?'),
742 b'(' => nfa_char(b'('),
743 b')' => nfa_char(b')'),
744 b'[' => nfa_char(b'['),
745 b']' => nfa_char(b']'),
746 b'{' => nfa_char(b'{'),
747 b'}' => nfa_char(b'}'),
748 b'-' => nfa_char(b'-'),
749 b'0'..=b'9' => {
750 nfa_char(c)
752 }
753 _ => nfa_char(c),
754 }
755 }
756 RegexToken::ClassStart => self.parse_char_class()?,
757 _ => return Err(format!("Unexpected token: {:?}", token)),
758 };
759
760 self.parse_quantifier(base)
762 }
763
764 fn parse_quantifier(&mut self, frag: NfaFragment) -> Result<NfaFragment, String> {
766 match self.peek() {
767 Some(b'*') => {
768 self.advance();
769 Ok(kleene_star(frag))
770 }
771 Some(b'+') => {
772 self.advance();
773 Ok(plus(frag))
774 }
775 Some(b'?') => {
776 self.advance();
777 Ok(optional(frag))
778 }
779 Some(b'{') => {
780 self.advance();
781 self.parse_brace_quantifier(frag)
782 }
783 _ => Ok(frag),
784 }
785 }
786
787 fn parse_brace_quantifier(&mut self, frag: NfaFragment) -> Result<NfaFragment, String> {
789 let mut min: u32 = 0;
791 while let Some(b'0'..=b'9') = self.peek() {
792 let d = self.advance().unwrap() - b'0';
793 min = min * 10 + d as u32;
794 }
795
796 let mut max: Option<u32> = None;
797
798 match self.peek() {
799 Some(b',') => {
800 self.advance();
801 let mut max_val: u32 = 0;
803 let mut has_max = false;
804 while let Some(b'0'..=b'9') = self.peek() {
805 let d = self.advance().unwrap() - b'0';
806 max_val = max_val * 10 + d as u32;
807 has_max = true;
808 }
809 if has_max {
810 max = Some(max_val);
811 }
812 }
813 Some(b'}') => {
814 max = Some(min);
815 }
816 _ => return Err("Expected '}' in quantifier".to_string()),
817 }
818
819 match self.peek() {
821 Some(b'}') => {
822 self.advance();
823 }
824 _ => return Err("Expected '}' in quantifier".to_string()),
825 }
826
827 if min == 0 && max.is_none() {
832 Ok(kleene_star(frag))
834 } else if min == 0 && max == Some(0) {
835 Ok(nfa_epsilon())
837 } else if min == 1 && max.is_none() {
838 Ok(plus(frag))
840 } else if min == 0 && max == Some(1) {
841 Ok(optional(frag))
843 } else {
844 let mut result = nfa_epsilon();
847 for _ in 0..min {
848 result = concat(result, frag.clone());
849 }
850 if let Some(max_val) = max {
851 for _ in min..max_val {
852 result = concat(result, optional(frag.clone()));
853 }
854 } else {
855 result = concat(result, kleene_star(frag.clone()));
857 }
858 Ok(result)
859 }
860 }
861
862 fn parse_char_class(&mut self) -> Result<NfaFragment, String> {
864 let mut chars: Vec<u8> = Vec::new();
865 let mut ranges: Vec<(u8, u8)> = Vec::new();
866 let mut negated = false;
867
868 if let Some(b'^') = self.peek() {
870 negated = true;
871 self.advance();
872 }
873
874 let mut prev: Option<u8> = None;
876
877 loop {
878 match self.peek() {
879 None => return Err("Unterminated character class".to_string()),
880 Some(b']') => {
881 if prev.is_some() {
882 if let Some(c) = prev {
884 chars.push(c);
885 }
886 prev = None;
887 }
888 self.advance();
889 break;
890 }
891 Some(b'-') if prev.is_some() => {
892 self.advance();
894 let lo = prev.take().unwrap();
895 match self.peek() {
896 Some(b']') => {
897 chars.push(lo);
899 chars.push(b'-');
900 prev = None;
901 }
902 Some(ch) => {
903 self.advance();
904 if lo <= ch {
905 ranges.push((lo, ch));
906 }
907 prev = None;
908 }
909 None => {
910 chars.push(lo);
911 chars.push(b'-');
912 prev = None;
913 }
914 }
915 }
916 Some(b'\\') => {
917 self.advance();
918 if let Some(ch) = self.advance() {
919 match ch {
923 b'd' | b'D' | b's' | b'S' | b'w' | b'W' => {
924 if let Some(p) = prev.take() {
926 chars.push(p);
927 }
928 let class = match ch {
932 b'd' => PredefinedClass::Digit,
933 b'D' => PredefinedClass::NotDigit,
934 b's' => PredefinedClass::Space,
935 b'S' => PredefinedClass::NotSpace,
936 b'w' => PredefinedClass::Word,
937 b'W' => PredefinedClass::NotWord,
938 _ => unreachable!(),
939 };
940 match class {
945 PredefinedClass::Digit => {
946 ranges.push((b'0', b'9'));
947 }
948 PredefinedClass::NotDigit => {
949 ranges.push((0x00, b'/' - 1));
950 ranges.push((b':', 0xFF));
951 }
952 PredefinedClass::Space => {
953 chars.push(b' ');
954 chars.push(b'\t');
955 chars.push(b'\n');
956 chars.push(b'\r');
957 }
958 PredefinedClass::NotSpace => {
959 ranges.push((0x00, b' ' - 1));
961 ranges.push((b'!' + 1, b'\t' - 1));
962 ranges.push((b'\t' + 1, b'\n' - 1));
963 ranges.push((b'\n' + 1, b'\r' - 1));
964 ranges.push((b'\r' + 1, 0xFF));
965 }
966 PredefinedClass::Word => {
967 ranges.push((b'0', b'9'));
968 ranges.push((b'A', b'Z'));
969 ranges.push((b'a', b'z'));
970 chars.push(b'_');
971 }
972 PredefinedClass::NotWord => {
973 ranges.push((0x00, b'0' - 1));
974 ranges.push((b'9' + 1, b'A' - 1));
975 ranges.push((b'Z' + 1, b'_' - 1));
976 ranges.push((b'_' + 1, b'a' - 1));
977 ranges.push((b'z' + 1, 0xFF));
978 }
979 }
980 }
981 _ => {
982 let c = match ch {
983 b'n' => b'\n',
984 b'r' => b'\r',
985 b't' => b'\t',
986 b'\\' => b'\\',
987 b'0'..=b'9' => ch, _ => ch,
989 };
990 if let Some(p) = prev.take() {
991 chars.push(p);
992 }
993 prev = Some(c);
994 }
995 }
996 }
997 }
998 Some(ch) => {
999 self.advance();
1000 if let Some(p) = prev.take() {
1001 chars.push(p);
1002 }
1003 prev = Some(ch);
1004 }
1005 }
1006 }
1007
1008 if let Some(c) = prev {
1010 chars.push(c);
1011 }
1012
1013 let mut combined_ranges: Vec<(u8, u8)> = ranges;
1015 for &c in &chars {
1016 combined_ranges.push((c, c));
1017 }
1018
1019 if combined_ranges.is_empty() {
1020 return if negated {
1021 Ok(nfa_epsilon())
1023 } else {
1024 Ok(nfa_epsilon())
1026 };
1027 }
1028
1029 combined_ranges.sort_by_key(|a| a.0);
1031 let mut merged: Vec<(u8, u8)> = Vec::new();
1032 for (lo, hi) in combined_ranges {
1033 if let Some(last) = merged.last_mut() {
1034 if lo <= last.1 + 1 {
1035 last.1 = last.1.max(hi);
1036 continue;
1037 }
1038 }
1039 merged.push((lo, hi));
1040 }
1041
1042 if negated {
1044 if merged.len() == 1 && merged[0].0 == 0 && merged[0].1 == 255 {
1045 Ok(nfa_epsilon())
1047 } else {
1048 Ok(nfa_not_range(0, 255))
1049 }
1050 } else if merged.len() == 1 {
1051 let (lo, hi) = merged[0];
1052 if lo == hi {
1053 Ok(nfa_char(lo))
1054 } else {
1055 Ok(nfa_range(lo, hi))
1056 }
1057 } else {
1058 let mut result = nfa_range(merged[0].0, merged[0].1);
1060 for &(lo, hi) in &merged[1..] {
1061 result = union(result, nfa_range(lo, hi));
1062 }
1063 Ok(result)
1064 }
1065 }
1066}
1067
1068fn matches_transition(c: u8, trans: &Transition) -> bool {
1074 match trans {
1075 Transition::Char(ch) => c == *ch,
1076 Transition::Range(lo, hi) => c >= *lo && c <= *hi,
1077 Transition::Set(chars) => chars.contains(&c),
1078 Transition::NotRange(lo, hi) => c < *lo || c > *hi,
1079 Transition::NotSet(chars) => !chars.contains(&c),
1080 Transition::Wildcard => true,
1081 Transition::Predefined(class) => matches_predefined(c, *class),
1082 Transition::Epsilon => false, Transition::Anchor(_) => false, }
1085}
1086
1087const fn matches_predefined(c: u8, class: PredefinedClass) -> bool {
1089 match class {
1090 PredefinedClass::Digit => c >= b'0' && c <= b'9',
1091 PredefinedClass::NotDigit => c < b'0' || c > b'9',
1092 PredefinedClass::Space => {
1093 c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' || c == 0x0b || c == 0x0c
1094 }
1095 PredefinedClass::NotSpace => {
1096 !(c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' || c == 0x0b || c == 0x0c)
1097 }
1098 PredefinedClass::Word => {
1099 (c >= b'0' && c <= b'9')
1100 || (c >= b'A' && c <= b'Z')
1101 || (c >= b'a' && c <= b'z')
1102 || c == b'_'
1103 }
1104 PredefinedClass::NotWord => {
1105 !((c >= b'0' && c <= b'9')
1106 || (c >= b'A' && c <= b'Z')
1107 || (c >= b'a' && c <= b'z')
1108 || c == b'_')
1109 }
1110 }
1111}
1112
1113fn epsilon_closure(nfa: &Nfa, states: &[usize]) -> Vec<usize> {
1115 let mut visited = vec![false; nfa.states.len()];
1116 let mut result = Vec::new();
1117 let mut stack: Vec<usize> = states.to_vec();
1118
1119 while let Some(s) = stack.pop() {
1120 if s >= nfa.states.len() || visited[s] {
1121 continue;
1122 }
1123 visited[s] = true;
1124 result.push(s);
1125
1126 for (cond, target) in &nfa.states[s].transitions {
1127 if matches!(cond, Transition::Epsilon)
1128 && *target < nfa.states.len()
1129 && !visited[*target]
1130 {
1131 stack.push(*target);
1132 }
1133 }
1134 }
1135
1136 result
1137}
1138
1139fn move_on_char(nfa: &Nfa, states: &[usize], c: u8, is_start: bool, is_end: bool) -> Vec<usize> {
1144 let mut expanded = states.to_vec();
1147 let mut more = true;
1148 while more {
1149 more = false;
1150 let mut new_states = Vec::new();
1151 for &s in &expanded {
1152 if s >= nfa.states.len() {
1153 continue;
1154 }
1155 for (cond, target) in &nfa.states[s].transitions {
1156 if *target >= nfa.states.len() {
1157 continue;
1158 }
1159 let should_follow = match cond {
1160 Transition::Anchor(AnchorType::Start) => {
1161 is_start && !expanded.contains(target) && !new_states.contains(target)
1162 }
1163 Transition::Anchor(AnchorType::End) => {
1164 is_end && !expanded.contains(target) && !new_states.contains(target)
1165 }
1166 _ => false,
1167 };
1168 if should_follow {
1169 new_states.push(*target);
1170 more = true;
1171 }
1172 }
1173 }
1174 expanded.extend(new_states);
1175 }
1176
1177 let mut eps_expanded = expanded.clone();
1181 let mut more_eps = true;
1182 while more_eps {
1183 more_eps = false;
1184 let mut new_eps = Vec::new();
1185 for &s in &eps_expanded {
1186 if s >= nfa.states.len() {
1187 continue;
1188 }
1189 for (cond, target) in &nfa.states[s].transitions {
1190 if let Transition::Epsilon = cond {
1191 if *target < nfa.states.len()
1192 && !eps_expanded.contains(target)
1193 && !new_eps.contains(target)
1194 {
1195 new_eps.push(*target);
1196 more_eps = true;
1197 }
1198 }
1199 }
1200 }
1201 eps_expanded.extend(new_eps);
1202 }
1203
1204 let mut next = Vec::new();
1206 for &s in &eps_expanded {
1207 if s >= nfa.states.len() {
1208 continue;
1209 }
1210 for (cond, target) in &nfa.states[s].transitions {
1211 if *target >= nfa.states.len() {
1212 continue;
1213 }
1214 match cond {
1215 Transition::Epsilon => continue,
1216 Transition::Anchor(_) => continue, _ => {
1218 if matches_transition(c, cond) && !next.contains(target) {
1219 next.push(*target);
1220 }
1221 }
1222 }
1223 }
1224 }
1225
1226 next
1227}
1228
1229fn has_accept_state(nfa: &Nfa, states: &[usize]) -> bool {
1231 states
1232 .iter()
1233 .any(|&s| s < nfa.states.len() && nfa.states[s].is_accept)
1234}
1235
1236fn nfa_exec(nfa: &Nfa, input: &[u8]) -> c_int {
1241 if nfa.states.is_empty() {
1242 return REGEXP_ERROR;
1243 }
1244
1245 let mut current = epsilon_closure(nfa, &[nfa.start]);
1247
1248 if input.is_empty() {
1251 let mut final_states = current.clone();
1252 for &s in ¤t {
1253 for (cond, target) in &nfa.states[s].transitions {
1254 if let Transition::Anchor(AnchorType::End) = cond {
1255 if *target < nfa.states.len() {
1256 let ec = epsilon_closure(nfa, &[*target]);
1257 final_states.extend(ec);
1258 }
1259 }
1260 }
1261 }
1262 final_states = epsilon_closure(nfa, &final_states);
1263 return if has_accept_state(nfa, &final_states) {
1264 REGEXP_MATCH
1265 } else {
1266 REGEXP_NOMATCH
1267 };
1268 }
1269
1270 for (i, &c) in input.iter().enumerate() {
1271 let is_start = i == 0;
1272 let next_states = move_on_char(nfa, ¤t, c, is_start, false);
1275 if next_states.is_empty() {
1276 return REGEXP_NOMATCH;
1277 }
1278
1279 current = epsilon_closure(nfa, &next_states);
1280 if current.is_empty() {
1281 return REGEXP_NOMATCH;
1282 }
1283 }
1284
1285 let mut final_states = current.clone();
1289 for &s in ¤t {
1290 for (cond, target) in &nfa.states[s].transitions {
1291 if let Transition::Anchor(AnchorType::End) = cond {
1292 if *target < nfa.states.len() {
1293 let ec = epsilon_closure(nfa, &[*target]);
1294 final_states.extend(ec);
1295 }
1296 }
1297 }
1298 }
1299 final_states = epsilon_closure(nfa, &final_states);
1300
1301 if has_accept_state(nfa, &final_states) {
1302 REGEXP_MATCH
1303 } else {
1304 REGEXP_NOMATCH
1305 }
1306}
1307
1308#[derive(Debug)]
1318#[repr(C)]
1319pub struct XmlRegexp {
1320 pattern: *mut xmlChar,
1322 nfa: Option<Box<Nfa>>,
1324 is_deterministic: c_int,
1326}
1327
1328fn compile_pattern(pattern: &[u8]) -> Option<Box<Nfa>> {
1330 if pattern.is_empty() {
1331 let mut nfa = Nfa::new();
1333 nfa.set_accept(nfa.start);
1334 return Some(Box::new(nfa));
1335 }
1336
1337 let mut parser = RegexParser::new(pattern);
1338 match parser.parse() {
1339 Ok(fragment) => {
1340 let mut nfa = fragment.nfa;
1346 for state in &mut nfa.states {
1347 state.is_accept = false;
1348 }
1349 for &s in &fragment.out {
1350 if s < nfa.states.len() {
1351 nfa.set_accept(s);
1352 }
1353 }
1354 Some(Box::new(nfa))
1355 }
1356 Err(_) => None,
1357 }
1358}
1359
1360fn is_deterministic(nfa: &Nfa) -> bool {
1362 for (state_idx, state) in nfa.states.iter().enumerate() {
1383 let epsilon_count = state
1385 .transitions
1386 .iter()
1387 .filter(|(cond, _)| matches!(cond, Transition::Epsilon))
1388 .count();
1389
1390 let consuming_count = state
1392 .transitions
1393 .iter()
1394 .filter(|(cond, _)| !matches!(cond, Transition::Epsilon | Transition::Anchor(_)))
1395 .count();
1396
1397 if epsilon_count > 1 {
1399 return false;
1400 }
1401
1402 if epsilon_count > 0 && consuming_count > 0 {
1404 return false;
1405 }
1406
1407 let closure = if epsilon_count == 0 {
1410 epsilon_closure(nfa, &[state_idx])
1411 } else {
1412 continue;
1414 };
1415
1416 let mut has_wildcard = false;
1417 let mut chars = Vec::new();
1418 let mut has_range = false;
1419 let mut has_not = false;
1420 let mut has_predefined = false;
1421
1422 for &s_idx in &closure {
1423 if s_idx >= nfa.states.len() {
1424 continue;
1425 }
1426 for (cond, _) in &nfa.states[s_idx].transitions {
1427 match cond {
1428 Transition::Epsilon | Transition::Anchor(_) => {}
1429 Transition::Wildcard => {
1430 if has_wildcard {
1431 return false;
1432 }
1433 has_wildcard = true;
1434 }
1435 Transition::Char(c) => {
1436 chars.push(*c);
1437 }
1438 Transition::Range(_, _) => {
1439 has_range = true;
1440 }
1441 Transition::Set(_) => {}
1442 Transition::NotRange(_, _) | Transition::NotSet(_) => {
1443 has_not = true;
1444 }
1445 Transition::Predefined(_) => {
1446 has_predefined = true;
1447 }
1448 }
1449 }
1450 }
1451
1452 if has_wildcard && (!chars.is_empty() || has_range || has_not || has_predefined) {
1454 return false;
1455 }
1456
1457 if has_range && (has_wildcard || has_not || has_predefined) {
1458 return false;
1459 }
1460
1461 if has_not && (has_wildcard || has_range || has_predefined) {
1462 return false;
1463 }
1464
1465 if has_predefined && (has_wildcard || has_range || has_not) {
1466 return false;
1467 }
1468
1469 chars.sort();
1471 let dedup_len = {
1472 chars.dedup();
1473 chars.len()
1474 };
1475 if dedup_len > 1 && (has_range || has_wildcard || has_not || has_predefined) {
1484 return false;
1485 }
1486 }
1487
1488 true
1489}
1490
1491const unsafe fn xml_strlen(s: *const xmlChar) -> usize {
1497 if s.is_null() {
1498 return 0;
1499 }
1500 let mut len: usize = 0;
1501 while *s.add(len) != 0 {
1502 len += 1;
1503 }
1504 len
1505}
1506
1507unsafe fn xml_strdup(s: *const xmlChar) -> *mut xmlChar {
1509 if s.is_null() {
1510 return ptr::null_mut();
1511 }
1512 let len = xml_strlen(s);
1513 let new_ptr = xmlMallocImpl((len + 1) * core::mem::size_of::<xmlChar>()) as *mut xmlChar;
1514 if new_ptr.is_null() {
1515 return ptr::null_mut();
1516 }
1517 for i in 0..=len {
1518 unsafe { *new_ptr.add(i) = *s.add(i) };
1519 }
1520 new_ptr
1521}
1522
1523#[no_mangle]
1537pub unsafe extern "C" fn xmlRegexpCompile(pattern: *const xmlChar) -> *mut XmlRegexp {
1538 if pattern.is_null() {
1539 return ptr::null_mut();
1540 }
1541
1542 let len = xml_strlen(pattern);
1543 let pattern_bytes = unsafe { core::slice::from_raw_parts(pattern, len) };
1544
1545 let nfa = compile_pattern(pattern_bytes);
1546 let pattern_copy = xml_strdup(pattern);
1547
1548 let compiled = xmlMallocImpl(core::mem::size_of::<XmlRegexp>()) as *mut XmlRegexp;
1549 if compiled.is_null() {
1550 if !pattern_copy.is_null() {
1551 xmlFreeImpl(pattern_copy as *mut c_void);
1552 }
1553 return ptr::null_mut();
1554 }
1555
1556 let det = nfa
1557 .as_ref()
1558 .map_or(0, |n| if is_deterministic(n) { 1 } else { 0 });
1559
1560 unsafe {
1561 (*compiled).pattern = pattern_copy;
1562 core::ptr::write(&mut (*compiled).nfa, nfa);
1566 (*compiled).is_deterministic = det;
1567 }
1568
1569 compiled
1570}
1571
1572#[no_mangle]
1587pub unsafe extern "C" fn xmlRegexpExec(compiled: *const XmlRegexp, value: *const xmlChar) -> c_int {
1588 if compiled.is_null() || value.is_null() {
1589 return REGEXP_ERROR;
1590 }
1591
1592 let regex = unsafe { &*compiled };
1593 let nfa = match ®ex.nfa {
1594 Some(nfa) => nfa,
1595 None => return REGEXP_ERROR,
1596 };
1597
1598 let len = xml_strlen(value);
1599 let input = unsafe { core::slice::from_raw_parts(value, len) };
1600
1601 nfa_exec(nfa, input)
1602}
1603
1604#[no_mangle]
1618pub const unsafe extern "C" fn xmlRegexpIsDeterministic(compiled: *const XmlRegexp) -> c_int {
1619 if compiled.is_null() {
1620 return 0;
1621 }
1622 unsafe { (*compiled).is_deterministic }
1623}
1624
1625#[no_mangle]
1639pub unsafe extern "C" fn xmlRegexpPrint(output: *mut c_void, compiled: *const XmlRegexp) {
1640 let mut msg = String::new();
1641 if compiled.is_null() {
1642 msg.push_str("(null regex)\n");
1643 } else {
1644 let regex = unsafe { &*compiled };
1645 let pattern_str = if regex.pattern.is_null() {
1646 "(null)".to_string()
1647 } else {
1648 let len = xml_strlen(regex.pattern);
1649 let slice = unsafe { core::slice::from_raw_parts(regex.pattern, len) };
1650 core::str::from_utf8(slice)
1651 .unwrap_or("(invalid utf-8)")
1652 .to_string()
1653 };
1654 msg.push_str(&format!("Regex: /{}/\n", pattern_str));
1655 msg.push_str(&format!(" deterministic: {}\n", regex.is_deterministic));
1656
1657 if let Some(ref nfa) = regex.nfa {
1658 msg.push_str(&format!(" states: {}\n", nfa.states.len()));
1659 msg.push_str(&format!(" start state: {}\n", nfa.start));
1660 for (i, state) in nfa.states.iter().enumerate() {
1661 msg.push_str(&format!(" state[{}]: ", i));
1662 if state.is_accept {
1663 msg.push_str("(accept) ");
1664 }
1665 for (j, (cond, target)) in state.transitions.iter().enumerate() {
1666 if j > 0 {
1667 msg.push_str(", ");
1668 }
1669 match cond {
1670 Transition::Epsilon => msg.push_str(&format!("ε->{}", target)),
1671 Transition::Char(c) => {
1672 if *c >= 0x20 && *c <= 0x7e {
1673 msg.push_str(&format!("'{}'->{}", *c as char, target));
1674 } else {
1675 msg.push_str(&format!("0x{:02x}->{}", c, target));
1676 }
1677 }
1678 Transition::Range(lo, hi) => {
1679 msg.push_str(&format!("[{:02x}-{:02x}]->{}", lo, hi, target));
1680 }
1681 Transition::Set(chars) => {
1682 msg.push('{');
1683 for (k, c) in chars.iter().enumerate() {
1684 if k > 0 {
1685 msg.push(',');
1686 }
1687 msg.push_str(&format!("0x{:02x}", c));
1688 }
1689 msg.push_str("}->");
1690 msg.push_str(&target.to_string());
1691 }
1692 Transition::NotRange(lo, hi) => {
1693 msg.push_str(&format!("[^{:02x}-{:02x}]->{}", lo, hi, target));
1694 }
1695 Transition::NotSet(chars) => {
1696 msg.push_str("^{{");
1697 for (k, c) in chars.iter().enumerate() {
1698 if k > 0 {
1699 msg.push(',');
1700 }
1701 msg.push_str(&format!("0x{:02x}", c));
1702 }
1703 msg.push_str("}}->");
1704 msg.push_str(&target.to_string());
1705 }
1706 Transition::Wildcard => msg.push_str(&format!(".*->{}", target)),
1707 Transition::Predefined(class) => {
1708 let name = match class {
1709 PredefinedClass::Digit => "\\d",
1710 PredefinedClass::NotDigit => "\\D",
1711 PredefinedClass::Space => "\\s",
1712 PredefinedClass::NotSpace => "\\S",
1713 PredefinedClass::Word => "\\w",
1714 PredefinedClass::NotWord => "\\W",
1715 };
1716 msg.push_str(&format!("{}->{}", name, target));
1717 }
1718 Transition::Anchor(at) => match at {
1719 AnchorType::Start => msg.push_str(&format!("^->{}", target)),
1720 AnchorType::End => msg.push_str(&format!("$->{}", target)),
1721 },
1722 }
1723 }
1724 msg.push('\n');
1725 }
1726 }
1727 }
1728 unsafe {
1729 let out = if output.is_null() {
1730 libc::fdopen(2, b"w\0" as *const u8 as *const c_char) as *mut c_void
1731 } else {
1732 output
1733 };
1734 if out.is_null() {
1735 return;
1736 }
1737 libc::fwrite(
1738 msg.as_ptr() as *const c_void,
1739 1,
1740 msg.len(),
1741 out as *mut libc::FILE,
1742 );
1743 }
1744}
1745
1746#[no_mangle]
1759pub unsafe extern "C" fn xmlRegFreeRegexp(regexp: *mut XmlRegexp) {
1760 if regexp.is_null() {
1761 return;
1762 }
1763 unsafe {
1764 if !(*regexp).pattern.is_null() {
1765 xmlFreeImpl((*regexp).pattern as *mut c_void);
1766 }
1767 let _ = (*regexp).nfa.take();
1769 xmlFreeImpl(regexp as *mut c_void);
1770 }
1771}
1772
1773#[derive(Debug)]
1783#[repr(C)]
1784pub struct RegExecCtxt {
1785 compiled: *mut XmlRegexp,
1787 current_states: Vec<usize>,
1789 started: bool,
1791 callback: Option<crate::abi::callbacks::xmlRegExecCallbacks>,
1795 data: *mut c_void,
1797}
1798
1799#[no_mangle]
1816pub unsafe extern "C" fn xmlRegNewExecCtxt(
1817 compiled: *mut XmlRegexp,
1818 callback: Option<crate::abi::callbacks::xmlRegExecCallbacks>,
1819 data: *mut c_void,
1820) -> *mut RegExecCtxt {
1821 if compiled.is_null() {
1822 return ptr::null_mut();
1823 }
1824
1825 let ctxt = xmlMallocImpl(core::mem::size_of::<RegExecCtxt>()) as *mut RegExecCtxt;
1826 if ctxt.is_null() {
1827 return ptr::null_mut();
1828 }
1829
1830 unsafe {
1831 (*ctxt).compiled = compiled;
1832 core::ptr::write(&mut (*ctxt).current_states, Vec::new());
1836 (*ctxt).started = false;
1837 (*ctxt).callback = callback;
1838 (*ctxt).data = data;
1839 }
1840
1841 ctxt
1842}
1843
1844#[no_mangle]
1863pub unsafe extern "C" fn xmlRegExecPushString(
1864 ctxt: *mut RegExecCtxt,
1865 value: *const xmlChar,
1866 _data: *mut c_void,
1867) -> c_int {
1868 if ctxt.is_null() {
1869 return REGEXP_ERROR;
1870 }
1871
1872 let exec_ctxt = unsafe { &mut *ctxt };
1873 let regex = match unsafe { exec_ctxt.compiled.as_mut() } {
1874 Some(r) => r,
1875 None => return REGEXP_ERROR,
1876 };
1877
1878 let nfa = match ®ex.nfa {
1879 Some(nfa) => nfa,
1880 None => return REGEXP_ERROR,
1881 };
1882
1883 if value.is_null() {
1884 if !exec_ctxt.started {
1887 exec_ctxt.current_states = epsilon_closure(nfa, &[nfa.start]);
1888 exec_ctxt.started = true;
1889 }
1890 return if has_accept_state(nfa, &exec_ctxt.current_states) {
1891 REGEXP_MATCH
1892 } else {
1893 REGEXP_NOMATCH
1894 };
1895 }
1896
1897 let len = xml_strlen(value);
1898 let input = unsafe { core::slice::from_raw_parts(value, len) };
1899
1900 if input.is_empty() {
1901 return REGEXP_NOMATCH;
1902 }
1903
1904 if !exec_ctxt.started {
1905 exec_ctxt.current_states = epsilon_closure(nfa, &[nfa.start]);
1907 exec_ctxt.started = true;
1908 }
1909
1910 for (i, &c) in input.iter().enumerate() {
1911 let is_end = i == input.len() - 1 && true; let next_states = move_on_char(nfa, &exec_ctxt.current_states, c, i == 0, is_end);
1913 if next_states.is_empty() {
1914 exec_ctxt.current_states = Vec::new();
1915 return REGEXP_NOMATCH;
1916 }
1917 exec_ctxt.current_states = epsilon_closure(nfa, &next_states);
1918 }
1919
1920 if has_accept_state(nfa, &exec_ctxt.current_states) {
1922 REGEXP_MATCH
1923 } else {
1924 REGEXP_NOMATCH
1925 }
1926}
1927
1928#[no_mangle]
1941pub unsafe extern "C" fn xmlRegFreeExecCtxt(ctxt: *mut RegExecCtxt) {
1942 if ctxt.is_null() {
1943 return;
1944 }
1945 unsafe {
1946 core::ptr::drop_in_place(&mut (*ctxt).current_states);
1949 xmlFreeImpl(ctxt as *mut c_void);
1950 }
1951}
1952
1953#[cfg(test)]
1958mod tests {
1959 use super::*;
1960 use core::ptr;
1961
1962 fn to_xml_str(s: &[u8]) -> *mut xmlChar {
1973 let len = s.len();
1974 let ptr =
1975 unsafe { xmlMallocImpl((len + 1) * core::mem::size_of::<xmlChar>()) } as *mut xmlChar;
1976 if ptr.is_null() {
1977 return ptr::null_mut();
1978 }
1979 unsafe {
1980 core::ptr::copy_nonoverlapping(s.as_ptr(), ptr, len);
1981 *ptr.add(len) = 0;
1982 }
1983 ptr
1984 }
1985
1986 fn match_regex(pattern: &[u8], input: &[u8]) -> c_int {
1994 let pat = to_xml_str(pattern);
1995 let val = to_xml_str(input);
1996 unsafe {
1997 let compiled = xmlRegexpCompile(pat);
1998 if compiled.is_null() {
1999 return REGEXP_ERROR;
2000 }
2001 let ret = xmlRegexpExec(compiled, val);
2002 xmlRegFreeRegexp(compiled);
2003 ret
2004 }
2005 }
2006
2007 fn compile(pattern: &[u8]) -> *mut XmlRegexp {
2015 let pat = to_xml_str(pattern);
2016 unsafe { xmlRegexpCompile(pat) }
2017 }
2018
2019 #[test]
2022 fn test_literal_exact() {
2023 assert_eq!(match_regex(b"hello", b"hello"), REGEXP_MATCH);
2024 }
2025
2026 #[test]
2027 fn test_literal_no_match() {
2028 assert_eq!(match_regex(b"hello", b"world"), REGEXP_NOMATCH);
2029 }
2030
2031 #[test]
2032 fn test_literal_partial_prefix() {
2033 assert_eq!(match_regex(b"hello", b"hel"), REGEXP_NOMATCH);
2035 }
2036
2037 #[test]
2038 fn test_literal_empty_pattern() {
2039 assert_eq!(match_regex(b"", b""), REGEXP_MATCH);
2041 }
2042
2043 #[test]
2044 fn test_literal_empty_pattern_nonempty() {
2045 assert_eq!(match_regex(b"", b"a"), REGEXP_NOMATCH);
2047 }
2048
2049 #[test]
2050 fn test_literal_single_char() {
2051 assert_eq!(match_regex(b"a", b"a"), REGEXP_MATCH);
2052 assert_eq!(match_regex(b"a", b"b"), REGEXP_NOMATCH);
2053 }
2054
2055 #[test]
2058 fn test_alternation_simple() {
2059 assert_eq!(match_regex(b"a|b", b"a"), REGEXP_MATCH);
2060 assert_eq!(match_regex(b"a|b", b"b"), REGEXP_MATCH);
2061 assert_eq!(match_regex(b"a|b", b"c"), REGEXP_NOMATCH);
2062 }
2063
2064 #[test]
2065 fn test_alternation_three() {
2066 assert_eq!(match_regex(b"a|b|c", b"a"), REGEXP_MATCH);
2067 assert_eq!(match_regex(b"a|b|c", b"b"), REGEXP_MATCH);
2068 assert_eq!(match_regex(b"a|b|c", b"c"), REGEXP_MATCH);
2069 assert_eq!(match_regex(b"a|b|c", b"d"), REGEXP_NOMATCH);
2070 }
2071
2072 #[test]
2075 fn test_zero_or_more() {
2076 assert_eq!(match_regex(b"a*", b""), REGEXP_MATCH);
2077 assert_eq!(match_regex(b"a*", b"a"), REGEXP_MATCH);
2078 assert_eq!(match_regex(b"a*", b"aaa"), REGEXP_MATCH);
2079 }
2080
2081 #[test]
2082 fn test_one_or_more() {
2083 assert_eq!(match_regex(b"a+", b"a"), REGEXP_MATCH);
2084 assert_eq!(match_regex(b"a+", b"aaa"), REGEXP_MATCH);
2085 assert_eq!(match_regex(b"a+", b""), REGEXP_NOMATCH);
2086 }
2087
2088 #[test]
2089 fn test_zero_or_one() {
2090 assert_eq!(match_regex(b"a?", b""), REGEXP_MATCH);
2091 assert_eq!(match_regex(b"a?", b"a"), REGEXP_MATCH);
2092 assert_eq!(match_regex(b"a?", b"aa"), REGEXP_NOMATCH);
2093 }
2094
2095 #[test]
2096 fn test_zero_or_more_middle() {
2097 assert_eq!(match_regex(b"a*b", b"b"), REGEXP_MATCH);
2099 assert_eq!(match_regex(b"a*b", b"ab"), REGEXP_MATCH);
2100 assert_eq!(match_regex(b"a*b", b"aaab"), REGEXP_MATCH);
2101 assert_eq!(match_regex(b"a*b", b"a"), REGEXP_NOMATCH);
2102 }
2103
2104 #[test]
2107 fn test_grouping() {
2108 assert_eq!(match_regex(b"(a)", b"a"), REGEXP_MATCH);
2109 assert_eq!(match_regex(b"(a)", b"b"), REGEXP_NOMATCH);
2110 }
2111
2112 #[test]
2113 fn test_grouping_with_quantifier() {
2114 assert_eq!(match_regex(b"(ab)+", b"ab"), REGEXP_MATCH);
2115 assert_eq!(match_regex(b"(ab)+", b"abab"), REGEXP_MATCH);
2116 assert_eq!(match_regex(b"(ab)+", b"a"), REGEXP_NOMATCH);
2117 }
2118
2119 #[test]
2122 fn test_start_anchor() {
2123 assert_eq!(match_regex(b"^a", b"a"), REGEXP_MATCH);
2124 assert_eq!(match_regex(b"^a", b"ba"), REGEXP_NOMATCH);
2125 }
2126
2127 #[test]
2128 fn test_end_anchor() {
2129 assert_eq!(match_regex(b"a$", b"a"), REGEXP_MATCH);
2130 assert_eq!(match_regex(b"a$", b"ba"), REGEXP_NOMATCH);
2131 }
2132
2133 #[test]
2134 fn test_both_anchors() {
2135 assert_eq!(match_regex(b"^a$", b"a"), REGEXP_MATCH);
2136 assert_eq!(match_regex(b"^a$", b"ab"), REGEXP_NOMATCH);
2137 assert_eq!(match_regex(b"^a$", b"ba"), REGEXP_NOMATCH);
2138 }
2139
2140 #[test]
2143 fn test_wildcard() {
2144 assert_eq!(match_regex(b".", b"a"), REGEXP_MATCH);
2145 assert_eq!(match_regex(b".", b"1"), REGEXP_MATCH);
2146 assert_eq!(match_regex(b"...", b"abc"), REGEXP_MATCH);
2147 assert_eq!(match_regex(b"...", b"ab"), REGEXP_NOMATCH);
2148 }
2149
2150 #[test]
2151 fn test_wildcard_with_literal() {
2152 assert_eq!(match_regex(b"a.b", b"axb"), REGEXP_MATCH);
2153 assert_eq!(match_regex(b"a.b", b"azb"), REGEXP_MATCH);
2154 assert_eq!(match_regex(b"a.b", b"ab"), REGEXP_NOMATCH);
2155 }
2156
2157 #[test]
2160 fn test_escaped_newline() {
2161 assert_eq!(match_regex(b"a\\nb", b"a\nb"), REGEXP_MATCH);
2162 assert_eq!(match_regex(b"a\\nb", b"ab"), REGEXP_NOMATCH);
2163 }
2164
2165 #[test]
2166 fn test_escaped_tab() {
2167 assert_eq!(match_regex(b"a\\tb", b"a\tb"), REGEXP_MATCH);
2168 }
2169
2170 #[test]
2171 fn test_escaped_metachar() {
2172 assert_eq!(match_regex(b"\\.", b"."), REGEXP_MATCH);
2174 assert_eq!(match_regex(b"\\.", b"a"), REGEXP_NOMATCH);
2175 }
2176
2177 #[test]
2180 fn test_char_class_single() {
2181 assert_eq!(match_regex(b"[a]", b"a"), REGEXP_MATCH);
2182 assert_eq!(match_regex(b"[a]", b"b"), REGEXP_NOMATCH);
2183 }
2184
2185 #[test]
2186 fn test_char_class_multiple_chars() {
2187 assert_eq!(match_regex(b"[abc]", b"a"), REGEXP_MATCH);
2188 assert_eq!(match_regex(b"[abc]", b"b"), REGEXP_MATCH);
2189 assert_eq!(match_regex(b"[abc]", b"c"), REGEXP_MATCH);
2190 assert_eq!(match_regex(b"[abc]", b"d"), REGEXP_NOMATCH);
2191 }
2192
2193 #[test]
2194 fn test_char_class_range() {
2195 assert_eq!(match_regex(b"[a-z]", b"a"), REGEXP_MATCH);
2196 assert_eq!(match_regex(b"[a-z]", b"m"), REGEXP_MATCH);
2197 assert_eq!(match_regex(b"[a-z]", b"z"), REGEXP_MATCH);
2198 assert_eq!(match_regex(b"[a-z]", b"1"), REGEXP_NOMATCH);
2199 }
2200
2201 #[test]
2202 fn test_char_class_range_with_escape() {
2203 assert_eq!(match_regex(b"[\\d]", b"5"), REGEXP_MATCH);
2204 assert_eq!(match_regex(b"[\\d]", b"a"), REGEXP_NOMATCH);
2205 }
2206
2207 #[test]
2208 fn test_multiple_char_classes() {
2209 assert_eq!(match_regex(b"[a-z0-9]+", b"abc123"), REGEXP_MATCH);
2210 assert_eq!(match_regex(b"[a-z0-9]+", b"ABC"), REGEXP_NOMATCH);
2211 }
2212
2213 #[test]
2216 fn test_digit_class() {
2217 assert_eq!(match_regex(b"\\d", b"5"), REGEXP_MATCH);
2218 assert_eq!(match_regex(b"\\d", b"a"), REGEXP_NOMATCH);
2219 }
2220
2221 #[test]
2222 fn test_word_class() {
2223 assert_eq!(match_regex(b"\\w+", b"hello"), REGEXP_MATCH);
2224 assert_eq!(match_regex(b"\\w+", b"hello123"), REGEXP_MATCH);
2225 assert_eq!(match_regex(b"\\w+", b""), REGEXP_NOMATCH);
2226 }
2227
2228 #[test]
2229 fn test_space_class() {
2230 assert_eq!(match_regex(b"\\s", b" "), REGEXP_MATCH);
2231 assert_eq!(match_regex(b"\\s", b"\t"), REGEXP_MATCH);
2232 assert_eq!(match_regex(b"\\s", b"a"), REGEXP_NOMATCH);
2233 }
2234
2235 #[test]
2238 fn test_complex_email_like() {
2239 assert_eq!(
2241 match_regex(b"\\w+@\\w+\\.\\w+", b"user@example.com"),
2242 REGEXP_MATCH
2243 );
2244 assert_eq!(match_regex(b"\\w+@\\w+\\.\\w+", b"invalid"), REGEXP_NOMATCH);
2245 }
2246
2247 #[test]
2248 fn test_complex_phone_like() {
2249 assert_eq!(
2251 match_regex(b"\\d{3}-\\d{3}-\\d{4}", b"555-123-4567"),
2252 REGEXP_MATCH
2253 );
2254 assert_eq!(
2255 match_regex(b"\\d{3}-\\d{3}-\\d{4}", b"555-123-456"),
2256 REGEXP_NOMATCH
2257 );
2258 }
2259
2260 #[test]
2261 fn test_pattern_with_all_features() {
2262 assert_eq!(match_regex(b"^(a|b)+c$", b"ac"), REGEXP_MATCH);
2264 assert_eq!(match_regex(b"^(a|b)+c$", b"bc"), REGEXP_MATCH);
2265 assert_eq!(match_regex(b"^(a|b)+c$", b"ababc"), REGEXP_MATCH);
2266 assert_eq!(match_regex(b"^(a|b)+c$", b"abd"), REGEXP_NOMATCH);
2267 }
2268
2269 #[test]
2272 fn test_exact_quantifier() {
2273 assert_eq!(match_regex(b"a{3}", b"aaa"), REGEXP_MATCH);
2274 assert_eq!(match_regex(b"a{3}", b"aa"), REGEXP_NOMATCH);
2275 assert_eq!(match_regex(b"a{3}", b"aaaa"), REGEXP_NOMATCH);
2276 }
2277
2278 #[test]
2279 fn test_between_quantifier() {
2280 assert_eq!(match_regex(b"a{2,4}", b"aa"), REGEXP_MATCH);
2281 assert_eq!(match_regex(b"a{2,4}", b"aaa"), REGEXP_MATCH);
2282 assert_eq!(match_regex(b"a{2,4}", b"aaaa"), REGEXP_MATCH);
2283 assert_eq!(match_regex(b"a{2,4}", b"a"), REGEXP_NOMATCH);
2284 assert_eq!(match_regex(b"a{2,4}", b"aaaaa"), REGEXP_NOMATCH);
2285 }
2286
2287 #[test]
2288 fn test_at_least_quantifier() {
2289 assert_eq!(match_regex(b"a{2,}", b"aa"), REGEXP_MATCH);
2290 assert_eq!(match_regex(b"a{2,}", b"aaaa"), REGEXP_MATCH);
2291 assert_eq!(match_regex(b"a{2,}", b"a"), REGEXP_NOMATCH);
2292 }
2293
2294 #[test]
2303 fn test_deterministic_literal() {
2304 let compiled = compile(b"hello");
2305 assert!(!compiled.is_null());
2306 assert_eq!(unsafe { xmlRegexpIsDeterministic(compiled) }, 1);
2307 unsafe { xmlRegFreeRegexp(compiled) };
2308 }
2309
2310 #[test]
2317 fn test_non_deterministic() {
2318 let compiled = compile(b"a|b");
2320 assert!(!compiled.is_null());
2321 assert_eq!(unsafe { xmlRegexpIsDeterministic(compiled) }, 0);
2322 unsafe { xmlRegFreeRegexp(compiled) };
2323 }
2324
2325 #[test]
2335 fn test_incremental_empty_input() {
2336 let compiled = compile(b"a*");
2337 assert!(!compiled.is_null());
2338 let ctxt = unsafe { xmlRegNewExecCtxt(compiled, None, ptr::null_mut()) };
2339 assert!(!ctxt.is_null());
2340 let ret = unsafe { xmlRegExecPushString(ctxt, ptr::null_mut(), ptr::null_mut()) };
2342 assert_eq!(ret, REGEXP_MATCH);
2344 unsafe { xmlRegFreeExecCtxt(ctxt) };
2345 unsafe { xmlRegFreeRegexp(compiled) };
2346 }
2347
2348 #[test]
2356 fn test_incremental_simple_match() {
2357 let compiled = compile(b"abc");
2358 assert!(!compiled.is_null());
2359 let ctxt = unsafe { xmlRegNewExecCtxt(compiled, None, ptr::null_mut()) };
2360 assert!(!ctxt.is_null());
2361 let val = to_xml_str(b"abc");
2362 let ret = unsafe { xmlRegExecPushString(ctxt, val, ptr::null_mut()) };
2363 assert_eq!(ret, REGEXP_MATCH);
2364 unsafe { xmlRegFreeExecCtxt(ctxt) };
2365 unsafe { xmlRegFreeRegexp(compiled) };
2366 }
2367
2368 #[test]
2377 fn test_null_pattern() {
2378 let compiled = unsafe { xmlRegexpCompile(ptr::null()) };
2379 assert!(compiled.is_null());
2380 }
2381
2382 #[test]
2390 fn test_null_input() {
2391 let compiled = compile(b"a");
2392 assert!(!compiled.is_null());
2393 let ret = unsafe { xmlRegexpExec(compiled, ptr::null()) };
2394 assert_eq!(ret, REGEXP_ERROR);
2395 unsafe { xmlRegFreeRegexp(compiled) };
2396 }
2397
2398 #[test]
2405 fn test_double_free() {
2406 let compiled = compile(b"test");
2407 assert!(!compiled.is_null());
2408 unsafe { xmlRegFreeRegexp(compiled) };
2409 }
2412
2413 #[test]
2421 fn test_print() {
2422 let compiled = compile(b"hello");
2423 assert!(!compiled.is_null());
2424 unsafe { xmlRegexpPrint(ptr::null_mut(), compiled) };
2425 unsafe { xmlRegFreeRegexp(compiled) };
2426 }
2427}