comparison src/goodjava/bbcode/BBCode.java @ 1705:a6e27c8e7ef4

bbcode now returns data
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 03 Jul 2022 21:59:38 -0600
parents 3a61451f8130
children 05d14db623b6
comparison
equal deleted inserted replaced
1704:19df8abc9805 1705:a6e27c8e7ef4
1 package goodjava.bbcode; 1 package goodjava.bbcode;
2 2
3 import java.util.List; 3 import java.util.List;
4 import java.util.ArrayList; 4 import java.util.ArrayList;
5 import java.util.Map;
6 import java.util.LinkedHashMap;
5 import java.util.regex.Matcher; 7 import java.util.regex.Matcher;
6 import java.util.regex.Pattern; 8 import java.util.regex.Pattern;
7 import goodjava.parser.Parser; 9 import goodjava.parser.Parser;
8 10
9 11
10 public final class BBCode { 12 public final class BBCode {
11 13
12 public enum Target { HTML, TEXT } 14 public final class Element {
13 15 public final String name;
14 public interface Quoter { 16 public final String param;
15 public String quote(Target target,String text,String param); 17 public final Object contents; // String, Element, or List
16 } 18 public final Map<String,String> extra;
17 19
18 public static final Quoter defaultQuoter = new Quoter() { 20 private Element(String name,Object contents) {
19 public String quote(Target target,String text,String param) { 21 this(name,null,contents);
20 StringBuilder sb = new StringBuilder(); 22 }
21 sb.append( "<blockquote>" ); 23
22 if( param != null ) { 24 private Element(String name,String param,Object contents) {
23 sb.append( htmlEncode(param) ); 25 this(name,param,contents,null);
24 sb.append( " wrote:\n" ); 26 }
25 } 27
26 sb.append( text ); 28 private Element(String name,String param,Object contents,Map<String,String> extra) {
27 sb.append( "</blockquote>" ); 29 this.name = name.toLowerCase();
28 return sb.toString(); 30 this.param = param;
29 } 31 this.contents = contents;
30 }; 32 this.extra = extra;
31 33 }
32 public Target target = Target.HTML; 34 }
33 public Quoter quoter = defaultQuoter; 35
36 public static Object parse(String text) {
37 if( text.indexOf('[') == -1 )
38 return text;
39 return new BBCode(text).parse();
40 }
41
34 private final Parser parser; 42 private final Parser parser;
35 43
36 public BBCode(String text) { 44 private BBCode(String text) {
37 this.parser = new Parser(text); 45 this.parser = new Parser(text);
38 } 46 }
39 47
40 public String parse() { 48 private Object parse() {
41 StringBuilder sb = new StringBuilder(); 49 List list = new ArrayList();
42 StringBuilder text = new StringBuilder(); 50 StringBuilder text = new StringBuilder();
43 while( !parser.endOfInput() ) { 51 while( !parser.endOfInput() ) {
44 String block = parseBlock(); 52 Element block = parseBlock();
45 if( block != null ) { 53 if( block != null ) {
46 sb.append( textToString(text) ); 54 add(list,text);
47 sb.append(block); 55 list.add( block );
48 } else { 56 } else {
49 text.append( parser.currentChar() ); 57 text.append( parser.currentChar() );
50 parser.anyChar(); 58 parser.anyChar();
51 } 59 }
52 } 60 }
53 sb.append( textToString(text) ); 61 add(list,text);
54 return sb.toString(); 62 return list.size()==1 ? list.get(0) : list;
55 } 63 }
56 64
57 private String parseWellFormed() { 65 private Object parseWellFormed() {
58 StringBuilder sb = new StringBuilder(); 66 List list = new ArrayList();
59 StringBuilder text = new StringBuilder(); 67 StringBuilder text = new StringBuilder();
60 while( !parser.endOfInput() ) { 68 while( !parser.endOfInput() ) {
61 String block = parseBlock(); 69 Element block = parseBlock();
62 if( block != null ) { 70 if( block != null ) {
63 sb.append( textToString(text) ); 71 add(list,text);
64 sb.append(block); 72 list.add( block );
65 continue; 73 continue;
66 } 74 }
67 if( couldBeTag() ) 75 if( couldBeTag() )
68 break; 76 break;
69 text.append( parser.currentChar() ); 77 text.append( parser.currentChar() );
70 parser.anyChar(); 78 parser.anyChar();
71 } 79 }
72 sb.append( textToString(text) ); 80 add(list,text);
73 return sb.toString(); 81 return list.size()==1 ? list.get(0) : list;
74 } 82 }
75 83
76 private String textToString(StringBuilder text) { 84 private void add(List list,StringBuilder text) {
77 String s = text.toString(); 85 if( text.length() > 0 ) {
78 text.setLength(0); 86 list.add( text.toString() );
79 if( target == Target.HTML ) 87 text.setLength(0);
80 s = htmlEncode(s); 88 }
81 return s;
82 } 89 }
83 90
84 private boolean couldBeTag() { 91 private boolean couldBeTag() {
85 if( parser.currentChar() != '[' ) 92 if( parser.currentChar() != '[' )
86 return false; 93 return false;
107 || parser.testIgnoreCase("[quote=") 114 || parser.testIgnoreCase("[quote=")
108 || parser.testIgnoreCase("[/quote]") 115 || parser.testIgnoreCase("[/quote]")
109 ; 116 ;
110 } 117 }
111 118
112 private String parseBlock() { 119 private Element parseBlock() {
113 if( parser.currentChar() != '[' ) 120 if( parser.currentChar() != '[' )
114 return null; 121 return null;
115 String s; 122 Element s;
116 s = parseB(); if(s!=null) return s; 123 s = parseB(); if(s!=null) return s;
117 s = parseI(); if(s!=null) return s; 124 s = parseI(); if(s!=null) return s;
118 s = parseU(); if(s!=null) return s; 125 s = parseU(); if(s!=null) return s;
119 s = parseUrl1(); if(s!=null) return s; 126 s = parseUrl1(); if(s!=null) return s;
120 s = parseUrl2(); if(s!=null) return s; 127 s = parseUrl2(); if(s!=null) return s;
126 s = parseQuote1(); if(s!=null) return s; 133 s = parseQuote1(); if(s!=null) return s;
127 s = parseQuote2(); if(s!=null) return s; 134 s = parseQuote2(); if(s!=null) return s;
128 return null; 135 return null;
129 } 136 }
130 137
131 private String parseB() { 138 private Element parseB() {
132 parser.begin(); 139 parser.begin();
133 if( !parser.matchIgnoreCase("[b]") ) 140 if( !parser.matchIgnoreCase("[b]") )
134 return parser.failure(null); 141 return parser.failure(null);
135 String content = parseWellFormed(); 142 Object content = parseWellFormed();
136 if( !parser.matchIgnoreCase("[/b]") ) 143 if( !parser.matchIgnoreCase("[/b]") )
137 return parser.failure(null); 144 return parser.failure(null);
138 String rtn = target==Target.HTML ? "<b>"+content+"</b>" : content; 145 Element rtn = new Element("b",content);
139 return parser.success(rtn); 146 return parser.success(rtn);
140 } 147 }
141 148
142 private String parseI() { 149 private Element parseI() {
143 parser.begin(); 150 parser.begin();
144 if( !parser.matchIgnoreCase("[i]") ) 151 if( !parser.matchIgnoreCase("[i]") )
145 return parser.failure(null); 152 return parser.failure(null);
146 String content = parseWellFormed(); 153 Object content = parseWellFormed();
147 if( !parser.matchIgnoreCase("[/i]") ) 154 if( !parser.matchIgnoreCase("[/i]") )
148 return parser.failure(null); 155 return parser.failure(null);
149 String rtn = target==Target.HTML ? "<i>"+content+"</i>" : content; 156 Element rtn = new Element("i",content);
150 return parser.success(rtn); 157 return parser.success(rtn);
151 } 158 }
152 159
153 private String parseU() { 160 private Element parseU() {
154 parser.begin(); 161 parser.begin();
155 if( !parser.matchIgnoreCase("[u]") ) 162 if( !parser.matchIgnoreCase("[u]") )
156 return parser.failure(null); 163 return parser.failure(null);
157 String content = parseWellFormed(); 164 Object content = parseWellFormed();
158 if( !parser.matchIgnoreCase("[/u]") ) 165 if( !parser.matchIgnoreCase("[/u]") )
159 return parser.failure(null); 166 return parser.failure(null);
160 String rtn = target==Target.HTML ? "<u>"+content+"</u>" : content; 167 Element rtn = new Element("u",content);
161 return parser.success(rtn); 168 return parser.success(rtn);
162 } 169 }
163 170
164 private String parseUrl1() { 171 private Element parseUrl1() {
165 parser.begin(); 172 parser.begin();
166 if( !parser.matchIgnoreCase("[url]") ) 173 if( !parser.matchIgnoreCase("[url]") )
167 return parser.failure(null); 174 return parser.failure(null);
168 String url = parseRealUrl(); 175 String url = parseRealUrl();
169 if( !parser.matchIgnoreCase("[/url]") ) 176 if( !parser.matchIgnoreCase("[/url]") )
170 return parser.failure(null); 177 return parser.failure(null);
171 String rtn = target==Target.HTML ? "<a href='" + url + "'>" + url + "</a>" : url; 178 Element rtn = new Element("url",url);
172 return parser.success(rtn); 179 return parser.success(rtn);
173 } 180 }
174 181
175 private String parseUrl2() { 182 private Element parseUrl2() {
176 parser.begin(); 183 parser.begin();
177 if( !parser.matchIgnoreCase("[url=") ) 184 if( !parser.matchIgnoreCase("[url=") )
178 return parser.failure(null); 185 return parser.failure(null);
179 String url = parseRealUrl(); 186 String url = parseRealUrl();
180 if( !parser.match(']') ) 187 if( !parser.match(']') )
181 return parser.failure(null); 188 return parser.failure(null);
182 String content = parseWellFormed(); 189 Object content = parseWellFormed();
183 if( !parser.matchIgnoreCase("[/url]") ) 190 if( !parser.matchIgnoreCase("[/url]") )
184 return parser.failure(null); 191 return parser.failure(null);
185 String rtn = target==Target.HTML ? "<a href='" + url + "'>" + content + "</a>" : content; 192 Element rtn = new Element("url",url,content);
186 return parser.success(rtn); 193 return parser.success(rtn);
187 } 194 }
188 195
189 private String parseRealUrl() { 196 private String parseRealUrl() {
190 parser.begin(); 197 parser.begin();
199 String url = parser.textFrom(start); 206 String url = parser.textFrom(start);
200 while( parser.match(' ') ); 207 while( parser.match(' ') );
201 return parser.success(url); 208 return parser.success(url);
202 } 209 }
203 210
204 private String parseCode() { 211 private Element parseCode() {
205 parser.begin(); 212 parser.begin();
206 if( !parser.matchIgnoreCase("[code]") ) 213 if( !parser.matchIgnoreCase("[code]") )
207 return parser.failure(null); 214 return parser.failure(null);
208 int start = parser.currentIndex(); 215 int start = parser.currentIndex();
209 while( !parser.testIgnoreCase("[/code]") ) { 216 while( !parser.testIgnoreCase("[/code]") ) {
210 if( !parser.anyChar() ) 217 if( !parser.anyChar() )
211 return parser.failure(null); 218 return parser.failure(null);
212 } 219 }
213 String content = parser.textFrom(start); 220 String content = parser.textFrom(start);
214 if( !parser.matchIgnoreCase("[/code]") ) throw new RuntimeException(); 221 if( !parser.matchIgnoreCase("[/code]") ) throw new RuntimeException();
215 String rtn = target==Target.HTML ? "<code>"+content+"</code>" : content; 222 Element rtn = new Element("code",content);
216 return parser.success(rtn); 223 return parser.success(rtn);
217 } 224 }
218 225
219 private String parseImg() { 226 private Element parseImg() {
220 parser.begin(); 227 parser.begin();
221 if( !parser.matchIgnoreCase("[img]") ) 228 if( !parser.matchIgnoreCase("[img]") )
222 return parser.failure(null); 229 return parser.failure(null);
223 String url = parseRealUrl(); 230 String url = parseRealUrl();
224 if( !parser.matchIgnoreCase("[/img]") ) 231 if( !parser.matchIgnoreCase("[/img]") )
225 return parser.failure(null); 232 return parser.failure(null);
226 String rtn = target==Target.HTML ? "<img src='" + url + "'>" : ""; 233 Element rtn = new Element("img",url);
227 return parser.success(rtn); 234 return parser.success(rtn);
228 } 235 }
229 236
230 private String parseColor() { 237 private Element parseColor() {
231 parser.begin(); 238 parser.begin();
232 if( !parser.matchIgnoreCase("[color=") ) 239 if( !parser.matchIgnoreCase("[color=") )
233 return parser.failure(null); 240 return parser.failure(null);
234 int start = parser.currentIndex(); 241 int start = parser.currentIndex();
235 parser.match('#'); 242 parser.match('#');
238 || parser.inCharRange('A','Z') 245 || parser.inCharRange('A','Z')
239 ); 246 );
240 String color = parser.textFrom(start); 247 String color = parser.textFrom(start);
241 if( !parser.match(']') ) 248 if( !parser.match(']') )
242 return parser.failure(null); 249 return parser.failure(null);
243 String content = parseWellFormed(); 250 Object content = parseWellFormed();
244 if( !parser.matchIgnoreCase("[/color]") ) 251 if( !parser.matchIgnoreCase("[/color]") )
245 return parser.failure(null); 252 return parser.failure(null);
246 String rtn = target==Target.HTML ? "<span style='color: "+color+"'>"+content+"</span>" : content; 253 Element rtn = new Element("color",color,content);
247 return parser.success(rtn); 254 return parser.success(rtn);
248 } 255 }
249 256
250 private String parseSize() { 257 private Element parseSize() {
251 parser.begin(); 258 parser.begin();
252 if( !parser.matchIgnoreCase("[size=") ) 259 if( !parser.matchIgnoreCase("[size=") )
253 return parser.failure(null); 260 return parser.failure(null);
254 int start = parser.currentIndex(); 261 int start = parser.currentIndex();
255 while( parser.match('.') || parser.inCharRange('0','9') ); 262 while( parser.match('.') || parser.inCharRange('0','9') );
256 String size = parser.textFrom(start); 263 String size = parser.textFrom(start);
257 if( !parser.match(']') ) 264 if( !parser.match(']') )
258 return parser.failure(null); 265 return parser.failure(null);
259 String content = parseWellFormed(); 266 Object content = parseWellFormed();
260 if( !parser.matchIgnoreCase("[/size]") ) 267 if( !parser.matchIgnoreCase("[/size]") )
261 return parser.failure(null); 268 return parser.failure(null);
262 String rtn = target==Target.HTML ? "<span style='font-size: "+size+"em'>"+content+"</span>" : content; 269 Element rtn = new Element("size",size,content);
263 return parser.success(rtn); 270 return parser.success(rtn);
264 } 271 }
265 272
266 private static final Pattern youtubePtn1 = Pattern.compile("https://youtu.be/([a-zA-Z0-9_-]+)(?:\\?t=([0-9]+))?"); 273 private static final Pattern youtubePtn1 = Pattern.compile("https://youtu.be/([a-zA-Z0-9_-]+)(?:\\?t=([0-9]+))?");
267 private static final Pattern youtubePtn2 = Pattern.compile("https://www.youtube.com/watch?v=([a-zA-Z0-9_-]+)(?:\\?t=([0-9]+)s)?"); 274 private static final Pattern youtubePtn2 = Pattern.compile("https://www.youtube.com/watch?v=([a-zA-Z0-9_-]+)(?:\\?t=([0-9]+)s)?");
270 private static Matcher find(Pattern ptn,String s) { 277 private static Matcher find(Pattern ptn,String s) {
271 Matcher m = ptn.matcher(s); 278 Matcher m = ptn.matcher(s);
272 return m.find() ? m : null; 279 return m.find() ? m : null;
273 } 280 }
274 281
275 private String parseVideo() { 282 private Element parseVideo() {
276 parser.begin(); 283 parser.begin();
277 if( !parser.matchIgnoreCase("[video]") ) 284 if( !parser.matchIgnoreCase("[video]") )
278 return parser.failure(null); 285 return parser.failure(null);
279 String url = parseRealUrl(); 286 String url = parseRealUrl();
280 if( !parser.matchIgnoreCase("[/video]") ) 287 if( !parser.matchIgnoreCase("[/video]") )
281 return parser.failure(null); 288 return parser.failure(null);
282 if( target==Target.TEXT ) 289 Map<String,String> extra = new LinkedHashMap<String,String>();
283 return parser.success( "" ); 290 Element rtn = new Element("video",null,url,extra);
284 Matcher m; 291 Matcher m;
285 m = find(youtubePtn1,url); 292 m = find(youtubePtn1,url);
286 if( m == null ) 293 if( m == null )
287 m = find(youtubePtn2,url); 294 m = find(youtubePtn2,url);
288 if( m != null ) { 295 if( m != null ) {
289 StringBuilder sb = new StringBuilder(); 296 extra.put( "site", "youtube" );
290 sb.append( "<iframe width='420' height='315' src='https://www.youtube.com/embed/" ); 297 extra.put( "id", m.group(1) );
291 sb.append( m.group(1) );
292 String t = m.group(2); 298 String t = m.group(2);
293 if( t != null ) { 299 if( t != null )
294 sb.append( "?start=" ); 300 extra.put( "start", t );
295 sb.append( t ); 301 return parser.success(rtn);
296 }
297 sb.append( "' frameborder='0' allowfullscreen></iframe>" );
298 return parser.success( sb.toString() );
299 } 302 }
300 m = find(bitchutePtn,url); 303 m = find(bitchutePtn,url);
301 if( m != null ) { 304 if( m != null ) {
302 StringBuilder sb = new StringBuilder(); 305 extra.put( "site", "bitchute" );
303 sb.append( "<iframe width='420' height='315' scrolling='no' frameborder='0' style='border: none;' src='https://www.bitchute.com/embed/" ); 306 extra.put( "id", m.group(1) );
304 sb.append( m.group(1) ); 307 return parser.success(rtn);
305 sb.append( "/'></iframe>" ); 308 }
306 return parser.success( sb.toString() ); 309 return parser.success(rtn);
307 } 310 }
308 return parser.success( "<a href='" + url + "'>" + url + "</a>" ); 311
309 } 312
310 313 private Element parseQuote1() {
311
312 private String parseQuote1() {
313 parser.begin(); 314 parser.begin();
314 if( !parser.matchIgnoreCase("[quote]") ) 315 if( !parser.matchIgnoreCase("[quote]") )
315 return parser.failure(null); 316 return parser.failure(null);
316 String content = parseWellFormed(); 317 Object content = parseWellFormed();
317 if( !parser.matchIgnoreCase("[/quote]") ) 318 if( !parser.matchIgnoreCase("[/quote]") )
318 return parser.failure(null); 319 return parser.failure(null);
319 String rtn = quoter.quote(target,content,null); 320 Element rtn = new Element("quote",content);
320 return parser.success(rtn); 321 return parser.success(rtn);
321 } 322 }
322 323
323 private String parseQuote2() { 324 private Element parseQuote2() {
324 parser.begin(); 325 parser.begin();
325 if( !parser.matchIgnoreCase("[quote=") ) 326 if( !parser.matchIgnoreCase("[quote=") )
326 return parser.failure(null); 327 return parser.failure(null);
327 List args = new ArrayList(); 328 List args = new ArrayList();
328 int start = parser.currentIndex(); 329 int start = parser.currentIndex();
329 while( parser.noneOf("[]") ); 330 while( parser.noneOf("[]") );
330 String name = parser.textFrom(start).trim(); 331 String name = parser.textFrom(start).trim();
331 if( !parser.match(']') ) 332 if( !parser.match(']') )
332 return parser.failure(null); 333 return parser.failure(null);
333 String content = parseWellFormed(); 334 Object content = parseWellFormed();
334 if( !parser.matchIgnoreCase("[/quote]") ) 335 if( !parser.matchIgnoreCase("[/quote]") )
335 return parser.failure(null); 336 return parser.failure(null);
336 String rtn = quoter.quote(target,content,name); 337 Element rtn = new Element("quote",name,content);
337 return parser.success(rtn); 338 return parser.success(rtn);
338 }
339
340 public static String htmlEncode(String s) {
341 final char[] a = s.toCharArray();
342 StringBuilder buf = new StringBuilder();
343 for( char c : a ) {
344 switch(c) {
345 case '&':
346 buf.append("&amp;");
347 break;
348 case '<':
349 buf.append("&lt;");
350 break;
351 case '>':
352 buf.append("&gt;");
353 break;
354 case '"':
355 buf.append("&quot;");
356 break;
357 default:
358 buf.append(c);
359 }
360 }
361 return buf.toString();
362 } 339 }
363 340
364 } 341 }