1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package fr.paris.lutece.util.parser;
35
36 import java.util.ArrayDeque;
37 import java.util.Collections;
38 import java.util.Deque;
39 import java.util.HashSet;
40 import java.util.LinkedList;
41 import java.util.List;
42 import java.util.Set;
43 import java.util.regex.Matcher;
44 import java.util.regex.Pattern;
45
46 import fr.paris.lutece.portal.business.editor.ParserComplexElement;
47 import fr.paris.lutece.portal.business.editor.ParserElement;
48
49
50
51
52
53
54 public final class BbcodeUtil
55 {
56 private static final String CR_LF = "(?:\r\n|\r|\n)?";
57
58
59
60
61 private BbcodeUtil( )
62 {
63 }
64
65
66
67
68
69
70
71
72
73
74
75
76 public static String parse( String value, List<ParserElement> listParserElement, List<ParserComplexElement> listParserComplexElement )
77 {
78 StringBuilder buffer = new StringBuilder( value );
79
80
81 if ( listParserComplexElement != null )
82 {
83 for ( ParserComplexElement element : listParserComplexElement )
84 {
85 processNestedTags( buffer, element.getTagName( ), element.getOpenSubstWithParam( ), element.getCloseSubstWithParam( ),
86 element.getOpenSubstWithoutParam( ), element.getCloseSubstWithoutParam( ), element.getInternalSubst( ),
87 element.isProcessInternalTags( ), element.isAcceptParam( ), element.isRequiresQuotedParam( ) );
88 }
89 }
90
91 String str = buffer.toString( );
92
93
94 if ( listParserElement != null )
95 {
96 for ( ParserElement element : listParserElement )
97 {
98 str = str.replaceAll( element.getCode( ), element.getValue( ) );
99 }
100 }
101
102 return str;
103 }
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130 private static void processNestedTags( StringBuilder buffer, String tagName, String openSubstWithParam, String closeSubstWithParam,
131 String openSubstWithoutParam, String closeSubstWithoutParam, String internalSubst, boolean processInternalTags, boolean acceptParam,
132 boolean requiresQuotedParam )
133 {
134 String str = buffer.toString( );
135
136 Deque<MutableCharSequence> openStack = new ArrayDeque<>( );
137 Set<MutableCharSequence> subsOpen = new HashSet<>( );
138 Set<MutableCharSequence> subsClose = new HashSet<>( );
139 Set<MutableCharSequence> subsInternal = new HashSet<>( );
140
141 String openTag = CR_LF + "\\[" + tagName + getOpenTag( acceptParam, requiresQuotedParam ) + "\\]" + CR_LF;
142 String closeTag = CR_LF + "\\[/" + tagName + "\\]" + CR_LF;
143 String internTag = CR_LF + "\\[\\*\\]" + CR_LF;
144
145 String patternString = "(" + openTag + ")|(" + closeTag + ")";
146
147 if ( processInternalTags )
148 {
149 patternString += ( "|(" + internTag + ")" );
150 }
151
152 Pattern tagsPattern = Pattern.compile( patternString );
153 Matcher matcher = tagsPattern.matcher( str );
154
155 int openTagGroup;
156 int paramGroup;
157 int closeTagGroup;
158 int internalTagGroup;
159
160 if ( acceptParam )
161 {
162 openTagGroup = 1;
163 paramGroup = 2;
164 closeTagGroup = 3;
165 internalTagGroup = 4;
166 }
167 else
168 {
169 openTagGroup = 1;
170 paramGroup = -1;
171 closeTagGroup = 2;
172 internalTagGroup = 3;
173 }
174
175 while ( matcher.find( ) )
176 {
177 int length = matcher.end( ) - matcher.start( );
178 MutableCharSequence matchedSeq = new MutableCharSequence( str, matcher.start( ), length );
179
180
181 if ( matcher.group( openTagGroup ) != null )
182 {
183 if ( acceptParam && ( matcher.group( paramGroup ) != null ) )
184 {
185 matchedSeq._strParam = matcher.group( paramGroup );
186 }
187
188 openStack.push( matchedSeq );
189
190
191 }
192 else
193 if ( ( matcher.group( closeTagGroup ) != null ) && !openStack.isEmpty( ) )
194 {
195 MutableCharSequence openSeq = openStack.pop( );
196
197 if ( acceptParam )
198 {
199 matchedSeq._strParam = openSeq._strParam;
200 }
201
202 subsOpen.add( openSeq );
203 subsClose.add( matchedSeq );
204
205
206 }
207 else
208 if ( processInternalTags && ( matcher.group( internalTagGroup ) != null ) && ( !openStack.isEmpty( ) ) )
209 {
210 subsInternal.add( matchedSeq );
211 }
212 }
213
214 LinkedList<MutableCharSequence> subst = new LinkedList<>( );
215 subst.addAll( subsOpen );
216 subst.addAll( subsClose );
217 subst.addAll( subsInternal );
218
219 Collections.sort( subst, ( Object o1, Object o2 ) -> {
220 MutableCharSequence s1 = (MutableCharSequence) o1;
221 MutableCharSequence s2 = (MutableCharSequence) o2;
222
223 return -( s1._nStart - s2._nStart );
224 } );
225
226 buffer.delete( 0, buffer.length( ) );
227
228 int start = 0;
229
230 while ( !subst.isEmpty( ) )
231 {
232 MutableCharSequence seq = subst.removeLast( );
233 buffer.append( str.substring( start, seq._nStart ) );
234
235 if ( subsClose.contains( seq ) )
236 {
237 if ( seq._strParam != null )
238 {
239 buffer.append( closeSubstWithParam );
240 }
241 else
242 {
243 buffer.append( closeSubstWithoutParam );
244 }
245 }
246 else
247 if ( subsInternal.contains( seq ) )
248 {
249 buffer.append( internalSubst );
250 }
251 else
252 if ( subsOpen.contains( seq ) )
253 {
254 Matcher m = Pattern.compile( openTag ).matcher( str.substring( seq._nStart, seq._nStart + seq._bLength ) );
255
256 if ( m.matches( ) )
257 {
258 if ( acceptParam && ( seq._strParam != null ) )
259 {
260 buffer.append(
261 openSubstWithParam.replaceAll( "\\{BBCODE_PARAM\\}", seq._strParam ) );
262 }
263 else
264 {
265 buffer.append( openSubstWithoutParam );
266 }
267 }
268 }
269
270 start = seq._nStart + seq._bLength;
271 }
272
273 buffer.append( str.substring( start ) );
274 }
275
276
277
278
279
280
281 static class MutableCharSequence implements CharSequence
282 {
283
284 private CharSequence _cBase;
285
286
287 private int _nStart;
288
289
290 private int _bLength;
291
292
293 private String _strParam;
294
295
296
297
298 public MutableCharSequence( )
299 {
300 }
301
302
303
304
305
306
307
308
309
310 public MutableCharSequence( CharSequence base, int start, int length )
311 {
312 reset( base, start, length );
313 }
314
315
316
317
318 @Override
319 public int length( )
320 {
321 return _bLength;
322 }
323
324
325
326
327
328
329 @Override
330 public char charAt( int index )
331 {
332 return _cBase.charAt( _nStart + index );
333 }
334
335
336
337
338
339
340
341
342 @Override
343 public CharSequence subSequence( int pStart, int end )
344 {
345 return new MutableCharSequence( _cBase, _nStart + pStart, _nStart + ( end - pStart ) );
346 }
347
348
349
350
351
352
353
354
355
356
357
358
359 public CharSequence reset( CharSequence pBase, int pStart, int pLength )
360 {
361 _cBase = pBase;
362 _nStart = pStart;
363 _bLength = pLength;
364
365 return this;
366 }
367
368
369
370
371
372 @Override
373 public String toString( )
374 {
375 StringBuilder sb = new StringBuilder( );
376
377 for ( int i = _nStart; i < ( _nStart + _bLength ); i++ )
378 {
379 sb.append( _cBase.charAt( i ) );
380 }
381
382 return sb.toString( );
383 }
384 }
385
386 private static String getOpenTag( boolean acceptParam, boolean requiresQuotedParam )
387 {
388 String tag = "";
389 if ( acceptParam )
390 {
391 if ( requiresQuotedParam )
392 {
393 tag = "(?:=\"(.*?)\")?";
394 }
395 else
396 {
397 tag = "(?:=\"?(.*?)\"?)?";
398 }
399 }
400 return tag;
401 }
402 }