OSDN Git Service

Fix an issue where items with different case are not displayed correctly in the folde...
[winmerge-jp/winmerge-jp.git] / Src / SubstitutionFiltersList.cpp
1 /** 
2  * @file  SubstitutionFiltersList.cpp
3  *
4  * @brief Implementation for SubstitutionFiltersList class.
5  */
6
7 #include "pch.h"
8 #include "SubstitutionFiltersList.h"
9 #include "SubstitutionList.h"
10 #include <vector>
11 #include <cassert>
12 #include <Poco/Exception.h>
13 #include "OptionsMgr.h"
14 #include "OptionsDef.h"
15 #include "UnicodeString.h"
16
17 /** @brief Registry key for saving Substitution filters. */
18 static const TCHAR SubstitutionFiltersRegPath[] = _T("SubstitutionFilters");
19
20 /**
21  * @brief Default constructor.
22  */
23 SubstitutionFiltersList::SubstitutionFiltersList()
24 : m_pOptionsMgr(nullptr)
25 {
26 }
27
28 /**
29  * @brief Destructor, empties the list.
30  */
31 SubstitutionFiltersList::~SubstitutionFiltersList() = default;
32
33 /**
34  * @brief Add new filter to the list.
35  * @param [in] filter Filter string to add.
36  * @param [in] enabled Is filter enabled?
37  */
38 void SubstitutionFiltersList::Add(const String& filter0, const String& filter1,
39         bool useRegExp, bool caseSensitive, bool matchWholeWordOnly, bool enabled)
40 {
41         SubstitutionFilter item;
42         item.useRegExp = useRegExp;
43         item.caseSensitive = caseSensitive;
44         item.matchWholeWordOnly = matchWholeWordOnly;
45         item.pattern = filter0;
46         item.replacement = filter1;
47         item.enabled = enabled;
48         m_items.emplace_back(item);
49 }
50
51 /**
52  * @brief Return filter from given index.
53  * @param [in] ind Index of filter.
54  * @return Filter item from the index. If the index is beyond table limit,
55  *  return the last item in the list.
56  */
57 const SubstitutionFilter& SubstitutionFiltersList::GetAt(size_t ind) const
58 {
59         if (ind < m_items.size())
60                 return m_items[ind];
61         else
62                 return m_items.back();
63 }
64
65 /**
66  * @brief Clone filter list from another list.
67  * This function clones filter list from another list. Current items in the
68  * list are removed and new items added from the given list.
69  * @param [in] list List to clone.
70  */
71 void SubstitutionFiltersList::CloneFrom(const SubstitutionFiltersList *list)
72 {
73         Empty();
74         size_t count = list->GetCount();
75         m_enabled = list->m_enabled;
76         for (size_t i = 0; i < count; i++)
77         {
78                 const SubstitutionFilter &item = list->GetAt(i);
79                 Add(item.pattern, item.replacement, item.useRegExp,
80                         item.caseSensitive, item.matchWholeWordOnly, item.enabled);
81         }
82 }
83
84 /**
85  * @brief Compare filter lists.
86  * @param [in] list List to compare.
87  * @return true if lists are identical, false otherwise.
88  */
89 bool SubstitutionFiltersList::Compare(const SubstitutionFiltersList *list) const
90 {
91         if (list->GetCount() != GetCount())
92                 return false;
93         if (list->GetEnabled() != GetEnabled())
94                 return false;
95
96         for (size_t i = 0; i < GetCount(); i++)
97         {
98                 const SubstitutionFilter &item1 = list->GetAt(i);
99                 const SubstitutionFilter &item2 = GetAt(i);
100
101                 if
102                 (
103                            item1.enabled != item2.enabled
104                         || item1.useRegExp != item2.useRegExp
105                         || item1.caseSensitive != item2.caseSensitive
106                         || item1.matchWholeWordOnly != item2.matchWholeWordOnly
107                         || item1.pattern != item2.pattern
108                         || item1.replacement != item2.replacement
109                 )
110                         return false;
111         }
112         return true;
113 }
114
115 /**
116  * @brief Read filter list from the options system.
117  * @param [in] pOptionsMgr Pointer to options system.
118  */
119 void SubstitutionFiltersList::Initialize(COptionsMgr *pOptionsMgr)
120 {
121         assert(pOptionsMgr != nullptr);
122         String valuename(SubstitutionFiltersRegPath);
123
124         m_pOptionsMgr = pOptionsMgr;
125
126         m_enabled = m_pOptionsMgr->GetBool(OPT_SUBSTITUTION_FILTERS_ENABLED);
127
128         size_t count = m_items.size();
129         valuename += _T("/Values");
130         m_pOptionsMgr->InitOption(valuename, static_cast<int>(count));
131         count = m_pOptionsMgr->GetInt(valuename);
132
133         for (unsigned i = 0; i < count; i++)
134         {
135                 String nameEnabled = strutils::format(_T("%s/Enabled%02u"), SubstitutionFiltersRegPath, i);
136                 m_pOptionsMgr->InitOption(nameEnabled, true);
137                 bool enabled = m_pOptionsMgr->GetBool(nameEnabled);
138
139                 String nameUseRegExp = strutils::format(_T("%s/UseRegExp%02u"), SubstitutionFiltersRegPath, i);
140                 m_pOptionsMgr->InitOption(nameUseRegExp, false);
141                 bool useRegExp = m_pOptionsMgr->GetBool(nameUseRegExp);
142
143                 String nameCaseSensitive = strutils::format(_T("%s/CaseSensitive%02u"), SubstitutionFiltersRegPath, i);
144                 m_pOptionsMgr->InitOption(nameCaseSensitive, false);
145                 bool caseSensitive = m_pOptionsMgr->GetBool(nameCaseSensitive);
146
147                 String nameMatchWholeWordOnly = strutils::format(_T("%s/MatchWholeWordOnly%02u"), SubstitutionFiltersRegPath, i);
148                 m_pOptionsMgr->InitOption(nameMatchWholeWordOnly, false);
149                 bool matchWholeWordOnly = m_pOptionsMgr->GetBool(nameMatchWholeWordOnly);
150
151                 String name0 = strutils::format(_T("%s/Pattern%02u"), SubstitutionFiltersRegPath, i);
152                 m_pOptionsMgr->InitOption(name0, _T(""));
153                 String pattern = m_pOptionsMgr->GetString(name0);
154
155                 String name1 = strutils::format(_T("%s/Replacement%02u"), SubstitutionFiltersRegPath, i);
156                 m_pOptionsMgr->InitOption(name1, _T(""));
157                 String replacement = m_pOptionsMgr->GetString(name1);
158
159                 Add(pattern, replacement, useRegExp, caseSensitive, matchWholeWordOnly, enabled);
160         }
161 }
162
163 /**
164  * @brief Save Substitution Filters to options system.
165  */
166 void SubstitutionFiltersList::SaveFilters()
167 {
168         assert(m_pOptionsMgr != nullptr);
169         String valuename(SubstitutionFiltersRegPath);
170
171         m_pOptionsMgr->SaveOption(OPT_SUBSTITUTION_FILTERS_ENABLED, m_enabled);
172
173         size_t count = m_items.size();
174         valuename += _T("/Values");
175         m_pOptionsMgr->SaveOption(valuename, static_cast<int>(count));
176
177         for (size_t i = 0; i < count; i++)
178         {
179                 const SubstitutionFilter& item = m_items[i];
180
181                 String nameEnabled = strutils::format(_T("%s/Enabled%02u"), SubstitutionFiltersRegPath, i);
182                 m_pOptionsMgr->InitOption(nameEnabled, true);
183                 m_pOptionsMgr->SaveOption(nameEnabled, item.enabled);
184
185                 String nameUseRegExp = strutils::format(_T("%s/UseRegExp%02u"), SubstitutionFiltersRegPath, i);
186                 m_pOptionsMgr->InitOption(nameUseRegExp, false);
187                 m_pOptionsMgr->SaveOption(nameUseRegExp, item.useRegExp);
188
189                 String nameCaseSensitive = strutils::format(_T("%s/CaseSensitive%02u"), SubstitutionFiltersRegPath, i);
190                 m_pOptionsMgr->InitOption(nameCaseSensitive, false);
191                 m_pOptionsMgr->SaveOption(nameCaseSensitive, item.caseSensitive);
192
193                 String nameMatchWholeWordOnly = strutils::format(_T("%s/MatchWholeWordOnly%02u"), SubstitutionFiltersRegPath, i);
194                 m_pOptionsMgr->InitOption(nameMatchWholeWordOnly, false);
195                 m_pOptionsMgr->SaveOption(nameMatchWholeWordOnly, item.matchWholeWordOnly);
196
197                 String name0 = strutils::format(_T("%s/Pattern%02u"), SubstitutionFiltersRegPath, i);
198                 m_pOptionsMgr->InitOption(name0, _T(""));
199                 m_pOptionsMgr->SaveOption(name0, item.pattern);
200
201                 String name1 = strutils::format(_T("%s/Replacement%02u"), SubstitutionFiltersRegPath, i);
202                 m_pOptionsMgr->InitOption(name1, _T(""));
203                 m_pOptionsMgr->SaveOption(name1, item.replacement);
204         }
205
206         // Remove options we don't need anymore
207         // We could have earlier 10 pcs but now we only need 5
208         String filterEnabled = strutils::format(_T("%s/Enabled%02u"), SubstitutionFiltersRegPath, count);
209         int retvalEnabled = m_pOptionsMgr->RemoveOption(filterEnabled);
210
211         String filterUseRegExp = strutils::format(_T("%s/UseRegExp%02u"), SubstitutionFiltersRegPath, count);
212         int retvalUseRegExp = m_pOptionsMgr->RemoveOption(filterUseRegExp);
213
214         String filterCaseSensitive = strutils::format(_T("%s/CaseSensitive%02u"), SubstitutionFiltersRegPath, count);
215         int retvalCaseSensitive = m_pOptionsMgr->RemoveOption(filterCaseSensitive);
216
217         String filterMatchWholeWordOnly = strutils::format(_T("%s/MatchWholeWordOnly%02u"), SubstitutionFiltersRegPath, count);
218         int retvalMatchWholeWordOnly = m_pOptionsMgr->RemoveOption(filterMatchWholeWordOnly);
219
220         String filter0 = strutils::format(_T("%s/Pattern%02u"), SubstitutionFiltersRegPath, count);
221         int retval0 = m_pOptionsMgr->RemoveOption(filter0);
222
223         String filter1 = strutils::format(_T("%s/Replacement%02u"), SubstitutionFiltersRegPath, count);
224         int retval1 = m_pOptionsMgr->RemoveOption(filter1);
225
226         while (retvalEnabled == COption::OPT_OK || retvalUseRegExp == COption::OPT_OK ||
227                 retvalCaseSensitive == COption::OPT_OK || retvalMatchWholeWordOnly == COption::OPT_OK ||
228                 retval0 == COption::OPT_OK || retval1 == COption::OPT_OK)
229         {
230                 ++count;
231                 filterEnabled = strutils::format(_T("%s/Enabled%02u"), SubstitutionFiltersRegPath, count);
232                 retvalEnabled = m_pOptionsMgr->RemoveOption(filterEnabled);
233                 filterUseRegExp = strutils::format(_T("%s/UseRegExp%02u"), SubstitutionFiltersRegPath, count);
234                 retvalUseRegExp = m_pOptionsMgr->RemoveOption(filterUseRegExp);
235                 filterCaseSensitive = strutils::format(_T("%s/CaseSensitive%02u"), SubstitutionFiltersRegPath, count);
236                 retvalCaseSensitive = m_pOptionsMgr->RemoveOption(filterCaseSensitive);
237                 filterMatchWholeWordOnly = strutils::format(_T("%s/MatchWholeWordOnly%02u"), SubstitutionFiltersRegPath, count);
238                 retvalMatchWholeWordOnly = m_pOptionsMgr->RemoveOption(filterMatchWholeWordOnly);
239                 filter0 = strutils::format(_T("%s/Pattern%02u"), SubstitutionFiltersRegPath, count);
240                 retval0 = m_pOptionsMgr->RemoveOption(filter0);
241                 filter1 = strutils::format(_T("%s/Replacement%02u"), SubstitutionFiltersRegPath, count);
242                 retval1 = m_pOptionsMgr->RemoveOption(filter1);
243         }
244 }
245
246 std::shared_ptr<SubstitutionList> SubstitutionFiltersList::MakeSubstitutionList(bool throwIfInvalid)
247 {
248         int i = 0;
249         std::shared_ptr<SubstitutionList> plist(new SubstitutionList);
250         for (auto& item : m_items)
251         {
252                 if (item.enabled && !item.pattern.empty())
253                 {
254                         try
255                         {
256                                 if (item.useRegExp)
257                                 {
258                                         plist->Add(
259                                                 ucr::toUTF8(item.pattern),
260                                                 ucr::toUTF8(item.replacement),
261                                                 (item.caseSensitive ? 0 : Poco::RegularExpression::RE_CASELESS) |
262                                                  Poco::RegularExpression::RE_MULTILINE);
263                                 }
264                                 else
265                                 {
266                                         plist->Add(
267                                                 ucr::toUTF8(item.pattern),
268                                                 ucr::toUTF8(item.replacement),
269                                                 item.caseSensitive, item.matchWholeWordOnly);
270                                 }
271                         }
272                         catch (const Poco::RegularExpressionException& e)
273                         {
274                                 if (throwIfInvalid)
275                                 {
276                                         plist.reset();
277                                         char msg[512];
278                                         _snprintf_s(msg, _TRUNCATE, "#%d: %s", i + 1, e.message().c_str());
279                                         throw Poco::RegularExpressionException(msg, e.code());
280                                 }
281                         }
282                 }
283                 i++;
284         }
285         return plist;
286 }