WordPress.org

Make WordPress Core

Ticket #18549: 18549_wptexturize.2.diff

File 18549_wptexturize.2.diff, 22.7 KB (added by gitlost, 6 years ago)

Individual statics instead of arrays.

  • src/wp-includes/formatting.php

     
    5454                $closing_quote = null,
    5555                $opening_single_quote = null,
    5656                $closing_single_quote = null,
    57                 $open_q_flag = '<!--oq-->',
    58                 $open_sq_flag = '<!--osq-->',
    59                 $apos_flag = '<!--apos-->';
     57                $apos_flag, $open_sq_flag, $open_q_flag, $close_sq_flag, $close_q_flag, $prime_sq_flag, $prime_q_flag, $sq_flag, $q_flag,
     58                $flags_sq, $flags_q, $reals_sq, $reals_q,
     59                $nonsplit_regex, $comment_regex,
     60                $static_no_texturize_shortcodes = null, $no_texturize_shortcode_regex,
     61                $static_shortcode_tags = null, $shortcode_regex,
     62                $spaces;
    6063
    6164        // If there's nothing to do, just stop.
    6265        if ( empty( $text ) || false === $run_texturize ) {
     
    107110                /* translators: em dash */
    108111                $em_dash = _x( '&#8212;', 'em dash' );
    109112
     113                // Standardize size of flags to max of primes/quotes manipulated by wptexturize_primes().
     114                // This will allow wptexturize_primes() to do its replacements without worrying about offsets changing.
     115                $dummy_len = max( 5, strlen( $closing_quote ), strlen( $prime ), strlen( $double_prime ), strlen( $closing_single_quote ) );
     116
     117                $apos_flag = str_pad( '<i a>', $dummy_len, '>' );
     118                $open_sq_flag = str_pad( '<i o>', $dummy_len, '>' );
     119                $open_q_flag = str_pad( '<i O>', $dummy_len, '>' );
     120                $close_sq_flag = str_pad( '<i c>', $dummy_len, '>' );
     121                $close_q_flag = str_pad( '<i C>', $dummy_len, '>' );
     122                $prime_sq_flag = str_pad( '<i p>', $dummy_len, '>' );
     123                $prime_q_flag = str_pad( '<i P>', $dummy_len, '>' );
     124                $sq_flag = str_repeat( "'", $dummy_len );
     125                $q_flag = str_repeat( '"', $dummy_len );
     126
     127                // Flags & reals arrays - used to reinstate the real values.
     128                $flags_sq = array( $sq_flag, $prime_sq_flag, $open_sq_flag, $close_sq_flag, $apos_flag );
     129                $flags_q = array( $q_flag, $prime_q_flag, $open_q_flag, $close_q_flag );
     130                $reals_sq = array( "'", $prime, $opening_single_quote, $closing_single_quote, $apos );
     131                $reals_q = array( '"', $double_prime, $opening_quote, $closing_quote );
     132
    110133                $default_no_texturize_tags = array('pre', 'code', 'kbd', 'style', 'script', 'tt');
    111134                $default_no_texturize_shortcodes = array('code');
    112135
     
    139162
    140163                // '99' and '99" are ambiguous among other patterns; assume it's an abbreviated year at the end of a quotation.
    141164                if ( "'" !== $apos || "'" !== $closing_single_quote ) {
    142                         $dynamic[ '/\'(\d\d)\'(?=\Z|[.,:;!?)}\-\]]|&gt;|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_single_quote;
     165                        $dynamic[ '/\'(\d\d)\'(?=\Z|[.,:;!?)}\-\]]|&gt;|' . $spaces . ')/' ] = $apos_flag . '$1' . $close_sq_flag;
    143166                }
    144167                if ( "'" !== $apos || '"' !== $closing_quote ) {
    145                         $dynamic[ '/\'(\d\d)"(?=\Z|[.,:;!?)}\-\]]|&gt;|' . $spaces . ')/' ] = $apos_flag . '$1' . $closing_quote;
     168                        $dynamic[ '/\'(\d\d)"(?=\Z|[.,:;!?)}\-\]]|&gt;|' . $spaces . ')/' ] = $apos_flag . '$1' . $close_q_flag;
    146169                }
    147170
    148171                // '99 '99s '99's (apostrophe)  But never '9 or '99% or '999 or '99.0.
     
    152175
    153176                // Quoted Numbers like '0.42'
    154177                if ( "'" !== $opening_single_quote && "'" !== $closing_single_quote ) {
    155                         $dynamic[ '/(?<=\A|' . $spaces . ')\'(\d[.,\d]*)\'/' ] = $open_sq_flag . '$1' . $closing_single_quote;
     178                        $dynamic[ '/(?<=\A|' . $spaces . ')\'(\d[.,\d]*)\'/' ] = $open_sq_flag . '$1' . $close_sq_flag;
    156179                }
    157180
    158181                // Single quote at start, or preceded by (, {, <, [, ", -, or spaces.
     
    171194
    172195                // Quoted Numbers like "42"
    173196                if ( '"' !== $opening_quote && '"' !== $closing_quote ) {
    174                         $dynamic[ '/(?<=\A|' . $spaces . ')"(\d[.,\d]*)"/' ] = $open_q_flag . '$1' . $closing_quote;
     197                        $dynamic[ '/(?<=\A|' . $spaces . ')"(\d[.,\d]*)"/' ] = $open_q_flag . '$1' . $close_q_flag;
    175198                }
    176199
    177200                // Double quote at start, or preceded by (, {, <, [, -, or spaces, and not followed by spaces.
     
    191214
    192215                $dynamic_characters['dash'] = array_keys( $dynamic );
    193216                $dynamic_replacements['dash'] = array_values( $dynamic );
     217
     218                $nonsplit_regex = '\/?(?:a\b|abbr|b\b|big|br|dfn|em|i\b|samp|small|span|strong|sub|sup|var)[^>]*>';
     219
     220                // Might as well initialize the comment regex once seeing as it's invariant.
     221                $comment_regex =
     222                          '!'           // Start of comment, after the <.
     223                        . '(?:'         // Unroll the loop: Consume everything until --> is found.
     224                        .     '-(?!->)' // Dash not followed by end of comment.
     225                        .     '[^\-]*+' // Consume non-dashes.
     226                        . ')*+'         // Loop possessively.
     227                        . '(?:-->)?';   // End of comment. If not found, match all input.
    194228        }
    195229
    196230        // Must do this every time in case plugins use these filters in a context sensitive manner
     
    214248        $no_texturize_tags_stack = array();
    215249        $no_texturize_shortcodes_stack = array();
    216250
    217         // Look for shortcodes and HTML elements.
     251        // Set up shortcodes regular expression (used to strip within each split text part), if haven't already or if things changed.
     252        if ( $static_shortcode_tags === null || $shortcode_tags !== $static_shortcode_tags ) {
     253                $static_shortcode_tags = $shortcode_tags;
     254                $static_no_texturize_shortcodes = null; // Force reset of no texturize shortcodes as they need to be registered to be ignored.
     255                $tagnames = array_keys( $shortcode_tags );
     256                if ( $tagnames ) {
     257                        $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
     258                        $tagregexp = "(?:$tagregexp)(?![\\w-])"; // Excerpt of get_shortcode_regex().
     259                        $shortcode_regex =
     260                                '|'
     261                                . '\['              // Find start of shortcode.
     262                                . '[\/\[]?'         // Shortcodes may begin with [/ or [[
     263                                . $tagregexp        // Only match registered shortcodes, because performance.
     264                                . '(?:'
     265                                .     '[^\[\]<>]+'  // Shortcodes do not contain other shortcodes. Quantifier critical.
     266                                . '|'
     267                                .     '<[^\[\]>]*>' // HTML elements permitted. Prevents matching ] before >.
     268                                . ')*+'             // Possessive critical.
     269                                . '\]'              // Find end of shortcode.
     270                                . '\]?';            // Shortcodes may end with ]]
     271                } else {
     272                        $shortcode_regex = '';
     273                }
     274        }
    218275
    219         $tagnames = array_keys( $shortcode_tags );
    220         $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
    221         $tagregexp = "(?:$tagregexp)(?![\\w-])"; // Excerpt of get_shortcode_regex().
     276        // Set up no texturize shortcodes regular expression (used to split text input), if haven't already or if things changed.
     277        if ( $static_no_texturize_shortcodes === null || $no_texturize_shortcodes !== $static_no_texturize_shortcodes ) {
     278                $static_no_texturize_shortcodes = $no_texturize_shortcodes;
     279                // No texturize shortcodes must also be registered to be ignored, so intersect with registered shortcodes array.
     280                $tagnames = array_intersect( $no_texturize_shortcodes, array_keys( $static_shortcode_tags ) );
     281                if ( $tagnames ) {
     282                        $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
     283                        $tagregexp = "(?:$tagregexp)(?![\\w-])"; // Excerpt of get_shortcode_regex().
     284                        $no_texturize_shortcode_regex =
     285                                '|'
     286                                . '\['              // Find start of shortcode.
     287                                . '[\/\[]?'         // Shortcodes may begin with [/ or [[
     288                                . $tagregexp        // Only match no texturize shortcodes.
     289                                . '(?:'
     290                                .     '[^\[\]<>]+'  // Shortcodes do not contain other shortcodes. Quantifier critical.
     291                                . '|'
     292                                .     '<[^\[\]>]*>' // HTML elements permitted. Prevents matching ] before >.
     293                                . ')*+'             // Possessive critical.
     294                                . '\]'              // Find end of shortcode.
     295                                . '\]?';            // Shortcodes may end with ]]
     296                } else {
     297                        $no_texturize_shortcode_regex = '';
     298                }
     299        }
    222300
    223         $comment_regex =
    224                   '!'           // Start of comment, after the <.
    225                 . '(?:'         // Unroll the loop: Consume everything until --> is found.
    226                 .     '-(?!->)' // Dash not followed by end of comment.
    227                 .     '[^\-]*+' // Consume non-dashes.
    228                 . ')*+'         // Loop possessively.
    229                 . '(?:-->)?';   // End of comment. If not found, match all input.
     301        // Look for comments, non-inline (non-split) HTML elements and no texturize shortcodes.
    230302
    231         $shortcode_regex =
    232                   '\['              // Find start of shortcode.
    233                 . '[\/\[]?'         // Shortcodes may begin with [/ or [[
    234                 . $tagregexp        // Only match registered shortcodes, because performance.
    235                 . '(?:'
    236                 .     '[^\[\]<>]+'  // Shortcodes do not contain other shortcodes. Quantifier critical.
    237                 . '|'
    238                 .     '<[^\[\]>]*>' // HTML elements permitted. Prevents matching ] before >.
    239                 . ')*+'             // Possessive critical.
    240                 . '\]'              // Find end of shortcode.
    241                 . '\]?';            // Shortcodes may end with ]]
    242 
    243303        $regex =
    244304                  '/('                   // Capture the entire match.
    245305                .     '<'                // Find start of element.
     
    246306                .     '(?(?=!--)'        // Is this a comment?
    247307                .         $comment_regex // Find end of comment.
    248308                .     '|'
     309                .     '(?!' . $nonsplit_regex . ')' // Exclude inline html elements.
    249310                .         '[^>]*>'       // Find end of element.
    250311                .     ')'
    251                 . '|'
    252                 .     $shortcode_regex   // Find shortcodes.
     312                .     $no_texturize_shortcode_regex   // Find no texturize shortcodes.
    253313                . ')/s';
    254314
    255         $textarr = preg_split( $regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
     315        $textarr = preg_split( $regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
    256316
    257         foreach ( $textarr as &$curl ) {
    258                 // Only call _wptexturize_pushpop_element if $curl is a delimiter.
    259                 $first = $curl[0];
    260                 if ( '<' === $first && '<!--' === substr( $curl, 0, 4 ) ) {
    261                         // This is an HTML comment delimeter.
     317        foreach ( $textarr as $curl_idx => &$curl ) {
     318                if ( 1 === $curl_idx % 2 ) {
     319                        // Delimiter.
     320                        $first = $curl[0];
     321                        if ( '<' === $first ) {
     322                                // If not a comment.
     323                                if ( '<!--' !== substr( $curl, 0, 4 ) ) {
     324                                        // This is an HTML element delimiter.
    262325
    263                         continue;
     326                                        _wptexturize_pushpop_element( $curl, $no_texturize_tags_stack, $no_texturize_tags );
     327                                }
     328                        } elseif ( '[' === $first ) {
     329                                // This is a shortcode delimiter.
    264330
    265                 } elseif ( '<' === $first && '>' === substr( $curl, -1 ) ) {
    266                         // This is an HTML element delimiter.
     331                                if ( '[[' !== substr( $curl, 0, 2 ) && ']]' !== substr( $curl, -2 ) ) {
     332                                        // Looks like a normal shortcode.
     333                                        _wptexturize_pushpop_element( $curl, $no_texturize_shortcodes_stack, $no_texturize_shortcodes );
     334                                } else {
     335                                        // Looks like an escaped shortcode.
     336                                }
     337                        }
     338                } elseif ( empty( $no_texturize_shortcodes_stack ) && empty( $no_texturize_tags_stack ) && '' !== trim( $curl ) ) {
     339                        // This is neither a delimiter, nor is this content inside of no_texturize pairs.  Do texturize.
    267340
    268                         _wptexturize_pushpop_element( $curl, $no_texturize_tags_stack, $no_texturize_tags );
    269 
    270                 } elseif ( '' === trim( $curl ) ) {
    271                         // This is a newline between delimiters.  Performance improves when we check this.
    272 
    273                         continue;
    274 
    275                 } elseif ( '[' === $first && 1 === preg_match( '/^' . $shortcode_regex . '$/', $curl ) ) {
    276                         // This is a shortcode delimiter.
    277 
    278                         if ( '[[' !== substr( $curl, 0, 2 ) && ']]' !== substr( $curl, -2 ) ) {
    279                                 // Looks like a normal shortcode.
    280                                 _wptexturize_pushpop_element( $curl, $no_texturize_shortcodes_stack, $no_texturize_shortcodes );
    281                         } else {
    282                                 // Looks like an escaped shortcode.
    283                                 continue;
     341                        // Add a space to any <br>s so that when stripped will be recognized as whitespace.
     342                        if ( $have_br = ( false !== stripos( $curl, '<br' ) ) ) {
     343                                $curl = preg_replace( '/<br[^>]*>/i', '$0 ', $curl );
    284344                        }
    285345
    286                 } elseif ( empty( $no_texturize_shortcodes_stack ) && empty( $no_texturize_tags_stack ) ) {
    287                         // This is neither a delimiter, nor is this content inside of no_texturize pairs.  Do texturize.
     346                        wptexturize_replace_init( $curl, '/<[^>]*>' . $shortcode_regex . '/' );
    288347
    289                         $curl = str_replace( $static_characters, $static_replacements, $curl );
     348                        wptexturize_replace_str( $curl, $static_characters, $static_replacements );
    290349
    291350                        if ( false !== strpos( $curl, "'" ) ) {
    292                                 $curl = preg_replace( $dynamic_characters['apos'], $dynamic_replacements['apos'], $curl );
    293                                 $curl = wptexturize_primes( $curl, "'", $prime, $open_sq_flag, $closing_single_quote );
    294                                 $curl = str_replace( $apos_flag, $apos, $curl );
    295                                 $curl = str_replace( $open_sq_flag, $opening_single_quote, $curl );
     351                                wptexturize_replace_regex( $curl, $dynamic_characters['apos'], $dynamic_replacements['apos'] );
     352                                // Substitute single quotes with same-sized dummy so that wptexturize_primes() doesn't alter size of string.
     353                                wptexturize_replace_str( $curl, "'", $sq_flag );
     354                                $curl = wptexturize_primes( $curl, $sq_flag, $prime_sq_flag, $open_sq_flag, $close_sq_flag, $spaces );
     355                                // Reinstate real values.
     356                                wptexturize_replace_str( $curl, $flags_sq, $reals_sq );
    296357                        }
    297358                        if ( false !== strpos( $curl, '"' ) ) {
    298                                 $curl = preg_replace( $dynamic_characters['quote'], $dynamic_replacements['quote'], $curl );
    299                                 $curl = wptexturize_primes( $curl, '"', $double_prime, $open_q_flag, $closing_quote );
    300                                 $curl = str_replace( $open_q_flag, $opening_quote, $curl );
     359                                wptexturize_replace_regex( $curl, $dynamic_characters['quote'], $dynamic_replacements['quote'] );
     360                                // Substitute double quotes with same-sized dummy so that wptexturize_primes() doesn't alter size of string.
     361                                wptexturize_replace_str( $curl, '"', $q_flag );
     362                                $curl = wptexturize_primes( $curl, $q_flag, $prime_q_flag, $open_q_flag, $close_q_flag, $spaces );
     363                                // Reinstate real values.
     364                                wptexturize_replace_str( $curl, $flags_q, $reals_q );
    301365                        }
    302366                        if ( false !== strpos( $curl, '-' ) ) {
    303                                 $curl = preg_replace( $dynamic_characters['dash'], $dynamic_replacements['dash'], $curl );
     367                                wptexturize_replace_regex( $curl, $dynamic_characters['dash'], $dynamic_replacements['dash'] );
    304368                        }
    305369
    306370                        // 9x9 (times), but never 0x9999
    307371                        if ( 1 === preg_match( '/(?<=\d)x\d/', $curl ) ) {
    308372                                // Searching for a digit is 10 times more expensive than for the x, so we avoid doing this one!
    309                                 $curl = preg_replace( '/\b(\d(?(?<=0)[\d\.,]+|[\d\.,]*))x(\d[\d\.,]*)\b/', '$1&#215;$2', $curl );
     373                                wptexturize_replace_regex( $curl, '/\b(\d(?(?<=0)[\d\.,]+|[\d\.,]*))x(?=\d[\d\.,]*\b)/', '$1&#215;' ); // Changed to use look ahead as can only deal with a single sub-replacement.
    310374                        }
     375
     376                        wptexturize_replace_final( $curl );
     377
     378                        // Remove any spaces added to <br>s at the start.
     379                        if ( $have_br ) {
     380                                $curl = preg_replace( '/(<br[^>]*>) /i', '$1', $curl );
     381                        }
    311382                }
    312383        }
    313384        $text = implode( '', $textarr );
     
    330401 * @param string $close_quote The closing quote char to use for replacement.
    331402 * @return string The $haystack value after primes and quotes replacements.
    332403 */
    333 function wptexturize_primes( $haystack, $needle, $prime, $open_quote, $close_quote ) {
    334         $spaces = wp_spaces_regexp();
    335         $flag = '<!--wp-prime-or-quote-->';
     404function wptexturize_primes( $haystack, $needle, $prime, $open_quote, $close_quote, $spaces ) {
     405        $flag = str_pad( '<i f>', strlen( $needle ), '>' ); // Making flag same size as the passed-in dummy.
     406        $flag_len = strlen( $flag );
    336407        $quote_pattern = "/$needle(?=\\Z|[.,:;!?)}\\-\\]]|&gt;|" . $spaces . ")/";
    337408        $prime_pattern    = "/(?<=\\d)$needle/";
    338409        $flag_after_digit = "/(?<=\\d)$flag/";
     
    359430                                                // This is most likely to be problematic in the context of bug #18549.
    360431                                                $pos = strrpos( $sentence, $flag );
    361432                                        }
    362                                         $sentence = substr_replace( $sentence, $close_quote, $pos, strlen( $flag ) );
     433                                        $sentence = substr_replace( $sentence, $close_quote, $pos, $flag_len );
    363434                                }
    364435                                // Use conventional replacement on any remaining primes and quotes.
    365436                                $sentence = preg_replace( $prime_pattern, $prime, $sentence );
    366437                                $sentence = preg_replace( $flag_after_digit, $prime, $sentence );
    367438                                $sentence = str_replace( $flag, $close_quote, $sentence );
    368                         } elseif ( 1 == $count ) {
     439                        } elseif ( 1 === $count ) {
    369440                                // Found only one closing quote candidate, so give it priority over primes.
    370441                                $sentence = str_replace( $flag, $close_quote, $sentence );
    371442                                $sentence = preg_replace( $prime_pattern, $prime, $sentence );
     
    377448                        $sentence = preg_replace( $prime_pattern, $prime, $sentence );
    378449                        $sentence = preg_replace( $quote_pattern, $close_quote, $sentence );
    379450                }
    380                 if ( '"' == $needle && false !== strpos( $sentence, '"' ) ) {
    381                         $sentence = str_replace( '"', $close_quote, $sentence );
     451                if ( '"' === $needle[0] && false !== strpos( $sentence, $needle ) ) {
     452                        $sentence = str_replace( $needle, $close_quote, $sentence );
    382453                }
    383454        }
    384455
     
    440511}
    441512
    442513/**
     514 * Initialize the stripped string routines wptexturize_replace_XXX, setting the globals used.
     515 * $str will be stripped of any strings that match the regular expression $search.
     516 */
     517function wptexturize_replace_init( &$str, $search ) {
     518        global $wptexturize_strip_cnt, $wptexturize_strips, $wptexturize_adjusts;
     519
     520        $wptexturize_strip_cnt = 0;
     521
     522        if ( preg_match_all( $search, $str, $matches, PREG_OFFSET_CAPTURE ) ) {
     523                $wptexturize_strips = $wptexturize_adjusts = $strs = array();
     524                $diff = 0;
     525                foreach ( $matches[0] as list( $match, $offset ) ) {
     526                        $len = strlen( $match );
     527                        // Save details of stripped string.
     528                        $wptexturize_strips[] = array( $match, $offset - $diff /*, $len /* Store len if not using byte array in wptexturize_replace_final(). */ );
     529                        $diff += $len;
     530                        $strs[] = $match; // If using str_replace rather than (safer) preg_replace.
     531                }
     532                $wptexturize_strip_cnt = count( $wptexturize_strips );
     533                $str = str_replace( $strs, '', $str ); // Assuming simple matches replaceable in whole string (otherwise need to do preg_replace( $search, '', $str )).
     534        }
     535        return $wptexturize_strip_cnt;
     536}
     537
     538/**
     539 * Do a straight (non-regexp) string substitution, keeping tabs on the offset adjustments if have a stripped string.
     540 */
     541function wptexturize_replace_str( &$str, $search, $repl ) {
     542        global $wptexturize_strip_cnt, $wptexturize_adjusts;
     543
     544        if ( $wptexturize_strip_cnt ) {
     545                // Process simple string search, given replacement string $repl.
     546                $searches = is_array( $search ) ? $search : array( $search );
     547                $repls = is_array( $repl ) ? $repl : array( $repl );
     548
     549                // As replacements could interfere with later ones, treat each separately.
     550                foreach ( $searches as $idx => $search_str ) {
     551                        if ( false !== ( $offset = strpos( $str, $search_str ) ) ) {
     552                                $repl_str = $repls[$idx];
     553                                $repl_len = strlen( $repl_str );
     554                                $len = strlen( $search_str );
     555                                $diff_len = $repl_len - $len;
     556                                if ( $diff_len ) {
     557                                        $diff = 0;
     558                                        do {
     559                                                // Store adjustment details.
     560                                                $wptexturize_adjusts[] = array( $offset + $diff, $repl_len - 1 );
     561                                                if ( $len > 1 ) { // Do it this way (rather than one adjust of $repl_len - $len) to keep adjustments "atomic", ie to keep stripped elements outside replacement.
     562                                                        $wptexturize_adjusts[] = array( $offset + $diff + $repl_len, 1 - $len );
     563                                                }
     564                                                $diff += $diff_len;
     565                                        } while ( false !== ( $offset = strpos( $str, $search_str, $offset + $len ) ) );
     566                                }
     567                                $str = str_replace( $search_str, $repl_str, $str );
     568                        }
     569                }
     570        } else {
     571                $str = str_replace( $search, $repl, $str );
     572        }
     573}
     574
     575/**
     576 * Do a regexp string substitution, keeping tabs on the offset adjustments if have a stripped string.
     577 */
     578function wptexturize_replace_regex( &$str, $search, $repl ) {
     579        global $wptexturize_strip_cnt, $wptexturize_adjusts;
     580
     581        if ( $wptexturize_strip_cnt ) {
     582                // Process regex, given replacement string $repl.
     583                $searches = is_array( $search ) ? $search : array( $search );
     584                $repls = is_array( $repl ) ? $repl : array( $repl );
     585
     586                // As replacements could interfere with later ones, treat each separately.
     587                foreach ( $searches as $idx => $re ) {
     588                        if ( preg_match_all( $re, $str, $matches, PREG_OFFSET_CAPTURE ) ) {
     589                                $repl_str = $repls[$idx];
     590                                $repl_len = strlen( $repl_str );
     591                                $diff = 0;
     592                                // Allow for a single captured replacement.
     593                                if ( false !== ( $pos1 = strpos( $repl_str, '$1' ) ) ) {
     594                                        foreach ( $matches[0] as $i => list( $match, $offset ) ) {
     595                                                // For a 'pre$1post' replacement, need to track pre-submatch replace and then post-submatch replace.
     596                                                $pre_repl_len = $pos1;
     597                                                $pre_len = $matches[1][$i][1] - $offset; // Submatch offset less full match offset.
     598                                                if ( $pre_repl_len !== $pre_len ) {
     599                                                        // Store adjustment details.
     600                                                        $wptexturize_adjusts[] = array( $offset + $diff, $pre_repl_len - 1 );
     601                                                        if ( $pre_len > 1 ) { // Keep adjustments atomic.
     602                                                                $wptexturize_adjusts[] = array( $offset + $diff + $pre_repl_len, 1 - $pre_len );
     603                                                        }
     604                                                        $diff += $pre_repl_len - $pre_len;
     605                                                }
     606                                                $len1 = strlen( $matches[1][$i][0] ); // Length of submatch string.
     607                                                $post_repl_len = $repl_len - ( $pre_repl_len + 2 );
     608                                                $post_len = strlen( $match ) - ( $pre_len + $len1 );
     609                                                if ( $post_repl_len !== $post_len ) {
     610                                                        // Store adjustment details.
     611                                                        $offset += $pre_len + $len1; // Jump over substituted pre-string & submatch.
     612                                                        $wptexturize_adjusts[] = array( $offset + $diff, $post_repl_len - 1 );
     613                                                        if ( $post_len > 1 ) { // Keep adjustments atomic.
     614                                                                $wptexturize_adjusts[] = array( $offset + $diff + $post_repl_len, 1 - $post_len );
     615                                                        }
     616                                                        $diff += $post_repl_len - $post_len;
     617                                                }
     618                                        }
     619                                } else {
     620                                        foreach ( $matches[0] as list( $match, $offset ) ) {
     621                                                $len = strlen( $match );
     622                                                if ( $repl_len !== $len ) {
     623                                                        // Store adjustment details.
     624                                                        $wptexturize_adjusts[] = array( $offset + $diff, $repl_len - 1 );
     625                                                        if ( $len > 1 ) { // Keep adjustments atomic.
     626                                                                $wptexturize_adjusts[] = array( $offset + $diff + $repl_len, 1 - $len );
     627                                                        }
     628                                                        $diff += $repl_len - $len;
     629                                                }
     630                                        }
     631                                }
     632                                $str = preg_replace( $re, $repl_str, $str );
     633                        }
     634                }
     635        } else {
     636                $str = preg_replace( $search, $repl, $str );
     637        }
     638}
     639
     640/**
     641 * Restore stripped strings to $str.
     642 */
     643function wptexturize_replace_final( &$str ) {
     644        global $wptexturize_strip_cnt, $wptexturize_strips, $wptexturize_adjusts;
     645
     646        // Finalize - restore stripped strings.
     647        if ( $wptexturize_strip_cnt ) {
     648                // Calculate offset adjustments.
     649                foreach ( $wptexturize_adjusts as list( $offset, $diff_len ) ) {
     650                        for ( $i = $wptexturize_strip_cnt - 1; $i >= 0 && $offset < $wptexturize_strips[$i][1]; $i-- ) {
     651                                $wptexturize_strips[$i][1] += $diff_len;
     652                        }
     653                }
     654
     655                // Restore stripped strings.
     656                $str_arr = str_split( $str ); // Using byte array (seems to be a bit quicker than substr_replace()).
     657                array_unshift( $str_arr, '' );
     658                foreach ( $wptexturize_strips as list( $strip, $offset ) ) {
     659                        $str_arr[$offset] .= $strip;
     660                }
     661                $str = implode( '', $str_arr );
     662                unset( $str_arr );
     663                /* If not using byte array. (Note need to store $len in wptexturize_replace_init()).
     664                $diff = 0;
     665                foreach ( $wptexturize_strips as list( $strip, $offset, $len ) ) {
     666                        $str = substr_replace( $str, $strip, $offset + $diff, 0 );
     667                        $diff += $len;
     668                }
     669                /**/
     670                $wptexturize_strip_cnt = 0;
     671        }
     672}
     673
     674/**
    443675 * Replaces double line-breaks with paragraph elements.
    444676 *
    445677 * A group of regex replaces used to identify text formatted with newlines and