修改php限制文本函数,为其添加某种偏移量

修改php限制文本函数,为其添加某种偏移量

问题描述:

Maybe you guys can help:

I have a variable called $bio with bio data.

$bio = "Hello, I am John, I'm 25, I like fast cars and boats. I work as a blogger and I'm way cooler then the author of the question";

I search the $bio using a set of functions to search for a certain word, lets say "author" which adds a span class around that word, and I get:

$bio = "Hello, I am John, I'm 25, I like fast cars and boats. I work as a blogger and I'm way cooler then the <span class=\"highlight\">author</span> of the question";

I use a function to limit the text to 85 chars:

$bio = limit_text($bio,85);

The problem is when there are more then 80 chars before the word "author" in $bio. When the limit_text() is applied, I won't see the highlighted word author.

What I need is for the limit_text() function to work as normal, adding all the words that contain the span class highlight at the end. Something like this:

*"This is the limited text to 85 chars, but there are no words with the span class highlight so I am putting to be continued ... **author**, **author2** (and all the other words that have a span class highlight around them separate by comma "*

Hope you understood what I mean, if not, please comment and I'll try to explain better.

Here is my limit_text() function:

function limit_text($text, $length){ // Limit Text
        if(strlen($text) > $length) {
        $stringCut = substr($text, 0, $length);
        $text = substr($stringCut, 0, strrpos($stringCut, ' '));
        }
        return $text;
    }

UPDATE:

$xturnons = str_replace(",", ", ", $xturnons);
$xbio = str_replace(",", ", ", $xbio);

$xbio = customHighlights($xbio,$toHighlight); 
$xturnons = customHighlights($xturnons,$toHighlight);

$xbio = limit_text($xbio,85);
$xturnons = limit_text($xturnons,85);

The customHighlights function which adds the span class highlighted:

function addRegEx($word){ // Highlight Words
        return "/" . $word . '[^ ,\,,.,?,\.]*/i';
    }
    function highlight($word){
        return "<span class='highlighted'>".$word[0]."</span>";
    }
    function customHighlights($searchString,$toHighlight){
        $searchFor = array_map('addRegEx',$toHighlight);
        $result = preg_replace_callback($searchFor,'highlight',$searchString);
        return $result;
    }

First, you need to make sure you don't break words apart by shortening the string. Then you need to append all of the <span class="highlight"> tokens to the end of the shortened string. Here is what I came up with (in about 8 lines!):

