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 fn match_decoded_data(
162 &mut self,
163 text: &str,
164 pattern: &str,
165 case_sensitive: bool,
166 matching_mode: &MatchingMode,
167 fields_processed: &[Field],
168 column_number: u32,
169 row_number: i64,
170 ) {
171 match matching_mode {
172 MatchingMode::Regex(regex) => {
173 for entry_match in regex.find_iter(text) {
174 let column_name = fields_processed[column_number as usize].name();
175 self.matches.push(TableMatch::new(column_name, column_number, row_number, entry_match.start(), entry_match.end(), text));
176 }
177 }
178
179 MatchingMode::Pattern(regex) => {
180 for (start, end, _) in &find_in_string(text, pattern, case_sensitive, regex) {
181 let column_name = fields_processed[column_number as usize].name();
182 self.matches.push(TableMatch::new(column_name, column_number, row_number, *start, *end, text));
183 }
184 }
185 }
186 }
187}
188
189impl TableMatch {
191
192 pub fn new(column_name: &str, column_number: u32, row_number: i64, start: usize, end: usize, text: &str) -> Self {
194 Self {
195 column_name: column_name.to_owned(),
196 column_number,
197 row_number,
198 start,
199 end,
200 text: text.to_owned(),
201 }
202 }
203
204 fn replace(&self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, data: &mut DecodedData) -> bool {
206 let (previous_data, mut current_data) = (data.data_to_string().to_string(), data.data_to_string().to_string());
207 let edited = replace_match_string(pattern, replace_pattern, case_sensitive, matching_mode, self.start, self.end, &previous_data, &mut current_data);
208 data.set_data(¤t_data).is_ok() && edited
209 }
210}