Support for IRC control codes.

The following IRC control codes are now supported:
* Bold
* Italics
* Underline
* Normal (clears all outstanding codes)
* Color (foreground and background)
* 16 colors (00 up to and including 15)
fix-message-formatting
Danny van Heumen 12 years ago
parent 9241928247
commit 16f5f552d1

@ -0,0 +1,56 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license. See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.irc;
/**
* IRC color codes that can be specified in the color control code.
*
* @author Danny van Heumen
*/
public enum Color
{
WHITE("White"),
BLACK("Black"),
BLUE("Navy"),
GREEN("Green"),
RED("Red"),
BROWN("Maroon"),
PURPLE("Purple"),
ORANGE("Orange"),
YELLOW("Yellow"),
LIGHT_GREEN("Lime"),
TEAL("Teal"),
LIGHT_CYAN("Cyan"),
LIGHT_BLUE("RoyalBlue"),
PINK("Fuchsia"),
GREY("Grey"),
LIGHT_GREY("Silver");
/**
* Instance containing the html representation of this color.
*/
private String html;
/**
* Constructor for enum entries.
*
* @param html HTML representation for color
*/
private Color(String html)
{
this.html = html;
}
/**
* Get the HTML representation of this color.
*
* @return returns html representation or null if none exist
*/
public String getHtml()
{
return this.html;
}
}

@ -0,0 +1,96 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license. See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.irc;
/**
* Enum with available IRC control characters.
*
* @author Danny van Heumen
*/
public enum ControlChar
{
BOLD('\u0002', "b"),
COLOR('\u0003', "font"),
NORMAL('\u000F', null),
ITALICS('\u001D', "i"),
UNDERLINE('\u001F', "u");
/**
* The IRC control code.
*/
private char code;
/**
* HTML tag that expresses the specific formatting requirement.
*/
private String tag;
/**
* Constructor.
*
* @param code the control code
*/
private ControlChar(char code, String htmlTag)
{
this.code = code;
this.tag = htmlTag;
}
/**
* Find enum instance by IRC control code.
*
* @param code IRC control code
* @return returns enum instance or null if no instance was found
*/
public static ControlChar byCode(char code)
{
for (ControlChar controlChar : values())
{
if (controlChar.getCode() == code)
return controlChar;
}
return null;
}
/**
* Get the IRC control code.
*
* @return returns the IRC control code
*/
public char getCode()
{
return this.code;
}
/**
* Get the HTML start tag, optionally including extra parameters.
*
* @param addition optional addition to be included before closing the start
* tag
* @return returns HTML start tag.
*/
public String getHtmlStart(String... addition)
{
StringBuilder tag = new StringBuilder("<" + this.tag);
for (String add : addition)
{
tag.append(" ");
tag.append(add);
}
tag.append('>');
return tag.toString();
}
/**
* Get the HTML end tag.
*
* @return returns the HTML end tag
*/
public String getHtmlEnd()
{
return "</" + this.tag + ">";
}
}

