From 5d21021b92a3d7d5755e80a5be10cfbdf984b9db Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Sun, 4 Aug 2013 21:45:19 +0100
Subject: Factor Chat Message Parsing out and test it

Change-Id: Ia11dbebc736ecf9996f6d0fcc4550b749c55d433

diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 333ae93..1ccd7c1 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -39,6 +39,7 @@
 #include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swift/Controllers/Highlighter.h>
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
 
 
 namespace Swift {
@@ -46,8 +47,8 @@ namespace Swift {
 /**
  * The controller does not gain ownership of the stanzaChannel, nor the factory.
  */
-ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::map<std::string, std::string>* emoticons)
-	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, emoticons), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {
+ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser)
+	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {
 	isInMUC_ = isInMUC;
 	lastWasPresence_ = false;
 	chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
@@ -79,7 +80,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ
 	lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None;
 	chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available);
 	startMessage += ".";
-	chatWindow_->addSystemMessage(parseMessageBody(startMessage), ChatWindow::DefaultDirection);
+	chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection);
 	chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
 	chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
 	chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2));
@@ -445,9 +446,9 @@ void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresenc
 	std::string newStatusChangeString = getStatusChangeString(newPresence);
 	if (newStatusChangeString != lastStatusChangeString_) {
 		if (lastWasPresence_) {
-			chatWindow_->replaceLastMessage(parseMessageBody(newStatusChangeString));
+			chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString));
 		} else {
-			chatWindow_->addPresenceMessage(parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection);
+			chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection);
 		}
 		lastStatusChangeString_ = newStatusChangeString;
 		lastWasPresence_ = true;
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 8863c3e..414af09 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -27,7 +27,7 @@ namespace Swift {
 
 	class ChatController : public ChatControllerBase {
 		public:
-			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::map<std::string, std::string>* emoticons);
+			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser);
 			virtual ~ChatController();
 			virtual void setToJID(const JID& jid);
 			virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 656133c..143e3d2 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -27,9 +27,6 @@
 #include <Swiften/Disco/EntityCapsProvider.h>
 #include <Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h>
 #include <Swiften/Avatars/AvatarManager.h>
-#include <Swiften/Base/Regex.h>
-
-#include <SwifTools/Linkify.h>
 
 #include <Swift/Controllers/Intl.h>
 #include <Swift/Controllers/XMPPEvents/EventController.h>
@@ -38,10 +35,11 @@
 #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
 #include <Swift/Controllers/HighlightManager.h>
 #include <Swift/Controllers/Highlighter.h>
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
 
 namespace Swift {
 
-ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::map<std::string, std::string>* emoticons) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), emoticons_(*emoticons) {
+ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser) {
 	chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
 	chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
 	chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
@@ -80,7 +78,7 @@ void ChatControllerBase::createDayChangeTimer() {
 void ChatControllerBase::handleDayChangeTick() {
 	dateChangeTimer_->stop();
 	boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
-	chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection);
+	chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection);
 	dayTicked();
 	createDayChangeTimer();
 }
@@ -186,17 +184,17 @@ void ChatControllerBase::activateChatWindow() {
 
 std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
 	if (boost::starts_with(message, "/me ")) {
-		return chatWindow_->addAction(parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
+		return chatWindow_->addAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
 	} else {
-		return chatWindow_->addMessage(parseMessageBody(message), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
+		return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
 	}
 }
 
 void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
 	if (boost::starts_with(message, "/me ")) {
-		chatWindow_->replaceWithAction(parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight);
+		chatWindow_->replaceWithAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight);
 	} else {
-		chatWindow_->replaceMessage(parseMessageBody(message), id, time, highlight);
+		chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message), id, time, highlight);
 	}
 }
 
@@ -218,7 +216,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
 	if (message->isError()) {
 		if (!message->getTo().getResource().empty()) {
 			std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>()));
-			chatWindow_->addErrorMessage(parseMessageBody(errorMessage));
+			chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
 		}
 	}
 	else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) {
@@ -243,7 +241,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
 			boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
 			std::ostringstream s;
 			s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 <<  " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << ".";
-			chatWindow_->addSystemMessage(parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection);
+			chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection);
 		}
 		boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>();
 
@@ -349,91 +347,4 @@ void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) {
 	handleGeneralMUCInvitation(inviteEvent);
 }
 
