mirror of
https://github.com/moparisthebest/Yaaic
synced 2024-11-25 10:22:16 -05:00
Render html in messages to spannables.
This commit is contained in:
parent
bd19398d65
commit
e612d3b09c
BIN
application/res/drawable/unknown_image.png
Normal file
BIN
application/res/drawable/unknown_image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 208 B |
@ -22,11 +22,15 @@ package org.yaaic.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.yaaic.utils.Html2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.widget.TextView;
|
||||
@ -225,8 +229,11 @@ public class Message
|
||||
String prefix = hasIcon() && settings.showIcons() ? " " : "";
|
||||
String nick = hasSender() ? "<" + sender + "> " : "";
|
||||
String timestamp = settings.showTimestamp() ? renderTimeStamp(settings.use24hFormat()) : "";
|
||||
Spanned colortext = Html2.fromHtml(text);
|
||||
|
||||
canvas = new SpannableString(prefix + timestamp + nick);
|
||||
canvas = new SpannableString(TextUtils.concat(canvas, colortext));
|
||||
|
||||
canvas = new SpannableString(prefix + timestamp + nick + text);
|
||||
|
||||
if (hasSender()) {
|
||||
int start = (prefix + timestamp).length() + 1;
|
||||
|
39
application/src/org/yaaic/utils/Colors.java
Normal file
39
application/src/org/yaaic/utils/Colors.java
Normal file
@ -0,0 +1,39 @@
|
||||
package org.yaaic.utils;
|
||||
|
||||
|
||||
public class Colors {
|
||||
/*
|
||||
* Colors from the "Classic" theme in mIRC.
|
||||
*/
|
||||
public static final String[] colors = {
|
||||
"#FFFFFF", // White
|
||||
"#000000", // Black
|
||||
"#00007F", // Blue (navy)
|
||||
"#009300", // Green
|
||||
"#FC0000", // Red
|
||||
"#7F0000", // Brown (maroon)
|
||||
"#9C009C", // Purple
|
||||
"#FC7F00", // Orange (olive)
|
||||
"#FFFF00", // Yellow
|
||||
"#00FC00", // Light Green (lime)
|
||||
"#008080", // Teal (a green/blue cyan)
|
||||
"#00FFFF", // Light Cyan (cyan) (aqua)
|
||||
"#0000FF", // Light Blue (royal)
|
||||
"#FF00FF", // Pink (light purple) (fuchsia)
|
||||
"#7F7F7F", // Grey
|
||||
"#D2D2D2" // Light Grey (silver)
|
||||
};
|
||||
public Colors() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string with mIRC color codes to a HTML string.
|
||||
*
|
||||
* @param text A string with mIRC color codes.
|
||||
* @return HTML string.
|
||||
*/
|
||||
public static String mircColorParser(String text) {
|
||||
return text;
|
||||
}
|
||||
}
|
970
application/src/org/yaaic/utils/Html2.java
Normal file
970
application/src/org/yaaic/utils/Html2.java
Normal file
@ -0,0 +1,970 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.yaaic.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.ccil.cowan.tagsoup.HTMLSchema;
|
||||
import org.ccil.cowan.tagsoup.Parser;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.ContentHandler;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.Locator;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
import org.yaaic.R;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Editable;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.AbsoluteSizeSpan;
|
||||
import android.text.style.AlignmentSpan;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.CharacterStyle;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.text.style.ParagraphStyle;
|
||||
import android.text.style.QuoteSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.SubscriptSpan;
|
||||
import android.text.style.SuperscriptSpan;
|
||||
import android.text.style.TextAppearanceSpan;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
|
||||
/**
|
||||
* This class processes HTML strings into displayable styled text.
|
||||
* Not all HTML tags are supported.
|
||||
*
|
||||
* The only difference between Html2 and android.text.Html is that Html2 adds support for
|
||||
* a bgcolor attribute in the font tag.
|
||||
*/
|
||||
public class Html2 {
|
||||
/**
|
||||
* Retrieves images for HTML <img> tags.
|
||||
*/
|
||||
public static interface ImageGetter {
|
||||
/**
|
||||
* This methos is called when the HTML parser encounters an
|
||||
* <img> tag. The <code>source</code> argument is the
|
||||
* string from the "src" attribute; the return value should be
|
||||
* a Drawable representation of the image or <code>null</code>
|
||||
* for a generic replacement image. Make sure you call
|
||||
* setBounds() on your Drawable if it doesn't already have
|
||||
* its bounds set.
|
||||
*/
|
||||
public Drawable getDrawable(String source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is notified when HTML tags are encountered that the parser does
|
||||
* not know how to interpret.
|
||||
*/
|
||||
public static interface TagHandler {
|
||||
/**
|
||||
* This method will be called whenn the HTML parser encounters
|
||||
* a tag that it does not know how to interpret.
|
||||
*/
|
||||
public void handleTag(boolean opening, String tag,
|
||||
Editable output, XMLReader xmlReader);
|
||||
}
|
||||
|
||||
private Html2() { }
|
||||
|
||||
/**
|
||||
* Returns displayable styled text from the provided HTML string.
|
||||
* Any <img> tags in the HTML will display as a generic
|
||||
* replacement image which your program can then go through and
|
||||
* replace with real images.
|
||||
*
|
||||
* <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild.
|
||||
*/
|
||||
public static Spanned fromHtml(String source) {
|
||||
return fromHtml(source, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy initialization holder for HTML parser. This class will
|
||||
* a) be preloaded by the zygote, or b) not loaded until absolutely
|
||||
* necessary.
|
||||
*/
|
||||
private static class HtmlParser {
|
||||
private static final HTMLSchema schema = new HTMLSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns displayable styled text from the provided HTML string.
|
||||
* Any <img> tags in the HTML will use the specified ImageGetter
|
||||
* to request a representation of the image (use null if you don't
|
||||
* want this) and the specified TagHandler to handle unknown tags
|
||||
* (specify null if you don't want this).
|
||||
*
|
||||
* <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild.
|
||||
*/
|
||||
public static Spanned fromHtml(String source, ImageGetter imageGetter,
|
||||
TagHandler tagHandler) {
|
||||
Parser parser = new Parser();
|
||||
try {
|
||||
parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
|
||||
} catch (org.xml.sax.SAXNotRecognizedException e) {
|
||||
// Should not happen.
|
||||
throw new RuntimeException(e);
|
||||
} catch (org.xml.sax.SAXNotSupportedException e) {
|
||||
// Should not happen.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
HtmlToSpannedConverter converter =
|
||||
new HtmlToSpannedConverter(source, imageGetter, tagHandler,
|
||||
parser);
|
||||
return converter.convert();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML representation of the provided Spanned text.
|
||||
*/
|
||||
public static String toHtml(Spanned text) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
withinHtml(out, text);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static void withinHtml(StringBuilder out, Spanned text) {
|
||||
int len = text.length();
|
||||
|
||||
int next;
|
||||
for (int i = 0; i < text.length(); i = next) {
|
||||
next = text.nextSpanTransition(i, len, ParagraphStyle.class);
|
||||
ParagraphStyle[] style = text.getSpans(i, next, ParagraphStyle.class);
|
||||
String elements = " ";
|
||||
boolean needDiv = false;
|
||||
|
||||
for(int j = 0; j < style.length; j++) {
|
||||
if (style[j] instanceof AlignmentSpan) {
|
||||
Layout.Alignment align =
|
||||
((AlignmentSpan) style[j]).getAlignment();
|
||||
needDiv = true;
|
||||
if (align == Layout.Alignment.ALIGN_CENTER) {
|
||||
elements = "align=\"center\" " + elements;
|
||||
} else if (align == Layout.Alignment.ALIGN_OPPOSITE) {
|
||||
elements = "align=\"right\" " + elements;
|
||||
} else {
|
||||
elements = "align=\"left\" " + elements;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needDiv) {
|
||||
out.append("<div " + elements + ">");
|
||||
}
|
||||
|
||||
withinDiv(out, text, i, next);
|
||||
|
||||
if (needDiv) {
|
||||
out.append("</div>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void withinDiv(StringBuilder out, Spanned text,
|
||||
int start, int end) {
|
||||
int next;
|
||||
for (int i = start; i < end; i = next) {
|
||||
next = text.nextSpanTransition(i, end, QuoteSpan.class);
|
||||
QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class);
|
||||
|
||||
for (QuoteSpan quote: quotes) {
|
||||
out.append("<blockquote>");
|
||||
}
|
||||
|
||||
withinBlockquote(out, text, i, next);
|
||||
|
||||
for (QuoteSpan quote: quotes) {
|
||||
out.append("</blockquote>\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void withinBlockquote(StringBuilder out, Spanned text,
|
||||
int start, int end) {
|
||||
out.append("<p>");
|
||||
|
||||
int next;
|
||||
for (int i = start; i < end; i = next) {
|
||||
next = TextUtils.indexOf(text, '\n', i, end);
|
||||
if (next < 0) {
|
||||
next = end;
|
||||
}
|
||||
|
||||
int nl = 0;
|
||||
|
||||
while (next < end && text.charAt(next) == '\n') {
|
||||
nl++;
|
||||
next++;
|
||||
}
|
||||
|
||||
withinParagraph(out, text, i, next - nl, nl, next == end);
|
||||
}
|
||||
|
||||
out.append("</p>\n");
|
||||
}
|
||||
|
||||
private static void withinParagraph(StringBuilder out, Spanned text,
|
||||
int start, int end, int nl,
|
||||
boolean last) {
|
||||
int next;
|
||||
for (int i = start; i < end; i = next) {
|
||||
next = text.nextSpanTransition(i, end, CharacterStyle.class);
|
||||
CharacterStyle[] style = text.getSpans(i, next,
|
||||
CharacterStyle.class);
|
||||
|
||||
for (int j = 0; j < style.length; j++) {
|
||||
if (style[j] instanceof StyleSpan) {
|
||||
int s = ((StyleSpan) style[j]).getStyle();
|
||||
|
||||
if ((s & Typeface.BOLD) != 0) {
|
||||
out.append("<b>");
|
||||
}
|
||||
if ((s & Typeface.ITALIC) != 0) {
|
||||
out.append("<i>");
|
||||
}
|
||||
}
|
||||
if (style[j] instanceof TypefaceSpan) {
|
||||
String s = ((TypefaceSpan) style[j]).getFamily();
|
||||
|
||||
if (s.equals("monospace")) {
|
||||
out.append("<tt>");
|
||||
}
|
||||
}
|
||||
if (style[j] instanceof SuperscriptSpan) {
|
||||
out.append("<sup>");
|
||||
}
|
||||
if (style[j] instanceof SubscriptSpan) {
|
||||
out.append("<sub>");
|
||||
}
|
||||
if (style[j] instanceof UnderlineSpan) {
|
||||
out.append("<u>");
|
||||
}
|
||||
if (style[j] instanceof StrikethroughSpan) {
|
||||
out.append("<strike>");
|
||||
}
|
||||
if (style[j] instanceof URLSpan) {
|
||||
out.append("<a href=\"");
|
||||
out.append(((URLSpan) style[j]).getURL());
|
||||
out.append("\">");
|
||||
}
|
||||
if (style[j] instanceof ImageSpan) {
|
||||
out.append("<img src=\"");
|
||||
out.append(((ImageSpan) style[j]).getSource());
|
||||
out.append("\">");
|
||||
|
||||
// Don't output the dummy character underlying the image.
|
||||
i = next;
|
||||
}
|
||||
if (style[j] instanceof AbsoluteSizeSpan) {
|
||||
out.append("<font size =\"");
|
||||
out.append(((AbsoluteSizeSpan) style[j]).getSize() / 6);
|
||||
out.append("\">");
|
||||
}
|
||||
if (style[j] instanceof ForegroundColorSpan) {
|
||||
out.append("<font color =\"#");
|
||||
String color = Integer.toHexString(((ForegroundColorSpan)
|
||||
style[j]).getForegroundColor() + 0x01000000);
|
||||
while (color.length() < 6) {
|
||||
color = "0" + color;
|
||||
}
|
||||
out.append(color);
|
||||
out.append("\">");
|
||||
}
|
||||
if (style[j] instanceof BackgroundColorSpan) {
|
||||
out.append("<font bgcolor =\"#");
|
||||
String color = Integer.toHexString(((BackgroundColorSpan)
|
||||
style[j]).getBackgroundColor() + 0x01000000);
|
||||
while (color.length() < 6) {
|
||||
color = "0" + color;
|
||||
}
|
||||
out.append(color);
|
||||
out.append("\">");
|
||||
}
|
||||
}
|
||||
|
||||
withinStyle(out, text, i, next);
|
||||
|
||||
for (int j = style.length - 1; j >= 0; j--) {
|
||||
if (style[j] instanceof ForegroundColorSpan) {
|
||||
out.append("</font>");
|
||||
}
|
||||
if (style[j] instanceof BackgroundColorSpan) {
|
||||
out.append("</font>");
|
||||
}
|
||||
if (style[j] instanceof AbsoluteSizeSpan) {
|
||||
out.append("</font>");
|
||||
}
|
||||
if (style[j] instanceof URLSpan) {
|
||||
out.append("</a>");
|
||||
}
|
||||
if (style[j] instanceof StrikethroughSpan) {
|
||||
out.append("</strike>");
|
||||
}
|
||||
if (style[j] instanceof UnderlineSpan) {
|
||||
out.append("</u>");
|
||||
}
|
||||
if (style[j] instanceof SubscriptSpan) {
|
||||
out.append("</sub>");
|
||||
}
|
||||
if (style[j] instanceof SuperscriptSpan) {
|
||||
out.append("</sup>");
|
||||
}
|
||||
if (style[j] instanceof TypefaceSpan) {
|
||||
String s = ((TypefaceSpan) style[j]).getFamily();
|
||||
|
||||
if (s.equals("monospace")) {
|
||||
out.append("</tt>");
|
||||
}
|
||||
}
|
||||
if (style[j] instanceof StyleSpan) {
|
||||
int s = ((StyleSpan) style[j]).getStyle();
|
||||
|
||||
if ((s & Typeface.BOLD) != 0) {
|
||||
out.append("</b>");
|
||||
}
|
||||
if ((s & Typeface.ITALIC) != 0) {
|
||||
out.append("</i>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String p = last ? "" : "</p>\n<p>";
|
||||
|
||||
if (nl == 1) {
|
||||
out.append("<br>\n");
|
||||
} else if (nl == 2) {
|
||||
out.append(p);
|
||||
} else {
|
||||
for (int i = 2; i < nl; i++) {
|
||||
out.append("<br>");
|
||||
}
|
||||
|
||||
out.append(p);
|
||||
}
|
||||
}
|
||||
|
||||
private static void withinStyle(StringBuilder out, Spanned text,
|
||||
int start, int end) {
|
||||
for (int i = start; i < end; i++) {
|
||||
char c = text.charAt(i);
|
||||
|
||||
if (c == '<') {
|
||||
out.append("<");
|
||||
} else if (c == '>') {
|
||||
out.append(">");
|
||||
} else if (c == '&') {
|
||||
out.append("&");
|
||||
} else if (c > 0x7E || c < ' ') {
|
||||
out.append("&#" + ((int) c) + ";");
|
||||
} else if (c == ' ') {
|
||||
while (i + 1 < end && text.charAt(i + 1) == ' ') {
|
||||
out.append(" ");
|
||||
i++;
|
||||
}
|
||||
|
||||
out.append(' ');
|
||||
} else {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HtmlToSpannedConverter implements ContentHandler {
|
||||
|
||||
private static final float[] HEADER_SIZES = {
|
||||
1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f,
|
||||
};
|
||||
|
||||
private String mSource;
|
||||
private XMLReader mReader;
|
||||
private SpannableStringBuilder mSpannableStringBuilder;
|
||||
private Html2.ImageGetter mImageGetter;
|
||||
private Html2.TagHandler mTagHandler;
|
||||
|
||||
public HtmlToSpannedConverter(
|
||||
String source, Html2.ImageGetter imageGetter, Html2.TagHandler tagHandler,
|
||||
Parser parser) {
|
||||
mSource = source;
|
||||
mSpannableStringBuilder = new SpannableStringBuilder();
|
||||
mImageGetter = imageGetter;
|
||||
mTagHandler = tagHandler;
|
||||
mReader = parser;
|
||||
}
|
||||
|
||||
public Spanned convert() {
|
||||
|
||||
mReader.setContentHandler(this);
|
||||
try {
|
||||
mReader.parse(new InputSource(new StringReader(mSource)));
|
||||
} catch (IOException e) {
|
||||
// We are reading from a string. There should not be IO problems.
|
||||
throw new RuntimeException(e);
|
||||
} catch (SAXException e) {
|
||||
// TagSoup doesn't throw parse exceptions.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// Fix flags and range for paragraph-type markup.
|
||||
Object[] obj = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class);
|
||||
for (int i = 0; i < obj.length; i++) {
|
||||
int start = mSpannableStringBuilder.getSpanStart(obj[i]);
|
||||
int end = mSpannableStringBuilder.getSpanEnd(obj[i]);
|
||||
|
||||
// If the last line of the range is blank, back off by one.
|
||||
if (end - 2 >= 0) {
|
||||
if (mSpannableStringBuilder.charAt(end - 1) == '\n' &&
|
||||
mSpannableStringBuilder.charAt(end - 2) == '\n') {
|
||||
end--;
|
||||
}
|
||||
}
|
||||
|
||||
if (end == start) {
|
||||
mSpannableStringBuilder.removeSpan(obj[i]);
|
||||
} else {
|
||||
mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_PARAGRAPH);
|
||||
}
|
||||
}
|
||||
|
||||
return mSpannableStringBuilder;
|
||||
}
|
||||
|
||||
private void handleStartTag(String tag, Attributes attributes) {
|
||||
if (tag.equalsIgnoreCase("br")) {
|
||||
// We don't need to handle this. TagSoup will ensure that there's a </br> for each <br>
|
||||
// so we can safely emite the linebreaks when we handle the close tag.
|
||||
} else if (tag.equalsIgnoreCase("p")) {
|
||||
handleP(mSpannableStringBuilder);
|
||||
} else if (tag.equalsIgnoreCase("div")) {
|
||||
handleP(mSpannableStringBuilder);
|
||||
} else if (tag.equalsIgnoreCase("em")) {
|
||||
start(mSpannableStringBuilder, new Bold());
|
||||
} else if (tag.equalsIgnoreCase("b")) {
|
||||
start(mSpannableStringBuilder, new Bold());
|
||||
} else if (tag.equalsIgnoreCase("strong")) {
|
||||
start(mSpannableStringBuilder, new Italic());
|
||||
} else if (tag.equalsIgnoreCase("cite")) {
|
||||
start(mSpannableStringBuilder, new Italic());
|
||||
} else if (tag.equalsIgnoreCase("dfn")) {
|
||||
start(mSpannableStringBuilder, new Italic());
|
||||
} else if (tag.equalsIgnoreCase("i")) {
|
||||
start(mSpannableStringBuilder, new Italic());
|
||||
} else if (tag.equalsIgnoreCase("big")) {
|
||||
start(mSpannableStringBuilder, new Big());
|
||||
} else if (tag.equalsIgnoreCase("small")) {
|
||||
start(mSpannableStringBuilder, new Small());
|
||||
} else if (tag.equalsIgnoreCase("font")) {
|
||||
startFont(mSpannableStringBuilder, attributes);
|
||||
} else if (tag.equalsIgnoreCase("blockquote")) {
|
||||
handleP(mSpannableStringBuilder);
|
||||
start(mSpannableStringBuilder, new Blockquote());
|
||||
} else if (tag.equalsIgnoreCase("tt")) {
|
||||
start(mSpannableStringBuilder, new Monospace());
|
||||
} else if (tag.equalsIgnoreCase("a")) {
|
||||
startA(mSpannableStringBuilder, attributes);
|
||||
} else if (tag.equalsIgnoreCase("u")) {
|
||||
start(mSpannableStringBuilder, new Underline());
|
||||
} else if (tag.equalsIgnoreCase("sup")) {
|
||||
start(mSpannableStringBuilder, new Super());
|
||||
} else if (tag.equalsIgnoreCase("sub")) {
|
||||
start(mSpannableStringBuilder, new Sub());
|
||||
} else if (tag.length() == 2 &&
|
||||
Character.toLowerCase(tag.charAt(0)) == 'h' &&
|
||||
tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
|
||||
handleP(mSpannableStringBuilder);
|
||||
start(mSpannableStringBuilder, new Header(tag.charAt(1) - '1'));
|
||||
} else if (tag.equalsIgnoreCase("img")) {
|
||||
startImg(mSpannableStringBuilder, attributes, mImageGetter);
|
||||
} else if (mTagHandler != null) {
|
||||
mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEndTag(String tag) {
|
||||
if (tag.equalsIgnoreCase("br")) {
|
||||
handleBr(mSpannableStringBuilder);
|
||||
} else if (tag.equalsIgnoreCase("p")) {
|
||||
handleP(mSpannableStringBuilder);
|
||||
} else if (tag.equalsIgnoreCase("div")) {
|
||||
handleP(mSpannableStringBuilder);
|
||||
} else if (tag.equalsIgnoreCase("em")) {
|
||||
end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
|
||||
} else if (tag.equalsIgnoreCase("b")) {
|
||||
end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
|
||||
} else if (tag.equalsIgnoreCase("strong")) {
|
||||
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
|
||||
} else if (tag.equalsIgnoreCase("cite")) {
|
||||
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
|
||||
} else if (tag.equalsIgnoreCase("dfn")) {
|
||||
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
|
||||
} else if (tag.equalsIgnoreCase("i")) {
|
||||
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
|
||||
} else if (tag.equalsIgnoreCase("big")) {
|
||||
end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f));
|
||||
} else if (tag.equalsIgnoreCase("small")) {
|
||||
end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f));
|
||||
} else if (tag.equalsIgnoreCase("font")) {
|
||||
endFont(mSpannableStringBuilder);
|
||||
} else if (tag.equalsIgnoreCase("blockquote")) {
|
||||
handleP(mSpannableStringBuilder);
|
||||
end(mSpannableStringBuilder, Blockquote.class, new QuoteSpan());
|
||||
} else if (tag.equalsIgnoreCase("tt")) {
|
||||
end(mSpannableStringBuilder, Monospace.class,
|
||||
new TypefaceSpan("monospace"));
|
||||
} else if (tag.equalsIgnoreCase("a")) {
|
||||
endA(mSpannableStringBuilder);
|
||||
} else if (tag.equalsIgnoreCase("u")) {
|
||||
end(mSpannableStringBuilder, Underline.class, new UnderlineSpan());
|
||||
} else if (tag.equalsIgnoreCase("sup")) {
|
||||
end(mSpannableStringBuilder, Super.class, new SuperscriptSpan());
|
||||
} else if (tag.equalsIgnoreCase("sub")) {
|
||||
end(mSpannableStringBuilder, Sub.class, new SubscriptSpan());
|
||||
} else if (tag.length() == 2 &&
|
||||
Character.toLowerCase(tag.charAt(0)) == 'h' &&
|
||||
tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
|
||||
handleP(mSpannableStringBuilder);
|
||||
endHeader(mSpannableStringBuilder);
|
||||
} else if (mTagHandler != null) {
|
||||
mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleP(SpannableStringBuilder text) {
|
||||
int len = text.length();
|
||||
|
||||
if (len >= 1 && text.charAt(len - 1) == '\n') {
|
||||
if (len >= 2 && text.charAt(len - 2) == '\n') {
|
||||
return;
|
||||
}
|
||||
|
||||
text.append("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (len != 0) {
|
||||
text.append("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleBr(SpannableStringBuilder text) {
|
||||
text.append("\n");
|
||||
}
|
||||
|
||||
private static Object getLast(Spanned text, Class kind) {
|
||||
/*
|
||||
* This knows that the last returned object from getSpans()
|
||||
* will be the most recently added.
|
||||
*/
|
||||
Object[] objs = text.getSpans(0, text.length(), kind);
|
||||
|
||||
if (objs.length == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return objs[objs.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
private static void start(SpannableStringBuilder text, Object mark) {
|
||||
int len = text.length();
|
||||
text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
|
||||
}
|
||||
|
||||
private static void end(SpannableStringBuilder text, Class kind,
|
||||
Object repl) {
|
||||
int len = text.length();
|
||||
Object obj = getLast(text, kind);
|
||||
int where = text.getSpanStart(obj);
|
||||
|
||||
text.removeSpan(obj);
|
||||
|
||||
if (where != len) {
|
||||
text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private static void startImg(SpannableStringBuilder text,
|
||||
Attributes attributes, Html2.ImageGetter img) {
|
||||
String src = attributes.getValue("", "src");
|
||||
Drawable d = null;
|
||||
|
||||
if (img != null) {
|
||||
d = img.getDrawable(src);
|
||||
}
|
||||
|
||||
if (d == null) {
|
||||
d = Resources.getSystem().
|
||||
getDrawable(R.drawable.unknown_image);
|
||||
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
|
||||
}
|
||||
|
||||
int len = text.length();
|
||||
text.append("\uFFFC");
|
||||
|
||||
text.setSpan(new ImageSpan(d, src), len, text.length(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
private static void startFont(SpannableStringBuilder text,
|
||||
Attributes attributes) {
|
||||
String color = attributes.getValue("", "color");
|
||||
String face = attributes.getValue("", "face");
|
||||
String bgColor = attributes.getValue("", "bgcolor");
|
||||
|
||||
int len = text.length();
|
||||
text.setSpan(new Font(color, face, bgColor), len, len, Spannable.SPAN_MARK_MARK);
|
||||
}
|
||||
|
||||
private static void endFont(SpannableStringBuilder text) {
|
||||
int len = text.length();
|
||||
Object obj = getLast(text, Font.class);
|
||||
int where = text.getSpanStart(obj);
|
||||
|
||||
text.removeSpan(obj);
|
||||
|
||||
if (where != len) {
|
||||
Font f = (Font) obj;
|
||||
|
||||
if (!TextUtils.isEmpty(f.mColor)) {
|
||||
if (f.mColor.startsWith("@")) {
|
||||
Resources res = Resources.getSystem();
|
||||
String name = f.mColor.substring(1);
|
||||
int colorRes = res.getIdentifier(name, "color", "android");
|
||||
if (colorRes != 0) {
|
||||
ColorStateList colors = res.getColorStateList(colorRes);
|
||||
text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null),
|
||||
where, len,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
} else {
|
||||
int c = getHtmlColor(f.mColor);
|
||||
if (c != -1) {
|
||||
text.setSpan(new ForegroundColorSpan(c | 0xFF000000),
|
||||
where, len,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(f.mBgColor)) {
|
||||
if (f.mBgColor.startsWith("@")) {
|
||||
Resources res = Resources.getSystem();
|
||||
String name = f.mBgColor.substring(1);
|
||||
int colorRes = res.getIdentifier(name, "color", "android");
|
||||
if (colorRes != 0) {
|
||||
ColorStateList colors = res.getColorStateList(colorRes);
|
||||
text.setSpan(new BackgroundColorSpan(colors.getDefaultColor()),
|
||||
where, len,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
} else {
|
||||
int c = getHtmlColor(f.mBgColor);
|
||||
if (c != -1) {
|
||||
text.setSpan(new BackgroundColorSpan(c | 0xFF000000),
|
||||
where, len,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (f.mFace != null) {
|
||||
text.setSpan(new TypefaceSpan(f.mFace), where, len,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void startA(SpannableStringBuilder text, Attributes attributes) {
|
||||
String href = attributes.getValue("", "href");
|
||||
|
||||
int len = text.length();
|
||||
text.setSpan(new Href(href), len, len, Spannable.SPAN_MARK_MARK);
|
||||
}
|
||||
|
||||
private static void endA(SpannableStringBuilder text) {
|
||||
int len = text.length();
|
||||
Object obj = getLast(text, Href.class);
|
||||
int where = text.getSpanStart(obj);
|
||||
|
||||
text.removeSpan(obj);
|
||||
|
||||
if (where != len) {
|
||||
Href h = (Href) obj;
|
||||
|
||||
if (h.mHref != null) {
|
||||
text.setSpan(new URLSpan(h.mHref), where, len,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void endHeader(SpannableStringBuilder text) {
|
||||
int len = text.length();
|
||||
Object obj = getLast(text, Header.class);
|
||||
|
||||
int where = text.getSpanStart(obj);
|
||||
|
||||
text.removeSpan(obj);
|
||||
|
||||
// Back off not to change only the text, not the blank line.
|
||||
while (len > where && text.charAt(len - 1) == '\n') {
|
||||
len--;
|
||||
}
|
||||
|
||||
if (where != len) {
|
||||
Header h = (Header) obj;
|
||||
|
||||
text.setSpan(new RelativeSizeSpan(HEADER_SIZES[h.mLevel]),
|
||||
where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
text.setSpan(new StyleSpan(Typeface.BOLD),
|
||||
where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDocumentLocator(Locator locator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDocument() throws SAXException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endDocument() throws SAXException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPrefixMapping(String prefix, String uri) throws SAXException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endPrefixMapping(String prefix) throws SAXException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes)
|
||||
throws SAXException {
|
||||
handleStartTag(localName, attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName) throws SAXException {
|
||||
handleEndTag(localName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char ch[], int start, int length) throws SAXException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
/*
|
||||
* Ignore whitespace that immediately follows other whitespace;
|
||||
* newlines count as spaces.
|
||||
*/
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = ch[i + start];
|
||||
|
||||
if (c == ' ' || c == '\n') {
|
||||
char pred;
|
||||
int len = sb.length();
|
||||
|
||||
if (len == 0) {
|
||||
len = mSpannableStringBuilder.length();
|
||||
|
||||
if (len == 0) {
|
||||
pred = '\n';
|
||||
} else {
|
||||
pred = mSpannableStringBuilder.charAt(len - 1);
|
||||
}
|
||||
} else {
|
||||
pred = sb.charAt(len - 1);
|
||||
}
|
||||
|
||||
if (pred != ' ' && pred != '\n') {
|
||||
sb.append(' ');
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
mSpannableStringBuilder.append(sb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processingInstruction(String target, String data) throws SAXException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skippedEntity(String name) throws SAXException {
|
||||
}
|
||||
|
||||
private static class Bold { }
|
||||
private static class Italic { }
|
||||
private static class Underline { }
|
||||
private static class Big { }
|
||||
private static class Small { }
|
||||
private static class Monospace { }
|
||||
private static class Blockquote { }
|
||||
private static class Super { }
|
||||
private static class Sub { }
|
||||
|
||||
private static class Font {
|
||||
public String mColor;
|
||||
public String mBgColor;
|
||||
public String mFace;
|
||||
|
||||
public Font(String color, String face, String bgColor) {
|
||||
mColor = color;
|
||||
mFace = face;
|
||||
mBgColor = bgColor;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Href {
|
||||
public String mHref;
|
||||
|
||||
public Href(String href) {
|
||||
mHref = href;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Header {
|
||||
private int mLevel;
|
||||
|
||||
public Header(int level) {
|
||||
mLevel = level;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashMap<String,Integer> COLORS = buildColorMap();
|
||||
|
||||
private static HashMap<String,Integer> buildColorMap() {
|
||||
HashMap<String,Integer> map = new HashMap<String,Integer>();
|
||||
map.put("aqua", 0x00FFFF);
|
||||
map.put("black", 0x000000);
|
||||
map.put("blue", 0x0000FF);
|
||||
map.put("fuchsia", 0xFF00FF);
|
||||
map.put("green", 0x008000);
|
||||
map.put("grey", 0x808080);
|
||||
map.put("lime", 0x00FF00);
|
||||
map.put("maroon", 0x800000);
|
||||
map.put("navy", 0x000080);
|
||||
map.put("olive", 0x808000);
|
||||
map.put("purple", 0x800080);
|
||||
map.put("red", 0xFF0000);
|
||||
map.put("silver", 0xC0C0C0);
|
||||
map.put("teal", 0x008080);
|
||||
map.put("white", 0xFFFFFF);
|
||||
map.put("yellow", 0xFFFF00);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HTML color (named or numeric) to an integer RGB value.
|
||||
*
|
||||
* @param color Non-null color string.
|
||||
* @return A color value, or {@code -1} if the color string could not be interpreted.
|
||||
*/
|
||||
private static int getHtmlColor(String color) {
|
||||
Integer i = COLORS.get(color.toLowerCase());
|
||||
if (i != null) {
|
||||
return i;
|
||||
} else {
|
||||
try {
|
||||
return convertValueToInt(color, -1);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Method ripped from com.android.internal.util.XmlUtils
|
||||
*/
|
||||
public static final int
|
||||
convertValueToInt(CharSequence charSeq, int defaultValue)
|
||||
{
|
||||
if (null == charSeq) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
String nm = charSeq.toString();
|
||||
|
||||
// XXX This code is copied from Integer.decode() so we don't
|
||||
// have to instantiate an Integer!
|
||||
|
||||
int value;
|
||||
int sign = 1;
|
||||
int index = 0;
|
||||
int len = nm.length();
|
||||
int base = 10;
|
||||
|
||||
if ('-' == nm.charAt(0)) {
|
||||
sign = -1;
|
||||
index++;
|
||||
}
|
||||
|
||||
if ('0' == nm.charAt(index)) {
|
||||
// Quick check for a zero by itself
|
||||
if (index == (len - 1)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char c = nm.charAt(index + 1);
|
||||
|
||||
if ('x' == c || 'X' == c) {
|
||||
index += 2;
|
||||
base = 16;
|
||||
} else {
|
||||
index++;
|
||||
base = 8;
|
||||
}
|
||||
}
|
||||
else if ('#' == nm.charAt(index))
|
||||
{
|
||||
index++;
|
||||
base = 16;
|
||||
}
|
||||
|
||||
return Integer.parseInt(nm.substring(index), base) * sign;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user