From 05f2ef44c2203317a6fc766cd0c5a9a1f241af23 Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Fri, 25 Apr 2014 11:04:55 +0100
Subject: Make emoticons match only on word boundaries

Change-Id: Ibe6469f8dc60e89ff87f7e65b6c35c5d50858554

diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp
index ce184ea..698b766 100644
--- a/Swift/Controllers/Chat/ChatMessageParser.cpp
+++ b/Swift/Controllers/Chat/ChatMessageParser.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Kevin Smith
+ * Copyright (c) 2013-2014 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -57,12 +57,19 @@ namespace Swift {
 		std::string regexString;
 		/* Parse two, emoticons */
 		foreach (StringPair emoticon, emoticons_) {
-			/* Construct a regexp that finds an instance of any of the emoticons inside a group */
-			regexString += regexString.empty() ? "(" : "|";
-			regexString += Regex::escape(emoticon.first);
+			/* Construct a regexp that finds an instance of any of the emoticons inside a group
+			 * at the start or end of the line, or beside whitespace.
+			 */
+			regexString += regexString.empty() ? "" : "|";
+			std::string escaped = "(" + Regex::escape(emoticon.first) + ")";
+			regexString += "^" + escaped + "|";
+			regexString += escaped + "$|";
+			regexString += "\\s" + escaped + "|";
+			regexString += escaped + "\\s";
+
 		}
 		if (!regexString.empty()) {
-			regexString += ")";
+			regexString += "";
 			boost::regex emoticonRegex(regexString);
 
 			ChatWindow::ChatMessage newMessage;
@@ -74,14 +81,22 @@ namespace Swift {
 						const std::string& text = textPart->text;
 						std::string::const_iterator start = text.begin();
 						while (regex_search(start, text.end(), match, emoticonRegex)) {
-							std::string::const_iterator matchStart = match[0].first;
-							std::string::const_iterator matchEnd = match[0].second;
+							int matchIndex = 0;
+							for (matchIndex = 1; matchIndex < static_cast<int>(match.size()); matchIndex++) {
+								if (match[matchIndex].length() > 0) {
+									//This is the matching subgroup
+									break;
+								}
+							}
+							std::string::const_iterator matchStart = match[matchIndex].first;
+							std::string::const_iterator matchEnd = match[matchIndex].second;
 							if (start != matchStart) {
 								/* If we're skipping over plain text since the previous emoticon, record it as plain text */
 								newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
 							}
 							boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>();
-							std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(match.str());
+							std::string matchString = match[matchIndex].str();
+							std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(matchString);
 							assert (emoticonIterator != emoticons_.end());
 							const StringPair& emoticon = *emoticonIterator;
 							emoticonPart->imagePath = emoticon.second;
diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
index 44d7834..0a14303 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Kevin Smith
+ * Copyright (c) 2013-2014 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -15,6 +15,11 @@ using namespace Swift;
 class ChatMessageParserTest : public CppUnit::TestFixture {
 	CPPUNIT_TEST_SUITE(ChatMessageParserTest);
 	CPPUNIT_TEST(testFullBody);
+	CPPUNIT_TEST(testOneEmoticon);
+	CPPUNIT_TEST(testBareEmoticon);
+	CPPUNIT_TEST(testHiddenEmoticon);
+	CPPUNIT_TEST(testEndlineEmoticon);
+	CPPUNIT_TEST(testBoundedEmoticons);
 	CPPUNIT_TEST_SUITE_END();
 	
 public:
@@ -38,6 +43,7 @@ public:
 
 	void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) {
 		boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]);
+		CPPUNIT_ASSERT(!!part);
 		CPPUNIT_ASSERT_EQUAL(text, part->alternativeText);
 		CPPUNIT_ASSERT_EQUAL(path, part->imagePath);
 	}
@@ -62,6 +68,51 @@ public:
 		assertText(result, 9, " boom boom");
 	}
 
+	void testOneEmoticon() {
+		ChatMessageParser testling(emoticons_);
+		ChatWindow::ChatMessage result = testling.parseMessageBody(" :) ");
+		assertText(result, 0, " ");
+		assertEmoticon(result, 1, smile1_, smile1Path_);
+		assertText(result, 2, " ");
+	}
+
+
+	void testBareEmoticon() {
+		ChatMessageParser testling(emoticons_);
+		ChatWindow::ChatMessage result = testling.parseMessageBody(":)");
+		assertEmoticon(result, 0, smile1_, smile1Path_);
+	}
+
+	void testHiddenEmoticon() {
+		ChatMessageParser testling(emoticons_);
+		ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a");
+		assertText(result, 0, "b:)a");
+	}
+
+	void testEndlineEmoticon() {
+		ChatMessageParser testling(emoticons_);
+		ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)");
+		assertText(result, 0, "Lazy");
+		assertEmoticon(result, 1, smile1_, smile1Path_);
+	}
+
+	void testBoundedEmoticons() {
+		ChatMessageParser testling(emoticons_);
+		ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:(");
+		assertEmoticon(result, 0, smile1_, smile1Path_);
+		assertText(result, 1, "Lazy");
+		assertEmoticon(result, 2, smile2_, smile2Path_);
+	}
+
+	void testEmoticonParenthesis() {
+		ChatMessageParser testling(emoticons_);
+		ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))");
+		assertText(result, 0, "(Like this ");
+		assertEmoticon(result, 1, smile1_, smile1Path_);
+		assertText(result, 2, ")");
+	}
+
+
 private:
 	std::map<std::string, std::string> emoticons_;
 	std::string smile1_;
-- 
cgit v0.10.2-6-g49f6