Mercurial Hosting > luan
diff src/goodjava/bbcode/BBCode.java @ 1702:8ad468cc88d4
add goodjava/bbcode
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 30 Jun 2022 20:04:34 -0600 |
parents | src/luan/modules/parsers/BBCode.java@8fbcc4747091 |
children | 3a61451f8130 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/bbcode/BBCode.java Thu Jun 30 20:04:34 2022 -0600 @@ -0,0 +1,334 @@ +package goodjava.bbcode; + +import java.util.List; +import java.util.ArrayList; +import goodjava.parser.Parser; + + +public final class BBCode { + + public enum Target { HTML, TEXT } + + public interface Quoter { + public String quote(Target target,String text,String param); + } + + public static final Quoter defaultQuoter = new Quoter() { + public String quote(Target target,String text,String param) { + StringBuilder sb = new StringBuilder(); + sb.append( "<blockquote>" ); + if( param != null ) { + sb.append( htmlEncode(param) ); + sb.append( " wrote:\n" ); + } + sb.append( text ); + sb.append( "</blockquote>" ); + return sb.toString(); + } + }; + + public Target target = Target.HTML; + public Quoter quoter = defaultQuoter; + private final Parser parser; + + public BBCode(String text) { + this.parser = new Parser(text); + } + + public String parse() { + StringBuilder sb = new StringBuilder(); + StringBuilder text = new StringBuilder(); + while( !parser.endOfInput() ) { + String block = parseBlock(); + if( block != null ) { + sb.append( textToString(text) ); + sb.append(block); + } else { + text.append( parser.currentChar() ); + parser.anyChar(); + } + } + sb.append( textToString(text) ); + return sb.toString(); + } + + private String parseWellFormed() { + StringBuilder sb = new StringBuilder(); + StringBuilder text = new StringBuilder(); + while( !parser.endOfInput() ) { + String block = parseBlock(); + if( block != null ) { + sb.append( textToString(text) ); + sb.append(block); + continue; + } + if( couldBeTag() ) + break; + text.append( parser.currentChar() ); + parser.anyChar(); + } + sb.append( textToString(text) ); + return sb.toString(); + } + + private String textToString(StringBuilder text) { + String s = text.toString(); + text.setLength(0); + if( target == Target.HTML ) + s = htmlEncode(s); + return s; + } + + private boolean couldBeTag() { + if( parser.currentChar() != '[' ) + return false; + return parser.testIgnoreCase("[b]") + || parser.testIgnoreCase("[/b]") + || parser.testIgnoreCase("[i]") + || parser.testIgnoreCase("[/i]") + || parser.testIgnoreCase("[u]") + || parser.testIgnoreCase("[/u]") + || parser.testIgnoreCase("[url]") + || parser.testIgnoreCase("[url=") + || parser.testIgnoreCase("[/url]") + || parser.testIgnoreCase("[code]") + || parser.testIgnoreCase("[/code]") + || parser.testIgnoreCase("[img]") + || parser.testIgnoreCase("[/img]") + || parser.testIgnoreCase("[color=") + || parser.testIgnoreCase("[/color]") + || parser.testIgnoreCase("[size=") + || parser.testIgnoreCase("[/size]") + || parser.testIgnoreCase("[youtube]") + || parser.testIgnoreCase("[/youtube]") + || parser.testIgnoreCase("[quote]") + || parser.testIgnoreCase("[quote=") + || parser.testIgnoreCase("[/quote]") + ; + } + + private String parseBlock() { + if( parser.currentChar() != '[' ) + return null; + String s; + s = parseB(); if(s!=null) return s; + s = parseI(); if(s!=null) return s; + s = parseU(); if(s!=null) return s; + s = parseUrl1(); if(s!=null) return s; + s = parseUrl2(); if(s!=null) return s; + s = parseCode(); if(s!=null) return s; + s = parseImg(); if(s!=null) return s; + s = parseColor(); if(s!=null) return s; + s = parseSize(); if(s!=null) return s; + s = parseYouTube(); if(s!=null) return s; + s = parseQuote1(); if(s!=null) return s; + s = parseQuote2(); if(s!=null) return s; + return null; + } + + private String parseB() { + parser.begin(); + if( !parser.matchIgnoreCase("[b]") ) + return parser.failure(null); + String content = parseWellFormed(); + if( !parser.matchIgnoreCase("[/b]") ) + return parser.failure(null); + String rtn = target==Target.HTML ? "<b>"+content+"</b>" : content; + return parser.success(rtn); + } + + private String parseI() { + parser.begin(); + if( !parser.matchIgnoreCase("[i]") ) + return parser.failure(null); + String content = parseWellFormed(); + if( !parser.matchIgnoreCase("[/i]") ) + return parser.failure(null); + String rtn = target==Target.HTML ? "<i>"+content+"</i>" : content; + return parser.success(rtn); + } + + private String parseU() { + parser.begin(); + if( !parser.matchIgnoreCase("[u]") ) + return parser.failure(null); + String content = parseWellFormed(); + if( !parser.matchIgnoreCase("[/u]") ) + return parser.failure(null); + String rtn = target==Target.HTML ? "<u>"+content+"</u>" : content; + return parser.success(rtn); + } + + private String parseUrl1() { + parser.begin(); + if( !parser.matchIgnoreCase("[url]") ) + return parser.failure(null); + String url = parseRealUrl(); + if( !parser.matchIgnoreCase("[/url]") ) + return parser.failure(null); + String rtn = target==Target.HTML ? "<a href='"+url+"'>"+url+"</a>" : url; + return parser.success(rtn); + } + + private String parseUrl2() { + parser.begin(); + if( !parser.matchIgnoreCase("[url=") ) + return parser.failure(null); + String url = parseRealUrl(); + if( !parser.match(']') ) + return parser.failure(null); + String content = parseWellFormed(); + if( !parser.matchIgnoreCase("[/url]") ) + return parser.failure(null); + String rtn = target==Target.HTML ? "<a href='"+url+"'>"+content+"</a>" : content; + return parser.success(rtn); + } + + private String parseRealUrl() { + parser.begin(); + while( parser.match(' ') ); + int start = parser.currentIndex(); + if( !parser.matchIgnoreCase("http") ) + return parser.failure(null); + parser.matchIgnoreCase("s"); + if( !parser.matchIgnoreCase("://") ) + return parser.failure(null); + while( parser.noneOf(" []'") ); + String url = parser.textFrom(start); + while( parser.match(' ') ); + return parser.success(url); + } + + private String parseCode() { + parser.begin(); + if( !parser.matchIgnoreCase("[code]") ) + return parser.failure(null); + int start = parser.currentIndex(); + while( !parser.testIgnoreCase("[/code]") ) { + if( !parser.anyChar() ) + return parser.failure(null); + } + String content = parser.textFrom(start); + if( !parser.matchIgnoreCase("[/code]") ) throw new RuntimeException(); + String rtn = target==Target.HTML ? "<code>"+content+"</code>" : content; + return parser.success(rtn); + } + + private String parseImg() { + parser.begin(); + if( !parser.matchIgnoreCase("[img]") ) + return parser.failure(null); + String url = parseRealUrl(); + if( !parser.matchIgnoreCase("[/img]") ) + return parser.failure(null); + String rtn = target==Target.HTML ? "<img src='"+url+"'>" : ""; + return parser.success(rtn); + } + + private String parseColor() { + parser.begin(); + if( !parser.matchIgnoreCase("[color=") ) + return parser.failure(null); + int start = parser.currentIndex(); + parser.match('#'); + while( parser.inCharRange('0','9') + || parser.inCharRange('a','z') + || parser.inCharRange('A','Z') + ); + String color = parser.textFrom(start); + if( !parser.match(']') ) + return parser.failure(null); + String content = parseWellFormed(); + if( !parser.matchIgnoreCase("[/color]") ) + return parser.failure(null); + String rtn = target==Target.HTML ? "<span style='color: "+color+"'>"+content+"</span>" : content; + return parser.success(rtn); + } + + private String parseSize() { + parser.begin(); + if( !parser.matchIgnoreCase("[size=") ) + return parser.failure(null); + int start = parser.currentIndex(); + while( parser.match('.') || parser.inCharRange('0','9') ); + String size = parser.textFrom(start); + if( !parser.match(']') ) + return parser.failure(null); + String content = parseWellFormed(); + if( !parser.matchIgnoreCase("[/size]") ) + return parser.failure(null); + String rtn = target==Target.HTML ? "<span style='font-size: "+size+"em'>"+content+"</span>" : content; + return parser.success(rtn); + } + + private String parseYouTube() { + parser.begin(); + if( !parser.matchIgnoreCase("[youtube]") ) + return parser.failure(null); + int start = parser.currentIndex(); + while( parser.inCharRange('0','9') + || parser.inCharRange('a','z') + || parser.inCharRange('A','Z') + || parser.match('-') + || parser.match('_') + ); + String id = parser.textFrom(start); + if( id.length()==0 || !parser.matchIgnoreCase("[/youtube]") ) + return parser.failure(null); + String rtn = target==Target.HTML ? "<iframe width='420' height='315' src='https://www.youtube.com/embed/"+id+"' frameborder='0' allowfullscreen></iframe>" : ""; + return parser.success(rtn); + } + + private String parseQuote1() { + parser.begin(); + if( !parser.matchIgnoreCase("[quote]") ) + return parser.failure(null); + String content = parseWellFormed(); + if( !parser.matchIgnoreCase("[/quote]") ) + return parser.failure(null); + String rtn = quoter.quote(target,content,null); + return parser.success(rtn); + } + + private String parseQuote2() { + parser.begin(); + if( !parser.matchIgnoreCase("[quote=") ) + return parser.failure(null); + List args = new ArrayList(); + int start = parser.currentIndex(); + while( parser.noneOf("[]") ); + String name = parser.textFrom(start).trim(); + if( !parser.match(']') ) + return parser.failure(null); + String content = parseWellFormed(); + if( !parser.matchIgnoreCase("[/quote]") ) + return parser.failure(null); + String rtn = quoter.quote(target,content,name); + return parser.success(rtn); + } + + public static String htmlEncode(String s) { + final char[] a = s.toCharArray(); + StringBuilder buf = new StringBuilder(); + for( char c : a ) { + switch(c) { + case '&': + buf.append("&"); + break; + case '<': + buf.append("<"); + break; + case '>': + buf.append(">"); + break; + case '"': + buf.append("""); + break; + default: + buf.append(c); + } + } + return buf.toString(); + } + +}