caiquearaujo
caiquearaujo

Reputation: 397

Android: Highlight <em> tags in SpannableString

I've the following code:

public static SpannableString getSpannable ( String content )
{
    SpannableString s = new SpannableString ( content );

    Matcher m = Pattern.compile ( "<em>(.*?)</em>" ).matcher ( content );

    while ( m.find () )
    { s.setSpan ( new BackgroundColorSpan ( R.color.colorItalic ), m.start ( 1 ), m.end ( 1 ), 0 ); }

    return s;
}

It get all <em>(.*?)</em> and set a BackgroundColorSpan to the group one (.*?) by your respective position.

It works so well! But the problem is <em> also is inside string content... after set all spans, how can I remove <em> from a SpannableString?

Upvotes: 0

Views: 643

Answers (2)

Neerajlal K
Neerajlal K

Reputation: 6828

Instead of SpannableString use SpannableStringBuilder and use the replace method.

Try this code,

String content = "Yeah, <em>This</em> is bold.";

SpannableStringBuilder s = new SpannableStringBuilder(content);

String startTag = "<em>";
int startTagLength = startTag.length();
String endTag = "</em>";
int endTagLength = endTag.length();
Matcher m = Pattern.compile(startTag + "(.*?)" + endTag).matcher(content);

while (m.find()) {
    Log.e(TAG, "" + m.start(1));
    Log.e(TAG, "" + m.end(1));
    s.setSpan(new BackgroundColorSpan(R.color.red), m.start(1), m.end(1), 0);
    s.replace(m.end(1), m.end(1) + endTagLength, "");
    s.replace(m.start(1) - startTagLength, m.start(1), "");
}

TextView out = (TextView) findViewById(R.id.out);
out.setText(s);

EDIT

Caique Monteiro Araujo's has mentioned in the comments that it doesn't work for multiple tags. The reason for this is that the string lengths were getting messed up in the second iteration since I was replacing the string within the while loop itself.

I am happy that Caique Monteiro Araujo has found a workaround. The folloing is mine. I used a TreeMap to store the (start, end) pairs and replace the tags in another loop. I don't know if this is an overkill.

String content = "<em>This</em> is <em>bold</em>.";

SpannableStringBuilder s = new SpannableStringBuilder(content);

String startTag = "<em>";
int startTagLength = startTag.length();
String endTag = "</em>";
int endTagLength = endTag.length();
Matcher m = Pattern.compile(startTag + "(.*?)" + endTag).matcher(content);

// TreeMap to store the start and end pair
TreeMap<Integer, Integer> pair = new TreeMap<>();
while (m.find()) {

    // Store the start and end
    pair.put(m.start(1), m.end(1));
    s.setSpan(new BackgroundColorSpan(R.color.red), m.start(1), m.end(1), 0);
}

// Use descendingMap to reverse the Map
NavigableMap<Integer, Integer> reversePair = pair.descendingMap();

// Replace the tags starting from the last occurrence to avoid messing the length
for (Integer key : reversePair.keySet()) {
    Integer end = reversePair.get(key);
    s.replace(end, end + endTagLength, "");
    s.replace(key - startTagLength, key, "");
}

TextView out = (TextView) findViewById(R.id.out);
out.setText(s);

Upvotes: 2

caiquearaujo
caiquearaujo

Reputation: 397

I've found a solution by using the @K Neeraj Lal help, which I'm so grateful. The point is, his answer didn't work inside the while loop. So, I had to create a counter to update char position, after a remove the chars. This was necessary because Matcher is linked with content variable. So, when it replaces s variable it changes your length, but the content variable still have the same lenght (where Matcher is linked to). The solution code is:

public static SpannableStringBuilder getSpannable ( String content )
{
    SpannableStringBuilder s = new SpannableStringBuilder ( content );

    String startAt = "<em>";
    String endAt   = "</em>";

    Matcher m = Pattern.compile ( startAt + "(.*?)" + endAt ).matcher ( content );

    int counter = 0, length  = startAt.length() + endAt.length();
    int startA, startB, endA, endB;

    while ( m.find () )
    {
        startA = m.start()-counter;
        endA   = m.end(1)-counter;
        startB = m.start(1)-counter;
        endB   = m.end()-counter;

        s.setSpan ( new BackgroundColorSpan ( R.color.colorItalic ), startB, endA, 0 );
        s.replace ( endA, endB, "" );
        s.replace ( startA, startB, "" );

        counter += length;
    }

    return s;
}

Anyway, I don't know if it is the best approach.

Upvotes: 0

Related Questions