|
|
|
--- /home/marcel/Android/Sdk/sources/android-30/android/text/Html.java 2020-09-10 08:18:47.009487012 +0200
|
|
|
|
+++ ./app/src/main/java/eu/faircode/email/HtmlEx.java 2020-10-03 11:19:59.338719036 +0200
|
|
|
|
@@ -1,3 +1,5 @@
|
|
|
|
+package eu.faircode.email;
|
|
|
|
+
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2007 The Android Open Source Project
|
|
|
|
*
|
|
|
|
@@ -14,15 +16,12 @@
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
-package android.text;
|
|
|
|
-
|
|
|
|
-import android.app.ActivityThread;
|
|
|
|
-import android.app.Application;
|
|
|
|
-import android.compat.annotation.UnsupportedAppUsage;
|
|
|
|
-import android.content.res.Resources;
|
|
|
|
-import android.graphics.Color;
|
|
|
|
+import android.content.Context;
|
|
|
|
import android.graphics.Typeface;
|
|
|
|
-import android.graphics.drawable.Drawable;
|
|
|
|
+import android.text.Layout;
|
|
|
|
+import android.text.Spanned;
|
|
|
|
+import android.text.TextDirectionHeuristics;
|
|
|
|
+import android.text.TextUtils;
|
|
|
|
import android.text.style.AbsoluteSizeSpan;
|
|
|
|
import android.text.style.AlignmentSpan;
|
|
|
|
import android.text.style.BackgroundColorSpan;
|
|
|
|
@@ -41,213 +40,22 @@ import android.text.style.TypefaceSpan;
|
|
|
|
import android.text.style.URLSpan;
|
|
|
|
import android.text.style.UnderlineSpan;
|
|
|
|
|
|
|
|
-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 java.io.IOException;
|
|
|
|
-import java.io.StringReader;
|
|
|
|
-import java.util.HashMap;
|
|
|
|
-import java.util.Locale;
|
|
|
|
-import java.util.Map;
|
|
|
|
-import java.util.regex.Matcher;
|
|
|
|
-import java.util.regex.Pattern;
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * This class processes HTML strings into displayable styled text.
|
|
|
|
- * Not all HTML tags are supported.
|
|
|
|
- */
|
|
|
|
-public class Html {
|
|
|
|
- /**
|
|
|
|
- * Retrieves images for HTML <img> tags.
|
|
|
|
- */
|
|
|
|
- public static interface ImageGetter {
|
|
|
|
- /**
|
|
|
|
- * This method 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);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Option for {@link #toHtml(Spanned, int)}: Wrap consecutive lines of text delimited by '\n'
|
|
|
|
- * inside <p> elements. {@link BulletSpan}s are ignored.
|
|
|
|
- */
|
|
|
|
- public static final int TO_HTML_PARAGRAPH_LINES_CONSECUTIVE = 0x00000000;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Option for {@link #toHtml(Spanned, int)}: Wrap each line of text delimited by '\n' inside a
|
|
|
|
- * <p> or a <li> element. This allows {@link ParagraphStyle}s attached to be
|
|
|
|
- * encoded as CSS styles within the corresponding <p> or <li> element.
|
|
|
|
- */
|
|
|
|
- public static final int TO_HTML_PARAGRAPH_LINES_INDIVIDUAL = 0x00000001;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Flag indicating that texts inside <p> elements will be separated from other texts with
|
|
|
|
- * one newline character by default.
|
|
|
|
- */
|
|
|
|
- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH = 0x00000001;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Flag indicating that texts inside <h1>~<h6> elements will be separated from
|
|
|
|
- * other texts with one newline character by default.
|
|
|
|
- */
|
|
|
|
- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_HEADING = 0x00000002;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Flag indicating that texts inside <li> elements will be separated from other texts
|
|
|
|
- * with one newline character by default.
|
|
|
|
- */
|
|
|
|
- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM = 0x00000004;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Flag indicating that texts inside <ul> elements will be separated from other texts
|
|
|
|
- * with one newline character by default.
|
|
|
|
- */
|
|
|
|
- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST = 0x00000008;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Flag indicating that texts inside <div> elements will be separated from other texts
|
|
|
|
- * with one newline character by default.
|
|
|
|
- */
|
|
|
|
- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_DIV = 0x00000010;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Flag indicating that texts inside <blockquote> elements will be separated from other
|
|
|
|
- * texts with one newline character by default.
|
|
|
|
- */
|
|
|
|
- public static final int FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE = 0x00000020;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Flag indicating that CSS color values should be used instead of those defined in
|
|
|
|
- * {@link Color}.
|
|
|
|
- */
|
|
|
|
- public static final int FROM_HTML_OPTION_USE_CSS_COLORS = 0x00000100;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Flags for {@link #fromHtml(String, int, ImageGetter, TagHandler)}: Separate block-level
|
|
|
|
- * elements with blank lines (two newline characters) in between. This is the legacy behavior
|
|
|
|
- * prior to N.
|
|
|
|
- */
|
|
|
|
- public static final int FROM_HTML_MODE_LEGACY = 0x00000000;
|
|
|
|
+import static android.text.Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE;
|
|
|
|
|
|
|
|
- /**
|
|
|
|
- * Flags for {@link #fromHtml(String, int, ImageGetter, TagHandler)}: Separate block-level
|
|
|
|
- * elements with line breaks (single newline character) in between. This inverts the
|
|
|
|
- * {@link Spanned} to HTML string conversion done with the option
|
|
|
|
- * {@link #TO_HTML_PARAGRAPH_LINES_INDIVIDUAL}.
|
|
|
|
- */
|
|
|
|
- public static final int FROM_HTML_MODE_COMPACT =
|
|
|
|
- FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH
|
|
|
|
- | FROM_HTML_SEPARATOR_LINE_BREAK_HEADING
|
|
|
|
- | FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM
|
|
|
|
- | FROM_HTML_SEPARATOR_LINE_BREAK_LIST
|
|
|
|
- | FROM_HTML_SEPARATOR_LINE_BREAK_DIV
|
|
|
|
- | FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE;
|
|
|
|
+public class HtmlEx {
|
|
|
|
+ private Context context;
|
|
|
|
|
|
|
|
- /**
|
|
|
|
- * The bit which indicates if lines delimited by '\n' will be grouped into <p> elements.
|
|
|
|
- */
|
|
|
|
private static final int TO_HTML_PARAGRAPH_FLAG = 0x00000001;
|
|
|
|
|
|
|
|
- private Html() { }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Returns displayable styled text from the provided HTML string with the legacy flags
|
|
|
|
- * {@link #FROM_HTML_MODE_LEGACY}.
|
|
|
|
- *
|
|
|
|
- * @deprecated use {@link #fromHtml(String, int)} instead.
|
|
|
|
- */
|
|
|
|
- @Deprecated
|
|
|
|
- public static Spanned fromHtml(String source) {
|
|
|
|
- return fromHtml(source, FROM_HTML_MODE_LEGACY, null, null);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 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, int flags) {
|
|
|
|
- return fromHtml(source, flags, 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 with the legacy flags
|
|
|
|
- * {@link #FROM_HTML_MODE_LEGACY}.
|
|
|
|
- *
|
|
|
|
- * @deprecated use {@link #fromHtml(String, int, ImageGetter, TagHandler)} instead.
|
|
|
|
- */
|
|
|
|
- @Deprecated
|
|
|
|
- public static Spanned fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler) {
|
|
|
|
- return fromHtml(source, FROM_HTML_MODE_LEGACY, imageGetter, tagHandler);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 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, int flags, 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, flags);
|
|
|
|
- return converter.convert();
|
|
|
|
+ public HtmlEx(Context context){
|
|
|
|
+ this.context = context;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @deprecated use {@link #toHtml(Spanned, int)} instead.
|
|
|
|
*/
|
|
|
|
@Deprecated
|
|
|
|
- public static String toHtml(Spanned text) {
|
|
|
|
+ public /* static */ String toHtml(Spanned text) {
|
|
|
|
return toHtml(text, TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
|
|
|
|
}
|
|
|
|
|
|
|
|
@@ -261,7 +69,7 @@ public class Html {
|
|
|
|
* {@link #TO_HTML_PARAGRAPH_LINES_INDIVIDUAL}
|
|
|
|
* @return string containing input converted to HTML
|
|
|
|
*/
|
|
|
|
- public static String toHtml(Spanned text, int option) {
|
|
|
|
+ public /* static */ String toHtml(Spanned text, int option) {
|
|
|
|
StringBuilder out = new StringBuilder();
|
|
|
|
withinHtml(out, text, option);
|
|
|
|
return out.toString();
|
|
|
|
@@ -270,13 +78,13 @@ public class Html {
|
|
|
|
/**
|
|
|
|
* Returns an HTML escaped representation of the given plain text.
|
|
|
|
*/
|
|
|
|
- public static String escapeHtml(CharSequence text) {
|
|
|
|
+ public /* static */ String escapeHtml(CharSequence text) {
|
|
|
|
StringBuilder out = new StringBuilder();
|
|
|
|
withinStyle(out, text, 0, text.length());
|
|
|
|
return out.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
- private static void withinHtml(StringBuilder out, Spanned text, int option) {
|
|
|
|
+ private /* static */ void withinHtml(StringBuilder out, Spanned text, int option) {
|
|
|
|
if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) {
|
|
|
|
encodeTextAlignmentByDiv(out, text, option);
|
|
|
|
return;
|
|
|
|
@@ -285,7 +93,7 @@ public class Html {
|
|
|
|
withinDiv(out, text, 0, text.length(), option);
|
|
|
|
}
|
|
|
|
|
|
|
|
- private static void encodeTextAlignmentByDiv(StringBuilder out, Spanned text, int option) {
|
|
|
|
+ private /* static */ void encodeTextAlignmentByDiv(StringBuilder out, Spanned text, int option) {
|
|
|
|
int len = text.length();
|
|
|
|
|
|
|
|
int next;
|
|
|
|
@@ -298,7 +106,7 @@ public class Html {
|
|
|
|
for(int j = 0; j < style.length; j++) {
|
|
|
|
if (style[j] instanceof AlignmentSpan) {
|
|
|
|
Layout.Alignment align =
|
|
|
|
- ((AlignmentSpan) style[j]).getAlignment();
|
|
|
|
+ ((AlignmentSpan) style[j]).getAlignment();
|
|
|
|
needDiv = true;
|
|
|
|
if (align == Layout.Alignment.ALIGN_CENTER) {
|
|
|
|
elements = "align=\"center\" " + elements;
|
|
|
|
@@ -321,8 +129,8 @@ public class Html {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- private static void withinDiv(StringBuilder out, Spanned text, int start, int end,
|
|
|
|
- int option) {
|
|
|
|
+ private /* static */ void withinDiv(StringBuilder out, Spanned text, int start, int end,
|
|
|
|
+ int option) {
|
|
|
|
int next;
|
|
|
|
for (int i = start; i < end; i = next) {
|
|
|
|
next = text.nextSpanTransition(i, end, QuoteSpan.class);
|
|
|
|
@@ -340,7 +148,7 @@ public class Html {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- private static String getTextDirection(Spanned text, int start, int end) {
|
|
|
|
+ private /* static */ String getTextDirection(Spanned text, int start, int end) {
|
|
|
|
if (TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(text, start, end - start)) {
|
|
|
|
return " dir=\"rtl\"";
|
|
|
|
} else {
|
|
|
|
@@ -348,8 +156,8 @@ public class Html {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- private static String getTextStyles(Spanned text, int start, int end,
|
|
|
|
- boolean forceNoVerticalMargin, boolean includeTextAlign) {
|
|
|
|
+ private /* static */ String getTextStyles(Spanned text, int start, int end,
|
|
|
|
+ boolean forceNoVerticalMargin, boolean includeTextAlign) {
|
|
|
|
String margin = null;
|
|
|
|
String textAlign = null;
|
|
|
|
|
|
|
|
@@ -362,7 +170,7 @@ public class Html {
|
|
|
|
// Only use the last AlignmentSpan with flag SPAN_PARAGRAPH
|
|
|
|
for (int i = alignmentSpans.length - 1; i >= 0; i--) {
|
|
|
|
AlignmentSpan s = alignmentSpans[i];
|
|
|
|
- if ((text.getSpanFlags(s) & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH) {
|
|
|
|
+ if ((text.getSpanFlags(s) & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH || true) {
|
|
|
|
final Layout.Alignment alignment = s.getAlignment();
|
|
|
|
if (alignment == Layout.Alignment.ALIGN_NORMAL) {
|
|
|
|
textAlign = "text-align:start;";
|
|
|
|
@@ -392,8 +200,8 @@ public class Html {
|
|
|
|
return style.append("\"").toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
- private static void withinBlockquote(StringBuilder out, Spanned text, int start, int end,
|
|
|
|
- int option) {
|
|
|
|
+ private /* static */ void withinBlockquote(StringBuilder out, Spanned text, int start, int end,
|
|
|
|
+ int option) {
|
|
|
|
if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) {
|
|
|
|
withinBlockquoteConsecutive(out, text, start, end);
|
|
|
|
} else {
|
|
|
|
@@ -401,9 +209,9 @@ public class Html {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- private static void withinBlockquoteIndividual(StringBuilder out, Spanned text, int start,
|
|
|
|
- int end) {
|
|
|
|
- boolean isInList = false;
|
|
|
|
+ private /* static */ void withinBlockquoteIndividual(StringBuilder out, Spanned text, int start,
|
|
|
|
+ int end) {
|
|
|
|
+ Boolean isInBulletList = null;
|
|
|
|
int next;
|
|
|
|
for (int i = start; i <= end; i = next) {
|
|
|
|
next = TextUtils.indexOf(text, '\n', i, end);
|
|
|
|
@@ -412,42 +220,48 @@ public class Html {
|
|
|
|
}
|
|
|
|
|
|
|
|
if (next == i) {
|
|
|
|
- if (isInList) {
|
|
|
|
+ if (isInBulletList != null) {
|
|
|
|
// Current paragraph is no longer a list item; close the previously opened list
|
|
|
|
- isInList = false;
|
|
|
|
- out.append("</ul>\n");
|
|
|
|
+ out.append(isInBulletList ? "</ul>\n" : "</ol>\n");
|
|
|
|
+ isInBulletList = null;
|
|
|
|
}
|
|
|
|
- out.append("<br>\n");
|
|
|
|
+ if (i != text.length())
|
|
|
|
+ out.append("<br>\n");
|
|
|
|
} else {
|
|
|
|
- boolean isListItem = false;
|
|
|
|
+ Boolean isBulletListItem = null;
|
|
|
|
ParagraphStyle[] paragraphStyles = text.getSpans(i, next, ParagraphStyle.class);
|
|
|
|
for (ParagraphStyle paragraphStyle : paragraphStyles) {
|
|
|
|
final int spanFlags = text.getSpanFlags(paragraphStyle);
|
|
|
|
if ((spanFlags & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH
|
|
|
|
&& paragraphStyle instanceof BulletSpan) {
|
|
|
|
- isListItem = true;
|
|
|
|
+ isBulletListItem = !(paragraphStyle instanceof eu.faircode.email.NumberSpan);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- if (isListItem && !isInList) {
|
|
|
|
+ if (isBulletListItem != null && isInBulletList != null && isBulletListItem != isInBulletList) {
|
|
|
|
+ out.append(isInBulletList ? "</ul>\n" : "</ol>\n");
|
|
|
|
+ isInBulletList = null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (isBulletListItem != null && isInBulletList == null) {
|
|
|
|
// Current paragraph is the first item in a list
|
|
|
|
- isInList = true;
|
|
|
|
- out.append("<ul")
|
|
|
|
+ isInBulletList = isBulletListItem;
|
|
|
|
+ out.append(isInBulletList ? "<ul" : "<ol")
|
|
|
|
.append(getTextStyles(text, i, next, true, false))
|
|
|
|
.append(">\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
- if (isInList && !isListItem) {
|
|
|
|
+ if (isInBulletList != null && isBulletListItem == null) {
|
|
|
|
// Current paragraph is no longer a list item; close the previously opened list
|
|
|
|
- isInList = false;
|
|
|
|
- out.append("</ul>\n");
|
|
|
|
+ out.append(isInBulletList ? "</ul>\n" : "</ol>\n");
|
|
|
|
+ isInBulletList = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
- String tagType = isListItem ? "li" : "p";
|
|
|
|
+ String tagType = isBulletListItem != null ? "li" : "span";
|
|
|
|
out.append("<").append(tagType)
|
|
|
|
.append(getTextDirection(text, i, next))
|
|
|
|
- .append(getTextStyles(text, i, next, !isListItem, true))
|
|
|
|
+ .append(getTextStyles(text, i, next, isBulletListItem == null, true))
|
|
|
|
.append(">");
|
|
|
|
|
|
|
|
withinParagraph(out, text, i, next);
|
|
|
|
@@ -455,10 +269,12 @@ public class Html {
|
|
|
|
out.append("</");
|
|
|
|
out.append(tagType);
|
|
|
|
out.append(">\n");
|
|
|
|
+ if (isBulletListItem == null)
|
|
|
|
+ out.append("<br>\n");
|
|
|
|
|
|
|
|
- if (next == end && isInList) {
|
|
|
|
- isInList = false;
|
|
|
|
- out.append("</ul>\n");
|
|
|
|
+ if (next == end && isInBulletList != null) {
|
|
|
|
+ out.append(isInBulletList ? "</ul>\n" : "</ol>\n");
|
|
|
|
+ isInBulletList = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@@ -466,9 +282,9 @@ public class Html {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- private static void withinBlockquoteConsecutive(StringBuilder out, Spanned text, int start,
|
|
|
|
- int end) {
|
|
|
|
- out.append("<p").append(getTextDirection(text, start, end)).append(">");
|
|
|
|
+ private /* static */ void withinBlockquoteConsecutive(StringBuilder out, Spanned text, int start,
|
|
|
|
+ int end) {
|
|
|
|
+ out.append("<span").append(getTextDirection(text, start, end)).append(">");
|
|
|
|
|
|
|
|
int next;
|
|
|
|
for (int i = start; i < end; i = next) {
|
|
|
|
@@ -486,24 +302,24 @@ public class Html {
|
|
|
|
|
|
|
|
withinParagraph(out, text, i, next - nl);
|
|
|
|
|
|
|
|
- if (nl == 1) {
|
|
|
|
+ if (nl == 0) {
|
|
|
|
out.append("<br>\n");
|
|
|
|
} else {
|
|
|
|
- for (int j = 2; j < nl; j++) {
|
|
|
|
+ for (int j = 0; j < nl; j++) {
|
|
|
|
out.append("<br>");
|
|
|
|
}
|
|
|
|
if (next != end) {
|
|
|
|
/* Paragraph should be closed and reopened */
|
|
|
|
- out.append("</p>\n");
|
|
|
|
- out.append("<p").append(getTextDirection(text, start, end)).append(">");
|
|
|
|
+ out.append("</span>\n");
|
|
|
|
+ out.append("<span").append(getTextDirection(text, start, end)).append(">");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- out.append("</p>\n");
|
|
|
|
+ out.append("</span>\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
- private static void withinParagraph(StringBuilder out, Spanned text, int start, int end) {
|
|
|
|
+ private /* static */ void withinParagraph(StringBuilder out, Spanned text, int start, int end) {
|
|
|
|
int next;
|
|
|
|
for (int i = start; i < end; i = next) {
|
|
|
|
next = text.nextSpanTransition(i, end, CharacterStyle.class);
|
|
|
|
@@ -523,9 +339,11 @@ public class Html {
|
|
|
|
if (style[j] instanceof TypefaceSpan) {
|
|
|
|
String s = ((TypefaceSpan) style[j]).getFamily();
|
|
|
|
|
|
|
|
- if ("monospace".equals(s)) {
|
|
|
|
- out.append("<tt>");
|
|
|
|
- }
|
|
|
|
+ //if ("monospace".equals(s)) {
|
|
|
|
+ // out.append("<tt>");
|
|
|
|
+ //}
|
|
|
|
+
|
|
|
|
+ out.append("<span style=\"font-family:" + s + ";\">");
|
|
|
|
}
|
|
|
|
if (style[j] instanceof SuperscriptSpan) {
|
|
|
|
out.append("<sup>");
|
|
|
|
@@ -556,8 +374,8 @@ public class Html {
|
|
|
|
AbsoluteSizeSpan s = ((AbsoluteSizeSpan) style[j]);
|
|
|
|
float sizeDip = s.getSize();
|
|
|
|
if (!s.getDip()) {
|
|
|
|
- Application application = ActivityThread.currentApplication();
|
|
|
|
- sizeDip /= application.getResources().getDisplayMetrics().density;
|
|
|
|
+ //Application application = ActivityThread.currentApplication();
|
|
|
|
+ sizeDip /= context.getResources().getDisplayMetrics().density;
|
|
|
|
}
|
|
|
|
|
|
|
|
// px in CSS is the equivalance of dip in Android
|
|
|
|
@@ -609,11 +427,13 @@ public class Html {
|
|
|
|
out.append("</sup>");
|
|
|
|
}
|
|
|
|
if (style[j] instanceof TypefaceSpan) {
|
|
|
|
- String s = ((TypefaceSpan) style[j]).getFamily();
|
|
|
|
+ //String s = ((TypefaceSpan) style[j]).getFamily();
|
|
|
|
|
|
|
|
- if (s.equals("monospace")) {
|
|
|
|
- out.append("</tt>");
|
|
|
|
- }
|
|
|
|
+ //if (s.equals("monospace")) {
|
|
|
|
+ // out.append("</tt>");
|
|
|
|
+ //}
|
|
|
|
+
|
|
|
|
+ out.append("</span>");
|
|
|
|
}
|
|
|
|
if (style[j] instanceof StyleSpan) {
|
|
|
|
int s = ((StyleSpan) style[j]).getStyle();
|
|
|
|
@@ -629,8 +449,8 @@ public class Html {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- @UnsupportedAppUsage
|
|
|
|
- private static void withinStyle(StringBuilder out, CharSequence text,
|
|
|
|
+ //@UnsupportedAppUsage
|
|
|
|
+ private /* static */ void withinStyle(StringBuilder out, CharSequence text,
|
|
|
|
int start, int end) {
|
|
|
|
for (int i = start; i < end; i++) {
|
|
|
|
char c = text.charAt(i);
|
|
|
|
@@ -665,668 +485,3 @@ public class Html {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
-
|
|
|
|
-class HtmlToSpannedConverter implements ContentHandler {
|
|
|
|
-
|
|
|
|
- private static final float[] HEADING_SIZES = {
|
|
|
|
- 1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f,
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- private String mSource;
|
|
|
|
- private XMLReader mReader;
|
|
|
|
- private SpannableStringBuilder mSpannableStringBuilder;
|
|
|
|
- private Html.ImageGetter mImageGetter;
|
|
|
|
- private Html.TagHandler mTagHandler;
|
|
|
|
- private int mFlags;
|
|
|
|
-
|
|
|
|
- private static Pattern sTextAlignPattern;
|
|
|
|
- private static Pattern sForegroundColorPattern;
|
|
|
|
- private static Pattern sBackgroundColorPattern;
|
|
|
|
- private static Pattern sTextDecorationPattern;
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Name-value mapping of HTML/CSS colors which have different values in {@link Color}.
|
|
|
|
- */
|
|
|
|
- private static final Map<String, Integer> sColorMap;
|
|
|
|
-
|
|
|
|
- static {
|
|
|
|
- sColorMap = new HashMap<>();
|
|
|
|
- sColorMap.put("darkgray", 0xFFA9A9A9);
|
|
|
|
- sColorMap.put("gray", 0xFF808080);
|
|
|
|
- sColorMap.put("lightgray", 0xFFD3D3D3);
|
|
|
|
- sColorMap.put("darkgrey", 0xFFA9A9A9);
|
|
|
|
- sColorMap.put("grey", 0xFF808080);
|
|
|
|
- sColorMap.put("lightgrey", 0xFFD3D3D3);
|
|
|
|
- sColorMap.put("green", 0xFF008000);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static Pattern getTextAlignPattern() {
|
|
|
|
- if (sTextAlignPattern == null) {
|
|
|
|
- sTextAlignPattern = Pattern.compile("(?:\\s+|\\A)text-align\\s*:\\s*(\\S*)\\b");
|
|
|
|
- }
|
|
|
|
- return sTextAlignPattern;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static Pattern getForegroundColorPattern() {
|
|
|
|
- if (sForegroundColorPattern == null) {
|
|
|
|
- sForegroundColorPattern = Pattern.compile(
|
|
|
|
- "(?:\\s+|\\A)color\\s*:\\s*(\\S*)\\b");
|
|
|
|
- }
|
|
|
|
- return sForegroundColorPattern;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static Pattern getBackgroundColorPattern() {
|
|
|
|
- if (sBackgroundColorPattern == null) {
|
|
|
|
- sBackgroundColorPattern = Pattern.compile(
|
|
|
|
- "(?:\\s+|\\A)background(?:-color)?\\s*:\\s*(\\S*)\\b");
|
|
|
|
- }
|
|
|
|
- return sBackgroundColorPattern;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static Pattern getTextDecorationPattern() {
|
|
|
|
- if (sTextDecorationPattern == null) {
|
|
|
|
- sTextDecorationPattern = Pattern.compile(
|
|
|
|
- "(?:\\s+|\\A)text-decoration\\s*:\\s*(\\S*)\\b");
|
|
|
|
- }
|
|
|
|
- return sTextDecorationPattern;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public HtmlToSpannedConverter( String source, Html.ImageGetter imageGetter,
|
|
|
|
- Html.TagHandler tagHandler, Parser parser, int flags) {
|
|
|
|
- mSource = source;
|
|
|
|
- mSpannableStringBuilder = new SpannableStringBuilder();
|
|
|
|
- mImageGetter = imageGetter;
|
|
|
|
- mTagHandler = tagHandler;
|
|
|
|
- mReader = parser;
|
|
|
|
- mFlags = flags;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- 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 emit the linebreaks when we handle the close tag.
|
|
|
|
- } else if (tag.equalsIgnoreCase("p")) {
|
|
|
|
- startBlockElement(mSpannableStringBuilder, attributes, getMarginParagraph());
|
|
|
|
- startCssStyle(mSpannableStringBuilder, attributes);
|
|
|
|
- } else if (tag.equalsIgnoreCase("ul")) {
|
|
|
|
- startBlockElement(mSpannableStringBuilder, attributes, getMarginList());
|
|
|
|
- } else if (tag.equalsIgnoreCase("li")) {
|
|
|
|
- startLi(mSpannableStringBuilder, attributes);
|
|
|
|
- } else if (tag.equalsIgnoreCase("div")) {
|
|
|
|
- startBlockElement(mSpannableStringBuilder, attributes, getMarginDiv());
|
|
|
|
- } else if (tag.equalsIgnoreCase("span")) {
|
|
|
|
- startCssStyle(mSpannableStringBuilder, attributes);
|
|
|
|
- } else if (tag.equalsIgnoreCase("strong")) {
|
|
|
|
- start(mSpannableStringBuilder, new Bold());
|
|
|
|
- } else if (tag.equalsIgnoreCase("b")) {
|
|
|
|
- start(mSpannableStringBuilder, new Bold());
|
|
|
|
- } else if (tag.equalsIgnoreCase("em")) {
|
|
|
|
- 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")) {
|
|
|
|
- startBlockquote(mSpannableStringBuilder, attributes);
|
|
|
|
- } 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("del")) {
|
|
|
|
- start(mSpannableStringBuilder, new Strikethrough());
|
|
|
|
- } else if (tag.equalsIgnoreCase("s")) {
|
|
|
|
- start(mSpannableStringBuilder, new Strikethrough());
|
|
|
|
- } else if (tag.equalsIgnoreCase("strike")) {
|
|
|
|
- start(mSpannableStringBuilder, new Strikethrough());
|
|
|
|
- } 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') {
|
|
|
|
- startHeading(mSpannableStringBuilder, attributes, 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")) {
|
|
|
|
- endCssStyle(mSpannableStringBuilder);
|
|
|
|
- endBlockElement(mSpannableStringBuilder);
|
|
|
|
- } else if (tag.equalsIgnoreCase("ul")) {
|
|
|
|
- endBlockElement(mSpannableStringBuilder);
|
|
|
|
- } else if (tag.equalsIgnoreCase("li")) {
|
|
|
|
- endLi(mSpannableStringBuilder);
|
|
|
|
- } else if (tag.equalsIgnoreCase("div")) {
|
|
|
|
- endBlockElement(mSpannableStringBuilder);
|
|
|
|
- } else if (tag.equalsIgnoreCase("span")) {
|
|
|
|
- endCssStyle(mSpannableStringBuilder);
|
|
|
|
- } else if (tag.equalsIgnoreCase("strong")) {
|
|
|
|
- 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("em")) {
|
|
|
|
- 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")) {
|
|
|
|
- endBlockquote(mSpannableStringBuilder);
|
|
|
|
- } 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("del")) {
|
|
|
|
- end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
|
|
|
|
- } else if (tag.equalsIgnoreCase("s")) {
|
|
|
|
- end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
|
|
|
|
- } else if (tag.equalsIgnoreCase("strike")) {
|
|
|
|
- end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
|
|
|
|
- } 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') {
|
|
|
|
- endHeading(mSpannableStringBuilder);
|
|
|
|
- } else if (mTagHandler != null) {
|
|
|
|
- mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private int getMarginParagraph() {
|
|
|
|
- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private int getMarginHeading() {
|
|
|
|
- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private int getMarginListItem() {
|
|
|
|
- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private int getMarginList() {
|
|
|
|
- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private int getMarginDiv() {
|
|
|
|
- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_DIV);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private int getMarginBlockquote() {
|
|
|
|
- return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Returns the minimum number of newline characters needed before and after a given block-level
|
|
|
|
- * element.
|
|
|
|
- *
|
|
|
|
- * @param flag the corresponding option flag defined in {@link Html} of a block-level element
|
|
|
|
- */
|
|
|
|
- private int getMargin(int flag) {
|
|
|
|
- if ((flag & mFlags) != 0) {
|
|
|
|
- return 1;
|
|
|
|
- }
|
|
|
|
- return 2;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void appendNewlines(Editable text, int minNewline) {
|
|
|
|
- final int len = text.length();
|
|
|
|
-
|
|
|
|
- if (len == 0) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- int existingNewlines = 0;
|
|
|
|
- for (int i = len - 1; i >= 0 && text.charAt(i) == '\n'; i--) {
|
|
|
|
- existingNewlines++;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- for (int j = existingNewlines; j < minNewline; j++) {
|
|
|
|
- text.append("\n");
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void startBlockElement(Editable text, Attributes attributes, int margin) {
|
|
|
|
- final int len = text.length();
|
|
|
|
- if (margin > 0) {
|
|
|
|
- appendNewlines(text, margin);
|
|
|
|
- start(text, new Newline(margin));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- String style = attributes.getValue("", "style");
|
|
|
|
- if (style != null) {
|
|
|
|
- Matcher m = getTextAlignPattern().matcher(style);
|
|
|
|
- if (m.find()) {
|
|
|
|
- String alignment = m.group(1);
|
|
|
|
- if (alignment.equalsIgnoreCase("start")) {
|
|
|
|
- start(text, new Alignment(Layout.Alignment.ALIGN_NORMAL));
|
|
|
|
- } else if (alignment.equalsIgnoreCase("center")) {
|
|
|
|
- start(text, new Alignment(Layout.Alignment.ALIGN_CENTER));
|
|
|
|
- } else if (alignment.equalsIgnoreCase("end")) {
|
|
|
|
- start(text, new Alignment(Layout.Alignment.ALIGN_OPPOSITE));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void endBlockElement(Editable text) {
|
|
|
|
- Newline n = getLast(text, Newline.class);
|
|
|
|
- if (n != null) {
|
|
|
|
- appendNewlines(text, n.mNumNewlines);
|
|
|
|
- text.removeSpan(n);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Alignment a = getLast(text, Alignment.class);
|
|
|
|
- if (a != null) {
|
|
|
|
- setSpanFromMark(text, a, new AlignmentSpan.Standard(a.mAlignment));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void handleBr(Editable text) {
|
|
|
|
- text.append('\n');
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void startLi(Editable text, Attributes attributes) {
|
|
|
|
- startBlockElement(text, attributes, getMarginListItem());
|
|
|
|
- start(text, new Bullet());
|
|
|
|
- startCssStyle(text, attributes);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void endLi(Editable text) {
|
|
|
|
- endCssStyle(text);
|
|
|
|
- endBlockElement(text);
|
|
|
|
- end(text, Bullet.class, new BulletSpan());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void startBlockquote(Editable text, Attributes attributes) {
|
|
|
|
- startBlockElement(text, attributes, getMarginBlockquote());
|
|
|
|
- start(text, new Blockquote());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void endBlockquote(Editable text) {
|
|
|
|
- endBlockElement(text);
|
|
|
|
- end(text, Blockquote.class, new QuoteSpan());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void startHeading(Editable text, Attributes attributes, int level) {
|
|
|
|
- startBlockElement(text, attributes, getMarginHeading());
|
|
|
|
- start(text, new Heading(level));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void endHeading(Editable text) {
|
|
|
|
- // RelativeSizeSpan and StyleSpan are CharacterStyles
|
|
|
|
- // Their ranges should not include the newlines at the end
|
|
|
|
- Heading h = getLast(text, Heading.class);
|
|
|
|
- if (h != null) {
|
|
|
|
- setSpanFromMark(text, h, new RelativeSizeSpan(HEADING_SIZES[h.mLevel]),
|
|
|
|
- new StyleSpan(Typeface.BOLD));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- endBlockElement(text);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static <T> T getLast(Spanned text, Class<T> kind) {
|
|
|
|
- /*
|
|
|
|
- * This knows that the last returned object from getSpans()
|
|
|
|
- * will be the most recently added.
|
|
|
|
- */
|
|
|
|
- T[] objs = text.getSpans(0, text.length(), kind);
|
|
|
|
-
|
|
|
|
- if (objs.length == 0) {
|
|
|
|
- return null;
|
|
|
|
- } else {
|
|
|
|
- return objs[objs.length - 1];
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void setSpanFromMark(Spannable text, Object mark, Object... spans) {
|
|
|
|
- int where = text.getSpanStart(mark);
|
|
|
|
- text.removeSpan(mark);
|
|
|
|
- int len = text.length();
|
|
|
|
- if (where != len) {
|
|
|
|
- for (Object span : spans) {
|
|
|
|
- text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void start(Editable text, Object mark) {
|
|
|
|
- int len = text.length();
|
|
|
|
- text.setSpan(mark, len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void end(Editable text, Class kind, Object repl) {
|
|
|
|
- int len = text.length();
|
|
|
|
- Object obj = getLast(text, kind);
|
|
|
|
- if (obj != null) {
|
|
|
|
- setSpanFromMark(text, obj, repl);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void startCssStyle(Editable text, Attributes attributes) {
|
|
|
|
- String style = attributes.getValue("", "style");
|
|
|
|
- if (style != null) {
|
|
|
|
- Matcher m = getForegroundColorPattern().matcher(style);
|
|
|
|
- if (m.find()) {
|
|
|
|
- int c = getHtmlColor(m.group(1));
|
|
|
|
- if (c != -1) {
|
|
|
|
- start(text, new Foreground(c | 0xFF000000));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- m = getBackgroundColorPattern().matcher(style);
|
|
|
|
- if (m.find()) {
|
|
|
|
- int c = getHtmlColor(m.group(1));
|
|
|
|
- if (c != -1) {
|
|
|
|
- start(text, new Background(c | 0xFF000000));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- m = getTextDecorationPattern().matcher(style);
|
|
|
|
- if (m.find()) {
|
|
|
|
- String textDecoration = m.group(1);
|
|
|
|
- if (textDecoration.equalsIgnoreCase("line-through")) {
|
|
|
|
- start(text, new Strikethrough());
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void endCssStyle(Editable text) {
|
|
|
|
- Strikethrough s = getLast(text, Strikethrough.class);
|
|
|
|
- if (s != null) {
|
|
|
|
- setSpanFromMark(text, s, new StrikethroughSpan());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Background b = getLast(text, Background.class);
|
|
|
|
- if (b != null) {
|
|
|
|
- setSpanFromMark(text, b, new BackgroundColorSpan(b.mBackgroundColor));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Foreground f = getLast(text, Foreground.class);
|
|
|
|
- if (f != null) {
|
|
|
|
- setSpanFromMark(text, f, new ForegroundColorSpan(f.mForegroundColor));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void startImg(Editable text, Attributes attributes, Html.ImageGetter img) {
|
|
|
|
- String src = attributes.getValue("", "src");
|
|
|
|
- Drawable d = null;
|
|
|
|
-
|
|
|
|
- if (img != null) {
|
|
|
|
- d = img.getDrawable(src);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (d == null) {
|
|
|
|
- d = Resources.getSystem().
|
|
|
|
- getDrawable(com.android.internal.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 void startFont(Editable text, Attributes attributes) {
|
|
|
|
- String color = attributes.getValue("", "color");
|
|
|
|
- String face = attributes.getValue("", "face");
|
|
|
|
-
|
|
|
|
- if (!TextUtils.isEmpty(color)) {
|
|
|
|
- int c = getHtmlColor(color);
|
|
|
|
- if (c != -1) {
|
|
|
|
- start(text, new Foreground(c | 0xFF000000));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!TextUtils.isEmpty(face)) {
|
|
|
|
- start(text, new Font(face));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void endFont(Editable text) {
|
|
|
|
- Font font = getLast(text, Font.class);
|
|
|
|
- if (font != null) {
|
|
|
|
- setSpanFromMark(text, font, new TypefaceSpan(font.mFace));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Foreground foreground = getLast(text, Foreground.class);
|
|
|
|
- if (foreground != null) {
|
|
|
|
- setSpanFromMark(text, foreground,
|
|
|
|
- new ForegroundColorSpan(foreground.mForegroundColor));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void startA(Editable text, Attributes attributes) {
|
|
|
|
- String href = attributes.getValue("", "href");
|
|
|
|
- start(text, new Href(href));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void endA(Editable text) {
|
|
|
|
- Href h = getLast(text, Href.class);
|
|
|
|
- if (h != null) {
|
|
|
|
- if (h.mHref != null) {
|
|
|
|
- setSpanFromMark(text, h, new URLSpan((h.mHref)));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private int getHtmlColor(String color) {
|
|
|
|
- if ((mFlags & Html.FROM_HTML_OPTION_USE_CSS_COLORS)
|
|
|
|
- == Html.FROM_HTML_OPTION_USE_CSS_COLORS) {
|
|
|
|
- Integer i = sColorMap.get(color.toLowerCase(Locale.US));
|
|
|
|
- if (i != null) {
|
|
|
|
- return i;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return Color.getHtmlColor(color);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void setDocumentLocator(Locator locator) {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void startDocument() throws SAXException {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void endDocument() throws SAXException {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void startPrefixMapping(String prefix, String uri) throws SAXException {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void endPrefixMapping(String prefix) throws SAXException {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void startElement(String uri, String localName, String qName, Attributes attributes)
|
|
|
|
- throws SAXException {
|
|
|
|
- handleStartTag(localName, attributes);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void endElement(String uri, String localName, String qName) throws SAXException {
|
|
|
|
- handleEndTag(localName);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- 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);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void processingInstruction(String target, String data) throws SAXException {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void skippedEntity(String name) throws SAXException {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static class Bold { }
|
|
|
|
- private static class Italic { }
|
|
|
|
- private static class Underline { }
|
|
|
|
- private static class Strikethrough { }
|
|
|
|
- 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 Bullet { }
|
|
|
|
-
|
|
|
|
- private static class Font {
|
|
|
|
- public String mFace;
|
|
|
|
-
|
|
|
|
- public Font(String face) {
|
|
|
|
- mFace = face;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static class Href {
|
|
|
|
- public String mHref;
|
|
|
|
-
|
|
|
|
- public Href(String href) {
|
|
|
|
- mHref = href;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static class Foreground {
|
|
|
|
- private int mForegroundColor;
|
|
|
|
-
|
|
|
|
- public Foreground(int foregroundColor) {
|
|
|
|
- mForegroundColor = foregroundColor;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static class Background {
|
|
|
|
- private int mBackgroundColor;
|
|
|
|
-
|
|
|
|
- public Background(int backgroundColor) {
|
|
|
|
- mBackgroundColor = backgroundColor;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static class Heading {
|
|
|
|
- private int mLevel;
|
|
|
|
-
|
|
|
|
- public Heading(int level) {
|
|
|
|
- mLevel = level;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static class Newline {
|
|
|
|
- private int mNumNewlines;
|
|
|
|
-
|
|
|
|
- public Newline(int numNewlines) {
|
|
|
|
- mNumNewlines = numNewlines;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static class Alignment {
|
|
|
|
- private Layout.Alignment mAlignment;
|
|
|
|
-
|
|
|
|
- public Alignment(Layout.Alignment alignment) {
|
|
|
|
- mAlignment = alignment;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|