diff --git a/edxsearch/__init__.py b/edxsearch/__init__.py
index 0f5cc993..2432599e 100644
--- a/edxsearch/__init__.py
+++ b/edxsearch/__init__.py
@@ -1,3 +1,3 @@
""" Container module for testing / demoing search """
-__version__ = '3.3.0'
+__version__ = '3.4.0'
diff --git a/search/result_processor.py b/search/result_processor.py
index cc5961ed..6674cca1 100644
--- a/search/result_processor.py
+++ b/search/result_processor.py
@@ -131,7 +131,12 @@ def excerpt(self):
return None
match_phrases = [self._match_phrase]
- separate_phrases = list(shlex.split(self._match_phrase))
+ try:
+ separate_phrases = list(shlex.split(self._match_phrase))
+ # This can happen when the phrase contains an apostrophe - the POSIX mode does not allow unclosed quotations.
+ except ValueError:
+ separate_phrases = list(shlex.split(self._match_phrase, posix=False))
+
if len(separate_phrases) > 1:
match_phrases.extend(separate_phrases)
else:
diff --git a/search/tests/test_search_result_processor.py b/search/tests/test_search_result_processor.py
index 3dbcce65..a144a3dc 100644
--- a/search/tests/test_search_result_processor.py
+++ b/search/tests/test_search_result_processor.py
@@ -267,6 +267,30 @@ def test_excerpt_ellipsis_undecorated(self):
srp = SearchResultProcessor(test_result, 'Just a line')
self.assertIn(ELLIPSIS, srp.excerpt)
+ @ddt.data(
+ ("shouldn't", "This shouldn't have failed."),
+ ("shouldn\'t", " This shouldn't have failed."),
+ ('"shouldn\'t have"', "This shouldn't have failed."),
+ ("shouldn\\'t\\'ve", "shouldn't've failed."), # An even number of apostrophes needs to be escaped.
+ ('"shouldn\'t\'ve failed"', "shouldn't've failed."),
+ ("shou\'dn\'t\'ve", "This shou'dn't've failed."),
+ ('"shou\'dn\'t\'ve failed"', "This shou'dn't've failed."),
+ )
+ @ddt.unpack
+ def test_excerpt_apostrophe(self, search_phrase, expected_excerpt):
+ test_result = {
+ "content": {
+ "notes": (
+ "This should not have failed. "
+ "This shouldn't have failed. "
+ "This shouldn't've failed. "
+ "This shou'dn't've failed."
+ )
+ }
+ }
+ srp = SearchResultProcessor(test_result, search_phrase)
+ self.assertIn(expected_excerpt, srp.excerpt)
+
class TestSearchResultProcessor(SearchResultProcessor):
"""