Mercurial Hosting > luan
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("&"); | |
347 break; | |
348 case '<': | |
349 buf.append("<"); | |
350 break; | |
351 case '>': | |
352 buf.append(">"); | |
353 break; | |
354 case '"': | |
355 buf.append("""); | |
356 break; | |
357 default: | |
358 buf.append(c); | |
359 } | |
360 } | |
361 return buf.toString(); | |
362 } | 339 } |
363 | 340 |
364 } | 341 } |