latexml_core/util/
text.rs1pub enum Delimiter {
3 Parenthesis,
5 Brace,
7 Bracket,
9}
10impl Delimiter {
11 fn open(&self) -> char {
12 use self::Delimiter::*;
13 match *self {
14 Parenthesis => '(',
15 Brace => '{',
16 Bracket => '[',
17 }
18 }
19 fn close(&self) -> char {
20 use self::Delimiter::*;
21 match *self {
22 Parenthesis => ')',
23 Brace => '}',
24 Bracket => ']',
25 }
26 }
27}
28
29pub fn extract_bracketed(text: &mut String, delimiter: Option<&Delimiter>) -> Option<String> {
31 let open_delim = match delimiter {
32 None => '(',
33 Some(d) => d.open(),
34 };
35 let close_delim = match delimiter {
36 None => ')',
37 Some(d) => d.close(),
38 };
39 let mut has_open = false;
40 let mut has_close = false;
41
42 let mut extracted = String::new();
43 let mut level = 0;
44 while !text.is_empty() {
45 match text.remove(0) {
46 c if c == close_delim => {
47 has_close = true;
48 level -= 1;
49 if level < 1 {
50 break;
51 } else {
52 extracted.push(c);
53 }
54 },
55 c if c == open_delim => {
56 has_open = true;
57 level += 1;
59 if level > 1 {
60 extracted.push(c);
61 }
62 },
63 c => {
64 if level > 0 {
65 extracted.push(c)
67 } else if !c.is_whitespace() {
68 *text = c.to_string() + text;
71 break;
72 }
73 },
74 }
75 }
76
77 if has_open && has_close && level == 0 {
78 Some(extracted)
79 } else {
80 None
82 }
83}
84
85pub fn trim_end_in_place(s: &mut String) {
87 let trimmed = s.trim_end();
88 s.truncate(trimmed.len());
89}
90
91pub fn trim_start_in_place(s: &mut String) {
93 let trimmed = s.trim_start();
94 s.replace_range(..(s.len() - trimmed.len()), "");
95}
96
97pub fn trim_in_place(s: &mut String) {
99 trim_end_in_place(s);
100 trim_start_in_place(s);
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn extract_bracketed_parens_default() {
109 let mut s = "(abc)rest".to_string();
110 let out = extract_bracketed(&mut s, None).expect("well-formed parens");
111 assert_eq!(out, "abc");
112 assert_eq!(s, "rest");
113 }
114
115 #[test]
116 fn extract_bracketed_braces() {
117 let mut s = "{foo}tail".to_string();
118 let out = extract_bracketed(&mut s, Some(&Delimiter::Brace)).expect("braces");
119 assert_eq!(out, "foo");
120 assert_eq!(s, "tail");
121 }
122
123 #[test]
124 fn extract_bracketed_brackets() {
125 let mut s = "[opt]body".to_string();
126 let out = extract_bracketed(&mut s, Some(&Delimiter::Bracket)).expect("brackets");
127 assert_eq!(out, "opt");
128 assert_eq!(s, "body");
129 }
130
131 #[test]
132 fn extract_bracketed_nested() {
133 let mut s = "(a(b)c)rest".to_string();
134 let out = extract_bracketed(&mut s, None).expect("nested parens");
135 assert_eq!(out, "a(b)c");
136 assert_eq!(s, "rest");
137 }
138
139 #[test]
140 fn extract_bracketed_malformed_returns_none() {
141 let mut s = "(unclosed".to_string();
142 let out = extract_bracketed(&mut s, None);
143 assert!(out.is_none(), "unclosed bracket returns None");
144 }
145
146 #[test]
147 fn trim_end_in_place_strips_trailing_space() {
148 let mut s = "foo ".to_string();
149 trim_end_in_place(&mut s);
150 assert_eq!(s, "foo");
151 }
152
153 #[test]
154 fn trim_end_in_place_preserves_leading() {
155 let mut s = " foo ".to_string();
156 trim_end_in_place(&mut s);
157 assert_eq!(s, " foo");
158 }
159
160 #[test]
161 fn trim_start_in_place_strips_leading_space() {
162 let mut s = " foo".to_string();
163 trim_start_in_place(&mut s);
164 assert_eq!(s, "foo");
165 }
166
167 #[test]
168 fn trim_start_in_place_preserves_trailing() {
169 let mut s = " foo ".to_string();
170 trim_start_in_place(&mut s);
171 assert_eq!(s, "foo ");
172 }
173
174 #[test]
175 fn trim_in_place_strips_both_ends() {
176 let mut s = " foo bar ".to_string();
177 trim_in_place(&mut s);
178 assert_eq!(s, "foo bar");
179 }
180
181 #[test]
182 fn trim_in_place_empty_input() {
183 let mut s = " ".to_string();
184 trim_in_place(&mut s);
185 assert_eq!(s, "");
186 }
187
188 #[test]
189 fn trim_in_place_no_whitespace() {
190 let mut s = "already_clean".to_string();
191 trim_in_place(&mut s);
192 assert_eq!(s, "already_clean");
193 }
194}