@ -833,7 +833,7 @@ private void deliverReceivedMessageToPrivateChat(
{
ChatRoomMember member = chatroom.getChatRoomMember(user);
MessageIrcImpl message =
new MessageIrcImpl(text, "text/plain", "UTF-8", null);
new MessageIrcImpl(text, "text/html", "UTF-8", null);
chatroom.fireMessageReceivedEvent(message, member, new Date(),
ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED);
}
@ -1071,7 +1071,7 @@ public void onChannelMessage(ChannelPrivMsg msg)
String text = Utils.parse(msg.getText());
MessageIrcImpl message =
new MessageIrcImpl(text, "text/plain", "UTF-8", null);
new MessageIrcImpl(text, "text/html", "UTF-8", null);
ChatRoomMemberIrcImpl member =
new ChatRoomMemberIrcImpl(IrcStack.this.provider,
this.chatroom, msg.getSource().getNick(),

@ -5,6 +5,10 @@
*/
package net.java.sip.communicator.impl.protocol.irc;
import java.util.*;
import net.java.sip.communicator.util.*;
/**
* Some IRC-related utility methods.
*
@ -13,11 +17,9 @@
public final class Utils
{
/**
* Starting value of HTML entities.
*
* The first 32 chars cannot be converted into HTML entities.
* Logger
*/
private static final int START_OF_HTML_ENTITIES = 32;
private static final Logger LOGGER = Logger.getLogger(Utils.class);
/**
* Private constructor since we do not need to construct anything.
@ -29,40 +31,178 @@ private Utils()
/**
* Parse IRC text message and process possible control codes.
*
* 1. Remove chars which have a value < 32, since there are no equivalent
* HTML entities available when storing them in the history log.
*
* TODO Support bold (0x02) control code.
* TODO Support italics (0x1D) control code.
* TODO Support underline (0x1F) control code.
* TODO Support reverse (0x16) control code?
* TODO Support color coding: 0x03<00-15>[,00-15]
*
* @param text message
* @param text the message
* @return returns the processed message or null if text message was null,
* since there is nothing to modify there
* since there is nothing to modify there
*/
public static String parse(String text)
{
if (text == null)
return null;
StringBuilder builder = new StringBuilder(text);
// TODO support IRC control codes for formatting (now only removes them)
final Stack<ControlChar> formatting = new Stack<ControlChar>();
for (int i = 0; i < builder.length(); )
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < text.length(); i++)
{
if (builder.charAt(i) < START_OF_HTML_ENTITIES)
char val = text.charAt(i);
ControlChar control = ControlChar.byCode(val);
if (control != null)
{
builder.deleteCharAt(i);
// value is a control character, so do something special
switch (control)
{
case ITALICS:
case UNDERLINE:
case BOLD:
if (formatting.size() > 0 && formatting.peek() == control)
{
formatting.pop();
builder.append(control.getHtmlEnd());
}
else
{
formatting.push(control);
builder.append(control.getHtmlStart());
}
break;
case NORMAL:
while (formatting.size() > 0)
{
ControlChar c = formatting.pop();
builder.append(c.getHtmlEnd());
}
break;
case COLOR:
if (formatting.size() > 0 && formatting.peek() == control)
{
formatting.pop();
builder.append(control.getHtmlEnd());
}
else
{
final List<String> adds = new LinkedList<String>();
try
{
// parse foreground color code
final Color foreground =
parseForegroundColor(text.substring(i + 1));
adds.add("color=\"" + foreground.getHtml() + "\"");
i += 2;
// parse background color code
final Color background =
parseBackgroundColor(text.substring(i + 1));
adds.add("bgcolor=\"" + background.getHtml() + "\"");
i += 3;
}
catch (IllegalArgumentException e)
{
LOGGER.debug("Invalid color code.", e);
}
formatting.push(control);
String htmlTag =
control.getHtmlStart(adds.toArray(new String[adds
.size()]));
builder.append(htmlTag);
}
break;
default:
LOGGER.warn("Unsupported IRC control code encountered: "
+ control);
break;
}
}
else
{
// nothing to do here, go to next char
i++;
// value is a normal character, just append
builder.append(val);
}
}
// Finish any outstanding formatting.
while (formatting.size() > 0)
{
builder.append(formatting.pop().getHtmlEnd());
}
return builder.toString();
}
/**
* Parse background color code starting with the separator.
*
* @param text the text starting with the background color (separator)
* @return returns the background color
*/
private static Color parseBackgroundColor(String text)
{
try
{
if (text.charAt(0) == ',')
{
// if available, also parse background color
int color =
Integer.parseInt("" + text.charAt(1) + text.charAt(2));
return Color.values()[color];
}
throw new IllegalArgumentException(
"no color separator present, hence no background color present");
}
catch (StringIndexOutOfBoundsException e)
{
// Abort parsing background color. Assume only
// foreground color available.
throw new IllegalArgumentException(
"text stopped before the background color code was finished");
}
catch (NumberFormatException e)
{
// No background color defined, ignoring ...
throw new IllegalArgumentException(
"value isn't a background color code");
}
catch (ArrayIndexOutOfBoundsException e)
{
LOGGER.info("Specified IRC color is not a known color code.", e);
throw new IllegalArgumentException(
"background color value is not a known color");
}
}
/**
* Parse foreground color and return corresponding Color instance.
*
* @param text the text to parse, starting with color code
* @return returns Color instance
*/
private static Color parseForegroundColor(String text)
{
try
{
int color = Integer.parseInt("" + text.charAt(0) + text.charAt(1));
return Color.values()[color];
}
catch (StringIndexOutOfBoundsException e)
{
// Invalid control code, since text has ended.
LOGGER
.trace("ArrayIndexOutOfBounds during foreground color control code parsing.");
throw new IllegalArgumentException("missing foreground color code");
}
catch (NumberFormatException e)
{
// Invalid text color value
LOGGER.trace("Invalid foreground color code encountered.");
throw new IllegalArgumentException("invalid foreground color code");
}
catch (ArrayIndexOutOfBoundsException e)
{
LOGGER.info("Specified IRC color is not a known color code.", e);
throw new IllegalArgumentException(
"foreground color value is not a known color");
}
}
}

