How to parse nested shortcodes?

The nested shortcodes won’t parse correctly:

[row]
    [col size="6"]...[/col]
    [col size="6"]
        [row]
            [col size="6"]...[/col]
            [col size="6"]...[/col]
        [/row]
    [/col]
[/row]

From the WordPress documentation, I understand that it is a WordPress shortcodes limitation. Is it still possible to get it work?

Read More

Edit:
Here’s my shortcodes code, it works fine if its not nested (ie., row shortcode is not used inside the col shortcode).

add_shortcode( 'row', 'row_cb' );
function row_cb( $atts, $content = null ) {             
        $output = '';
        $output .= '<div class="row">';
        $output .= do_shortcode( $content );        
        $output .= '</div>';

        return $output; 
}

add_shortcode( 'col', 'col_cb' );
function col_cb( $atts, $content = null ) {     
    extract( shortcode_atts( array(
            'size'  => '',
        ), $atts ) );


    $output = '';
    $output .= '<div class="col">';
    $output .= do_shortcode( $content );        
    $output .= '</div>';

    return $output; 
}

Related posts

3 comments

  1. There’s a solution for that, actually. The shortcode you are using has the variable $content, whithout the filter do_shortcode, like this:

    do_shortcode($content)
    

    Open the file where there are the shortcodes and change $content for do_shortcode($content). It will work.

  2. I created a solution for my nested shortcodes, maybe you could use something from it.

    I used a recursive regex (notice |(?R)), so it might be not that fast as the official do_shortcode, but it allows nesting shortcodes with the same name.

    My request for it was to not have a fixed list of shortcodes (so all existing in the content shortcodes must be parsed), to allow any level of nesting, and also to use a structure of $atts, which contain some parameters, existing at the time of filling this exact content.

    For $atts I wrote my own function, too, as I wanted $atts to include both everything coming from the content (and not only those, whose default value is defined), and the default values for those fields, existing in the default array and not coming from the shortcode values.

    Here’s my atts function:

    function complete_atts_with_defaults( $pairs, $atts = array() ) {
      $out = $atts;
      foreach ($pairs as $name => $default) {
        if ( array_key_exists($name, $atts)  && !empty( $atts[$name] ) ) { continue; }
        //else
        $out[$name] = $default;
      }
      return $out;
    }
    

    And here’s the other code:

    define( 'OUR_SHORTCODE_FULL_REGEX', '@[([a-z0-9_]+)([^]]*)]((?:[^[]|(?R))+)[/1]@' );
    define( 'OUR_SHORTCODE_HALF_REGEX', '@[([a-z0-9_]+)([^]]*)/]@' );
    
    function fill_all_post_placeholders( $text, $atts = array() ) {
      //...
      $text = str_replace( '%5B', '[', $text );
      $text = str_replace( '%5D', ']', $text );
    
      $atts = complete_atts_with_defaults(
            array(
                'value1' => 'Hi :)',
                'value2' => '',
                'value3' => '',
            ), $atts );
    
      //...
    
      $text = preg_replace_callback( OUR_SHORTCODE_HALF_REGEX, function( $match ) use ( $atts ) { return replace_half_shortcode_placeholder( $match[1], $match[2], $atts ); }, $text );
      $text = preg_replace_callback( OUR_SHORTCODE_FULL_REGEX, function( $match ) use ( $atts ) { return replace_shortcode_placeholder( $match[1], $match[2], $match[3], $atts ); }, $text );
    
      return $text;
    }
    
    function replace_shortcode_placeholder( $name, $params, $content, $atts ) {
      $content = preg_replace_callback( OUR_SHORTCODE_FULL_REGEX, function( $match ) use ( $atts ) { return replace_shortcode_placeholder( $match[1], $match[2], $match[3], $atts ); }, $content );
      //   $function_to_call = '_rp_' . $name;
      //   if ( function_exists( $function_to_call ) ) { return $function_to_call( $name, $params, $atts ); }
      //   //else
      //   $value = $atts[ $name ];
      //   if ( empty( $value ) ) { return ''; }
      //   //else
      //   return $value;
    
      return '<br>name: '  . $name . ' <br>params: ' . $params . ' <br>content: (' . $content . ')<br><br>';
    }
    
    function replace_half_shortcode_placeholder( $name, $params, $atts ) {
      //   $function_to_call = '_rp_' . $name;
      //   if ( function_exists( $function_to_call ) ) { return $function_to_call( $name, $params, $atts ); }
      //   //else
      //   $value = $atts[ $name ];
      //   if ( empty( $value ) ) { return ''; }
      //   //else
      //   return $value;
    
      return '<br>name half: '  . $name . ' <br>params half: ' . $params . '<br><br>';
    }
    

    I left for you the commented area intact, so that you could see how those functions could be used.

    These “shortcodes” of mine are not registered, as you can see in the commented code, they work differently, dynamically.

    Also, the necessary rule for these shortcodes to work would be, to always add ‘/’ to the self-closing tags, like one would do in XML. (In the code they are called “half-shortcodes”.)

    Example to the content parsed by this code (sorry for the gibberish):

    [show_text_if campaign="2899"]subscribing to this newsletter [show_text_if campaign="2901" field_value="'a href='http://ajshdash.com/asdhal/asdasdasd'" field_value2="айксчд асй даксчй д"]subscribing to this new newsletter[/show_text_if] [red value1="dfsdfs" value2="sdfkls dfh skjha skd"/][/show_text_if]
    
    [color value3="askdfjajls" value4="lskdfhjsldhfs df"/]
    
    [show_text_if_not campaign="2899"]asdklja slkd alkd alskdj alsjd alsj als jdalsk jals dalskj asjd <a href="http://example.com/experiment/" target="_blank">Experiment</a> once more hooray! [green value4="askljjlaskd"/] Я сегодня это Я и я! :)[/show_text_if_not]
    
    [show_text_if field_name="campaign" field_value="2899"]slkjd alsjkd aljsd ajs d new[/show_text_if]
    

    And I understand, that this code is not a direct solution to what you asked for, but maybe you could use some of it, to work out the solution yourself.

  3. Your shortcode callback should appears something like:

    add_shortcode('col', 'col_cb');
    
    add_shortcode('row', 'row_cb');
    
    function col_cb( $atts, $content ) {
      $content = do_shortcode($content);
      // ...
    }
    
    function row_cb( $atts, $content ) {
      $content = do_shortcode($content);
      // ...
    }
    

Comments are closed.