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.plugins.digglike.utils;
35  
36  import java.io.Serializable;
37  
38  import java.util.Collections;
39  import java.util.Comparator;
40  import java.util.HashSet;
41  import java.util.LinkedList;
42  import java.util.Set;
43  import java.util.Stack;
44  import java.util.regex.Matcher;
45  import java.util.regex.Pattern;
46  
47  
48  /*
49   * Copyright 2004 JavaFree.org
50   *
51   * Licensed under the Apache License, Version 2.0 (the "License");
52   * you may not use this file except in compliance with the License.
53   * You may obtain a copy of the License at
54   *
55   *     http://www.apache.org/licenses/LICENSE-2.0
56   *
57   * Unless required by applicable law or agreed to in writing, software
58   * distributed under the License is distributed on an "AS IS" BASIS,
59   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
60   * See the License for the specific language governing permissions and
61   * limitations under the License.
62   */
63  
64  /**
65   * $Id: ProcessBBCode.java,v 1.18.2.2.4.4 2007/04/17 17:27:08 daltoncamargo Exp
66   * $
67   * @author Dalton Camargo - <a href="mailto:dalton@javabb.org">dalton@javabb.org
68   *         </a> <br>
69   * @author Ronald Tetsuo Miura
70   */
71  public class ProcessBBCode implements Serializable
72  {
73      private static final long serialVersionUID = 2923413457835274235L;
74      private static final String CR_LF = "(?:\r\n|\r|\n)?";
75  
76      /** */
77      private boolean acceptHTML = false;
78  
79      /** */
80      private boolean acceptBBCode = true;
81  
82      /**
83       * @return acceptBBCode
84       */
85      public boolean getAcceptBBCode(  )
86      {
87          return acceptBBCode;
88      }
89  
90      /**
91       * @param acceptBBCode the new acceptBBCode value
92       */
93      public void setAcceptBBCode( boolean acceptBBCode )
94      {
95          this.acceptBBCode = acceptBBCode;
96      }
97  
98      /**
99       * @return htmlAccepted
100      */
101     public boolean getAcceptHTML(  )
102     {
103         return acceptHTML;
104     }
105 
106     /**
107      * @param acceptHTML the new acceptHTML value
108      */
109     public void setAcceptHTML( boolean acceptHTML )
110     {
111         this.acceptHTML = acceptHTML;
112     }
113 
114     /**
115      * @param texto
116      * @return TODO unuseful parameters.
117      */
118     public String preparePostText( String texto )
119     {
120         if ( !getAcceptHTML(  ) )
121         {
122             texto = escapeHtml( texto );
123         }
124 
125         if ( getAcceptBBCode(  ) )
126         {
127             texto = process( texto );
128         }
129 
130         return texto;
131     }
132 
133     /**
134      * @param string
135      * @return HTML-formated message
136      */
137     private String process( String string )
138     {
139         string = string.replaceAll( "(\r\n|\n\r|\n|\r)", "<br>" );
140 
141         StringBuffer buffer = new StringBuffer( string );
142         processCode( buffer );
143 
144         processNestedTags( buffer, "quote",
145             "<table width='90%' cellspacing='1' cellpadding='1' border='0' " +
146             "align='center'><tr><td><span class='genmed'><b>{BBCODE_PARAM}:</b></span></td></tr></table><table width='90%' cellspacing='1' cellpadding='1' border='0' align='center'><tr><td class='quote' bgcolor='#F3F5FF'>",
147             " &nbsp;</td></tr></table>",
148             "<table width='90%' cellspacing='1' cellpadding='3' border='0' " +
149             "align='center'><tr><td><span class='genmed'><b>Quote:</b></span></td></tr><tr><td class='quote'>",
150             " &nbsp;</td></tr></table>", "[*]", false, true, true );
151 
152         processNestedTags( buffer, "list", "<ol type=\"{BBCODE_PARAM}\">", "</ol>", "<ul>", "</ul>", "<li>", true,
153             true, false );
154 
155         String str = buffer.toString(  );
156 
157         //str = str.replaceAll("(\r\n|\n\r|\n|\r)", "<br>");
158 
159         // [color]
160         str = str.replaceAll( "\\[color=['\"]?(.*?[^'\"])['\"]?\\](.*?)\\[/color\\]", "<span style='color:$1'>$2</span>" );
161 
162         // [size]
163         str = str.replaceAll( "\\[size=['\"]?([0-9]|[1-5][0-9])['\"]?\\](.*?)\\[/size\\]",
164                 "<span style='font-size:$1px'>$2</span>" );
165         // [font]
166         // [color]
167         str = str.replaceAll( "\\[font=['\"]?(.*?[^'\"])['\"]?\\](.*?)\\[/font\\]", "<font face='$1'>$2</font>" );
168 
169         // [b][u][i]
170         str = str.replaceAll( "\\[b\\](.*?)\\[/b\\]", "<b>$1</b>" );
171         str = str.replaceAll( "\\[u\\](.*?)\\[/u\\]", "<u>$1</u>" );
172         str = str.replaceAll( "\\[i\\](.*?)\\[/i\\]", "<i>$1</i>" );
173 
174         // [img]
175         str = str.replaceAll( "\\[img\\](.*?)\\[/img\\]", "<img src='$1' border='0' alt='0'>" );
176 
177         // [url]
178         str = str.replaceAll( "\\[url\\](.*?)\\[/url\\]", "<a href='$1' target='_blank'>$1</a>" );
179         str = str.replaceAll( "\\[url=['\"]?(.*?[^'\"])['\"]?\\](.*?)\\[/url\\]",
180                 "<a href=\"$1\" target=\"_new\">$2</a>" );
181 
182         // [email]
183         str = str.replaceAll( "\\[email\\](.*?)\\[/email\\]", "<a href='mailto:$1'>$1</a>" );
184 
185         return str;
186     }
187 
188     private static void processCode( StringBuffer buffer )
189     {
190         int start = buffer.indexOf( "[code]" );
191         int end;
192 
193         for ( ; ( start >= 0 ) && ( start < buffer.length(  ) ); start = buffer.indexOf( "[code]", end ) )
194         {
195             end = buffer.indexOf( "[/code]", start );
196 
197             if ( end < 0 )
198             {
199                 break;
200             }
201 
202             end += "[/code]".length(  );
203 
204             String content = buffer.substring( start + "[code]".length(  ), end - "[/code]".length(  ) );
205             content = escapeBBcode( content );
206 
207             /*
208              * String replacement = "<!-- [ -code- ] --></span>"
209              * +
210              * "<table width='90%' cellspacing='1' cellpadding='3' border='0' align='center'>"
211              * + "<tr><td><span class='genmed'><b>Code:</b></span></td></tr>"
212              * + "<tr><td class='code'>"
213              * + content
214              * +
215              * "</td></tr></table><span class='postbody'><!-- [/ -code- ] -->";
216              */
217             content = content.replaceAll( "<br>", "\n" );
218 
219             String replacement = "<!-- [ -code- ] --></span>" //
220                  +"<textarea name=\"code\" id=\"code\" class=\"java\" rows=\"15\" cols=\"100\">" + content +
221                 "</textarea><span class='postbody'><!-- [/ -code- ] -->";
222 
223             buffer.replace( start, end, replacement );
224 
225             end = start + replacement.length(  );
226         }
227     }
228 
229     /**
230      * @param content
231      * @return -
232      */
233     public static String escapeBBcode( String content )
234     {
235         // escaping single characters
236         content = replaceAll( content, "[]\t".toCharArray(  ), new String[] { "&#91;", "&#93;", "&nbsp; &nbsp;" } );
237 
238         // taking off start and end line breaks
239         content = content.replaceAll( "\\A\r\n|\\A\r|\\A\n|\r\n\\z|\r\\z|\n\\z", "" );
240 
241         // replacing spaces for &nbsp; to keep indentation
242         content = content.replaceAll( "  ", "&nbsp; " );
243         content = content.replaceAll( "  ", " &nbsp;" );
244 
245         return content;
246     }
247 
248     /**
249      * @param content
250      * @return -
251      */
252     private static String escapeHtml( String content )
253     {
254         // escaping single characters
255         content = replaceAll( content, "&<>".toCharArray(  ), new String[] { "&amp;", "&lt;", "&gt;" } );
256 
257         // replacing line breaks for <br>
258         //content = content.replaceAll("\r\n", "<br>");
259         //content = replaceAll(content, "\n\r".toCharArray(), new String[] { "<br>", "<br>" });
260         return content;
261     }
262 
263     private static String replaceAll( String str, char[] chars, String[] replacement )
264     {
265         StringBuffer buffer = new StringBuffer(  );
266 
267         for ( int i = 0; i < str.length(  ); i++ )
268         {
269             char c = str.charAt( i );
270             boolean matched = false;
271 
272             for ( int j = 0; j < chars.length; j++ )
273             {
274                 if ( c == chars[j] )
275                 {
276                     buffer.append( replacement[j] );
277                     matched = true;
278                 }
279             }
280 
281             if ( !matched )
282             {
283                 buffer.append( c );
284             }
285         }
286 
287         return buffer.toString(  );
288     }
289 
290     /**
291      * @param buffer
292      * @param tagName
293      * @param openSubstWithParam
294      * @param closeSubstWithParam
295      * @param openSubstWithoutParam
296      * @param closeSubstWithoutParam
297      * @param internalSubst
298      * @param processInternalTags
299      * @param acceptParam
300      * @param requiresQuotedParam
301      */
302     private static void processNestedTags( StringBuffer buffer, String tagName, String openSubstWithParam,
303         String closeSubstWithParam, String openSubstWithoutParam, String closeSubstWithoutParam, String internalSubst,
304         boolean processInternalTags, boolean acceptParam, boolean requiresQuotedParam )
305     {
306         String str = buffer.toString(  );
307 
308         Stack<MutableCharSequence> openStack = new Stack<MutableCharSequence>(  );
309         Set<MutableCharSequence> subsOpen = new HashSet<MutableCharSequence>(  );
310         Set<MutableCharSequence> subsClose = new HashSet<MutableCharSequence>(  );
311         Set<MutableCharSequence> subsInternal = new HashSet<MutableCharSequence>(  );
312 
313         String openTag = CR_LF + "\\[" + tagName +
314             ( acceptParam ? ( requiresQuotedParam ? "(?:=\"(.*?)\")?" : "(?:=\"?(.*?)\"?)?" ) : "" ) + "\\]" + CR_LF;
315         String closeTag = CR_LF + "\\[/" + tagName + "\\]" + CR_LF;
316         String internTag = CR_LF + "\\[\\*\\]" + CR_LF;
317 
318         String patternString = "(" + openTag + ")|(" + closeTag + ")";
319 
320         if ( processInternalTags )
321         {
322             patternString += ( "|(" + internTag + ")" );
323         }
324 
325         Pattern tagsPattern = Pattern.compile( patternString );
326         Matcher matcher = tagsPattern.matcher( str );
327 
328         int openTagGroup;
329         int paramGroup;
330         int closeTagGroup;
331         int internalTagGroup;
332 
333         if ( acceptParam )
334         {
335             openTagGroup = 1;
336             paramGroup = 2;
337             closeTagGroup = 3;
338             internalTagGroup = 4;
339         }
340         else
341         {
342             openTagGroup = 1;
343             paramGroup = -1; // INFO
344             closeTagGroup = 2;
345             internalTagGroup = 3;
346         }
347 
348         while ( matcher.find(  ) )
349         {
350             int length = matcher.end(  ) - matcher.start(  );
351             MutableCharSequence matchedSeq = new MutableCharSequence( str, matcher.start(  ), length );
352 
353             // test opening tags
354             if ( matcher.group( openTagGroup ) != null )
355             {
356                 if ( acceptParam && ( matcher.group( paramGroup ) != null ) )
357                 {
358                     matchedSeq.param = matcher.group( paramGroup );
359                 }
360 
361                 openStack.push( matchedSeq );
362 
363                 // test closing tags
364             }
365             else if ( ( matcher.group( closeTagGroup ) != null ) && !openStack.isEmpty(  ) )
366             {
367                 MutableCharSequence openSeq = openStack.pop(  );
368 
369                 if ( acceptParam )
370                 {
371                     matchedSeq.param = openSeq.param;
372                 }
373 
374                 subsOpen.add( openSeq );
375                 subsClose.add( matchedSeq );
376 
377                 // test internal tags
378             }
379             else if ( processInternalTags && ( matcher.group( internalTagGroup ) != null ) && ( !openStack.isEmpty(  ) ) )
380             {
381                 subsInternal.add( matchedSeq );
382             }
383             else
384             {
385                 // assert (false);
386             }
387         }
388 
389         LinkedList<MutableCharSequence> subst = new LinkedList<MutableCharSequence>(  );
390         subst.addAll( subsOpen );
391         subst.addAll( subsClose );
392         subst.addAll( subsInternal );
393 
394         Collections.sort( subst,
395             new Comparator<MutableCharSequence>(  )
396             {
397                 public int compare( MutableCharSequence o1, MutableCharSequence o2 )
398                 {
399                     return -( o1.start - o2.start );
400                 }
401             } );
402 
403         buffer.delete( 0, buffer.length(  ) );
404 
405         int start = 0;
406 
407         while ( !subst.isEmpty(  ) )
408         {
409             MutableCharSequence seq = subst.removeLast(  );
410             buffer.append( str.substring( start, seq.start ) );
411 
412             if ( subsClose.contains( seq ) )
413             {
414                 if ( seq.param != null )
415                 {
416                     buffer.append( closeSubstWithParam );
417                 }
418                 else
419                 {
420                     buffer.append( closeSubstWithoutParam );
421                 }
422             }
423             else if ( subsInternal.contains( seq ) )
424             {
425                 buffer.append( internalSubst );
426             }
427             else if ( subsOpen.contains( seq ) )
428             {
429                 Matcher m = Pattern.compile( openTag ).matcher( str.substring( seq.start, seq.start + seq.length ) );
430 
431                 if ( m.matches(  ) )
432                 {
433                     if ( acceptParam && ( seq.param != null ) )
434                     {
435                         buffer.append(  //
436                             openSubstWithParam.replaceAll( "\\{BBCODE_PARAM\\}", seq.param ) );
437                     }
438                     else
439                     {
440                         buffer.append( openSubstWithoutParam );
441                     }
442                 }
443             }
444 
445             start = seq.start + seq.length;
446         }
447 
448         buffer.append( str.substring( start ) );
449     }
450 
451     static class MutableCharSequence implements CharSequence
452     {
453         /** */
454         public CharSequence base;
455 
456         /** */
457         public int start;
458 
459         /** */
460         public int length;
461 
462         /** */
463         public String param = null;
464 
465         /**
466          */
467         public MutableCharSequence(  )
468         {
469             //
470         }
471 
472         /**
473          * @param base
474          * @param start
475          * @param length
476          */
477         public MutableCharSequence( CharSequence base, int start, int length )
478         {
479             reset( base, start, length );
480         }
481 
482         /**
483          * @see java.lang.CharSequence#length()
484          */
485         public int length(  )
486         {
487             return this.length;
488         }
489 
490         /**
491          * @see java.lang.CharSequence#charAt(int)
492          */
493         public char charAt( int index )
494         {
495             return this.base.charAt( this.start + index );
496         }
497 
498         /**
499          * @see java.lang.CharSequence#subSequence(int, int)
500          */
501         public CharSequence subSequence( int pStart, int end )
502         {
503             return new MutableCharSequence( this.base, this.start + pStart, this.start + ( end - pStart ) );
504         }
505 
506         /**
507          * @param pBase
508          * @param pStart
509          * @param pLength
510          * @return -
511          */
512         public CharSequence reset( CharSequence pBase, int pStart, int pLength )
513         {
514             this.base = pBase;
515             this.start = pStart;
516             this.length = pLength;
517 
518             return this;
519         }
520 
521         /**
522          * @see java.lang.Object#toString()
523          */
524         public String toString(  )
525         {
526             StringBuffer sb = new StringBuffer(  );
527 
528             for ( int i = this.start; i < ( this.start + this.length ); i++ )
529             {
530                 sb.append( this.base.charAt( i ) );
531             }
532 
533             return sb.toString(  );
534         }
535     }
536 }