BbcodeUtil.java

  1. /*
  2.  * Copyright (c) 2002-2022, City of 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. import java.util.ArrayDeque;
  36. import java.util.Collections;
  37. import java.util.Deque;
  38. import java.util.HashSet;
  39. import java.util.LinkedList;
  40. import java.util.List;
  41. import java.util.Set;
  42. import java.util.regex.Matcher;
  43. import java.util.regex.Pattern;

  44. import fr.paris.lutece.portal.business.editor.ParserComplexElement;
  45. import fr.paris.lutece.portal.business.editor.ParserElement;

  46. /**
  47.  *
  48.  * This class provides parser utils for BBCODE parsing
  49.  *
  50.  */
  51. public final class BbcodeUtil
  52. {
  53.     private static final String CR_LF = "(?:\r\n|\r|\n)?";

  54.     /**
  55.      * Instantiates a new bbcode util.
  56.      */
  57.     private BbcodeUtil( )
  58.     {
  59.     }

  60.     /**
  61.      * Parse BBCODE text and return HTML text
  62.      *
  63.      * @param value
  64.      *            the value of the text
  65.      * @param listParserElement
  66.      *            the list of simple parser element using for parsing
  67.      * @param listParserComplexElement
  68.      *            the list of complex parser element using for parsing
  69.      * @return HTML Text
  70.      */
  71.     public static String parse( String value, List<ParserElement> listParserElement, List<ParserComplexElement> listParserComplexElement )
  72.     {
  73.         StringBuilder buffer = new StringBuilder( value );

  74.         // complex Element
  75.         if ( listParserComplexElement != null )
  76.         {
  77.             for ( ParserComplexElement element : listParserComplexElement )
  78.             {
  79.                 processNestedTags( buffer, element.getTagName( ), element.getOpenSubstWithParam( ), element.getCloseSubstWithParam( ),
  80.                         element.getOpenSubstWithoutParam( ), element.getCloseSubstWithoutParam( ), element.getInternalSubst( ),
  81.                         element.isProcessInternalTags( ), element.isAcceptParam( ), element.isRequiresQuotedParam( ) );
  82.             }
  83.         }

  84.         String str = buffer.toString( );

  85.         // SimpleElement
  86.         if ( listParserElement != null )
  87.         {
  88.             for ( ParserElement element : listParserElement )
  89.             {
  90.                 str = str.replaceAll( element.getCode( ), element.getValue( ) );
  91.             }
  92.         }

  93.         return str;
  94.     }

  95.     /**
  96.      *
  97.      * Method using for parsing complex element
  98.      *
  99.      * @param buffer
  100.      *            value to parse
  101.      * @param tagName
  102.      *            tagName
  103.      * @param openSubstWithParam
  104.      *            openSubstWithParam
  105.      * @param closeSubstWithParam
  106.      *            closeSubstWithParam
  107.      * @param openSubstWithoutParam
  108.      *            openSubstWithoutParam
  109.      * @param closeSubstWithoutParam
  110.      *            closeSubstWithoutParam
  111.      * @param internalSubst
  112.      *            internalSubst
  113.      * @param processInternalTags
  114.      *            processInternalTags
  115.      * @param acceptParam
  116.      *            acceptParam
  117.      * @param requiresQuotedParam
  118.      *            requiresQuotedParam
  119.      */
  120.     private static void processNestedTags( StringBuilder buffer, String tagName, String openSubstWithParam, String closeSubstWithParam,
  121.             String openSubstWithoutParam, String closeSubstWithoutParam, String internalSubst, boolean processInternalTags, boolean acceptParam,
  122.             boolean requiresQuotedParam )
  123.     {
  124.         String str = buffer.toString( );

  125.         Deque<MutableCharSequence> openStack = new ArrayDeque<>( );
  126.         Set<MutableCharSequence> subsOpen = new HashSet<>( );
  127.         Set<MutableCharSequence> subsClose = new HashSet<>( );
  128.         Set<MutableCharSequence> subsInternal = new HashSet<>( );

  129.         String openTag = CR_LF + "\\[" + tagName + getOpenTag( acceptParam, requiresQuotedParam ) + "\\]" + CR_LF;
  130.         String closeTag = CR_LF + "\\[/" + tagName + "\\]" + CR_LF;
  131.         String internTag = CR_LF + "\\[\\*\\]" + CR_LF;

  132.         String patternString = "(" + openTag + ")|(" + closeTag + ")";

  133.         if ( processInternalTags )
  134.         {
  135.             patternString += ( "|(" + internTag + ")" );
  136.         }

  137.         Pattern tagsPattern = Pattern.compile( patternString );
  138.         Matcher matcher = tagsPattern.matcher( str );

  139.         int openTagGroup;
  140.         int paramGroup;
  141.         int closeTagGroup;
  142.         int internalTagGroup;

  143.         if ( acceptParam )
  144.         {
  145.             openTagGroup = 1;
  146.             paramGroup = 2;
  147.             closeTagGroup = 3;
  148.             internalTagGroup = 4;
  149.         }
  150.         else
  151.         {
  152.             openTagGroup = 1;
  153.             paramGroup = -1; // INFO
  154.             closeTagGroup = 2;
  155.             internalTagGroup = 3;
  156.         }

  157.         while ( matcher.find( ) )
  158.         {
  159.             int length = matcher.end( ) - matcher.start( );
  160.             MutableCharSequence matchedSeq = new MutableCharSequence( str, matcher.start( ), length );

  161.             // test opening tags
  162.             if ( matcher.group( openTagGroup ) != null )
  163.             {
  164.                 if ( acceptParam && ( matcher.group( paramGroup ) != null ) )
  165.                 {
  166.                     matchedSeq._strParam = matcher.group( paramGroup );
  167.                 }

  168.                 openStack.push( matchedSeq );

  169.                 // test closing tags
  170.             }
  171.             else
  172.                 if ( ( matcher.group( closeTagGroup ) != null ) && !openStack.isEmpty( ) )
  173.                 {
  174.                     MutableCharSequence openSeq = openStack.pop( );

  175.                     if ( acceptParam )
  176.                     {
  177.                         matchedSeq._strParam = openSeq._strParam;
  178.                     }

  179.                     subsOpen.add( openSeq );
  180.                     subsClose.add( matchedSeq );

  181.                     // test internal tags
  182.                 }
  183.                 else
  184.                     if ( processInternalTags && ( matcher.group( internalTagGroup ) != null ) && ( !openStack.isEmpty( ) ) )
  185.                     {
  186.                         subsInternal.add( matchedSeq );
  187.                     }
  188.         }

  189.         LinkedList<MutableCharSequence> subst = new LinkedList<>( );
  190.         subst.addAll( subsOpen );
  191.         subst.addAll( subsClose );
  192.         subst.addAll( subsInternal );

  193.         Collections.sort( subst, ( Object o1, Object o2 ) -> {
  194.             MutableCharSequence s1 = (MutableCharSequence) o1;
  195.             MutableCharSequence s2 = (MutableCharSequence) o2;

  196.             return -( s1._nStart - s2._nStart );
  197.         } );

  198.         buffer.delete( 0, buffer.length( ) );

  199.         int start = 0;

  200.         while ( !subst.isEmpty( ) )
  201.         {
  202.             MutableCharSequence seq = subst.removeLast( );
  203.             buffer.append( str.substring( start, seq._nStart ) );

  204.             if ( subsClose.contains( seq ) )
  205.             {
  206.                 if ( seq._strParam != null )
  207.                 {
  208.                     buffer.append( closeSubstWithParam );
  209.                 }
  210.                 else
  211.                 {
  212.                     buffer.append( closeSubstWithoutParam );
  213.                 }
  214.             }
  215.             else
  216.                 if ( subsInternal.contains( seq ) )
  217.                 {
  218.                     buffer.append( internalSubst );
  219.                 }
  220.                 else
  221.                     if ( subsOpen.contains( seq ) )
  222.                     {
  223.                         Matcher m = Pattern.compile( openTag ).matcher( str.substring( seq._nStart, seq._nStart + seq._bLength ) );

  224.                         if ( m.matches( ) )
  225.                         {
  226.                             if ( acceptParam && ( seq._strParam != null ) )
  227.                             {
  228.                                 buffer.append( //
  229.                                         openSubstWithParam.replaceAll( "\\{BBCODE_PARAM\\}", seq._strParam ) );
  230.                             }
  231.                             else
  232.                             {
  233.                                 buffer.append( openSubstWithoutParam );
  234.                             }
  235.                         }
  236.                     }

  237.             start = seq._nStart + seq._bLength;
  238.         }

  239.         buffer.append( str.substring( start ) );
  240.     }

  241.     /**
  242.      *
  243.      * Inner class MutableCharSequence
  244.      *
  245.      */
  246.     static class MutableCharSequence implements CharSequence
  247.     {
  248.         /** _cBase */
  249.         private CharSequence _cBase;

  250.         /** _nStart */
  251.         private int _nStart;

  252.         /** _bLength */
  253.         private int _bLength;

  254.         /** */
  255.         private String _strParam;

  256.         /**
  257.          * MutableCharSequence
  258.          */
  259.         public MutableCharSequence( )
  260.         {
  261.         }

  262.         /**
  263.          * @param base
  264.          *            base
  265.          * @param start
  266.          *            start
  267.          * @param length
  268.          *            length
  269.          */
  270.         public MutableCharSequence( CharSequence base, int start, int length )
  271.         {
  272.             reset( base, start, length );
  273.         }

  274.         /**
  275.          * @return see {@link java.lang.CharSequence#length()}
  276.          */
  277.         @Override
  278.         public int length( )
  279.         {
  280.             return _bLength;
  281.         }

  282.         /**
  283.          * @param index
  284.          *            index
  285.          * @return see {@link java.lang.CharSequence#charAt(int)}
  286.          */
  287.         @Override
  288.         public char charAt( int index )
  289.         {
  290.             return _cBase.charAt( _nStart + index );
  291.         }

  292.         /**
  293.          * @param pStart
  294.          *            pStart
  295.          * @param end
  296.          *            end
  297.          * @return see {@link java.lang.CharSequence#subSequence(int, int)}
  298.          */
  299.         @Override
  300.         public CharSequence subSequence( int pStart, int end )
  301.         {
  302.             return new MutableCharSequence( _cBase, _nStart + pStart, _nStart + ( end - pStart ) );
  303.         }

  304.         /**
  305.          * return CharSequence
  306.          *
  307.          * @param pBase
  308.          *            pBase
  309.          * @param pStart
  310.          *            pStart
  311.          * @param pLength
  312.          *            pLength
  313.          * @return CharSequence
  314.          */
  315.         public CharSequence reset( CharSequence pBase, int pStart, int pLength )
  316.         {
  317.             _cBase = pBase;
  318.             _nStart = pStart;
  319.             _bLength = pLength;

  320.             return this;
  321.         }

  322.         /**
  323.          *
  324.          * @return see {@link java.lang.Object#toString()}
  325.          */
  326.         @Override
  327.         public String toString( )
  328.         {
  329.             StringBuilder sb = new StringBuilder( );

  330.             for ( int i = _nStart; i < ( _nStart + _bLength ); i++ )
  331.             {
  332.                 sb.append( _cBase.charAt( i ) );
  333.             }

  334.             return sb.toString( );
  335.         }
  336.     }

  337.     private static String getOpenTag( boolean acceptParam, boolean requiresQuotedParam )
  338.     {
  339.         String tag = "";
  340.         if ( acceptParam )
  341.         {
  342.             if ( requiresQuotedParam )
  343.             {
  344.                 tag = "(?:=\"(.*?)\")?";
  345.             }
  346.             else
  347.             {
  348.                 tag = "(?:=\"?(.*?)\"?)?";
  349.             }
  350.         }
  351.         return tag;
  352.     }
  353. }