1use getset::{Getters, MutGetters, Setters};
18use serde_derive::{Deserialize, Serialize};
19
20use rpfm_lib::files::{db::DB, loc::Loc, table::DecodedData};
21use rpfm_lib::schema::Field;
22
23use super::{find_in_string, MatchingMode, Replaceable, SearchSource, Searchable, replace_match_string};
24
25#[derive(Debug, Clone, Eq, PartialEq, Getters, MutGetters, Setters, Serialize, Deserialize)]
31#[getset(get = "pub", get_mut = "pub", set = "pub")]
32pub struct TableMatches {
33
34 path: String,
36
37 #[serde(default)]
39 source: SearchSource,
40
41 #[serde(default)]
43 container_name: String,
44
45 matches: Vec<TableMatch>,
47}
48
49#[derive(Debug, Clone, Eq, PartialEq, Getters, MutGetters, Serialize, Deserialize)]
51#[getset(get = "pub", get_mut = "pub")]
52pub struct TableMatch {
53
54 column_name: String,
56
57 column_number: u32,
59
60 row_number: i64,
62
63 start: usize,
65
66 end: usize,
68
69 text: String,
71}
72
73impl Searchable for DB {
78 type SearchMatches = TableMatches;
79
80 fn search(&self, file_path: &str, pattern_to_search: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> TableMatches {
81 let mut matches = TableMatches::new(file_path);
82
83 let fields_processed = self.definition().fields_processed();
84
85 for (row_number, row) in self.data().iter().enumerate() {
86 for (column_number, cell) in row.iter().enumerate() {
87 matches.match_decoded_data(&cell.data_to_string(), pattern_to_search, case_sensitive, matching_mode, &fields_processed, column_number as u32, row_number as i64);
88 }
89 }
90
91 matches
92 }
93}
94
95impl Searchable for Loc {
96 type SearchMatches = TableMatches;
97
98 fn search(&self, file_path: &str, pattern_to_search: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> TableMatches {
99 let mut matches = TableMatches::new(file_path);
100
101 let fields_processed = self.definition().fields_processed();
102
103 for (row_number, row) in self.data().iter().enumerate() {
104 for (column_number, cell) in row.iter().enumerate() {
105 matches.match_decoded_data(&cell.data_to_string(), pattern_to_search, case_sensitive, matching_mode, &fields_processed, column_number as u32, row_number as i64);
106 }
107 }
108
109 matches
110 }
111}
112
113impl Replaceable for DB {
114
115 fn replace(&mut self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_matches: &TableMatches) -> bool {
116 let mut edited = false;
117
118 for search_match in search_matches.matches() {
119 if let Some(row) = self.data_mut().get_mut(search_match.row_number as usize) {
120 if let Some(data) = row.get_mut(search_match.column_number as usize) {
121 edited |= search_match.replace(pattern, replace_pattern, case_sensitive, matching_mode, data);
122 }
123 }
124 }
125
126 edited
127 }
128}
129
130impl Replaceable for Loc {
131
132 fn replace(&mut self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_matches: &TableMatches) -> bool {
133 let mut edited = false;
134
135 for search_match in search_matches.matches() {
136 if let Some(row) = self.data_mut().get_mut(search_match.row_number as usize) {
137 if let Some(data) = row.get_mut(search_match.column_number as usize) {
138 edited |= search_match.replace(pattern, replace_pattern, case_sensitive, matching_mode, data);
139 }
140 }
141 }
142
143 edited
144 }
145}
146
147impl TableMatches {
149
150 pub fn new(path: &str) -> Self {
152 Self {
153 path: path.to_owned(),
154 matches: vec![],
155 source: SearchSource::default(),
156 container_name: String::new(),
157 }
158 }
159
160 #[allow(clippy::too_many_arguments)]
162 fn match_decoded_data(
163 &mut self,
164 text: &str,
165 pattern: &str,
166 case_sensitive: bool,
167 matching_mode: &MatchingMode,
168 fields_processed: &[Field],
169 column_number: u32,
170 row_number: i64,
171 ) {
172 match matching_mode {
173 MatchingMode::Regex(regex) => {
174 for entry_match in regex.find_iter(text) {
175 let column_name = fields_processed[column_number as usize].name();
176 self.matches.push(TableMatch::new(column_name, column_number, row_number, entry_match.start(), entry_match.end(), text));
177 }
178 }
179
180 MatchingMode::Pattern(regex) => {
181 for (start, end, _) in &find_in_string(text, pattern, case_sensitive, regex) {
182 let column_name = fields_processed[column_number as usize].name();
183 self.matches.push(TableMatch::new(column_name, column_number, row_number, *start, *end, text));
184 }
185 }
186 }
187 }
188}
189
190impl TableMatch {
192
193 pub fn new(column_name: &str, column_number: u32, row_number: i64, start: usize, end: usize, text: &str) -> Self {
195 Self {
196 column_name: column_name.to_owned(),
197 column_number,
198 row_number,
199 start,
200 end,
201 text: text.to_owned(),
202 }
203 }
204
205 fn replace(&self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, data: &mut DecodedData) -> bool {
207 let (previous_data, mut current_data) = (data.data_to_string().to_string(), data.data_to_string().to_string());
208 let edited = replace_match_string(pattern, replace_pattern, case_sensitive, matching_mode, self.start, self.end, &previous_data, &mut current_data);
209 data.set_data(¤t_data).is_ok() && edited
210 }
211}