-typedef std::pair<std::string, std::string> StringPair;
-
-ChatWindow::ChatMessage ChatControllerBase::parseMessageBody(const std::string& body) {
-	ChatWindow::ChatMessage parsedMessage;
-	std::string remaining = body;
-	/* Parse one, URLs */
-	while (!remaining.empty()) {
-		bool found = false;
-		std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining);
-		remaining = "";
-		for (size_t i = 0; i < links.first.size(); i++) {
-			const std::string& part = links.first[i];
-			if (found) {
-				// Must be on the last part, then
-				remaining = part;
-			}
-			else {
-				if (i == links.second) {
-					found = true;
-					parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part));
-				}
-				else {
-					parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part));
-				}
-			}
-		}
-	}
-	
-
-
-	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);
-	}
-	if (!regexString.empty()) {
-		regexString += ")";
-		boost::regex emoticonRegex(regexString);
-
-		ChatWindow::ChatMessage newMessage;
-		foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) {
-			boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
-			if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
-				try {
-					boost::match_results<std::string::const_iterator> match;
-					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;
-						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());
-						assert (emoticonIterator != emoticons_.end());
-						const StringPair& emoticon = *emoticonIterator;
-						emoticonPart->imagePath = emoticon.second;
-						emoticonPart->alternativeText = emoticon.first;
-						newMessage.append(emoticonPart);
-						start = matchEnd;
-					}
-					if (start != text.end()) {
-						/* If there's plain text after the last emoticon, record it */
-						newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
-					}
-
-				}
-				catch (std::runtime_error) {
-					/* Basically too expensive to compute the regex results and it gave up, so pass through as text */
-					newMessage.append(part);
-				}
-			}
-			else {
-				newMessage.append(part);
-			}
-		}
-		parsedMessage = newMessage;
-
-	}
-	return parsedMessage;
-}
-
-
 }
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 3c527ad..129c75b 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -44,6 +44,7 @@ namespace Swift {
 	class EntityCapsProvider;
 	class HighlightManager;
 	class Highlighter;
+	class ChatMessageParser;
 
 	class ChatControllerBase : public boost::bsignals::trackable {
 		public:
@@ -65,7 +66,7 @@ namespace Swift {
 			void handleCapsChanged(const JID& jid);
 
 		protected:
-			ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::map<std::string, std::string>* emoticons);
+			ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser);
 
 			/**
 			 * Pass the Message appended, and the stanza used to send it.
@@ -86,7 +87,6 @@ namespace Swift {
 			/** JID any iq for account should go to - bare except for PMs */
 			virtual JID getBaseJID();
 			virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0;
-			ChatWindow::ChatMessage parseMessageBody(const std::string& body);
 
 		private:
 			IDGenerator idGenerator_;
@@ -123,6 +123,6 @@ namespace Swift {
 			HistoryController* historyController_;
 			MUCRegistry* mucRegistry_;
 			Highlighter* highlighter_;
-			const std::map<std::string, std::string>& emoticons_;
+			ChatMessageParser* chatMessageParser_;
 	};
 }
diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp
new file mode 100644
index 0000000..ce184ea
--- /dev/null
+++ b/Swift/Controllers/Chat/ChatMessageParser.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2013 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
+
+#include <vector>
+#include <utility>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <Swiften/Base/Regex.h>
+#include <Swiften/Base/foreach.h>
+
+#include <SwifTools/Linkify.h>
+
+
+namespace Swift {
+
+	ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons) : emoticons_(emoticons) {
+
+	}
+
+	typedef std::pair<std::string, std::string> StringPair;
+
+	ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body) {
+		ChatWindow::ChatMessage parsedMessage;
+		std::string remaining = body;
+		/* Parse one, URLs */
+		while (!remaining.empty()) {
+			bool found = false;
+			std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining);
+			remaining = "";
+			for (size_t i = 0; i < links.first.size(); i++) {
+				const std::string& part = links.first[i];
+				if (found) {
+					// Must be on the last part, then
+					remaining = part;
+				}
+				else {
+					if (i == links.second) {
+						found = true;
+						parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part));
+					}
+					else {
+						parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part));
+					}
+				}
+			}
+		}
+		
+
+
+		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);
+		}
+		if (!regexString.empty()) {
+			regexString += ")";
+			boost::regex emoticonRegex(regexString);
+
+			ChatWindow::ChatMessage newMessage;
+			foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) {
+				boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
+				if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
+					try {
+						boost::match_results<std::string::const_iterator> match;
+						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;
+							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());
+							assert (emoticonIterator != emoticons_.end());
+							const StringPair& emoticon = *emoticonIterator;
+							emoticonPart->imagePath = emoticon.second;
+							emoticonPart->alternativeText = emoticon.first;
+							newMessage.append(emoticonPart);
+							start = matchEnd;
+						}
+						if (start != text.end()) {
+							/* If there's plain text after the last emoticon, record it */
+							newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
+						}
+
+					}
+					catch (std::runtime_error) {
+						/* Basically too expensive to compute the regex results and it gave up, so pass through as text */
+						newMessage.append(part);
+					}
+				}
+				else {
+					newMessage.append(part);
+				}
+			}
+			parsedMessage = newMessage;
+
+		}
+		return parsedMessage;
+	}
+}
diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h
new file mode 100644
index 0000000..c9b9456
--- /dev/null
+++ b/Swift/Controllers/Chat/ChatMessageParser.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2013 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+
+namespace Swift {
+
+	class ChatMessageParser {
+		public:
+			ChatMessageParser(const std::map<std::string, std::string>& emoticons);
+			ChatWindow::ChatMessage parseMessageBody(const std::string& body);
+		private:
+			std::map<std::string, std::string> emoticons_;
+
+	};
+}
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 5dd53c1..415931c 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -45,6 +45,7 @@
 #include <Swift/Controllers/Settings/SettingsProvider.h>
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swift/Controllers/WhiteboardManager.h>
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
 
 namespace Swift {
 
@@ -79,7 +80,7 @@ ChatsManager::ChatsManager(
 		WhiteboardManager* whiteboardManager,
 		HighlightManager* highlightManager,
 		ClientBlockListManager* clientBlockListManager,
-		std::map<std::string, std::string>* emoticons) :
+		const std::map<std::string, std::string>& emoticons) :
 			jid_(jid), 
 			joinMUCWindowFactory_(joinMUCWindowFactory), 
 			useDelayForLatency_(useDelayForLatency), 
