// CompositeChat.cpp // Implements the cCompositeChat class used to wrap a chat message with multiple parts (text, url, cmd) #include "Globals.h" #include "CompositeChat.h" #ifdef SELF_TEST /** A simple self-test that verifies that the composite chat parser is working properly. */ class SelfTest_CompositeChat { public: SelfTest_CompositeChat(void) { fprintf(stderr, "cCompositeChat self test...\n"); TestParser1(); TestParser2(); TestParser3(); TestParser4(); TestParser5(); fprintf(stderr, "cCompositeChat self test finished.\n"); } void TestParser1(void) { cCompositeChat Msg; Msg.ParseText("Testing @2color codes and http://links parser"); const cCompositeChat::cParts & Parts = Msg.GetParts(); assert_test(Parts.size() == 4); assert_test(Parts[0]->m_PartType == cCompositeChat::ptText); assert_test(Parts[1]->m_PartType == cCompositeChat::ptText); assert_test(Parts[2]->m_PartType == cCompositeChat::ptUrl); assert_test(Parts[3]->m_PartType == cCompositeChat::ptText); assert_test(Parts[0]->m_Style == ""); assert_test(Parts[1]->m_Style == "@2"); assert_test(Parts[2]->m_Style == "@2"); assert_test(Parts[3]->m_Style == "@2"); } void TestParser2(void) { cCompositeChat Msg; Msg.ParseText("@3Advanced stuff: @5overriding color codes and http://links.with/@4color-in-them handling"); const cCompositeChat::cParts & Parts = Msg.GetParts(); assert_test(Parts.size() == 4); assert_test(Parts[0]->m_PartType == cCompositeChat::ptText); assert_test(Parts[1]->m_PartType == cCompositeChat::ptText); assert_test(Parts[2]->m_PartType == cCompositeChat::ptUrl); assert_test(Parts[3]->m_PartType == cCompositeChat::ptText); assert_test(Parts[0]->m_Style == "@3"); assert_test(Parts[1]->m_Style == "@5"); assert_test(Parts[2]->m_Style == "@5"); assert_test(Parts[3]->m_Style == "@5"); } void TestParser3(void) { cCompositeChat Msg; Msg.ParseText("http://links.starting the text"); const cCompositeChat::cParts & Parts = Msg.GetParts(); assert_test(Parts.size() == 2); assert_test(Parts[0]->m_PartType == cCompositeChat::ptUrl); assert_test(Parts[1]->m_PartType == cCompositeChat::ptText); assert_test(Parts[0]->m_Style == ""); assert_test(Parts[1]->m_Style == ""); } void TestParser4(void) { cCompositeChat Msg; Msg.ParseText("links finishing the text: http://some.server"); const cCompositeChat::cParts & Parts = Msg.GetParts(); assert_test(Parts.size() == 2); assert_test(Parts[0]->m_PartType == cCompositeChat::ptText); assert_test(Parts[1]->m_PartType == cCompositeChat::ptUrl); assert_test(Parts[0]->m_Style == ""); assert_test(Parts[1]->m_Style == ""); } void TestParser5(void) { cCompositeChat Msg; Msg.ParseText("http://only.links"); const cCompositeChat::cParts & Parts = Msg.GetParts(); assert_test(Parts.size() == 1); assert_test(Parts[0]->m_PartType == cCompositeChat::ptUrl); assert_test(Parts[0]->m_Style == ""); } } gTest; #endif // SELF_TEST //////////////////////////////////////////////////////////////////////////////// // cCompositeChat: cCompositeChat::cCompositeChat(void) : m_MessageType(mtCustom) { } cCompositeChat::cCompositeChat(const AString & a_ParseText, eMessageType a_MessageType) : m_MessageType(a_MessageType) { ParseText(a_ParseText); } cCompositeChat::~cCompositeChat() { Clear(); } void cCompositeChat::Clear(void) { for (cParts::iterator itr = m_Parts.begin(), end = m_Parts.end(); itr != end; ++itr) { delete *itr; } // for itr - m_Parts[] m_Parts.clear(); } void cCompositeChat::AddTextPart(const AString & a_Message, const AString & a_Style) { m_Parts.push_back(new cTextPart(a_Message, a_Style)); } void cCompositeChat::AddClientTranslatedPart(const AString & a_TranslationID, const AStringVector & a_Parameters, const AString & a_Style) { m_Parts.push_back(new cClientTranslatedPart(a_TranslationID, a_Parameters, a_Style)); } void cCompositeChat::AddUrlPart(const AString & a_Text, const AString & a_Url, const AString & a_Style) { m_Parts.push_back(new cUrlPart(a_Text, a_Url, a_Style)); } void cCompositeChat::AddRunCommandPart(const AString & a_Text, const AString & a_Command, const AString & a_Style) { m_Parts.push_back(new cRunCommandPart(a_Text, a_Command, a_Style)); } void cCompositeChat::AddSuggestCommandPart(const AString & a_Text, const AString & a_SuggestedCommand, const AString & a_Style) { m_Parts.push_back(new cSuggestCommandPart(a_Text, a_SuggestedCommand, a_Style)); } void cCompositeChat::AddShowAchievementPart(const AString & a_PlayerName, const AString & a_Achievement, const AString & a_Style) { m_Parts.push_back(new cShowAchievementPart(a_PlayerName, a_Achievement, a_Style)); } void cCompositeChat::ParseText(const AString & a_ParseText) { size_t len = a_ParseText.length(); size_t first = 0; // First character of the currently parsed block AString CurrentStyle; AString CurrentText; for (size_t i = 0; i < len; i++) { switch (a_ParseText[i]) { case '@': { // Color code i++; if (i >= len) { // Not enough following text break; } if (a_ParseText[i] == '@') { // "@@" escape, just put a "@" into the current text and keep parsing as text if (i > first + 1) { CurrentText.append(a_ParseText.c_str() + first, i - first - 1); } first = i + 1; continue; } else { // True color code. Create a part for the CurrentText and start parsing anew: if (i >= first) { CurrentText.append(a_ParseText.c_str() + first, i - first - 1); first = i + 1; } if (!CurrentText.empty()) { m_Parts.push_back(new cTextPart(CurrentText, CurrentStyle)); CurrentText.clear(); } AddStyle(CurrentStyle, a_ParseText.substr(i - 1, 2)); } break; } case ':': { const char * LinkPrefixes[] = { "http", "https" }; for (size_t Prefix = 0; Prefix < ARRAYCOUNT(LinkPrefixes); Prefix++) { size_t PrefixLen = strlen(LinkPrefixes[Prefix]); if ( (i >= first + PrefixLen) && // There is enough space in front of the colon for the prefix (strncmp(a_ParseText.c_str() + i - PrefixLen, LinkPrefixes[Prefix], PrefixLen) == 0) // the prefix matches ) { // Add everything before this as a text part: if (i > first + PrefixLen) { CurrentText.append(a_ParseText.c_str() + first, i - first - PrefixLen); first = i - PrefixLen; } if (!CurrentText.empty()) { AddTextPart(CurrentText, CurrentStyle); CurrentText.clear(); } // Go till the last non-whitespace char in the text: for (; i < len; i++) { if (isspace(a_ParseText[i])) { break; } } AddUrlPart(a_ParseText.substr(first, i - first), a_ParseText.substr(first, i - first), CurrentStyle); first = i; break; } } // for Prefix - LinkPrefix[] break; } // case ':' } // switch (a_ParseText[i]) } // for i - a_ParseText[] if (first < len) { AddTextPart(a_ParseText.substr(first, len - first), CurrentStyle); } } void cCompositeChat::SetMessageType(eMessageType a_MessageType, const AString & a_AdditionalMessageTypeData) { m_MessageType = a_MessageType; m_AdditionalMessageTypeData = a_AdditionalMessageTypeData; } void cCompositeChat::UnderlineUrls(void) { for (cParts::iterator itr = m_Parts.begin(), end = m_Parts.end(); itr != end; ++itr) { if ((*itr)->m_PartType == ptUrl) { (*itr)->m_Style.append("u"); } } // for itr - m_Parts[] } AString cCompositeChat::ExtractText(void) const { AString Msg; for (cParts::const_iterator itr = m_Parts.begin(), end = m_Parts.end(); itr != end; ++itr) { switch ((*itr)->m_PartType) { case ptText: case ptClientTranslated: case ptRunCommand: case ptSuggestCommand: { Msg.append((*itr)->m_Text); break; } case ptUrl: { Msg.append(((cUrlPart *)(*itr))->m_Url); break; } } // switch (PartType) } // for itr - m_Parts[] return Msg; } cLogger::eLogLevel cCompositeChat::MessageTypeToLogLevel(eMessageType a_MessageType) { switch (a_MessageType) { case mtCustom: return cLogger::llRegular; case mtFailure: return cLogger::llWarning; case mtInformation: return cLogger::llInfo; case mtSuccess: return cLogger::llRegular; case mtWarning: return cLogger::llWarning; case mtFatal: return cLogger::llError; case mtDeath: return cLogger::llRegular; case mtPrivateMessage: return cLogger::llRegular; case mtJoin: return cLogger::llRegular; case mtLeave: return cLogger::llRegular; } ASSERT(!"Unhandled MessageType"); return cLogger::llError; } void cCompositeChat::AddStyle(AString & a_Style, const AString & a_AddStyle) { if (a_AddStyle.empty()) { return; } if (a_AddStyle[0] == '@') { size_t idx = a_Style.find('@'); if ((idx != AString::npos) && (idx != a_Style.length())) { a_Style.erase(idx, 2); } a_Style.append(a_AddStyle); return; } a_Style.append(a_AddStyle); } //////////////////////////////////////////////////////////////////////////////// // cCompositeChat::cBasePart: cCompositeChat::cBasePart::cBasePart(cCompositeChat::ePartType a_PartType, const AString & a_Text, const AString & a_Style) : m_PartType(a_PartType), m_Text(a_Text), m_Style(a_Style) { } //////////////////////////////////////////////////////////////////////////////// // cCompositeChat::cTextPart: cCompositeChat::cTextPart::cTextPart(const AString & a_Text, const AString &a_Style) : super(ptText, a_Text, a_Style) { } //////////////////////////////////////////////////////////////////////////////// // cCompositeChat::cClientTranslatedPart: cCompositeChat::cClientTranslatedPart::cClientTranslatedPart(const AString & a_TranslationID, const AStringVector & a_Parameters, const AString & a_Style) : super(ptClientTranslated, a_TranslationID, a_Style), m_Parameters(a_Parameters) { } //////////////////////////////////////////////////////////////////////////////// // cCompositeChat::cUrlPart: cCompositeChat::cUrlPart::cUrlPart(const AString & a_Text, const AString & a_Url, const AString & a_Style) : super(ptUrl, a_Text, a_Style), m_Url(a_Url) { } //////////////////////////////////////////////////////////////////////////////// // cCompositeChat::cCommandPart: cCompositeChat::cCommandPart::cCommandPart(ePartType a_PartType, const AString & a_Text, const AString & a_Command, const AString & a_Style) : super(a_PartType, a_Text, a_Style), m_Command(a_Command) { } //////////////////////////////////////////////////////////////////////////////// // cCompositeChat::cRunCommandPart: cCompositeChat::cRunCommandPart::cRunCommandPart(const AString & a_Text, const AString & a_Command, const AString & a_Style) : super(ptRunCommand, a_Text, a_Command, a_Style) { } //////////////////////////////////////////////////////////////////////////////// // cCompositeChat::cSuggestCommandPart: cCompositeChat::cSuggestCommandPart::cSuggestCommandPart(const AString & a_Text, const AString & a_Command, const AString & a_Style) : super(ptSuggestCommand, a_Text, a_Command, a_Style) { } //////////////////////////////////////////////////////////////////////////////// // cCompositeChat::cShowAchievementPart: cCompositeChat::cShowAchievementPart::cShowAchievementPart(const AString & a_PlayerName, const AString & a_Achievement, const AString & a_Style) : super(ptShowAchievement, a_Achievement, a_Style), m_PlayerName(a_PlayerName) { }