function limit_text($text, $length){
    if( strlen( $text) < $length) {
        return $text;
    }

    // Truncate the string without breaking words
    list( $wrapped) = explode("
", wordwrap( $text, $length));

    // Get the span of text occurring after the wrapped string
    $remainder = substr( $text, strlen( $wrapped));

    // Add the "to be continued" to $wrapped
    $wrapped .= ' to be continued ... ';

    // Now, grab all of the <span class="highlight"></span> tags in the $remainder
    preg_match_all( '#<span class="highlight">[^<]+</span>#i', $remainder, $matches);

    // Add the <span> tags to the end of the string, separated by a comma, if present
    $wrapped .= implode( ', ', $matches[0]);

    return $wrapped;
}

Now, with your original test:

$bio = "Hello, I am John, I'm 25, I like fast cars and boats. I work as a blogger and I'm way cooler then the <span class=\"highlight\">author</span> of the question";
$bio = limit_text( $bio,85);
var_dump( htmlentities( $bio));

This outputs:

string(165) "Hello, I am John, I'm 25, I like fast cars and boats. I work as a blogger and I'm way to be continued ... <span class="highlight">author</span>"

Now, another test with multiple <span> tags:

This outputs:

$bio = 'Hello, what about a <span class="highlight">span tag</span> before the limit? Or what if I have many <span class="highlight">span tags</span> <span class="highlight">after</span> <span class="highlight">the</span> limit?';
$bio = limit_text( $bio,85);
var_dump( htmlentities( $bio));

string(308) "Hello, what about a <span class="highlight">span tag</span> before the limit? Or what to be continued ... <span class="highlight">span tags</span>, <span class="highlight">after</span>, <span class="highlight">the</span>" 

If you have more test cases, or have a modification to the function above, let me know and I can fix it!

This change to your limit_text function will take the text, and cut it if it's longer than the given $length. If you pass a $needle to it, it will search for the first occurrence of it, and end your sentence with it.

Also, if the text is cut before it's actual length, it will add $addition to it, while still preserving the limit of $length characters.

I've included a usage and a sample output based on your given below:

<?php
/**
*   $text - The text to cut from
*   $length - The amount of characters that should be returned
*   $needle - If needle is given and found in the text, and it is 
*             at least $length far from the start of the string - it will end the sentence with it.
*   $addition - If the sentence was cut in the middle, will add it to the end of it.
**/
function limit_text($text, $length, $needle="", $addition="...") { 
    if(strlen($text) > $length) {
        $length -= strlen($addition);

        $start = 0;
        $trimLast = true;
        if (!empty($needle)) {
            $needleStart = strpos($text, $needle);

            if ($needleStart > $length) {
                $length -= strlen($needle);
                $start = $needleStart + strlen($needle) - $length;
                $trimLast = false;
            }
        }

        $stringCut = substr($text, max(0, $start), $length);
        if ($start > 0) {
            $stringCut = substr($stringCut, strpos($stringCut, ' ')+1);
        }
        if ($trimLast) {
            $lastWhitespace = strrpos($stringCut, ' ');
            $stringCut = substr($stringCut, 0, $lastWhitespace);
        }

        // split into words (so we won't replace words that contain it in the middle)
        // and wrap $needle with <span class="highlighted"></span>
        if (!empty($needle)) {
            $words = explode(" ", $stringCut);
            $needles = array_keys($words, $needle);
            foreach ($needles as $needleKey) {
                $words[$needleKey] = "<span class=\"highlighted\">$needle</span>";
            }

            $stringCut = implode(" ", $words);
        }
        $text = $stringCut.$addition;
    }
    return $text;
}

$bio = "Hello, I am John, I'm 25, I like fast cars and boats. I work as a blogger and I'm way cooler then the author of the question";
$text = limit_text($bio, 85, "author");
var_dump($text);

Output:

string (111) "fast cars and boats. I work as a blogger and I'm way cooler then the <span class="highlighted">author</span>..."

Judging from your requirements, this should do what you want:

function get_highlighted_string($s)
{
    return '<span class="highlight">' . htmlspecialchars($s) . '</span>';
}

function limit_text($text, $max_length, array $keywords = array(), $continued = '...')
{
    // highlights to put after the cut string
    $extra = array();

    // highlight keywords
    if ($keywords) {
        $re = '~\b(' . join('|', array_map('preg_quote', $keywords, array('~'))) . ')\b~i';
        // get all matches and capture their positions as well
        if (preg_match_all($re, $text, $matches, PREG_OFFSET_CAPTURE)) {
            // we reverse the matches by position to make replacement easier
            foreach (array_reverse($matches[1]) as $match) {
                // $match[0] = match
                // $match[1] = start position
                $match_len = strlen($match[0]);
                if ($match[1] + $match_len <= $max_length) {
                    // still fits in cut string
                    $match_replacement = get_highlighted_string($match[0]);
                    $text = substr_replace($text, $match_replacement, $match[1], $match_len);
                    // update max length
                    $max_length = $max_length - $match_len + strlen($match_replacement);
                } else {
                    // will not fit in the cut string, so we place it outside
                    array_unshift($extra, get_highlighted_string($match[0]));
                }
            }
        }
        // use wordwrap and strcspn to cut the string by word boundaries
        if (strlen($text) > $max_length) {
            $text = substr($text, 0, strcspn(wordwrap($text, $max_length, "\0"), "\0")) . " $continued";
        }
    }

    if ($extra) {
        // append what we couldn't fit in the cut string
        $text .= ' ' . join(', ', $extra);
    }

    return $text;
}

Example:

echo limit_text("Hello, I like fast cars and boats. I work as a blogger I'm way cooler then the author of the question", 85, array('author', 'question'));

Hello, I like fast cars and boats. I work as a blogger I'm way cooler then the <span class="highlight">author</span> ... <span class="highlight">question</span>

In the example, the cut-off is exactly at author so that highlight comes before the ... while the question keywords gets put behind.

Another example:

echo limit_text("Hello, I am John, I'm 25, I like fast cars and boats. I work as a blogger and I'm way cooler then the author of the question", 85, array('author', 'question'));

Hello, I am John, I'm 25, I like fast cars and boats. I work as a blogger and I'm way ... <span class="highlight">author</span>, <span class="highlight">question</span>

Both keywords are beyond the 85 character marker, so they are appended at the back, comma separated.

Let me know if this works for you :)