@@ -93,8 +94,7 @@ ChatsManager::ChatsManager(
 			historyController_(historyController),
 			whiteboardManager_(whiteboardManager),
 			highlightManager_(highlightManager),
-			clientBlockListManager_(clientBlockListManager),
-			emoticons_(emoticons) {
+			clientBlockListManager_(clientBlockListManager) {
 	timerFactory_ = timerFactory;
 	eventController_ = eventController;
 	stanzaChannel_ = stanzaChannel;
@@ -108,6 +108,7 @@ ChatsManager::ChatsManager(
 	uiEventStream_ = uiEventStream;
 	mucBookmarkManager_ = NULL;
 	profileSettings_ = profileSettings;
+	chatMessageParser_ = new ChatMessageParser(emoticons);
 	presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1));
 	uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1));
 
@@ -152,6 +153,7 @@ ChatsManager::~ChatsManager() {
 	}
 	delete mucBookmarkManager_;
 	delete mucSearchController_;
+	delete chatMessageParser_;
 }
 
 void ChatsManager::saveRecents() {
@@ -529,7 +531,7 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact)
 
 ChatController* ChatsManager::createNewChatController(const JID& contact) {
 	assert(chatControllers_.find(contact) == chatControllers_.end());
-	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, emoticons_);
+	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_);
 	chatControllers_[contact] = controller;
 	controller->setAvailableServerFeatures(serverDiscoInfo_);
 	controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
@@ -602,7 +604,7 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional
 		if (createAsReservedIfNew) {
 			muc->setCreateAsReservedIfNew();
 		}
-		MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, emoticons_);
+		MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser_);
 		mucControllers_[mucJID] = controller;
 		controller->setAvailableServerFeatures(serverDiscoInfo_);
 		controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller));
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 4d1266f..70c1ec8 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -54,10 +54,11 @@ namespace Swift {
 	class HistoryController;
 	class HighlightManager;
 	class ClientBlockListManager;
+	class ChatMessageParser;
 	
 	class ChatsManager {
 		public:
-			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::map<std::string, std::string>* emoticons);
+			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons);
 			virtual ~ChatsManager();
 			void setAvatarManager(AvatarManager* avatarManager);
 			void setOnline(bool enabled);
@@ -142,6 +143,6 @@ namespace Swift {
 			WhiteboardManager* whiteboardManager_;
 			HighlightManager* highlightManager_;
 			ClientBlockListManager* clientBlockListManager_;
-			std::map<std::string, std::string>* emoticons_;
+			ChatMessageParser* chatMessageParser_;
 	};
 }
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 0033297..c41c078 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -37,6 +37,7 @@
 #include <Swiften/Disco/EntityCapsProvider.h>
 #include <Swiften/Roster/XMPPRoster.h>
 #include <Swift/Controllers/Highlighter.h>
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
 
 
 #define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000
