comparison 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
comparison
equal deleted inserted replaced
1701:077366d117bb 1702:8ad468cc88d4
1 package goodjava.bbcode;
2
3 import java.util.List;
4 import java.util.ArrayList;
5 import goodjava.parser.Parser;
6
7
8 public final class BBCode {
9
10 public enum Target { HTML, TEXT }
11
12 public interface Quoter {
13 public String quote(Target target,String text,String param);
14 }
15
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 };
29
30 public Target target = Target.HTML;
31 public Quoter quoter = defaultQuoter;
32 private final Parser parser;
33
34 public BBCode(String text) {
35 this.parser = new Parser(text);
36 }
37
38 public String parse() {
39 StringBuilder sb = new StringBuilder();
40 StringBuilder text = new StringBuilder();
41 while( !parser.endOfInput() ) {
42 String block = parseBlock();
43 if( block != null ) {
44 sb.append( textToString(text) );
45 sb.append(block);
46 } else {
47 text.append( parser.currentChar() );
48 parser.anyChar();
49 }
50 }
51 sb.append( textToString(text) );
52 return sb.toString();
53 }
54
55 private String parseWellFormed() {
56 StringBuilder sb = new StringBuilder();
57 StringBuilder text = new StringBuilder();
58 while( !parser.endOfInput() ) {
59 String block = parseBlock();
60 if( block != null ) {
61 sb.append( textToString(text) );
62 sb.append(block);
63 continue;
64 }
65 if( couldBeTag() )
66 break;
67 text.append( parser.currentChar() );
68 parser.anyChar();
69 }
70 sb.append( textToString(text) );
71 return sb.toString();
72 }
73
74 private String textToString(StringBuilder text) {
75 String s = text.toString();
76 text.setLength(0);
77 if( target == Target.HTML )
78 s = htmlEncode(s);
79 return s;
80 }
81
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
110 private String parseBlock() {
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
129 private String parseB() {
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);
136 String rtn = target==Target.HTML ? "<b>"+content+"</b>" : content;
137 return parser.success(rtn);
138 }
139
140 private String parseI() {
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);
147 String rtn = target==Target.HTML ? "<i>"+content+"</i>" : content;
148 return parser.success(rtn);
149 }
150
151 private String parseU() {
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);
158 String rtn = target==Target.HTML ? "<u>"+content+"</u>" : content;
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);
169 String rtn = target==Target.HTML ? "<a href='"+url+"'>"+url+"</a>" : url;
170 return parser.success(rtn);
171 }
172
173 private String parseUrl2() {
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);
183 String rtn = target==Target.HTML ? "<a href='"+url+"'>"+content+"</a>" : content;
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();
213 String rtn = target==Target.HTML ? "<code>"+content+"</code>" : content;
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);
224 String rtn = target==Target.HTML ? "<img src='"+url+"'>" : "";
225 return parser.success(rtn);
226 }
227
228 private String parseColor() {
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);
244 String rtn = target==Target.HTML ? "<span style='color: "+color+"'>"+content+"</span>" : content;
245 return parser.success(rtn);
246 }
247
248 private String parseSize() {
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);
260 String rtn = target==Target.HTML ? "<span style='font-size: "+size+"em'>"+content+"</span>" : content;
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);
278 String rtn = target==Target.HTML ? "<iframe width='420' height='315' src='https://www.youtube.com/embed/"+id+"' frameborder='0' allowfullscreen></iframe>" : "";
279 return parser.success(rtn);
280 }
281
282 private String parseQuote1() {
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);
289 String rtn = quoter.quote(target,content,null);
290 return parser.success(rtn);
291 }
292
293 private String parseQuote2() {
294 parser.begin();
295 if( !parser.matchIgnoreCase("[quote=") )
296 return parser.failure(null);
297 List args = new ArrayList();
298 int start = parser.currentIndex();
299 while( parser.noneOf("[]") );
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);
306 String rtn = quoter.quote(target,content,name);
307 return parser.success(rtn);
308 }
309
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("&amp;");
317 break;
318 case '<':
319 buf.append("&lt;");
320 break;
321 case '>':
322 buf.append("&gt;");
323 break;
324 case '"':
325 buf.append("&quot;");
326 break;
327 default:
328 buf.append(c);
329 }
330 }
331 return buf.toString();
332 }
333
334 }