Integrated BBCode parser

This commit is contained in:
Pierre HUBERT 2019-03-16 11:40:06 +01:00
parent b09127efa2
commit 513330eea9
3 changed files with 314 additions and 17 deletions

View File

@ -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.WebLinkView;
import org.communiquons.android.comunic.client.ui.views.WebUserAccountImage; import org.communiquons.android.comunic.client.ui.views.WebUserAccountImage;
import org.communiquons.android.comunic.client.ui.views.YouTubeVideoView; import org.communiquons.android.comunic.client.ui.views.YouTubeVideoView;
import org.communiquons.bbcodeparser.BBCodeParser;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects;
/** /**
* Posts adapter * Posts adapter
@ -265,8 +267,8 @@ public class PostsAdapter extends BaseRecyclerViewAdapter {
else if(post.getPage_type() == PageType.USER_PAGE else if(post.getPage_type() == PageType.USER_PAGE
&& mList.getUsersInfo().containsKey(post.getPage_id())){ && mList.getUsersInfo().containsKey(post.getPage_id())){
mTargetPageName.setText(mList.getUsersInfo().get(post.getPage_id()) mTargetPageName.setText(Objects.requireNonNull(
.getDisplayFullName()); mList.getUsersInfo().get(post.getPage_id())).getDisplayFullName());
} }
@ -274,8 +276,8 @@ public class PostsAdapter extends BaseRecyclerViewAdapter {
else if(post.getPage_type() == PageType.GROUP_PAGE else if(post.getPage_type() == PageType.GROUP_PAGE
&& mList.getGroupsInfo().containsKey(post.getPage_id())){ && mList.getGroupsInfo().containsKey(post.getPage_id())){
mTargetPageName.setText(mList.getGroupsInfo().get(post.getPage_id()) mTargetPageName.setText(Objects.requireNonNull(
.getDisplayName()); mList.getGroupsInfo().get(post.getPage_id())).getDisplayName());
} }
@ -320,11 +322,7 @@ public class PostsAdapter extends BaseRecyclerViewAdapter {
//Set post content //Set post content
mPostContent.setParsedText( mPostContent.setParsedText(new BBCodeParser().parse(post.getContent()));
StringsUtils.RemoveBBCode(
UiUtils.prepareStringTextView(post.getContent())
)
);
//Post likes //Post likes

View File

@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; 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 * @param text The text to parse
*/ */
public void setParsedText(String text) { 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; int pos = 0;
for (String part : parts) { for (String part : parts) {
@ -106,7 +112,7 @@ public class ContentTextView extends android.support.v7.widget.AppCompatTextView
private abstract class BaseClickableSpan extends ClickableSpan { private abstract class BaseClickableSpan extends ClickableSpan {
@Override @Override
public void updateDrawState(TextPaint ds) { public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds); super.updateDrawState(ds);
ds.setUnderlineText(false); ds.setUnderlineText(false);
ds.setColor(mLinksColor); ds.setColor(mLinksColor);
@ -126,7 +132,7 @@ public class ContentTextView extends android.support.v7.widget.AppCompatTextView
} }
@Override @Override
public void onClick(View widget) { public void onClick(@NonNull View widget) {
getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(mURL))); getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(mURL)));
} }
} }
@ -143,7 +149,7 @@ public class ContentTextView extends android.support.v7.widget.AppCompatTextView
} }
@Override @Override
public void onClick(View widget) { public void onClick(@NonNull View widget) {
MainActivity.FollowTag(getActivity(), mTag); MainActivity.FollowTag(getActivity(), mTag);
} }
} }

View File

@ -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;
}
}