Mercurial Hosting > luan
annotate 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 |
| rev | line source |
|---|---|
| 1702 | 1 package goodjava.bbcode; |
| 585 | 2 |
|
637
6ea90dc10375
bbcode parser now takes a quoter function
Franklin Schmidt <fschmidt@gmail.com>
parents:
588
diff
changeset
|
3 import java.util.List; |
|
6ea90dc10375
bbcode parser now takes a quoter function
Franklin Schmidt <fschmidt@gmail.com>
parents:
588
diff
changeset
|
4 import java.util.ArrayList; |
|
1402
27efb1fcbcb5
move luan.lib to goodjava
Franklin Schmidt <fschmidt@gmail.com>
parents:
1335
diff
changeset
|
5 import goodjava.parser.Parser; |
|
637
6ea90dc10375
bbcode parser now takes a quoter function
Franklin Schmidt <fschmidt@gmail.com>
parents:
588
diff
changeset
|
6 |
| 585 | 7 |
| 8 public final class BBCode { | |
| 9 | |
| 1702 | 10 public enum Target { HTML, TEXT } |
| 585 | 11 |
| 1702 | 12 public interface Quoter { |
| 13 public String quote(Target target,String text,String param); | |
| 585 | 14 } |
| 15 | |
| 1702 | 16 public static final Quoter defaultQuoter = new Quoter() { |
| 17 public String quote(Target target,String text,String param) { | |
| 18 StringBuilder sb = new StringBuilder(); | |
| 19 sb.append( "<blockquote>" ); | |
| 20 if( param != null ) { | |
| 21 sb.append( htmlEncode(param) ); | |
| 22 sb.append( " wrote:\n" ); | |
| 23 } | |
| 24 sb.append( text ); | |
| 25 sb.append( "</blockquote>" ); | |
| 26 return sb.toString(); | |
| 27 } | |
| 28 }; | |
| 585 | 29 |
| 1702 | 30 public Target target = Target.HTML; |
| 31 public Quoter quoter = defaultQuoter; | |
| 32 private final Parser parser; | |
| 33 | |
| 34 public BBCode(String text) { | |
| 585 | 35 this.parser = new Parser(text); |
| 36 } | |
| 37 | |
| 1702 | 38 public String parse() { |
| 585 | 39 StringBuilder sb = new StringBuilder(); |
| 1247 | 40 StringBuilder text = new StringBuilder(); |
| 585 | 41 while( !parser.endOfInput() ) { |
| 42 String block = parseBlock(); | |
| 1247 | 43 if( block != null ) { |
| 44 sb.append( textToString(text) ); | |
| 585 | 45 sb.append(block); |
| 1247 | 46 } else { |
| 47 text.append( parser.currentChar() ); | |
| 585 | 48 parser.anyChar(); |
| 49 } | |
| 50 } | |
| 1247 | 51 sb.append( textToString(text) ); |
| 585 | 52 return sb.toString(); |
| 53 } | |
| 54 | |
| 1702 | 55 private String parseWellFormed() { |
| 585 | 56 StringBuilder sb = new StringBuilder(); |
| 1247 | 57 StringBuilder text = new StringBuilder(); |
| 585 | 58 while( !parser.endOfInput() ) { |
| 59 String block = parseBlock(); | |
| 60 if( block != null ) { | |
| 1247 | 61 sb.append( textToString(text) ); |
| 585 | 62 sb.append(block); |
| 63 continue; | |
| 64 } | |
| 65 if( couldBeTag() ) | |
| 66 break; | |
| 1247 | 67 text.append( parser.currentChar() ); |
| 585 | 68 parser.anyChar(); |
| 69 } | |
| 1247 | 70 sb.append( textToString(text) ); |
| 585 | 71 return sb.toString(); |
| 72 } | |
| 73 | |
| 1702 | 74 private String textToString(StringBuilder text) { |
| 1247 | 75 String s = text.toString(); |
| 76 text.setLength(0); | |
| 1702 | 77 if( target == Target.HTML ) |
| 78 s = htmlEncode(s); | |
| 1247 | 79 return s; |
| 80 } | |
| 81 | |
| 585 | 82 private boolean couldBeTag() { |
| 83 if( parser.currentChar() != '[' ) | |
| 84 return false; | |
| 85 return parser.testIgnoreCase("[b]") | |
| 86 || parser.testIgnoreCase("[/b]") | |
| 87 || parser.testIgnoreCase("[i]") | |
| 88 || parser.testIgnoreCase("[/i]") | |
| 89 || parser.testIgnoreCase("[u]") | |
| 90 || parser.testIgnoreCase("[/u]") | |
| 91 || parser.testIgnoreCase("[url]") | |
| 92 || parser.testIgnoreCase("[url=") | |
| 93 || parser.testIgnoreCase("[/url]") | |
| 94 || parser.testIgnoreCase("[code]") | |
| 95 || parser.testIgnoreCase("[/code]") | |
| 96 || parser.testIgnoreCase("[img]") | |
| 97 || parser.testIgnoreCase("[/img]") | |
| 98 || parser.testIgnoreCase("[color=") | |
| 99 || parser.testIgnoreCase("[/color]") | |
| 100 || parser.testIgnoreCase("[size=") | |
| 101 || parser.testIgnoreCase("[/size]") | |
| 102 || parser.testIgnoreCase("[youtube]") | |
| 103 || parser.testIgnoreCase("[/youtube]") | |
| 104 || parser.testIgnoreCase("[quote]") | |
| 105 || parser.testIgnoreCase("[quote=") | |
| 106 || parser.testIgnoreCase("[/quote]") | |
| 107 ; | |
| 108 } | |
| 109 | |
| 1702 | 110 private String parseBlock() { |
| 585 | 111 if( parser.currentChar() != '[' ) |
| 112 return null; | |
| 113 String s; | |
| 114 s = parseB(); if(s!=null) return s; | |
| 115 s = parseI(); if(s!=null) return s; | |
| 116 s = parseU(); if(s!=null) return s; | |
| 117 s = parseUrl1(); if(s!=null) return s; | |
| 118 s = parseUrl2(); if(s!=null) return s; | |
| 119 s = parseCode(); if(s!=null) return s; | |
| 120 s = parseImg(); if(s!=null) return s; | |
| 121 s = parseColor(); if(s!=null) return s; | |
| 122 s = parseSize(); if(s!=null) return s; | |
| 123 s = parseYouTube(); if(s!=null) return s; | |
| 124 s = parseQuote1(); if(s!=null) return s; | |
| 125 s = parseQuote2(); if(s!=null) return s; | |
| 126 return null; | |
| 127 } | |
| 128 | |
| 1702 | 129 private String parseB() { |
| 585 | 130 parser.begin(); |
| 131 if( !parser.matchIgnoreCase("[b]") ) | |
| 132 return parser.failure(null); | |
| 133 String content = parseWellFormed(); | |
| 134 if( !parser.matchIgnoreCase("[/b]") ) | |
| 135 return parser.failure(null); | |
| 1702 | 136 String rtn = target==Target.HTML ? "<b>"+content+"</b>" : content; |
| 585 | 137 return parser.success(rtn); |
| 138 } | |
| 139 | |
| 1702 | 140 private String parseI() { |
| 585 | 141 parser.begin(); |
| 142 if( !parser.matchIgnoreCase("[i]") ) | |
| 143 return parser.failure(null); | |
| 144 String content = parseWellFormed(); | |
| 145 if( !parser.matchIgnoreCase("[/i]") ) | |
| 146 return parser.failure(null); | |
| 1702 | 147 String rtn = target==Target.HTML ? "<i>"+content+"</i>" : content; |
| 585 | 148 return parser.success(rtn); |
| 149 } | |
| 150 | |
| 1702 | 151 private String parseU() { |
| 585 | 152 parser.begin(); |
| 153 if( !parser.matchIgnoreCase("[u]") ) | |
| 154 return parser.failure(null); | |
| 155 String content = parseWellFormed(); | |
| 156 if( !parser.matchIgnoreCase("[/u]") ) | |
| 157 return parser.failure(null); | |
| 1702 | 158 String rtn = target==Target.HTML ? "<u>"+content+"</u>" : content; |
| 585 | 159 return parser.success(rtn); |
| 160 } | |
| 161 | |
| 162 private String parseUrl1() { | |
| 163 parser.begin(); | |
| 164 if( !parser.matchIgnoreCase("[url]") ) | |
| 165 return parser.failure(null); | |
| 166 String url = parseRealUrl(); | |
| 167 if( !parser.matchIgnoreCase("[/url]") ) | |
| 168 return parser.failure(null); | |
| 1702 | 169 String rtn = target==Target.HTML ? "<a href='"+url+"'>"+url+"</a>" : url; |
| 585 | 170 return parser.success(rtn); |
| 171 } | |
| 172 | |
| 1702 | 173 private String parseUrl2() { |
| 585 | 174 parser.begin(); |
| 175 if( !parser.matchIgnoreCase("[url=") ) | |
| 176 return parser.failure(null); | |
| 177 String url = parseRealUrl(); | |
| 178 if( !parser.match(']') ) | |
| 179 return parser.failure(null); | |
| 180 String content = parseWellFormed(); | |
| 181 if( !parser.matchIgnoreCase("[/url]") ) | |
| 182 return parser.failure(null); | |
| 1702 | 183 String rtn = target==Target.HTML ? "<a href='"+url+"'>"+content+"</a>" : content; |
| 585 | 184 return parser.success(rtn); |
| 185 } | |
| 186 | |
| 187 private String parseRealUrl() { | |
| 188 parser.begin(); | |
| 189 while( parser.match(' ') ); | |
| 190 int start = parser.currentIndex(); | |
| 191 if( !parser.matchIgnoreCase("http") ) | |
| 192 return parser.failure(null); | |
| 193 parser.matchIgnoreCase("s"); | |
| 194 if( !parser.matchIgnoreCase("://") ) | |
| 195 return parser.failure(null); | |
| 196 while( parser.noneOf(" []'") ); | |
| 197 String url = parser.textFrom(start); | |
| 198 while( parser.match(' ') ); | |
| 199 return parser.success(url); | |
| 200 } | |
| 201 | |
| 202 private String parseCode() { | |
| 203 parser.begin(); | |
| 204 if( !parser.matchIgnoreCase("[code]") ) | |
| 205 return parser.failure(null); | |
| 206 int start = parser.currentIndex(); | |
| 207 while( !parser.testIgnoreCase("[/code]") ) { | |
| 208 if( !parser.anyChar() ) | |
| 209 return parser.failure(null); | |
| 210 } | |
| 211 String content = parser.textFrom(start); | |
| 212 if( !parser.matchIgnoreCase("[/code]") ) throw new RuntimeException(); | |
| 1702 | 213 String rtn = target==Target.HTML ? "<code>"+content+"</code>" : content; |
| 585 | 214 return parser.success(rtn); |
| 215 } | |
| 216 | |
| 217 private String parseImg() { | |
| 218 parser.begin(); | |
| 219 if( !parser.matchIgnoreCase("[img]") ) | |
| 220 return parser.failure(null); | |
| 221 String url = parseRealUrl(); | |
| 222 if( !parser.matchIgnoreCase("[/img]") ) | |
| 223 return parser.failure(null); | |
| 1702 | 224 String rtn = target==Target.HTML ? "<img src='"+url+"'>" : ""; |
| 585 | 225 return parser.success(rtn); |
| 226 } | |
| 227 | |
| 1702 | 228 private String parseColor() { |
| 585 | 229 parser.begin(); |
| 230 if( !parser.matchIgnoreCase("[color=") ) | |
| 231 return parser.failure(null); | |
| 232 int start = parser.currentIndex(); | |
| 233 parser.match('#'); | |
| 234 while( parser.inCharRange('0','9') | |
| 235 || parser.inCharRange('a','z') | |
| 236 || parser.inCharRange('A','Z') | |
| 237 ); | |
| 238 String color = parser.textFrom(start); | |
| 239 if( !parser.match(']') ) | |
| 240 return parser.failure(null); | |
| 241 String content = parseWellFormed(); | |
| 242 if( !parser.matchIgnoreCase("[/color]") ) | |
| 243 return parser.failure(null); | |
| 1702 | 244 String rtn = target==Target.HTML ? "<span style='color: "+color+"'>"+content+"</span>" : content; |
| 585 | 245 return parser.success(rtn); |
| 246 } | |
| 247 | |
| 1702 | 248 private String parseSize() { |
| 585 | 249 parser.begin(); |
| 250 if( !parser.matchIgnoreCase("[size=") ) | |
| 251 return parser.failure(null); | |
| 252 int start = parser.currentIndex(); | |
| 253 while( parser.match('.') || parser.inCharRange('0','9') ); | |
| 254 String size = parser.textFrom(start); | |
| 255 if( !parser.match(']') ) | |
| 256 return parser.failure(null); | |
| 257 String content = parseWellFormed(); | |
| 258 if( !parser.matchIgnoreCase("[/size]") ) | |
| 259 return parser.failure(null); | |
| 1702 | 260 String rtn = target==Target.HTML ? "<span style='font-size: "+size+"em'>"+content+"</span>" : content; |
| 585 | 261 return parser.success(rtn); |
| 262 } | |
| 263 | |
| 264 private String parseYouTube() { | |
| 265 parser.begin(); | |
| 266 if( !parser.matchIgnoreCase("[youtube]") ) | |
| 267 return parser.failure(null); | |
| 268 int start = parser.currentIndex(); | |
| 269 while( parser.inCharRange('0','9') | |
| 270 || parser.inCharRange('a','z') | |
| 271 || parser.inCharRange('A','Z') | |
| 272 || parser.match('-') | |
| 273 || parser.match('_') | |
| 274 ); | |
| 275 String id = parser.textFrom(start); | |
| 276 if( id.length()==0 || !parser.matchIgnoreCase("[/youtube]") ) | |
| 277 return parser.failure(null); | |
| 1702 | 278 String rtn = target==Target.HTML ? "<iframe width='420' height='315' src='https://www.youtube.com/embed/"+id+"' frameborder='0' allowfullscreen></iframe>" : ""; |
| 585 | 279 return parser.success(rtn); |
| 280 } | |
| 281 | |
| 1702 | 282 private String parseQuote1() { |
| 585 | 283 parser.begin(); |
| 284 if( !parser.matchIgnoreCase("[quote]") ) | |
| 285 return parser.failure(null); | |
| 286 String content = parseWellFormed(); | |
| 287 if( !parser.matchIgnoreCase("[/quote]") ) | |
| 288 return parser.failure(null); | |
| 1702 | 289 String rtn = quoter.quote(target,content,null); |
| 585 | 290 return parser.success(rtn); |
| 291 } | |
| 292 | |
| 1702 | 293 private String parseQuote2() { |
| 585 | 294 parser.begin(); |
| 295 if( !parser.matchIgnoreCase("[quote=") ) | |
| 296 return parser.failure(null); | |
|
637
6ea90dc10375
bbcode parser now takes a quoter function
Franklin Schmidt <fschmidt@gmail.com>
parents:
588
diff
changeset
|
297 List args = new ArrayList(); |
| 585 | 298 int start = parser.currentIndex(); |
| 1702 | 299 while( parser.noneOf("[]") ); |
| 585 | 300 String name = parser.textFrom(start).trim(); |
| 301 if( !parser.match(']') ) | |
| 302 return parser.failure(null); | |
| 303 String content = parseWellFormed(); | |
| 304 if( !parser.matchIgnoreCase("[/quote]") ) | |
| 305 return parser.failure(null); | |
| 1702 | 306 String rtn = quoter.quote(target,content,name); |
|
637
6ea90dc10375
bbcode parser now takes a quoter function
Franklin Schmidt <fschmidt@gmail.com>
parents:
588
diff
changeset
|
307 return parser.success(rtn); |
| 585 | 308 } |
| 309 | |
| 1702 | 310 public static String htmlEncode(String s) { |
| 311 final char[] a = s.toCharArray(); | |
| 312 StringBuilder buf = new StringBuilder(); | |
| 313 for( char c : a ) { | |
| 314 switch(c) { | |
| 315 case '&': | |
| 316 buf.append("&"); | |
| 317 break; | |
| 318 case '<': | |
| 319 buf.append("<"); | |
| 320 break; | |
| 321 case '>': | |
| 322 buf.append(">"); | |
| 323 break; | |
| 324 case '"': | |
| 325 buf.append("""); | |
| 326 break; | |
| 327 default: | |
| 328 buf.append(c); | |
| 329 } | |
| 330 } | |
| 331 return buf.toString(); | |
| 332 } | |
| 333 | |
| 585 | 334 } |
