From aebe05fed347da328cac72e3f4be64f8ac050cf9 Mon Sep 17 00:00:00 2001 From: Danny van Heumen Date: Sat, 23 Aug 2014 01:58:09 +0200 Subject: [PATCH] Replaced various processing methods with separate implementation. --- .../gui/main/chat/ChatConversationPanel.java | 547 +++++++++--------- 1 file changed, 284 insertions(+), 263 deletions(-) diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java index 452257a2b..8bb5163e8 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatConversationPanel.java @@ -577,8 +577,7 @@ else if (messageType.equals(Chat.STATUS_MESSAGE)) chatString += GuiUtils.formatTime(date) + " " - + processLinksAndHTMLChars(contactName, true, - ChatHtmlUtils.TEXT_CONTENT_TYPE) + " " + + StringEscapeUtils.escapeHtml4(contactName) + " " + formatMessageAsHTML(message, contentType, keyword) + endHeaderTag; } @@ -589,8 +588,7 @@ else if (messageType.equals(Chat.ACTION_MESSAGE)) endHeaderTag = "

"; chatString += "* " + GuiUtils.formatTime(date) - + " " + processLinksAndHTMLChars(contactName, true, - ChatHtmlUtils.TEXT_CONTENT_TYPE) + " " + + " " + StringEscapeUtils.escapeHtml4(contactName) + " " + formatMessageAsHTML(message, contentType, keyword) + endHeaderTag; } @@ -623,10 +621,8 @@ else if (messageType.equals(Chat.ERROR_MESSAGE)) if (messageTitle != null) { chatString += - errorIcon - + processLinksAndHTMLChars(messageTitle, true, - ChatHtmlUtils.TEXT_CONTENT_TYPE) + endHeaderTag - + "
" + errorIcon + StringEscapeUtils.escapeHtml4(messageTitle) + + endHeaderTag + "
" + formatMessageAsHTML(message, contentType, keyword) + "
"; } @@ -1021,55 +1017,13 @@ private void deleteAllMessagesWithoutHeader() deleteAllMessagesWithoutHeader(); } - /** - * Highlights keywords searched in the history. - * - * @param message the source message - * @param contentType the content type - * @param keyword the searched keyword - * @return the formatted message - */ - private String processKeyword(final String message, - final String contentType, final String keyword) - { - if(message == null) - return message; - - Matcher m - = Pattern.compile(Pattern.quote(keyword), Pattern.CASE_INSENSITIVE) - .matcher(message); - StringBuffer msgBuffer = new StringBuffer(); - int prevEnd = 0; - - while (m.find()) - { - msgBuffer.append(StringEscapeUtils.escapeHtml4(message.substring( - prevEnd, m.start()))); - prevEnd = m.end(); - - String keywordMatch = m.group().trim(); - - msgBuffer.append(""); - msgBuffer.append(StringEscapeUtils.escapeHtml4(keywordMatch)); - msgBuffer.append(""); - } - - /* - * If the keyword didn't match, let the outside world be able to - * discover it. - */ - if (prevEnd == 0) - return message; - - msgBuffer.append(StringEscapeUtils.escapeHtml4(message - .substring(prevEnd))); - return msgBuffer.toString(); - } - /** * Formats the given message. Processes all smiley chars, new lines and * links. * + * TODO correctly name this method: we only expect content in either HTML or + * PLAIN TEXT format. We DON'T WANT THE HEADER STUFF OF A HTML MESSAGE!!! + * * @param message the message to be formatted * @param contentType the content type of the message to be formatted * @param keyword the word to be highlighted @@ -1079,182 +1033,320 @@ private String formatMessageAsHTML(final String original, final String contentType, final String keyword) { - if(original == null) + if (original == null) + { return ""; + } - String message = original; + // prepare initial message source + String source; + if (ChatHtmlUtils.HTML_CONTENT_TYPE.equals(contentType)) + { + source = original; + } + else + { + source = StringEscapeUtils.escapeHtml4(original); + } - // If the message content type is HTML we won't process links and - // new lines, but only the smileys. - if (!ChatHtmlUtils.HTML_CONTENT_TYPE.equals(contentType)) + final Replacer[] replacers = new Replacer[] { + new NewlineReplacer(), + new URLReplacer(), + new KeywordReplacer(keyword), + new BrTagReplacer(), + new ImgTagReplacer() + }; - /* - * We disallow HTML in plain-text messages. But processKeyword - * introduces HTML. So we'll allow HTML if processKeyword has - * introduced it in order to not break highlighting. - */ - boolean processHTMLChars; + return processReplacers(source, replacers); + } - if ((keyword != null) && (keyword.length() != 0)) + // FIXME decent comments + private String processReplacers(final String content, final Replacer... replacers) + { + StringBuilder source = new StringBuilder(content); + for (Replacer replacer : replacers) + { + final StringBuilder target = new StringBuilder(); + if (replacer.expectsPlainText()) { - // TODO Doesn't replacing keywords first cause hyperlinks to be - // broken if the keyword is in the hyperlink? Maybe we should - // insert anchors first, highlighting keywords. - String messageWithProcessedKeyword - = processKeyword(message, contentType, keyword); - - /* - * The same String instance will be returned if there was no - * keyword match. Calling #equals() is expensive so == is - * intentional. - */ - processHTMLChars = (messageWithProcessedKeyword == message); - message = messageWithProcessedKeyword; + int startPos = 0; + final Matcher plainTextInHtmlMatcher = + TEXT_TO_REPLACE_PATTERN.matcher(source); + while (plainTextInHtmlMatcher.find()) + { + final String plainTextAsHtml = + plainTextInHtmlMatcher.group(1); + final int startMatchPosition = + plainTextInHtmlMatcher.start(1); + final int endMatchPosition = plainTextInHtmlMatcher.end(1); + target.append(source + .substring(startPos, startMatchPosition)); + final String plaintext = + StringEscapeUtils.unescapeHtml4(plainTextAsHtml); + + // Invoke replacer. + try + { + replacer.replace(target, plaintext); + } + catch (RuntimeException e) + { + logger.error("An error occurred in replacer: " + + replacer.getClass().getName(), e); + } + + startPos = endMatchPosition; + } + target.append(source.substring(startPos)); } else - processHTMLChars = true; + { + // Invoke replacer. + try + { + replacer.replace(target, source.toString()); + } + catch (RuntimeException e) + { + logger.error("An error occurred in replacer: " + + replacer.getClass().getName(), e); + } + } + source = target; + } + return source.toString(); + } + + // TODO check all processors for correct html escaping! + // FIXME decent comments + private static abstract class Replacer + { + /** + * If a replacer expects plain text strings, then html content is + * automatically unescaped. The replacer is responsible for correctly + * escaping normal text. + * + * @return returns true if it needs plain text or false if it wants html + * content + */ + abstract boolean expectsPlainText(); + // FIXME javadoc + abstract void replace(StringBuilder target, String piece); + } - message = processNewLines(processLinksAndHTMLChars( - message, processHTMLChars, contentType), contentType); + private static final class URLReplacer + extends Replacer + { + + @Override + boolean expectsPlainText() + { + return true; } - // If the message content is HTML, we process br and img tags. - else + + @Override + void replace(final StringBuilder target, final String piece) { - // For HTML message, also check for hyperlinks. - int startPos = 0; - final StringBuilder buff = new StringBuilder(); - final Matcher plainTextInHtmlMatcher = - TEXT_TO_REPLACE_PATTERN.matcher(message); - while (plainTextInHtmlMatcher.find()) + final Matcher m = URL_PATTERN.matcher(piece); + int prevEnd = 0; + + while (m.find()) { - final String plainTextAsHtml = plainTextInHtmlMatcher.group(1); - final int startMatchPosition = plainTextInHtmlMatcher.start(1); - final int endMatchPosition = plainTextInHtmlMatcher.end(1); + target.append(StringEscapeUtils.escapeHtml4(piece.substring( + prevEnd, m.start()))); + prevEnd = m.end(); - if (!StringUtils.isNullOrEmpty(plainTextAsHtml)) + String url = m.group().trim(); + target.append(""); + target.append(StringEscapeUtils.escapeHtml4(url)); + target.append(""); } - buff.append(message.substring(startPos)); - message = buff.toString(); + target.append(StringEscapeUtils.escapeHtml4(piece + .substring(prevEnd))); + } + } + + private static final class NewlineReplacer + extends Replacer + { - if ((keyword != null) && (keyword.length() != 0)) - message = processKeyword(message, contentType, keyword); - message = processImgTags(processBrTags(message)); + @Override + boolean expectsPlainText() + { + return false; } - return message; + @Override + void replace(final StringBuilder target, final String piece) + { + /* + *
tags are needed to visualize a new line in the html format, + * but when copied to the clipboard they are exported to the plain + * text format as ' ' and not as '\n'. + * + * See bug N4988885: + * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988885 + * + * To fix this we need " " - the HTML-Code for ASCII-Character + * No.10 (Line feed). + */ + target.append(piece.replaceAll("\n", "
")); + } } - /** - * Formats all links in a given message and optionally escapes special HTML - * characters such as <, >, & and " in order to prevent HTML - * injection in plain-text messages such as writing - * </PLAINTEXT>, HTML which is going to be rendered as - * such and <PLAINTEXT>. The two procedures are carried - * out in one call in order to not break URLs which contain special HTML - * characters such as &. - * - * @param message The source message string. - * @param processHTMLChars true to escape the special HTML chars; - * otherwise, false - * @param contentType the message content type (html or plain text) - * @return The message string with properly formatted links. - */ - private String processLinksAndHTMLChars(final String message, - final boolean processHTMLChars, - final String contentType) + private static final class KeywordReplacer + extends Replacer { - Matcher m = URL_PATTERN.matcher(message); - StringBuffer msgBuffer = new StringBuffer(); - int prevEnd = 0; + private final String keyword; - while (m.find()) + private KeywordReplacer(final String keyword) + { + this.keyword = keyword; + } + + @Override + boolean expectsPlainText() { - final String rawMessage = message.substring(prevEnd, m.start()); - final String fromPrevEndToStart; - if (processHTMLChars) + return true; + } + + @Override + void replace(final StringBuilder target, final String piece) + { + if (this.keyword == null || this.keyword.isEmpty()) { - fromPrevEndToStart = StringEscapeUtils.escapeHtml4(rawMessage); + target.append(StringEscapeUtils.escapeHtml4(piece)); + return; } - else + + final Matcher m = + Pattern.compile(Pattern.quote(keyword), + Pattern.CASE_INSENSITIVE).matcher(piece); + int prevEnd = 0; + while (m.find()) { - fromPrevEndToStart = rawMessage; + target.append(StringEscapeUtils.escapeHtml4(piece.substring( + prevEnd, m.start()))); + prevEnd = m.end(); + final String keywordMatch = m.group().trim(); + target.append(""); + target.append(StringEscapeUtils.escapeHtml4(keywordMatch)); + target.append(""); } - msgBuffer.append(fromPrevEndToStart); - prevEnd = m.end(); + target.append(StringEscapeUtils.escapeHtml4(piece + .substring(prevEnd))); + } + } - String url = m.group().trim(); + private static final class BrTagReplacer extends Replacer { - msgBuffer.append(""); - msgBuffer.append(StringEscapeUtils.escapeHtml4(url)); - msgBuffer.append(""); + @Override + boolean expectsPlainText() + { + return false; } - final String rawMessage = message.substring(prevEnd); - final String fromPrevEndToEnd; - if (processHTMLChars) + @Override + void replace(final StringBuilder target, final String piece) { - fromPrevEndToEnd = StringEscapeUtils.escapeHtml4(rawMessage); + // Compile the regex to match something like
or
. + // This regex is case sensitive and keeps the style or other + // attributes of the
tag. + Matcher m = + Pattern.compile("<\\s*[bB][rR](.*?)(/\\s*>)").matcher(piece); + int start = 0; + + // while we find some
closing tags with a slash inside. + while (m.find()) + { + // First, we have to copy all the message preceding the
+ // tag. + target.append(piece.substring(start, m.start())); + // Then, we find the position of the slash inside the tag. + final int slashIndex = m.group().lastIndexOf("/"); + // We copy the
tag till the slash exclude. + target.append(m.group().substring(0, slashIndex)); + // We copy all the end of the tag following the slash exclude. + target.append(m.group().substring(slashIndex + 1)); + start = m.end(); + } + // Finally, we have to add the end of the message following the last + //
tag, or the whole message if there is no
tag. + target.append(piece.substring(start)); } - else + } + + private static final class ImgTagReplacer + extends Replacer + { + + @Override + boolean expectsPlainText() { - fromPrevEndToEnd = rawMessage; + return false; } - msgBuffer.append(fromPrevEndToEnd); + @Override + void replace(final StringBuilder target, final String piece) + { + // Compile the regex to match something like or + // . This regex is case sensitive and keeps the style, + // src or other attributes of the tag. + final Pattern p = Pattern.compile("<\\s*[iI][mM][gG](.*?)(/\\s*>)"); + final Matcher m = p.matcher(piece); + int slashIndex; + int start = 0; - return msgBuffer.toString(); + // while we find some self-closing tags with a slash inside. + while (m.find()) + { + // First, we have to copy all the message preceding the + // tag. + target.append(piece.substring(start, m.start())); + // Then, we find the position of the slash inside the tag. + slashIndex = m.group().lastIndexOf("/"); + // We copy the tag till the slash exclude. + target.append(m.group().substring(0, slashIndex)); + // We copy all the end of the tag following the slash exclude. + target.append(m.group().substring(slashIndex + 1)); + // We close the tag with a separate closing tag. + target.append(""); + start = m.end(); + } + // Finally, we have to add the end of the message following the last + // tag, or the whole message if there is no tag. + target.append(piece.substring(start)); + } } + // FIXME delete this after everything works /** - * Formats message new lines. + * Formats all links in a given message and optionally escapes special HTML + * characters such as <, >, & and " in order to prevent HTML + * injection in plain-text messages such as writing + * </PLAINTEXT>, HTML which is going to be rendered as + * such and <PLAINTEXT>. The two procedures are carried + * out in one call in order to not break URLs which contain special HTML + * characters such as &. * * @param message The source message string. - * @param contentType message contentType (html or plain text) - * @return The message string with properly formatted new lines. + * @param processHTMLChars true to escape the special HTML chars; + * otherwise, false + * @param contentType the message content type (html or plain text) + * @return The message string with properly formatted links. */ - private String processNewLines(String message, String contentType) - { - - /* - *
tags are needed to visualize a new line in the html format, but - * when copied to the clipboard they are exported to the plain text - * format as ' ' and not as '\n'. - * - * See bug N4988885: - * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988885 - * - * To fix this we need " " - the HTML-Code for ASCII-Character No.10 - * (Line feed). - */ - Matcher divMatcher = DIV_PATTERN.matcher(message); - String openingTag = ""; - String closingTag = ""; - if (divMatcher.find()) - { - openingTag = divMatcher.group(1); - message = divMatcher.group(2); - closingTag = divMatcher.group(3); - } - return openingTag + message.replaceAll("\n", "
") + closingTag; - } +// processLinksAndHTMLChars(final String message, +// final boolean processHTMLChars, +// final String contentType) +// { +// } /** * Opens a link in the default browser when clicked and shows link url in a @@ -1552,6 +1644,7 @@ public Date getPageLastMsgTimestamp() return timestamp; } + // FIXME delete this after everything works /** * Formats HTML tags <br/> to <br> or <BR/> to <BR>. * The reason of this function is that the ChatPanel does not support @@ -1560,38 +1653,11 @@ public Date getPageLastMsgTimestamp() * @param message The source message string. * @return The message string with properly formatted <br> tags. */ - private String processBrTags(String message) - { - // The resulting message after being processed by this function. - StringBuffer processedMessage = new StringBuffer(); - - // Compile the regex to match something like
or
. - // This regex is case sensitive and keeps the style or other - // attributes of the
tag. - Matcher m - = Pattern.compile("<\\s*[bB][rR](.*?)(/\\s*>)").matcher(message); - int start = 0; - - // while we find some
closing tags with a slash inside. - while(m.find()) - { - // First, we have to copy all the message preceding the
tag. - processedMessage.append(message.substring(start, m.start())); - // Then, we find the position of the slash inside the tag. - int slash_index = m.group().lastIndexOf("/"); - // We copy the
tag till the slash exclude. - processedMessage.append(m.group().substring(0, slash_index)); - // We copy all the end of the tag following the slash exclude. - processedMessage.append(m.group().substring(slash_index+1)); - start = m.end(); - } - // Finally, we have to add the end of the message following the last - //
tag, or the whole message if there is no
tag. - processedMessage.append(message.substring(start)); - - return processedMessage.toString(); - } +// processBrTags(String message) +// { +// } + // FIXME delete this after everything works /** * Formats HTML tags <img ... /> to < img ... ></img> or * <IMG ... /> to <IMG></IMG>. @@ -1602,40 +1668,9 @@ private String processBrTags(String message) * @param message The source message string. * @return The message string with properly formatted <img> tags. */ - private String processImgTags(String message) - { - // The resulting message after being processed by this function. - StringBuffer processedMessage = new StringBuffer(); - - // Compile the regex to match something like or - // . This regex is case sensitive and keeps the style, - // src or other attributes of the tag. - Pattern p = Pattern.compile("<\\s*[iI][mM][gG](.*?)(/\\s*>)"); - Matcher m = p.matcher(message); - int slash_index; - int start = 0; - - // while we find some self-closing tags with a slash inside. - while(m.find()) - { - // First, we have to copy all the message preceding the tag. - processedMessage.append(message.substring(start, m.start())); - // Then, we find the position of the slash inside the tag. - slash_index = m.group().lastIndexOf("/"); - // We copy the tag till the slash exclude. - processedMessage.append(m.group().substring(0, slash_index)); - // We copy all the end of the tag following the slash exclude. - processedMessage.append(m.group().substring(slash_index+1)); - // We close the tag with a separate closing tag. - processedMessage.append(""); - start = m.end(); - } - // Finally, we have to add the end of the message following the last - // tag, or the whole message if there is no tag. - processedMessage.append(message.substring(start)); - - return processedMessage.toString(); - } +// processImgTags(String message) +// { +// } /** * Extend Editor pane to add URL tooltips. @@ -1748,20 +1783,6 @@ public void loadSkin() getRightButtonMenu().loadSkin(); } - /** - * Highlights the string in multi user chat. - * - * @param message the message to process - * @param contentType the content type of the message - * @param keyWord the keyword to highlight - * @return the message string with the keyword highlighted - */ - public String processChatRoomHighlight(String message, String contentType, - String keyWord) - { - return processKeyword(message, contentType, keyWord); - } - /** * Processes /me command in group chats. *