1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
pub trait SubstringReplace {
/// Return a substring by start and end character index
/// With multibyte characters this will not be the same as the byte indices
/// used by str slices
fn substring(&self, start: usize, end: usize) -> &str;
/// Return a substring from the start and to a specified end character index
fn substring_start(&self, end: usize) -> &str {
self.substring(0, end)
}
/// Return a substring from a specified start character index to a specified end
/// If start index is greater than the max character index, the function will yield an empty string
fn substring_end(&self, start: usize) -> &str {
let max_index = self.char_len();
self.substring(start, max_index)
}
// Replace substring delimited by start and end character index
// with any string (&str)
// To inject a string use substring_insert()
fn substring_replace(&self, replacement: &str, start: usize, end: usize) -> String;
/// Replace the start of a string to specified end character index
/// e.g. "brown".substring_replace_start("d", 2);
/// will replace the first two characters with "d", yield "down"
fn substring_replace_start(&self, replacement: &str, end: usize) -> String {
self.substring_replace(replacement, 0, end)
}
/// Replace the remainder of string from a specified start character index
/// e.g. "blue".substring_replace_last("ack", 2);
/// will replace the last 2 characters with "ack", yielding "black"
fn substring_replace_end(&self, replacement: &str, start: usize) -> String {
let end = self.char_len();
self.substring_replace(replacement, start, end)
}
/// Extract a substring from a start index for n characters to the right
/// A negative length in the second parameter will start at the start index
fn substring_offset(&self, position: usize, length: i32) -> &str {
let reverse = length < 0;
let start = if reverse {
position.checked_sub(length.abs() as usize).unwrap_or(0)
} else {
position
};
let start_i32 = if start > i32::MAX as usize { i32::MAX } else { start as i32 };
let end_i32 = start_i32 + length.abs();
let end = if end_i32 < 0 {
0
} else {
end_i32 as usize
};
self.substring(start, end)
}
/// Insert a string at a given character index
/// This differs from String::insert by using character rather than byte indices
/// to work better with multibyte characters
/// It also works directly with &str, while returning a new owned string
fn substring_insert(&self, replacement: &str, start: usize) -> String {
self.substring_replace(replacement, start, start)
}
/// Convert character index to start byte index
fn to_start_byte_index(&self, start: usize) -> usize;
/// Convert character index to end byte index
fn to_end_byte_index(&self, start: usize) -> usize;
/// Return the character length rather than the byte length
fn char_len(&self) -> usize;
}
impl SubstringReplace for str {
/// Extract substring by character indices and hand overflow gracefully
/// if the end index is equal or greater than start index, the function will yield an empty string
fn substring(&self, start: usize, end: usize) -> &str {
let end_index = if end > start { end } else { start };
&self[self.to_start_byte_index(start)..self.to_end_byte_index(end_index)]
}
/// Replace
fn substring_replace(&self, replacement: &str, start: usize, end: usize) -> String {
let end_index = if end > start { end } else { start };
[&self[0..self.to_start_byte_index(start)], replacement, &self[self.to_end_byte_index(end_index)..]].concat()
}
/// Translate the character start index to the start byte index
/// to avoid boundary collisions with multibyte characters
fn to_start_byte_index(&self, start: usize) -> usize {
self.char_indices().nth(start).map(|(i, _)| i).unwrap_or(0)
}
/// Translate the character end index to the end byte index
/// to avoid boundary collisions with multibyte characters
fn to_end_byte_index(&self, end: usize) -> usize {
self.char_indices().nth(end).map(|(i, _)| i).unwrap_or(self.len())
}
/// Return the character length as opposed to the byte length
/// This will differ from len() only multibyte characters
fn char_len(&self) -> usize {
self.char_indices().count()
}
}