@@ -65,8 +66,8 @@ MUCController::MUCController (
 		HistoryController* historyController,
 		MUCRegistry* mucRegistry,
 		HighlightManager* highlightManager,
-		std::map<std::string, std::string>* emoticons) :
-			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, emoticons), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0) {
+		ChatMessageParser* chatMessageParser) :
+			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0) {
 	parting_ = true;
 	joined_ = false;
 	lastWasPresence_ = false;
@@ -227,7 +228,7 @@ const std::string& MUCController::getNick() {
 
 void MUCController::handleJoinTimeoutTick() {
 	receivedActivity();
-	chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection);
+	chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection);
 }
 
 void MUCController::receivedActivity() {
@@ -278,7 +279,7 @@ void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) {
 		}
 	}
 	errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't join room: %1%.")) % errorMessage);
-	chatWindow_->addErrorMessage(parseMessageBody(errorMessage));
+	chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
 	parting_ = true;
 	if (!rejoinNick.empty() && renameCounter_ < 10) {
 		renameCounter_++;
@@ -295,7 +296,7 @@ void MUCController::handleJoinComplete(const std::string& nick) {
 	joined_ = true;
 	std::string joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick);
 	setNick(nick);
-	chatWindow_->addSystemMessage(parseMessageBody(joinMessage), ChatWindow::DefaultDirection);
+	chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(joinMessage), ChatWindow::DefaultDirection);
 
 #ifdef SWIFT_EXPERIMENTAL_HISTORY
 	addRecentLogs();
@@ -361,7 +362,7 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
 
 void MUCController::addPresenceMessage(const std::string& message) {
 	lastWasPresence_ = true;
-	chatWindow_->addPresenceMessage(parseMessageBody(message), ChatWindow::DefaultDirection);
+	chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(message), ChatWindow::DefaultDirection);
 }
 
 
@@ -448,7 +449,7 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes
 	joined_ = true;
 
 	if (message->hasSubject() && message->getBody().empty()) {
-		chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection);;
+		chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection);;
 		chatWindow_->setSubject(message->getSubject());
 		doneGettingHistory_ = true;
 	}
@@ -485,7 +486,7 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC
 	std::string group(roleToGroupName(occupant.getRole()));
 	roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid));
 	roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole()));
-	chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection);
+	chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection);
 	if (nick == nick_) {
 		setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
 	}
@@ -518,7 +519,7 @@ void MUCController::setOnline(bool online) {
 	} else {
 		if (shouldJoinOnReconnect_) {
 			renameCounter_ = 0;
-			chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection);
+			chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection);
 			if (loginCheckTimer_) {
 				loginCheckTimer_->start();
 			}