@ -0,0 +1,31 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license. See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.irc;
import junit.framework.*;
/**
* @author Danny van Heumen
*/
public class ColorTest
extends TestCase
{
protected void setUp() throws Exception
{
super.setUp();
}
public void testHtmlRepresentation()
{
Assert.assertEquals("White", Color.WHITE.getHtml());
}
protected void tearDown() throws Exception
{
super.tearDown();
}
}

@ -0,0 +1,52 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license. See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.irc;
import junit.framework.*;
/**
* @author Danny van Heumen
*/
public class ControlCharTest
extends TestCase
{
protected void setUp() throws Exception
{
super.setUp();
}
protected void tearDown() throws Exception
{
super.tearDown();
}
public void testFindByControlCharUnknown()
{
Assert.assertNull(ControlChar.byCode(' '));
}
public void testFindByControlCharBold()
{
Assert.assertSame(ControlChar.BOLD, ControlChar.byCode('\u0002'));
}
public void testGetHtmlStartSimple()
{
Assert.assertEquals("<b>", ControlChar.BOLD.getHtmlStart());
}
public void testGetHtmlStartAdvanced()
{
Assert.assertEquals("<b bla=\"foo\">",
ControlChar.BOLD.getHtmlStart("bla=\"foo\""));
}
public void testGetHtmlEnd()
{
Assert.assertEquals("</b>", ControlChar.BOLD.getHtmlEnd());
}
}

