1use rudb_common::{Error, Result};
18
19use crate::dialect::Dialect;
20
21pub fn record(
33 bytes: &[u8],
34 from: usize,
35 dialect: Dialect,
36 eof: bool,
37 out: &mut Vec<String>,
38) -> Result<Option<usize>> {
39 if from >= bytes.len() {
40 return Ok(None);
45 }
46 let quote = dialect.quote_byte();
47 let escape = dialect.escape_byte();
48 let mut at = from;
49 let mut count = 0;
50 let mut field = Vec::new();
51 loop {
52 field.clear();
53 if bytes.get(at) == Some("e) {
54 at += 1;
55 loop {
56 let Some(&byte) = bytes.get(at) else { return Ok(None) };
57 if byte == escape && bytes.get(at + 1) == Some("e) {
58 field.push(quote);
59 at += 2;
60 continue;
61 }
62 if byte == quote {
63 at += 1;
64 break;
65 }
66 field.push(byte);
67 at += 1;
68 }
69 match bytes.get(at) {
70 None if !eof => return Ok(None),
71 None => {}
72 Some(&byte) if byte == dialect.delimiter || byte == b'\n' || byte == b'\r' => {}
73 Some(&byte) => {
74 return Err(Error::io(format!(
75 "a quoted value is followed by '{}' rather than by a delimiter or the end \
76 of the line",
77 byte as char
78 )));
79 }
80 }
81 } else {
82 while let Some(&byte) = bytes.get(at) {
83 if byte == dialect.delimiter || byte == b'\n' || byte == b'\r' {
84 break;
85 }
86 field.push(byte);
87 at += 1;
88 }
89 if at >= bytes.len() && !eof {
90 return Ok(None);
91 }
92 }
93 place(out, count, &field);
94 count += 1;
95 match bytes.get(at) {
96 Some(&byte) if byte == dialect.delimiter => at += 1,
97 Some(b'\r') => {
98 at += 1;
99 if bytes.get(at) == Some(&b'\n') {
100 at += 1;
101 } else if at >= bytes.len() && !eof {
102 return Ok(None);
105 }
106 break;
107 }
108 Some(b'\n') => {
109 at += 1;
110 break;
111 }
112 Some(_) => unreachable!("a field stops at a delimiter, a line ending or the end"),
113 None => break,
114 }
115 }
116 out.truncate(count);
117 Ok(Some(at))
118}
119
120fn place(out: &mut Vec<String>, at: usize, field: &[u8]) {
127 let text = String::from_utf8_lossy(field);
128 match out.get_mut(at) {
129 Some(held) => {
130 held.clear();
131 held.push_str(&text);
132 }
133 None => out.push(text.into_owned()),
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 fn split(bytes: &[u8], dialect: Dialect) -> Vec<Vec<String>> {
142 let mut rows = Vec::new();
143 let mut fields = Vec::new();
144 let mut at = 0;
145 while at < bytes.len() {
146 let next = record(bytes, at, dialect, true, &mut fields)
147 .expect("splits")
148 .expect("a whole record");
149 rows.push(fields.clone());
150 at = next;
151 }
152 rows
153 }
154
155 fn comma() -> Dialect {
156 Dialect { delimiter: b',', quote: Some(b'"'), escape: Some(b'"'), header: true }
157 }
158
159 #[test]
160 fn a_line_of_fields_is_the_fields_of_that_line() {
161 assert_eq!(split(b"a,b,c\n1,2,3\n", comma()), [["a", "b", "c"], ["1", "2", "3"]]);
162 }
163
164 #[test]
165 fn the_last_line_does_not_need_a_newline_on_it() {
166 assert_eq!(split(b"a,b\n1,2", comma()), [["a", "b"], ["1", "2"]]);
167 }
168
169 #[test]
170 fn all_three_line_endings_end_a_line() {
171 assert_eq!(split(b"a\r\nb\rc\n", comma()), [["a"], ["b"], ["c"]]);
172 }
173
174 #[test]
175 fn a_quoted_field_may_hold_the_delimiter_and_a_newline() {
176 assert_eq!(split(b"1,\"x,y\"\n", comma()), [["1", "x,y"]]);
177 assert_eq!(split(b"1,\"x\ny\"\n", comma()), [["1", "x\ny"]]);
178 }
179
180 #[test]
181 fn a_doubled_quote_inside_a_quoted_field_is_one_quote() {
182 assert_eq!(split(b"1,\"say \"\"hi\"\"\"\n", comma()), [["1", "say \"hi\""]]);
183 }
184
185 #[test]
186 fn an_empty_field_is_an_empty_string_here_and_becomes_a_null_above() {
187 assert_eq!(split(b"1,,3\n", comma()), [["1", "", "3"]]);
188 assert_eq!(split(b"1,\"\",3\n", comma()), [["1", "", "3"]]);
189 }
190
191 #[test]
192 fn a_trailing_delimiter_makes_a_last_empty_field() {
193 assert_eq!(split(b"1|x|\n", Dialect { delimiter: b'|', ..comma() }), [["1", "x", ""]]);
194 }
195
196 #[test]
197 fn a_quote_in_the_middle_of_a_bare_field_is_just_a_character() {
198 assert_eq!(split(b"1,he said \"hi\"\n", comma()), [["1", "he said \"hi\""]]);
199 }
200
201 #[test]
202 fn a_record_that_the_buffer_does_not_hold_all_of_is_not_a_record_yet() {
203 let mut fields = Vec::new();
204 assert_eq!(record(b"a,b", 0, comma(), false, &mut fields).unwrap(), None);
205 assert_eq!(record(b"a,\"b", 0, comma(), true, &mut fields).unwrap(), None);
206 assert_eq!(record(b"a,b\n", 0, comma(), false, &mut fields).unwrap(), Some(4));
207 }
208
209 #[test]
210 fn rubbish_after_a_closing_quote_is_an_error_rather_than_a_guess() {
211 let mut fields = Vec::new();
212 let error = record(b"\"x\"y,2\n", 0, comma(), true, &mut fields).unwrap_err();
213 assert!(error.message().contains("quoted value"), "{error}");
214 }
215
216 #[test]
217 fn utf8_survives_being_read_one_byte_at_a_time() {
218 assert_eq!(split("a,héllo\n".as_bytes(), comma()), [["a", "héllo"]]);
219 }
220}