@@ -616,7 +617,7 @@ boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(boo
 }
 
 void MUCController::updateJoinParts() {
-	chatWindow_->replaceLastMessage(parseMessageBody(generateJoinPartString(joinParts_)));
+	chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_)));
 }
 
 void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) {
@@ -736,13 +737,13 @@ void MUCController::handleConfigureRequest(Form::ref form) {
 void MUCController::handleConfigurationFailed(ErrorPayload::ref error) {
 	std::string errorMessage = getErrorMessage(error);
 	errorMessage = str(format(QT_TRANSLATE_NOOP("", "Room configuration failed: %1%.")) % errorMessage);
-	chatWindow_->addErrorMessage(parseMessageBody(errorMessage));
+	chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
 }
 
 void MUCController::handleOccupantRoleChangeFailed(ErrorPayload::ref error, const JID&, MUCOccupant::Role) {
 	std::string errorMessage = getErrorMessage(error);
 	errorMessage = str(format(QT_TRANSLATE_NOOP("", "Occupant role change failed: %1%.")) % errorMessage);
-	chatWindow_->addErrorMessage(parseMessageBody(errorMessage));
+	chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
 }
 
 void MUCController::handleConfigurationFormReceived(Form::ref form) {
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 57fd9cd..cad0c94 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -48,7 +48,7 @@ namespace Swift {
 
 	class MUCController : public ChatControllerBase {
 		public:
-			MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::map<std::string, std::string>* emoticons);
+			MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser);
 			~MUCController();
 			boost::signal<void ()> onUserLeft;
 			boost::signal<void ()> onUserJoined;
diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
new file mode 100644
index 0000000..44d7834
--- /dev/null
+++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2013 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <hippomocks.h>
+
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
+
+using namespace Swift;
+
+class ChatMessageParserTest : public CppUnit::TestFixture {
+	CPPUNIT_TEST_SUITE(ChatMessageParserTest);
+	CPPUNIT_TEST(testFullBody);
+	CPPUNIT_TEST_SUITE_END();
+	
+public:
+	void setUp() {
+		smile1_ = ":)";
+		smile1Path_ = "/blah/smile1.png";
+		smile2_ = ":(";
+		smile2Path_ = "/blah/smile2.jpg";
+		emoticons_[smile1_] = smile1Path_;
+		emoticons_[smile2_] = smile2Path_;
+	}
+	
+	void tearDown() {
+		emoticons_.clear();
+	}
+
+	void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
+		boost::shared_ptr<ChatWindow::ChatTextMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]);
+		CPPUNIT_ASSERT_EQUAL(text, part->text);
+	}
+
+	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_EQUAL(text, part->alternativeText);
+		CPPUNIT_ASSERT_EQUAL(path, part->imagePath);
+	}
+
+	void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) {
+		boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]);
+		CPPUNIT_ASSERT_EQUAL(text, part->target);
+	}
+
+	void testFullBody() {
+		ChatMessageParser testling(emoticons_);
+		ChatWindow::ChatMessage result = testling.parseMessageBody(":) shiny :( :) http://wonderland.lit/blah http://denmark.lit boom boom");
+		assertEmoticon(result, 0, smile1_, smile1Path_);
+		assertText(result, 1, " shiny ");
+		assertEmoticon(result, 2, smile2_, smile2Path_);
+		assertText(result, 3, " ");
+		assertEmoticon(result, 4, smile1_, smile1Path_);
+		assertText(result, 5, " ");
+		assertURL(result, 6, "http://wonderland.lit/blah");
+		assertText(result, 7, " ");
+		assertURL(result, 8, "http://denmark.lit");
+		assertText(result, 9, " boom boom");
+	}
+
+private:
+	std::map<std::string, std::string> emoticons_;
+	std::string smile1_;
+	std::string smile1Path_;
+	std::string smile2_;
+	std::string smile2Path_;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest);
+
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 77a1cda..3c14bae 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -111,7 +111,7 @@ public:
 
 		mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
 		clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
-		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, &emoticons_);
+		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, emoticons_);
 
 		manager_->setAvatarManager(avatarManager_);
 	}
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index cf6c581..0fc6a18 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -27,6 +27,7 @@
 #include "Swiften/Elements/MUCUserPayload.h"
 #include "Swiften/Disco/DummyEntityCapsProvider.h"
 #include <Swift/Controllers/Settings/DummySettingsProvider.h>
+#include <Swift/Controllers/Chat/ChatMessageParser.h>
 
 using namespace Swift;
 
@@ -67,7 +68,8 @@ public:
 		highlightManager_ = new HighlightManager(settings_);
 		muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_);
 		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
-		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, &emoticons_);
+		chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>());
+		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_);
 	}
 
 	void tearDown() {
@@ -86,6 +88,7 @@ public:
 		delete iqChannel_;
 		delete mucRegistry_;
 		delete avatarManager_;
+		delete chatMessageParser_;
 	}
 
 	void finishJoin() {
@@ -345,7 +348,7 @@ private:
 	DummyEntityCapsProvider* entityCapsProvider_;
 	DummySettingsProvider* settings_;
 	HighlightManager* highlightManager_;
-	std::map<std::string, std::string> emoticons_;
+	ChatMessageParser* chatMessageParser_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index f99e0ad..1ae175c 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -345,7 +345,7 @@ void MainController::handleConnected() {
 #ifdef SWIFT_EXPERIMENTAL_HISTORY
 		historyController_ = new HistoryController(storages_->getHistoryStorage());
 		historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_);
-		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), &emoticons_);
+		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_);
 #else
 		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), &emoticons_);
 #endif
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 6f36a52..9461a8c 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -28,6 +28,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Chat/MUCController.cpp",
 			"Chat/MUCSearchController.cpp",
 			"Chat/UserSearchController.cpp",
+			"Chat/ChatMessageParser.cpp",
 			"MainController.cpp",
 			"ProfileController.cpp",
 			"ShowProfileController.cpp",
@@ -94,6 +95,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("UnitTest/PresenceNotifierTest.cpp"),
 			File("Chat/UnitTest/ChatsManagerTest.cpp"),
 			File("Chat/UnitTest/MUCControllerTest.cpp"),
+			File("Chat/UnitTest/ChatMessageParserTest.cpp"),
 			File("UnitTest/MockChatWindow.cpp"),
 			File("UnitTest/ChatMessageSummarizerTest.cpp"),
 			File("Settings/UnitTest/SettingsProviderHierachyTest.cpp"),
-- 
cgit v0.10.2-6-g49f6