BbcodeUtil.java
- /*
- * Copyright (c) 2002-2022, City of Paris
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice
- * and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice
- * and the following disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- * 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * License 1.0
- */
- package fr.paris.lutece.util.parser;
- import java.util.ArrayDeque;
- import java.util.Collections;
- import java.util.Deque;
- import java.util.HashSet;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Set;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import fr.paris.lutece.portal.business.editor.ParserComplexElement;
- import fr.paris.lutece.portal.business.editor.ParserElement;
- /**
- *
- * This class provides parser utils for BBCODE parsing
- *
- */
- public final class BbcodeUtil
- {
- private static final String CR_LF = "(?:\r\n|\r|\n)?";
- /**
- * Instantiates a new bbcode util.
- */
- private BbcodeUtil( )
- {
- }
- /**
- * Parse BBCODE text and return HTML text
- *
- * @param value
- * the value of the text
- * @param listParserElement
- * the list of simple parser element using for parsing
- * @param listParserComplexElement
- * the list of complex parser element using for parsing
- * @return HTML Text
- */
- public static String parse( String value, List<ParserElement> listParserElement, List<ParserComplexElement> listParserComplexElement )
- {
- StringBuilder buffer = new StringBuilder( value );
- // complex Element
- if ( listParserComplexElement != null )
- {
- for ( ParserComplexElement element : listParserComplexElement )
- {
- processNestedTags( buffer, element.getTagName( ), element.getOpenSubstWithParam( ), element.getCloseSubstWithParam( ),
- element.getOpenSubstWithoutParam( ), element.getCloseSubstWithoutParam( ), element.getInternalSubst( ),
- element.isProcessInternalTags( ), element.isAcceptParam( ), element.isRequiresQuotedParam( ) );
- }
- }
- String str = buffer.toString( );
- // SimpleElement
- if ( listParserElement != null )
- {
- for ( ParserElement element : listParserElement )
- {
- str = str.replaceAll( element.getCode( ), element.getValue( ) );
- }
- }
- return str;
- }
- /**
- *
- * Method using for parsing complex element
- *
- * @param buffer
- * value to parse
- * @param tagName
- * tagName
- * @param openSubstWithParam
- * openSubstWithParam
- * @param closeSubstWithParam
- * closeSubstWithParam
- * @param openSubstWithoutParam
- * openSubstWithoutParam
- * @param closeSubstWithoutParam
- * closeSubstWithoutParam
- * @param internalSubst
- * internalSubst
- * @param processInternalTags
- * processInternalTags
- * @param acceptParam
- * acceptParam
- * @param requiresQuotedParam
- * requiresQuotedParam
- */
- private static void processNestedTags( StringBuilder buffer, String tagName, String openSubstWithParam, String closeSubstWithParam,
- String openSubstWithoutParam, String closeSubstWithoutParam, String internalSubst, boolean processInternalTags, boolean acceptParam,
- boolean requiresQuotedParam )
- {
- String str = buffer.toString( );
- Deque<MutableCharSequence> openStack = new ArrayDeque<>( );
- Set<MutableCharSequence> subsOpen = new HashSet<>( );
- Set<MutableCharSequence> subsClose = new HashSet<>( );
- Set<MutableCharSequence> subsInternal = new HashSet<>( );
- String openTag = CR_LF + "\\[" + tagName + getOpenTag( acceptParam, requiresQuotedParam ) + "\\]" + CR_LF;
- String closeTag = CR_LF + "\\[/" + tagName + "\\]" + CR_LF;
- String internTag = CR_LF + "\\[\\*\\]" + CR_LF;
- String patternString = "(" + openTag + ")|(" + closeTag + ")";
- if ( processInternalTags )
- {
- patternString += ( "|(" + internTag + ")" );
- }
- Pattern tagsPattern = Pattern.compile( patternString );
- Matcher matcher = tagsPattern.matcher( str );
- int openTagGroup;
- int paramGroup;
- int closeTagGroup;
- int internalTagGroup;
- if ( acceptParam )
- {
- openTagGroup = 1;
- paramGroup = 2;
- closeTagGroup = 3;
- internalTagGroup = 4;
- }
- else
- {
- openTagGroup = 1;
- paramGroup = -1; // INFO
- closeTagGroup = 2;
- internalTagGroup = 3;
- }
- while ( matcher.find( ) )
- {
- int length = matcher.end( ) - matcher.start( );
- MutableCharSequence matchedSeq = new MutableCharSequence( str, matcher.start( ), length );
- // test opening tags
- if ( matcher.group( openTagGroup ) != null )
- {
- if ( acceptParam && ( matcher.group( paramGroup ) != null ) )
- {
- matchedSeq._strParam = matcher.group( paramGroup );
- }
- openStack.push( matchedSeq );
- // test closing tags
- }
- else
- if ( ( matcher.group( closeTagGroup ) != null ) && !openStack.isEmpty( ) )
- {
- MutableCharSequence openSeq = openStack.pop( );
- if ( acceptParam )
- {
- matchedSeq._strParam = openSeq._strParam;
- }
- subsOpen.add( openSeq );
- subsClose.add( matchedSeq );
- // test internal tags
- }
- else
- if ( processInternalTags && ( matcher.group( internalTagGroup ) != null ) && ( !openStack.isEmpty( ) ) )
- {
- subsInternal.add( matchedSeq );
- }
- }
- LinkedList<MutableCharSequence> subst = new LinkedList<>( );
- subst.addAll( subsOpen );
- subst.addAll( subsClose );
- subst.addAll( subsInternal );
- Collections.sort( subst, ( Object o1, Object o2 ) -> {
- MutableCharSequence s1 = (MutableCharSequence) o1;
- MutableCharSequence s2 = (MutableCharSequence) o2;
- return -( s1._nStart - s2._nStart );
- } );
- buffer.delete( 0, buffer.length( ) );
- int start = 0;
- while ( !subst.isEmpty( ) )
- {
- MutableCharSequence seq = subst.removeLast( );
- buffer.append( str.substring( start, seq._nStart ) );
- if ( subsClose.contains( seq ) )
- {
- if ( seq._strParam != null )
- {
- buffer.append( closeSubstWithParam );
- }
- else
- {
- buffer.append( closeSubstWithoutParam );
- }
- }
- else
- if ( subsInternal.contains( seq ) )
- {
- buffer.append( internalSubst );
- }
- else
- if ( subsOpen.contains( seq ) )
- {
- Matcher m = Pattern.compile( openTag ).matcher( str.substring( seq._nStart, seq._nStart + seq._bLength ) );
- if ( m.matches( ) )
- {
- if ( acceptParam && ( seq._strParam != null ) )
- {
- buffer.append( //
- openSubstWithParam.replaceAll( "\\{BBCODE_PARAM\\}", seq._strParam ) );
- }
- else
- {
- buffer.append( openSubstWithoutParam );
- }
- }
- }
- start = seq._nStart + seq._bLength;
- }
- buffer.append( str.substring( start ) );
- }
- /**
- *
- * Inner class MutableCharSequence
- *
- */
- static class MutableCharSequence implements CharSequence
- {
- /** _cBase */
- private CharSequence _cBase;
- /** _nStart */
- private int _nStart;
- /** _bLength */
- private int _bLength;
- /** */
- private String _strParam;
- /**
- * MutableCharSequence
- */
- public MutableCharSequence( )
- {
- }
- /**
- * @param base
- * base
- * @param start
- * start
- * @param length
- * length
- */
- public MutableCharSequence( CharSequence base, int start, int length )
- {
- reset( base, start, length );
- }
- /**
- * @return see {@link java.lang.CharSequence#length()}
- */
- @Override
- public int length( )
- {
- return _bLength;
- }
- /**
- * @param index
- * index
- * @return see {@link java.lang.CharSequence#charAt(int)}
- */
- @Override
- public char charAt( int index )
- {
- return _cBase.charAt( _nStart + index );
- }
- /**
- * @param pStart
- * pStart
- * @param end
- * end
- * @return see {@link java.lang.CharSequence#subSequence(int, int)}
- */
- @Override
- public CharSequence subSequence( int pStart, int end )
- {
- return new MutableCharSequence( _cBase, _nStart + pStart, _nStart + ( end - pStart ) );
- }
- /**
- * return CharSequence
- *
- * @param pBase
- * pBase
- * @param pStart
- * pStart
- * @param pLength
- * pLength
- * @return CharSequence
- */
- public CharSequence reset( CharSequence pBase, int pStart, int pLength )
- {
- _cBase = pBase;
- _nStart = pStart;
- _bLength = pLength;
- return this;
- }
- /**
- *
- * @return see {@link java.lang.Object#toString()}
- */
- @Override
- public String toString( )
- {
- StringBuilder sb = new StringBuilder( );
- for ( int i = _nStart; i < ( _nStart + _bLength ); i++ )
- {
- sb.append( _cBase.charAt( i ) );
- }
- return sb.toString( );
- }
- }
- private static String getOpenTag( boolean acceptParam, boolean requiresQuotedParam )
- {
- String tag = "";
- if ( acceptParam )
- {
- if ( requiresQuotedParam )
- {
- tag = "(?:=\"(.*?)\")?";
- }
- else
- {
- tag = "(?:=\"?(.*?)\"?)?";
- }
- }
- return tag;
- }
- }