Skip to content

Commit ad7c7da

Browse files
committed
replace() and rereplace() with flags
1 parent 84af4af commit ad7c7da

10 files changed

Lines changed: 135 additions & 33 deletions

File tree

PythonScript.Tests/tests/TestReplacer.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ void deleteEntry(ReplaceEntry* entry)
3232
TEST_F(ReplacerTest, SimpleReplace) {
3333
NppPythonScript::Replacer<NppPythonScript::Utf8CharTraits> replacer;
3434
std::list<NppPythonScript::ReplaceEntry* > entries;
35-
bool moreEntries = replacer.startReplace("aaabbbaaabb", 12, "(b+)", "x$1x", entries);
35+
bool moreEntries = replacer.startReplace("aaabbbaaabb", 12, "(b+)", "x$1x", NppPythonScript::python_re_flag_normal, entries);
3636
ASSERT_EQ(2, entries.size());
3737
std::list<NppPythonScript::ReplaceEntry*>::const_iterator it = entries.begin();
3838

@@ -56,7 +56,7 @@ TEST_F(ReplacerTest, SimpleReplace) {
5656
TEST_F(ReplacerTest, ReplaceUtf8) {
5757
NppPythonScript::Replacer<NppPythonScript::Utf8CharTraits> replacer;
5858
std::list<NppPythonScript::ReplaceEntry* > entries;
59-
bool moreEntries = replacer.startReplace("aaa\xc3\xb4" "bbbaaa\xc3\xbc" "bb", 15, "aaa([\xc3\xbc])", "x$1x", entries);
59+
bool moreEntries = replacer.startReplace("aaa\xc3\xb4" "bbbaaa\xc3\xbc" "bb", 15, "aaa([\xc3\xbc])", "x$1x", NppPythonScript::python_re_flag_normal,entries);
6060
ASSERT_EQ(1, entries.size());
6161
std::list<NppPythonScript::ReplaceEntry*>::const_iterator it = entries.begin();
6262

@@ -73,7 +73,7 @@ TEST_F(ReplacerTest, ReplaceUtf8) {
7373
TEST_F(ReplacerTest, ReplaceExtendedUtf8) {
7474
NppPythonScript::Replacer<NppPythonScript::Utf8CharTraits> replacer;
7575
std::list<NppPythonScript::ReplaceEntry* > entries;
76-
bool moreEntries = replacer.startReplace("aaa\xF0\x9F\x82\xB7" "ZZZ" "bbbaaa\xF0\x9F\x82\xB8" "ZZZ", 23, "aaa([\xF0\x9F\x82\xB8])", "x$1x", entries);
76+
bool moreEntries = replacer.startReplace("aaa\xF0\x9F\x82\xB7" "ZZZ" "bbbaaa\xF0\x9F\x82\xB8" "ZZZ", 23, "aaa([\xF0\x9F\x82\xB8])", "x$1x", NppPythonScript::python_re_flag_normal,entries);
7777
ASSERT_EQ(1, entries.size());
7878
std::list<NppPythonScript::ReplaceEntry*>::const_iterator it = entries.begin();
7979

@@ -90,7 +90,7 @@ TEST_F(ReplacerTest, ReplaceSimpleAnsi) {
9090

9191
NppPythonScript::Replacer<NppPythonScript::AnsiCharTraits> replacer;
9292
std::list<NppPythonScript::ReplaceEntry* > entries;
93-
bool moreEntries = replacer.startReplace("aaa\xF0\x9F" "ZZZ" "aaa\x9F\xB8" "ZZZ", 16, "aaa([\xF0\x9F])", "x$1x", entries);
93+
bool moreEntries = replacer.startReplace("aaa\xF0\x9F" "ZZZ" "aaa\x9F\xB8" "ZZZ", 16, "aaa([\xF0\x9F])", "x$1x", NppPythonScript::python_re_flag_normal,entries);
9494
ASSERT_EQ(2, entries.size());
9595
std::list<NppPythonScript::ReplaceEntry*>::const_iterator it = entries.begin();
9696
ASSERT_EQ(0, (*it)->getStart());

PythonScript/project/PythonScript2010.vcxproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ xcopy $(ProjectDir)..\python_tests\*.* "e:\notepadtest\unicode\plugins\config\py
232232
<ClInclude Include="..\python_tests\tests\ReplaceAnsiPythonFunction.py">
233233
<FileType>Document</FileType>
234234
</ClInclude>
235+
<ClInclude Include="..\python_tests\tests\ReplaceFlagsTestCase.py">
236+
<FileType>Document</FileType>
237+
</ClInclude>
235238
<None Include="..\python_tests\tests\__init__.py" />
236239
<None Include="..\python_tests\__init__.py" />
237240
<None Include="..\res\FolderClosed.ico" />

PythonScript/project/PythonScript2010.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@
281281
<ClInclude Include="..\python_tests\tests\ReplacePlainTestCase.py">
282282
<Filter>PythonTests\Tests</Filter>
283283
</ClInclude>
284+
<ClInclude Include="..\python_tests\tests\ReplaceFlagsTestCase.py">
285+
<Filter>PythonTests\Tests</Filter>
286+
</ClInclude>
284287
</ItemGroup>
285288
<ItemGroup>
286289
<ResourceCompile Include="..\res\PythonScript.rc">
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# -*- coding: utf-8 -*-
2+
import unittest
3+
import re
4+
from Npp import *
5+
6+
class ReplaceFlagsTestCase(unittest.TestCase):
7+
def setUp(self):
8+
notepad.new()
9+
notepad.runMenuCommand("Encoding", "Encode in UTF-8")
10+
editor.write(u'Abc123DEF4567 ghi8910\r\nAbc123\r\n')
11+
12+
def tearDown(self):
13+
editor.setSavePoint()
14+
notepad.close()
15+
16+
def test_replace_is_case_sensitive(self):
17+
editor.rereplace(r'([A-Z]{3})', 'TEST')
18+
text = editor.getText()
19+
self.assertEqual(text, u'Abc123TEST4567 ghi8910\r\nAbc123\r\n')
20+
21+
def test_replace_case_insensitive(self):
22+
editor.rereplace(r'([A-Z]{3})', 'TEST', re.IGNORECASE)
23+
text = editor.getText()
24+
self.assertEqual(text, u'TEST123TEST4567 TEST8910\r\nTEST123\r\n')
25+
26+
def test_replace_anchor_is_default_whole_file(self):
27+
editor.rereplace(r'^Abc', 'TEST')
28+
text = editor.getText()
29+
self.assertEqual(text, u'TEST123DEF4567 ghi8910\r\nAbc123\r\n')
30+
31+
def test_replace_anchor_is_per_line(self):
32+
editor.rereplace(r'^Abc', 'TEST', re.MULTILINE)
33+
text = editor.getText()
34+
self.assertEqual(text, u'TEST123DEF4567 ghi8910\r\nTEST123\r\n')
35+
36+
37+
def test_replace_literal_is_case_sensitive(self):
38+
editor.replace('ABC', 'TEST')
39+
text = editor.getText()
40+
self.assertEqual(text, u'Abc123DEF4567 ghi8910\r\nAbc123\r\n')
41+
42+
def test_replace_literal_case_insensitive(self):
43+
editor.replace('ABC', 'TEST', re.IGNORECASE)
44+
text = editor.getText()
45+
self.assertEqual(text, u'TEST123DEF4567 ghi8910\r\nTEST123\r\n')
46+
47+
suite = unittest.TestLoader().loadTestsFromTestCase(ReplaceFlagsTestCase)

PythonScript/python_tests/tests/ReplaceUTF8PythonFunction.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,6 @@ def test_group_tuples(self):
9595
self.assertEqual(text, '1 2 3\r\n4 5 6\r\n')
9696

9797

98+
99+
98100
suite = unittest.TestLoader().loadTestsFromTestCase(ReplaceUTF8PythonFunctionTestCase)

PythonScript/src/Match.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ namespace NppPythonScript
2020
virtual GroupDetail* groupName(const char *groupName) = 0;
2121
virtual void expand(const char* format, char **result, int *resultLength) = 0;
2222
virtual std::string getTextForGroup(GroupDetail* group) = 0;
23-
23+
virtual int groupIndexFromName(const char *groupName) = 0;
2424

2525
boost::python::str py_group_number(int groupNumber);
2626
boost::python::str py_group_name(boost::python::str groupName);
2727
boost::python::str py_expand(boost::python::object replaceFormat);
2828
boost::python::tuple py_groups();
29+
boost::python::dict py_groupdict();
2930

3031
boost::python::tuple py_group_tuple2(boost::python::object group1, boost::python::object group2);
3132
boost::python::tuple py_group_tuple3(boost::python::object group1, boost::python::object group2, boost::python::object group3);

PythonScript/src/Replacer.h

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ class BoostRegexMatch : public Match
7979

8080
virtual void expand(const char* format, char **result, int *resultLength);
8181

82+
virtual int groupIndexFromName(const char *groupName);
83+
8284
private:
8385
const char *m_text;
8486
boost::match_results<typename CharTraitsT::text_iterator_type>* m_match;
@@ -118,6 +120,13 @@ GroupDetail* BoostRegexMatch<CharTraitsT>::groupName(const char *groupName)
118120
return groupDetail;
119121
}
120122

123+
template <class CharTraitsT>
124+
int BoostRegexMatch<CharTraitsT>::groupIndexFromName(const char *groupName)
125+
{
126+
CharTraitsT::string_type groupNameU32 = toStringType<CharTraitsT::string_type>(ConstString<char>(groupName));
127+
return m_match->named_subexpression_index(groupNameU32.c_str(), groupNameU32.c_str() + groupNameU32.size());
128+
}
129+
121130
template <class CharTraitsT>
122131
void BoostRegexMatch<CharTraitsT>::expand(const char *format, char **result, int *resultLength)
123132
{
@@ -168,26 +177,18 @@ typename std::string BoostRegexMatch<CharTraitsT>::getTextForGroup(GroupDetail*
168177

169178
public:
170179
Replacer()
171-
: m_flags(python_re_flag_normal)
172180
{ }
173181

174-
explicit Replacer(python_re_flags flags)
175-
: m_flags(flags)
176-
{}
177182

178-
bool startReplace(const char *text, const int textLength, const char *search, matchConverter converter, void *converterState, std::list<ReplaceEntry*>& replacements);
179-
bool startReplace(const char *text, const int textLength, const char *search, const char *replace, std::list<ReplaceEntry*>& replacements);
183+
bool startReplace(const char *text, const int textLength, const char *search, matchConverter converter, void *converterState, python_re_flags flags, std::list<ReplaceEntry*>& replacements);
184+
bool startReplace(const char *text, const int textLength, const char *search, const char *replace, python_re_flags flags, std::list<ReplaceEntry*>& replacements);
180185

181186
private:
182187
static ReplaceEntry* matchToReplaceEntry(const char *text, Match *match, void *state);
183188

184-
boost::regex_constants::syntax_option_type getSyntaxFlags()
185-
{ return (m_flags & python_re_flag_literal)
186-
? boost::regex_constants::literal
187-
: boost::regex_constants::normal;
188-
}
189+
boost::regex_constants::match_flag_type getMatchFlags(python_re_flags flags);
190+
boost::regex_constants::syntax_option_type getSyntaxFlags(python_re_flags flags);
189191

190-
python_re_flags m_flags;
191192
const char *m_replaceFormat;
192193
};
193194

@@ -211,27 +212,71 @@ ReplaceEntry* NppPythonScript::Replacer<CharTraitsT>::matchToReplaceEntry(const
211212
template<class CharTraitsT>
212213
bool NppPythonScript::Replacer<CharTraitsT>::startReplace(const char *text, const int textLength, const char *search,
213214
const char *replace,
215+
python_re_flags flags,
214216
std::list<ReplaceEntry*>& replacements)
215217
{
216218
m_replaceFormat = replace;
217-
return startReplace(text, textLength, search, matchToReplaceEntry, this, replacements);
219+
return startReplace(text, textLength, search, matchToReplaceEntry, this, flags, replacements);
220+
}
221+
222+
template<class CharTraitsT>
223+
boost::regex_constants::match_flag_type Replacer<CharTraitsT>::getMatchFlags(python_re_flags flags)
224+
{
225+
boost::regex_constants::match_flag_type resultBoostFlags = boost::regex_constants::match_default;
226+
227+
// If we've not got the dotall flag, we want to add the match_not_dot_newline. I love negative based flags. grrr.
228+
if (0 == (flags & python_re_flag_dotall))
229+
{
230+
resultBoostFlags |= boost::regex_constants::match_not_dot_newline;
231+
}
232+
233+
if (0 == (flags & python_re_flag_multiline))
234+
{
235+
resultBoostFlags |= boost::regex_constants::match_single_line;
236+
}
237+
238+
return resultBoostFlags;
239+
}
240+
241+
242+
template<class CharTraitsT>
243+
boost::regex_constants::syntax_option_type Replacer<CharTraitsT>::getSyntaxFlags(python_re_flags flags)
244+
{
245+
boost::regex_constants::syntax_option_type resultBoostFlags;
246+
247+
if (flags & python_re_flag_literal)
248+
{
249+
resultBoostFlags = boost::regex_constants::literal;
250+
}
251+
else
252+
{
253+
resultBoostFlags = boost::regex_constants::normal;
254+
}
255+
256+
if (flags & python_re_flag_ignorecase)
257+
{
258+
resultBoostFlags |= boost::regex_constants::icase;
259+
}
260+
261+
return resultBoostFlags;
218262
}
219263

220264
template<class CharTraitsT>
221265
bool NppPythonScript::Replacer<CharTraitsT>::startReplace(const char *text, const int textLength, const char *search,
222266
matchConverter converter,
223267
void *converterState,
268+
python_re_flags flags,
224269
std::list<ReplaceEntry*> &replacements) {
225270

226-
boost::regex_constants::syntax_option_type syntax_flags = getSyntaxFlags();
271+
boost::regex_constants::syntax_option_type syntax_flags = getSyntaxFlags(flags);
227272

228273
CharTraitsT::regex_type r = CharTraitsT::regex_type(toStringType<CharTraitsT::string_type>(ConstString<char>(search)), syntax_flags);
229274

230275
CharTraitsT::text_iterator_type start(text, 0, textLength);
231276
CharTraitsT::text_iterator_type end(text, textLength, textLength);
232277
CharTraitsT::regex_iterator_type iteratorEnd;
233278
BoostRegexMatch<CharTraitsT> match(text);
234-
for(CharTraitsT::regex_iterator_type it(start, end, r); it != iteratorEnd; ++it) {
279+
for(CharTraitsT::regex_iterator_type it(start, end, r, getMatchFlags(flags)); it != iteratorEnd; ++it) {
235280
boost::match_results<CharTraitsT::text_iterator_type> boost_match_results(*it);
236281

237282
match.setMatchResults(&boost_match_results);

PythonScript/src/ScintillaPython.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ BOOST_PYTHON_MODULE(Npp)
3434
.def("replace", &ScintillaWrapper::replacePlain, "Simple search and replace. replace(searchFor, replaceWith[, flags]) where flags are members of Npp.FIND")
3535
.def("replace", &ScintillaWrapper::replacePlainFlags, "Simple search and replace. replace(searchFor, replaceWith[, flags]) where flags are members of Npp.FIND")
3636
.def("rereplace", &ScintillaWrapper::replaceRegex, "Simple regular expression search and replace (using Notepad++/Scintilla regular expressions). rereplace(searchExpression, replaceString[, flags]) Use Npp.FIND for the flags")
37-
.def("rereplace", &ScintillaWrapper::replaceRegexFlags, "Simple regular expression search and replace (using Notepad++/Scintilla regular expressions). rereplace(searchExpression, replaceString[, flags]) Use Npp.FIND for the flags")
37+
.def("rereplace", &ScintillaWrapper::replaceRegexFlags, "Simple regular expression search and replace (using Notepad++/Scintilla regular expressions). rereplace(searchExpression, replaceString[, flags]) Use python re module for the flags")
3838
/*.def("pyreplace", &ScintillaWrapper::pyreplace, "Python regular expression search and replace. Full support for Python regular expressions. Works line-by-line, so does not require significant memory overhead, however multiline regular expressions won't work (see pymlreplace). editor.pyreplace(search, replace[, count[, flags[, startLine[, endLine]]]]). Uses the python re.sub() method.")
3939
.def("pyreplace", &ScintillaWrapper::pyreplaceNoFlags, "Python regular expression search and replace. Full support for Python regular expressions. Works line-by-line, so does not require significant memory overhead, however multiline regular expressions won't work (see pymlreplace). editor.pyreplace(search, replace[, count[, flags[, startLine[, endLine]]]]). Uses the python re.sub() method.")
4040
.def("pyreplace", &ScintillaWrapper::pyreplaceNoFlagsNoCount, "Python regular expression search and replace. Full support for Python regular expressions. Works line-by-line, so does not require significant memory overhead, however multiline regular expressions won't work (see pymlreplace). editor.pyreplace(search, replace[, count[, flags[, startLine[, endLine]]]]). Uses the python re.sub() method.")

PythonScript/src/ScintillaWrapper.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -582,12 +582,13 @@ void ScintillaWrapper::replacePlain(boost::python::object searchStr, boost::pyth
582582
}
583583

584584

585-
void ScintillaWrapper::replacePlainFlags(boost::python::object searchStr, boost::python::object replaceStr, NppPythonScript::python_re_flags flags)
585+
586+
void ScintillaWrapper::replacePlainFlags(boost::python::object searchStr, boost::python::object replaceStr, int flags)
586587
{
587588
NppPythonScript::python_re_flags resultFlags = NppPythonScript::python_re_flag_literal;
588589

589590
// Mask off everything but ignorecase
590-
resultFlags = (NppPythonScript::python_re_flags)(resultFlags | (flags && NppPythonScript::python_re_flag_ignorecase));
591+
resultFlags = (NppPythonScript::python_re_flags)(resultFlags | (flags & NppPythonScript::python_re_flag_ignorecase));
591592

592593

593594
replaceImpl(searchStr, replaceStr,
@@ -605,9 +606,9 @@ void ScintillaWrapper::replaceRegex(boost::python::object searchStr, boost::pyth
605606
replaceImpl(searchStr, replaceStr, 0, NppPythonScript::python_re_flag_normal, -1, -1);
606607
}
607608

608-
void ScintillaWrapper::replaceRegexFlags(boost::python::object searchStr, boost::python::object replaceStr, NppPythonScript::python_re_flags flags)
609+
void ScintillaWrapper::replaceRegexFlags(boost::python::object searchStr, boost::python::object replaceStr, int flags)
609610
{
610-
replaceImpl(searchStr, replaceStr, 0, flags, -1, -1);
611+
replaceImpl(searchStr, replaceStr, 0, (NppPythonScript::python_re_flags)flags, -1, -1);
611612
}
612613

613614
void ScintillaWrapper::replaceImpl(boost::python::object searchStr, boost::python::object replaceStr,
@@ -635,30 +636,30 @@ void ScintillaWrapper::replaceImpl(boost::python::object searchStr, boost::pytho
635636

636637
if (CP_UTF8 == currentDocumentCodePage)
637638
{
638-
NppPythonScript::Replacer<NppPythonScript::Utf8CharTraits> replacer(flags);
639+
NppPythonScript::Replacer<NppPythonScript::Utf8CharTraits> replacer;
639640

640641
if (isPythonReplaceFunction)
641642
{
642643
m_pythonReplaceFunction = replaceStr;
643-
replacer.startReplace(text, length, searchChars.c_str(), &ScintillaWrapper::convertWithPython, reinterpret_cast<void*>(this), replacements);
644+
replacer.startReplace(text, length, searchChars.c_str(), &ScintillaWrapper::convertWithPython, reinterpret_cast<void*>(this), flags, replacements);
644645
}
645646
else
646647
{
647-
replacer.startReplace(text, length, searchChars.c_str(), replaceChars.c_str(), replacements);
648+
replacer.startReplace(text, length, searchChars.c_str(), replaceChars.c_str(), flags, replacements);
648649
}
649650
}
650651
else
651652
{
652-
NppPythonScript::Replacer<NppPythonScript::AnsiCharTraits> replacer(flags);
653+
NppPythonScript::Replacer<NppPythonScript::AnsiCharTraits> replacer;
653654

654655
if (isPythonReplaceFunction)
655656
{
656657
m_pythonReplaceFunction = replaceStr;
657-
replacer.startReplace(text, length, searchChars.c_str(), &ScintillaWrapper::convertWithPython, reinterpret_cast<void*>(this), replacements);
658+
replacer.startReplace(text, length, searchChars.c_str(), &ScintillaWrapper::convertWithPython, reinterpret_cast<void*>(this), flags, replacements);
658659
}
659660
else
660661
{
661-
replacer.startReplace(text, length, searchChars.c_str(), replaceChars.c_str(), replacements);
662+
replacer.startReplace(text, length, searchChars.c_str(), replaceChars.c_str(), flags, replacements);
662663
}
663664
}
664665

PythonScript/src/ScintillaWrapper.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ class ScintillaWrapper : public NppPythonScript::PyProducerConsumer<CallbackExec
7373
boost::python::tuple getUserCharSelection();
7474
void setTarget(int start, int end);
7575
void replacePlain(boost::python::object searchStr, boost::python::object replaceStr);
76-
void replacePlainFlags(boost::python::object searchStr, boost::python::object replaceStr, NppPythonScript::python_re_flags flags);
76+
void replacePlainFlags(boost::python::object searchStr, boost::python::object replaceStr, int flags);
7777
void replaceRegex(boost::python::object searchStr, boost::python::object replaceStr);
78-
void replaceRegexFlags(boost::python::object searchStr, boost::python::object replaceStr, NppPythonScript::python_re_flags flags);
78+
void replaceRegexFlags(boost::python::object searchStr, boost::python::object replaceStr, int flags);
7979

8080
void replaceImpl(boost::python::object searchStr, boost::python::object replaceStr, int count, NppPythonScript::python_re_flags flags, int startPosition, int endPosition);
8181

0 commit comments

Comments
 (0)