View Javadoc
1   /*
2    * Copyright (c) 2002-2014, Mairie de Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.util.parser;
35  
36  import fr.paris.lutece.portal.business.editor.ParserComplexElement;
37  import fr.paris.lutece.portal.business.editor.ParserElement;
38  
39  import java.util.Collections;
40  import java.util.Comparator;
41  import java.util.HashSet;
42  import java.util.LinkedList;
43  import java.util.List;
44  import java.util.Set;
45  import java.util.Stack;
46  import java.util.regex.Matcher;
47  import java.util.regex.Pattern;
48  
49  
50  /**
51   *
52   *  This class provides parser utils  for BBCODE parsing
53   *
54   */
55  public final class BbcodeUtil
56  {
57      private static final String CR_LF = "(?:\r\n|\r|\n)?";
58  
59      /**
60       * Instantiates a new bbcode util.
61       */
62      private BbcodeUtil(  )
63      {
64      }
65  
66      /**
67       * Parse BBCODE text and return HTML text
68       * @param value the value of the text
69       * @param listParserElement the list of simple parser element using for parsing
70       * @param listParserComplexElement the list of complex  parser element using for parsing
71       * @return HTML Text
72       */
73      public static String parse( String value, List<ParserElement> listParserElement,
74          List<ParserComplexElement> listParserComplexElement )
75      {
76          StringBuffer buffer = new StringBuffer( value );
77  
78          // complex Element
79          if ( listParserComplexElement != null )
80          {
81              for ( ParserComplexElement element : listParserComplexElement )
82              {
83                  processNestedTags( buffer, element.getTagName(  ), element.getOpenSubstWithParam(  ),
84                      element.getCloseSubstWithParam(  ), element.getOpenSubstWithoutParam(  ),
85                      element.getCloseSubstWithoutParam(  ), element.getInternalSubst(  ),
86                      element.isProcessInternalTags(  ), element.isAcceptParam(  ), element.isRequiresQuotedParam(  ) );
87              }
88          }
89  
90          String str = buffer.toString(  );
91  
92          // SimpleElement
93          if ( listParserElement != null )
94          {
95              for ( ParserElement element : listParserElement )
96              {
97                  str = str.replaceAll( element.getCode(  ), element.getValue(  ) );
98              }
99          }
100 
101         return str;
102     }
103 
104     /**
105      *
106      * Method using for parsing complex element
107      * @param buffer value to parse
108      * @param tagName tagName
109      * @param openSubstWithParam openSubstWithParam
110      * @param closeSubstWithParam closeSubstWithParam
111      * @param openSubstWithoutParam openSubstWithoutParam
112      * @param closeSubstWithoutParam closeSubstWithoutParam
113      * @param internalSubst internalSubst
114      * @param processInternalTags processInternalTags
115      * @param acceptParam acceptParam
116      * @param requiresQuotedParam requiresQuotedParam
117      */
118     private static void processNestedTags( StringBuffer buffer, String tagName, String openSubstWithParam,
119         String closeSubstWithParam, String openSubstWithoutParam, String closeSubstWithoutParam, String internalSubst,
120         boolean processInternalTags, boolean acceptParam, boolean requiresQuotedParam )
121     {
122         String str = buffer.toString(  );
123 
124         Stack openStack = new Stack(  );
125         Set subsOpen = new HashSet(  );
126         Set subsClose = new HashSet(  );
127         Set subsInternal = new HashSet(  );
128 
129         String openTag = CR_LF + "\\[" + tagName +
130             ( acceptParam ? ( requiresQuotedParam ? "(?:=\"(.*?)\")?" : "(?:=\"?(.*?)\"?)?" ) : "" ) + "\\]" + CR_LF;
131         String closeTag = CR_LF + "\\[/" + tagName + "\\]" + CR_LF;
132         String internTag = CR_LF + "\\[\\*\\]" + CR_LF;
133 
134         String patternString = "(" + openTag + ")|(" + closeTag + ")";
135 
136         if ( processInternalTags )
137         {
138             patternString += ( "|(" + internTag + ")" );
139         }
140 
141         Pattern tagsPattern = Pattern.compile( patternString );
142         Matcher matcher = tagsPattern.matcher( str );
143 
144         int openTagGroup;
145         int paramGroup;
146         int closeTagGroup;
147         int internalTagGroup;
148 
149         if ( acceptParam )
150         {
151             openTagGroup = 1;
152             paramGroup = 2;
153             closeTagGroup = 3;
154             internalTagGroup = 4;
155         }
156         else
157         {
158             openTagGroup = 1;
159             paramGroup = -1; // INFO
160             closeTagGroup = 2;
161             internalTagGroup = 3;
162         }
163 
164         while ( matcher.find(  ) )
165         {
166             int length = matcher.end(  ) - matcher.start(  );
167             MutableCharSequence matchedSeq = new MutableCharSequence( str, matcher.start(  ), length );
168 
169             // test opening tags
170             if ( matcher.group( openTagGroup ) != null )
171             {
172                 if ( acceptParam && ( matcher.group( paramGroup ) != null ) )
173                 {
174                     matchedSeq._strParam = matcher.group( paramGroup );
175                 }
176 
177                 openStack.push( matchedSeq );
178 
179                 // test closing tags
180             }
181             else if ( ( matcher.group( closeTagGroup ) != null ) && !openStack.isEmpty(  ) )
182             {
183                 MutableCharSequence openSeq = (MutableCharSequence) openStack.pop(  );
184 
185                 if ( acceptParam )
186                 {
187                     matchedSeq._strParam = openSeq._strParam;
188                 }
189 
190                 subsOpen.add( openSeq );
191                 subsClose.add( matchedSeq );
192 
193                 // test internal tags
194             }
195             else if ( processInternalTags && ( matcher.group( internalTagGroup ) != null ) && ( !openStack.isEmpty(  ) ) )
196             {
197                 subsInternal.add( matchedSeq );
198             }
199         }
200 
201         LinkedList subst = new LinkedList(  );
202         subst.addAll( subsOpen );
203         subst.addAll( subsClose );
204         subst.addAll( subsInternal );
205 
206         Collections.sort( subst,
207             new Comparator(  )
208             {
209                 @Override
210                 public int compare( Object o1, Object o2 )
211                 {
212                     MutableCharSequence s1 = (MutableCharSequence) o1;
213                     MutableCharSequence s2 = (MutableCharSequence) o2;
214 
215                     return -( s1._nStart - s2._nStart );
216                 }
217             } );
218 
219         buffer.delete( 0, buffer.length(  ) );
220 
221         int start = 0;
222 
223         while ( !subst.isEmpty(  ) )
224         {
225             MutableCharSequence seq = (MutableCharSequence) subst.removeLast(  );
226             buffer.append( str.substring( start, seq._nStart ) );
227 
228             if ( subsClose.contains( seq ) )
229             {
230                 if ( seq._strParam != null )
231                 {
232                     buffer.append( closeSubstWithParam );
233                 }
234                 else
235                 {
236                     buffer.append( closeSubstWithoutParam );
237                 }
238             }
239             else if ( subsInternal.contains( seq ) )
240             {
241                 buffer.append( internalSubst );
242             }
243             else if ( subsOpen.contains( seq ) )
244             {
245                 Matcher m = Pattern.compile( openTag ).matcher( str.substring( seq._nStart, seq._nStart + seq._bLength ) );
246 
247                 if ( m.matches(  ) )
248                 {
249                     if ( acceptParam && ( seq._strParam != null ) )
250                     {
251                         buffer.append(  //
252                             openSubstWithParam.replaceAll( "\\{BBCODE_PARAM\\}", seq._strParam ) );
253                     }
254                     else
255                     {
256                         buffer.append( openSubstWithoutParam );
257                     }
258                 }
259             }
260 
261             start = seq._nStart + seq._bLength;
262         }
263 
264         buffer.append( str.substring( start ) );
265     }
266 
267     /**
268      *
269      * Inner class MutableCharSequence
270      *
271      */
272     static class MutableCharSequence implements CharSequence
273     {
274         /** _cBase*/
275         private CharSequence _cBase;
276 
277         /** _nStart */
278         private int _nStart;
279 
280         /** _bLength*/
281         private int _bLength;
282 
283         /** */
284         private String _strParam;
285 
286         /**
287          * MutableCharSequence
288          */
289         public MutableCharSequence(  )
290         {
291         }
292 
293         /**
294          * @param base base
295          * @param start start
296          * @param length length
297          */
298         public MutableCharSequence( CharSequence base, int start, int length )
299         {
300             reset( base, start, length );
301         }
302 
303         /**
304          * @return @see java.lang.CharSequence#length()
305          */
306         @Override
307         public int length(  )
308         {
309             return _bLength;
310         }
311 
312         /**
313          * @param index index
314          * @return @see java.lang.CharSequence#charAt(int)
315          */
316         @Override
317         public char charAt( int index )
318         {
319             return _cBase.charAt( _nStart + index );
320         }
321 
322         /**
323          * @param pStart pStart
324          * @param end end
325          * @return @see java.lang.CharSequence#subSequence(int, int)
326          */
327         @Override
328         public CharSequence subSequence( int pStart, int end )
329         {
330             return new MutableCharSequence( _cBase, _nStart + pStart, _nStart + ( end - pStart ) );
331         }
332 
333         /**
334          * return CharSequence
335          * @param pBase pBase
336          * @param pStart pStart
337          * @param pLength pLength
338          * @return  CharSequence
339          */
340         public CharSequence reset( CharSequence pBase, int pStart, int pLength )
341         {
342             _cBase = pBase;
343             _nStart = pStart;
344             _bLength = pLength;
345 
346             return this;
347         }
348 
349         /**
350          *
351          * @return @see java.lang.Object#toString()
352          */
353         @Override
354         public String toString(  )
355         {
356             StringBuffer sb = new StringBuffer(  );
357 
358             for ( int i = _nStart; i < ( _nStart + _bLength ); i++ )
359             {
360                 sb.append( _cBase.charAt( i ) );
361             }
362 
363             return sb.toString(  );
364         }
365     }
366 }