mirror of
				https://github.com/pierre42100/ComunicAndroid
				synced 2025-11-04 11:34:06 +00:00 
			
		
		
		
	Integrated BBCode parser
This commit is contained in:
		@@ -35,8 +35,10 @@ import org.communiquons.android.comunic.client.ui.views.SurveyView;
 | 
			
		||||
import org.communiquons.android.comunic.client.ui.views.WebLinkView;
 | 
			
		||||
import org.communiquons.android.comunic.client.ui.views.WebUserAccountImage;
 | 
			
		||||
import org.communiquons.android.comunic.client.ui.views.YouTubeVideoView;
 | 
			
		||||
import org.communiquons.bbcodeparser.BBCodeParser;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Posts adapter
 | 
			
		||||
@@ -265,8 +267,8 @@ public class PostsAdapter extends BaseRecyclerViewAdapter {
 | 
			
		||||
            else if(post.getPage_type() == PageType.USER_PAGE
 | 
			
		||||
                    && mList.getUsersInfo().containsKey(post.getPage_id())){
 | 
			
		||||
 | 
			
		||||
                mTargetPageName.setText(mList.getUsersInfo().get(post.getPage_id())
 | 
			
		||||
                        .getDisplayFullName());
 | 
			
		||||
                mTargetPageName.setText(Objects.requireNonNull(
 | 
			
		||||
                        mList.getUsersInfo().get(post.getPage_id())).getDisplayFullName());
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -274,8 +276,8 @@ public class PostsAdapter extends BaseRecyclerViewAdapter {
 | 
			
		||||
            else if(post.getPage_type() == PageType.GROUP_PAGE
 | 
			
		||||
                    && mList.getGroupsInfo().containsKey(post.getPage_id())){
 | 
			
		||||
 | 
			
		||||
                mTargetPageName.setText(mList.getGroupsInfo().get(post.getPage_id())
 | 
			
		||||
                        .getDisplayName());
 | 
			
		||||
                mTargetPageName.setText(Objects.requireNonNull(
 | 
			
		||||
                        mList.getGroupsInfo().get(post.getPage_id())).getDisplayName());
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -320,11 +322,7 @@ public class PostsAdapter extends BaseRecyclerViewAdapter {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //Set post content
 | 
			
		||||
            mPostContent.setParsedText(
 | 
			
		||||
                    StringsUtils.RemoveBBCode(
 | 
			
		||||
                            UiUtils.prepareStringTextView(post.getContent())
 | 
			
		||||
                    )
 | 
			
		||||
            );
 | 
			
		||||
            mPostContent.setParsedText(new BBCodeParser().parse(post.getContent()));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //Post likes
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import android.content.Context;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.graphics.Color;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.support.annotation.NonNull;
 | 
			
		||||
import android.support.annotation.Nullable;
 | 
			
		||||
import android.text.SpannableStringBuilder;
 | 
			
		||||
import android.text.Spanned;
 | 
			
		||||
@@ -63,17 +64,22 @@ public class ContentTextView extends android.support.v7.widget.AppCompatTextView
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set na new text, with links parsed
 | 
			
		||||
     * Set a new text, with links parsed
 | 
			
		||||
     *
 | 
			
		||||
     * @param text The text to parse
 | 
			
		||||
     */
 | 
			
		||||
    public void setParsedText(String text) {
 | 
			
		||||
        super.setText(text);
 | 
			
		||||
        this.setParsedText(new SpannableStringBuilder(text));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        //Parse text
 | 
			
		||||
        SpannableStringBuilder ssb = new SpannableStringBuilder(text);
 | 
			
		||||
    /**
 | 
			
		||||
     * Set a new text, with links parsed
 | 
			
		||||
     *
 | 
			
		||||
     * @param ssb Builder to use
 | 
			
		||||
     */
 | 
			
		||||
    public void setParsedText(SpannableStringBuilder ssb){
 | 
			
		||||
 | 
			
		||||
        String[] parts = text.split("\\s+");
 | 
			
		||||
        String[] parts = ssb.toString().split("\\s+");
 | 
			
		||||
        int pos = 0;
 | 
			
		||||
        for (String part : parts) {
 | 
			
		||||
 | 
			
		||||
@@ -106,7 +112,7 @@ public class ContentTextView extends android.support.v7.widget.AppCompatTextView
 | 
			
		||||
    private abstract class BaseClickableSpan extends ClickableSpan {
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void updateDrawState(TextPaint ds) {
 | 
			
		||||
        public void updateDrawState(@NonNull TextPaint ds) {
 | 
			
		||||
            super.updateDrawState(ds);
 | 
			
		||||
            ds.setUnderlineText(false);
 | 
			
		||||
            ds.setColor(mLinksColor);
 | 
			
		||||
@@ -126,7 +132,7 @@ public class ContentTextView extends android.support.v7.widget.AppCompatTextView
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View widget) {
 | 
			
		||||
        public void onClick(@NonNull View widget) {
 | 
			
		||||
            getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(mURL)));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -143,7 +149,7 @@ public class ContentTextView extends android.support.v7.widget.AppCompatTextView
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View widget) {
 | 
			
		||||
        public void onClick(@NonNull View widget) {
 | 
			
		||||
            MainActivity.FollowTag(getActivity(), mTag);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,293 @@
 | 
			
		||||
package org.communiquons.bbcodeparser;
 | 
			
		||||
 | 
			
		||||
import android.graphics.Color;
 | 
			
		||||
import android.graphics.Typeface;
 | 
			
		||||
import android.os.Build;
 | 
			
		||||
import android.support.annotation.NonNull;
 | 
			
		||||
import android.support.annotation.Nullable;
 | 
			
		||||
import android.text.Layout;
 | 
			
		||||
import android.text.Spannable;
 | 
			
		||||
import android.text.SpannableStringBuilder;
 | 
			
		||||
import android.text.style.AlignmentSpan;
 | 
			
		||||
import android.text.style.ForegroundColorSpan;
 | 
			
		||||
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.UnderlineSpan;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * BBCode parser
 | 
			
		||||
 *
 | 
			
		||||
 * @author Pierre HUBERT
 | 
			
		||||
 */
 | 
			
		||||
public class BBCodeParser {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Debug tag
 | 
			
		||||
     */
 | 
			
		||||
    private static final String TAG = BBCodeParser.class.getSimpleName();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Recursion limit, default set to 10
 | 
			
		||||
     */
 | 
			
		||||
    private int mRecursionLimit = 10;
 | 
			
		||||
 | 
			
		||||
    private char[] text;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Construct new instance of BBCodeParser
 | 
			
		||||
     */
 | 
			
		||||
    public BBCodeParser(){
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getRecursionLimit() {
 | 
			
		||||
        return mRecursionLimit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BBCodeParser setRecursionLimit(int recursionLimit) {
 | 
			
		||||
        this.mRecursionLimit = recursionLimit;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Parse a BBCode string for a {@link TextView}
 | 
			
		||||
     *
 | 
			
		||||
     * @param text The text to parse
 | 
			
		||||
     * @return Parsed string
 | 
			
		||||
     */
 | 
			
		||||
    public SpannableStringBuilder parse(@NonNull String text){
 | 
			
		||||
 | 
			
		||||
        SpannableStringBuilder ssb = new SpannableStringBuilder();
 | 
			
		||||
 | 
			
		||||
        this.text = text.toCharArray();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            parseInternal(null, 0, 0, ssb);
 | 
			
		||||
        } catch (Exception e){
 | 
			
		||||
            Log.e(TAG, "Unable to parse text!");
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
            return new SpannableStringBuilder(text);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ssb;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int parseInternal(@Nullable String parentTag, int level, int pos, SpannableStringBuilder ssb){
 | 
			
		||||
 | 
			
		||||
        int new_pos = pos;
 | 
			
		||||
        int childNumber = 0;
 | 
			
		||||
        while(new_pos < text.length) {
 | 
			
		||||
 | 
			
		||||
            boolean stop = false;
 | 
			
		||||
            while (new_pos < text.length && !stop) {
 | 
			
		||||
                for (; new_pos < text.length && text[new_pos] != '['; new_pos++)
 | 
			
		||||
                    ssb.append(text[new_pos]);
 | 
			
		||||
                new_pos++;
 | 
			
		||||
 | 
			
		||||
                if(new_pos > text.length) new_pos = text.length;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                if(level < mRecursionLimit)
 | 
			
		||||
                    for (int i = new_pos; i < text.length && !stop && text[i] != '[' && text[i] != ' '; i++)
 | 
			
		||||
                        if (text[i] == ']')
 | 
			
		||||
                            stop = true;
 | 
			
		||||
 | 
			
		||||
                if (!stop && text[new_pos-1] == '[')
 | 
			
		||||
                    ssb.append('[');
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(new_pos >= text.length)
 | 
			
		||||
                return new_pos;
 | 
			
		||||
 | 
			
		||||
            int closeTagPos = findChar(new_pos, ']');
 | 
			
		||||
 | 
			
		||||
            //Process the new tag
 | 
			
		||||
            //Check if we have to exit current tag
 | 
			
		||||
            if(text[new_pos] == '/')
 | 
			
		||||
                return closeTagPos;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //Determine tag
 | 
			
		||||
            String tag = String.copyValueOf(text, new_pos, closeTagPos - new_pos);
 | 
			
		||||
 | 
			
		||||
            int length_before = ssb.length();
 | 
			
		||||
 | 
			
		||||
            //Pre decoding
 | 
			
		||||
            preDecodeTag(childNumber, parentTag, tag, ssb, length_before);
 | 
			
		||||
 | 
			
		||||
            //Parse tag content
 | 
			
		||||
            int end_pos = parseInternal(tag,level+1, closeTagPos + 1, ssb);
 | 
			
		||||
            int length_after = ssb.length();
 | 
			
		||||
 | 
			
		||||
            //Post decoding
 | 
			
		||||
            postDecodeTag(tag, ssb, length_before , length_after);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            new_pos = end_pos + 1;
 | 
			
		||||
            childNumber++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new_pos;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private void preDecodeTag(int childNumber, @Nullable String parentTag,
 | 
			
		||||
                              String tag, SpannableStringBuilder ssb, int begin){
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        switch (tag){
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            case "ul":
 | 
			
		||||
            case "ol":
 | 
			
		||||
                ssb.append(System.getProperty("line.separator"));
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            case "li":
 | 
			
		||||
 | 
			
		||||
                if (Objects.equals(parentTag, "ol")) {
 | 
			
		||||
                    ssb.append(String.valueOf(childNumber + 1)).append(". ");
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    ssb.append("\u2022 ");
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            case "quote":
 | 
			
		||||
            case "code":
 | 
			
		||||
                ssb.append(System.getProperty("line.separator"));
 | 
			
		||||
                ssb.append(System.getProperty("line.separator"));
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private void postDecodeTag(String tag, SpannableStringBuilder ssb, int begin, int end) {
 | 
			
		||||
 | 
			
		||||
        Object span = null;
 | 
			
		||||
        Object span2 = null;
 | 
			
		||||
 | 
			
		||||
        String[] args = null;
 | 
			
		||||
 | 
			
		||||
        if(tag.contains("=")){
 | 
			
		||||
            args = tag.split("=");
 | 
			
		||||
            tag = args[0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        switch (tag) {
 | 
			
		||||
 | 
			
		||||
            //Decode italic and bold
 | 
			
		||||
            case "i":
 | 
			
		||||
            case "b":
 | 
			
		||||
                int style = tag.equals("i") ? Typeface.ITALIC : Typeface.BOLD;
 | 
			
		||||
                span = new StyleSpan(style);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            //Underline text
 | 
			
		||||
            case "u":
 | 
			
		||||
                span = new UnderlineSpan();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            //Strikethrough
 | 
			
		||||
            case "s":
 | 
			
		||||
                span = new StrikethroughSpan();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //Superscript
 | 
			
		||||
            case "sup":
 | 
			
		||||
                span = new SuperscriptSpan();
 | 
			
		||||
                span2 = new RelativeSizeSpan(0.6f);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            //Subscript
 | 
			
		||||
            case "sub":
 | 
			
		||||
                span = new SubscriptSpan();
 | 
			
		||||
                span2 = new RelativeSizeSpan(0.6f);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            //Color
 | 
			
		||||
            case "color":
 | 
			
		||||
                if(args == null){
 | 
			
		||||
                    Log.e(TAG, "Invalid color!");
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                span = new ForegroundColorSpan(Color.parseColor(args[1]));
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //Lists - Lists are aligned to the left by default
 | 
			
		||||
            case "ul":
 | 
			
		||||
            case "ol":
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //Left alignment
 | 
			
		||||
            case "left":
 | 
			
		||||
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
 | 
			
		||||
                    span = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_LEFT);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            //Center alignment
 | 
			
		||||
            case "center":
 | 
			
		||||
                span = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            //Right alignment
 | 
			
		||||
            case "right":
 | 
			
		||||
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
 | 
			
		||||
                    span = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_RIGHT);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            //Line break for list items
 | 
			
		||||
            case "li":
 | 
			
		||||
                ssb.append(System.getProperty("line.separator"));
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //Quotes
 | 
			
		||||
            case "quote":
 | 
			
		||||
            case "code":
 | 
			
		||||
                postDecodeTag("left", ssb, begin, end);
 | 
			
		||||
                postDecodeTag("i", ssb, begin, end);
 | 
			
		||||
                ssb.append(System.getProperty("line.separator"));
 | 
			
		||||
                ssb.append(System.getProperty("line.separator"));
 | 
			
		||||
                ssb.append(System.getProperty("line.separator"));
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
                Log.e(TAG, tag + " not recognized: " + tag);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(span != null)
 | 
			
		||||
            ssb.setSpan(span, begin, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 | 
			
		||||
        if(span2 != null)
 | 
			
		||||
            ssb.setSpan(span2, begin, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private int findChar(int start, char c){
 | 
			
		||||
        for (int i = start; i < text.length; i++) {
 | 
			
		||||
            if(text[i] == c)
 | 
			
		||||
                return i;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return text.length;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user