@ -1,7 +1,15 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license. See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.irc;
import junit.framework.*;
/**
* @author Danny van Heumen
*/
public class UtilsTest
extends TestCase
{
@ -10,7 +18,7 @@ protected void setUp() throws Exception
{
super.setUp();
}
protected void tearDown() throws Exception
{
super.tearDown();
@ -20,50 +28,129 @@ public void testNullText()
{
Assert.assertEquals(null, Utils.parse(null));
}
public void testParseEmptyString()
{
Assert.assertEquals("", Utils.parse(""));
}
public void testParseStringWithoutControlCodes()
{
final String message = "My normal message without any control codes.";
Assert.assertEquals(message, Utils.parse(message));
}
public void testParseStringWithBoldCode()
{
final String ircMessage = "My \u0002bold\u0002 message.";
final String htmlMessage = "My <b>bold</b> message.";
final String ircMessage =
"My \u0002bold\u0002 message \u0002BOLD!\u0002.";
final String htmlMessage = "My <b>bold</b> message <b>BOLD!</b>.";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseStringWithItalicsCode()
{
final String ircMessage = "My \u001Ditalics\u001D message.";
final String htmlMessage = "My <i>italics</i> message.";
final String ircMessage =
"My \u001Ditalics\u001D message \u001DITALICS!\u001D.";
final String htmlMessage = "My <i>italics</i> message <i>ITALICS!</i>.";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseStringWithUnderlineCode()
{
final String ircMessage = "My \u001Funderlined\u001F message.";
final String htmlMessage = "My <u>italics</u> message.";
final String ircMessage =
"My \u001Funderlined\u001F message \u001FUNDERLINED!!!\u001F.";
final String htmlMessage =
"My <u>underlined</u> message <u>UNDERLINED!!!</u>.";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseStringWithForegroundColorCode()
{
final String ircMessage = "My \u000304RED\u0003 message.";
final String htmlMessage = "My <font color=\"red\">RED</font> message.";
final String htmlMessage = "My <font color=\"Red\">RED</font> message.";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseStringWithForegroundAndBackgroundColorCode()
{
final String ircMessage = "My \u000304,12RED on Light Blue\u0003 message.";
final String htmlMessage = "My <font color=\"red\" bgcolor=\"lightblue\">RED on Light Blue</font> message.";
final String ircMessage =
"My \u000304,12RED on Light Blue\u0003 message.";
final String htmlMessage =
"My <font color=\"Red\" bgcolor=\"RoyalBlue\">RED on Light Blue</font> message.";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseStringWithInvalidBgColorCode()
{
final String ircMessage =
"My \u000304,BRIGHT RED on Light Blue\u0003 message.";
final String htmlMessage =
"My <font color=\"Red\">,BRIGHT RED on Light Blue</font> message.";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseStringWithInvalidColorControlCode()
{
final String ircMessage =
"My \u0003BRIGHT RED on Light Blue\u0003 message.";
final String htmlMessage =
"My <font>BRIGHT RED on Light Blue</font> message.";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseStringWithInvalidSecondControlCode()
{
final String ircMessage =
"My \u000304,12RED on Light Blue\u000304,12 message.";
final String htmlMessage =
"My <font color=\"Red\" bgcolor=\"RoyalBlue\">RED on Light Blue</font>04,12 message.";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseStringWithIncompleteForegroundColorControlCode()
{
final String ircMessage = "My \u0003";
final String htmlMessage = "My <font>";
Assert.assertTrue(Utils.parse(ircMessage).startsWith(htmlMessage));
}
public void testParseStringWithIncompleteBackgroundColorControlCode()
{
final String ircMessage = "My \u000310,";
final String htmlMessage = "My <font color=\"Teal\">,";
Assert.assertTrue(Utils.parse(ircMessage).startsWith(htmlMessage));
}
public void testParseSringAndNeutralizeWithNormalControlCode()
{
final String ircMessage =
"My \u0002\u001D\u001F\u000304,12RED on Light Blue\u000F message.";
final String htmlMessage =
"My <b><i><u><font color=\"Red\" bgcolor=\"RoyalBlue\">RED on Light Blue</font></u></i></b> message.";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseStringWithUnclosedFormattingI()
{
final String ircMessage =
"My \u0002\u001D\u001F\u000304,12RED on Light Blue message.";
final String htmlMessage =
"My <b><i><u><font color=\"Red\" bgcolor=\"RoyalBlue\">RED on Light Blue message.</font></u></i></b>";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseUnknownForegroundColor()
{
final String ircMessage = "\u000399TEST";
final String htmlMessage = "<font>99TEST</font>";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
public void testParseUnknownBackgroundCOlor()
{
final String ircMessage = "\u000300,99TEST";
final String htmlMessage = "<font color=\"White\">,99TEST</font>";
Assert.assertEquals(htmlMessage, Utils.parse(ircMessage));
}
}

Loading…
Cancel
Save