Encurte a string sem cortar palavras em JavaScript

102

Não sou muito bom com manipulação de string em JavaScript e gostaria de saber como você faria para encurtar uma string sem cortar nenhuma palavra. Eu sei como usar substring, mas não indexOf ou nada muito bem.

Digamos que eu tenha a seguinte string:

text = "this is a long string I cant display"

Eu quero reduzir para 10 caracteres, mas se não terminar com um espaço, termine a palavra. Não quero que a variável string seja assim:

"esta é uma longa corda que eu não posso dis"

Quero que termine a palavra até que ocorra um espaço.

Josh Bedo
fonte
você quer dizer cortar uma corda? tentativa" too many spaces ".trim()
Anurag
1
Alguns exemplos de entrada e saída esperada ajudariam muito a responder a essa pergunta.
deceze
tudo bem, desculpe, diga que eu tinha a string text = "esta é uma string longa que eu não posso exibir" eu quero reduzi-la para 10 caracteres, mas se não terminar com um espaço termine a palavra eu não quero que a variável da string se pareça este "esta é uma longa corda que eu não posso dis"
Josh Bedo

Respostas:

180

Se bem entendi, você deseja encurtar uma string para um determinado comprimento (por exemplo, encurtar "The quick brown fox jumps over the lazy dog"para, digamos, 6 caracteres sem cortar nenhuma palavra).

Se for esse o caso, você pode tentar algo como o seguinte:

var yourString = "The quick brown fox jumps over the lazy dog"; //replace with your string.
var maxLength = 6 // maximum number of characters to extract

//Trim and re-trim only when necessary (prevent re-trim when string is shorted than maxLength, it causes last word cut) 
if(yourString.length > trimmedString.length){
    //trim the string to the maximum length
    var trimmedString = yourString.substr(0, maxLength);

    //re-trim if we are in the middle of a word and 
    trimmedString = trimmedString.substr(0, Math.min(trimmedString.length, trimmedString.lastIndexOf(" ")))
}
NT3RP
fonte
9
@josh não é absolutamente verdade que ".replace" não funcione em "funções jQuery". Não existe nem mesmo uma "função jQuery".
Pointy,
3
não deveria ser "maxLength + 1". E se maxLength for maior ou igual ao comprimento completo da frase, a última palavra não será incluída. mas obrigado pela solução.
Beytan Kurt
4
Se usar isso em uma string menor que maxLength, a última palavra será cortada. Talvez @AndrewJuniorHoward já tenha declarado a correção para este ( maxLength + 1), mas eu corrigi simplesmente adicionando esta linha no topo:var yourString += " ";
tylerl
3
Infelizmente, se você tirar uma fox jumps over the lazy dogparte, o resultado será The quick brown , quando deveria ser The quick brown fox.
Andrey Gordeev
2
Isso sempre corta a última palavra.
Chris Cinelli
108

Existem muitas maneiras de fazer isso, mas uma expressão regular é um método útil de uma linha:

"this is a longish string of text".replace(/^(.{11}[^\s]*).*/, "$1"); 
//"this is a longish"

Esta expressão retorna os primeiros 11 (quaisquer) caracteres mais quaisquer caracteres subsequentes que não sejam de espaço.

Script de exemplo:

<pre>
<script>
var t = "this is a longish string of text";

document.write("1:   " + t.replace(/^(.{1}[^\s]*).*/, "$1") + "\n");
document.write("2:   " + t.replace(/^(.{2}[^\s]*).*/, "$1") + "\n");
document.write("5:   " + t.replace(/^(.{5}[^\s]*).*/, "$1") + "\n");
document.write("11:  " + t.replace(/^(.{11}[^\s]*).*/, "$1") + "\n");
document.write("20:  " + t.replace(/^(.{20}[^\s]*).*/, "$1") + "\n");
document.write("100: " + t.replace(/^(.{100}[^\s]*).*/, "$1") + "\n");
</script>

Resultado:

1:   this
2:   this
5:   this is
11:  this is a longish
20:  this is a longish string
100: this is a longish string of text
Hamish
fonte
Incrível, eu literalmente pesquisei essa questão de um milhão de maneiras e só consegui encontrar uma versão funcional para php, nada perto disso e envolvendo loops.
Josh Bedo
1
Refere-se à primeira (e única, neste caso) correspondência de subexpressão - o que está entre colchetes. $ 0 se refere a toda a correspondência, que neste caso é a string inteira.
Hamish,
3
@josh Você deve ser capaz de tornar o comprimento máximo uma variável usando um objeto regexp:t.replace(new RegExp("^(.{"+length+"}[^\s]*).*"), "$1")
rjmackay
1
@Hamish sua opção funciona bem, mas inclui a última palavra também se o comprimento exceder. Tentei alterar a expressão regex para excluir a última palavra se o limite máximo de palavras for excedido, mas não consegui fazer funcionar. Como podemos conseguir isso?
Shashank Agrawal
1
Bom, realmente não está funcionando direito, às vezes passo o valor máximo como por exemplo se a última palavra já tiver 30 caracteres ela já estará com comprimento maior que 60 já! mesmo se o comprimento fosse definido como{30}
Al-Mothafar
65

Estou meio surpreso que para um problema simples como este haja tantas respostas que são difíceis de ler e algumas, incluindo a escolhida, não funcionam.

Normalmente, quero que a string de resultado tenha no máximo maxLen caracteres. Eu também uso essa mesma função para encurtar os slugs em URLs.

str.lastIndexOf(searchValue[, fromIndex]) recebe um segundo parâmetro que é o índice no qual iniciar a pesquisa para trás na string, tornando as coisas simples e eficientes.

// Shorten a string to less than maxLen characters without truncating words.
function shorten(str, maxLen, separator = ' ') {
  if (str.length <= maxLen) return str;
  return str.substr(0, str.lastIndexOf(separator, maxLen));
}

Este é um exemplo de saída:

for (var i = 0; i < 50; i += 3) 
  console.log(i, shorten("The quick brown fox jumps over the lazy dog", i));

 0 ""
 3 "The"
 6 "The"
 9 "The quick"
12 "The quick"
15 "The quick brown"
18 "The quick brown"
21 "The quick brown fox"
24 "The quick brown fox"
27 "The quick brown fox jumps"
30 "The quick brown fox jumps over"
33 "The quick brown fox jumps over"
36 "The quick brown fox jumps over the"
39 "The quick brown fox jumps over the lazy"
42 "The quick brown fox jumps over the lazy"
45 "The quick brown fox jumps over the lazy dog"
48 "The quick brown fox jumps over the lazy dog"

E para a lesma:

for (var i = 0; i < 50; i += 10) 
  console.log(i, shorten("the-quick-brown-fox-jumps-over-the-lazy-dog", i, '-'));

 0 ""
10 "the-quick"
20 "the-quick-brown-fox"
30 "the-quick-brown-fox-jumps-over"
40 "the-quick-brown-fox-jumps-over-the-lazy"
Chris Cinelli
fonte
1
Esqueci completamente o lastIndexOf (). Boa pegada!
Tici
2
Isso trava se por algum motivo strfor undefined. Eu adicioneiif (!str || str.length <= maxLen) return str;
Silvain
isso não
resolve
@shrewquest Funciona. Se o separador não estiver na string, ele retornará a própria string se str.length <= maxLen. Caso contrário, ele retorna uma string vazia.
Chris Cinelli
20

Todo mundo parece esquecer que indexOf leva dois argumentos - a string para corresponder e o índice de caractere para começar a procurar. Você pode quebrar a string no primeiro espaço após 10 caracteres.

function cutString(s, n){
    var cut= s.indexOf(' ', n);
    if(cut== -1) return s;
    return s.substring(0, cut)
}
var s= "this is a long string i cant display";
cutString(s, 10)

/*  returned value: (String)
this is a long
*/
kennebec
fonte
Observe que indexOf pode ser substituído por lastIndexOf se limites rígidos forem necessários.
Scheintod
14

Lodash tem uma função escrita especificamente para isso: _.truncate

const truncate = _.truncate
const str = 'The quick brown fox jumps over the lazy dog'

truncate(str, {
  length: 30, // maximum 30 characters
  separator: /,?\.* +/ // separate by spaces, including preceding commas and periods
})

// 'The quick brown fox jumps...'
Leon li
fonte
7

Com base na resposta do NT3RP que não trata de alguns casos extremos, fiz este código. Ele garante não retornar um texto com um evento tamanho> maxLength e reticências ...foram adicionadas no final.

Isso também lida com alguns casos extremos, como um texto que tem uma única palavra sendo> maxLength

shorten: function(text,maxLength,options) {
    if ( text.length <= maxLength ) {
        return text;
    }
    if ( !options ) options = {};
    var defaultOptions = {
        // By default we add an ellipsis at the end
        suffix: true,
        suffixString: " ...",
        // By default we preserve word boundaries
        preserveWordBoundaries: true,
        wordSeparator: " "
    };
    $.extend(options, defaultOptions);
    // Compute suffix to use (eventually add an ellipsis)
    var suffix = "";
    if ( text.length > maxLength && options.suffix) {
        suffix = options.suffixString;
    }

    // Compute the index at which we have to cut the text
    var maxTextLength = maxLength - suffix.length;
    var cutIndex;
    if ( options.preserveWordBoundaries ) {
        // We use +1 because the extra char is either a space or will be cut anyway
        // This permits to avoid removing an extra word when there's a space at the maxTextLength index
        var lastWordSeparatorIndex = text.lastIndexOf(options.wordSeparator, maxTextLength+1);
        // We include 0 because if have a "very long first word" (size > maxLength), we still don't want to cut it
        // But just display "...". But in this case the user should probably use preserveWordBoundaries:false...
        cutIndex = lastWordSeparatorIndex > 0 ? lastWordSeparatorIndex : maxTextLength;
    } else {
        cutIndex = maxTextLength;
    }

    var newText = text.substr(0,cutIndex);
    return newText + suffix;
}

Eu acho que você pode remover facilmente a dependência do jquery se isso o incomoda.

Sebastien Lorber
fonte
3
Eu gosto dessa solução, mas os argumentos não deveriam $.extendser revertidos?
JKesMc9tqIQe9M
6

Aqui está uma solução em uma linha.

text = "this is a long string I cant display"

function shorten(text,max) {
    return text && text.length > max ? text.slice(0,max).split(' ').slice(0, -1).join(' ') : text
}


console.log(shorten(text,10));

Joakim Poromaa Helger
fonte
3

Estou atrasado para a festa, mas aqui está uma solução pequena e fácil que encontrei para devolver uma quantidade de palavras.

Não está diretamente relacionado à sua exigência de personagens , mas serve ao mesmo resultado que acredito que você estava procurando.

function truncateWords(sentence, amount, tail) {
  const words = sentence.split(' ');

  if (amount >= words.length) {
    return sentence;
  }

  const truncated = words.slice(0, amount);
  return `${truncated.join(' ')}${tail}`;
}

const sentence = 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.';

console.log(truncateWords(sentence, 10, '...'));

Veja o exemplo de trabalho aqui: https://jsfiddle.net/bx7rojgL/

Michael Giovanni Pumo
fonte
Você escreveu uma função JS que trunca uma string em várias palavras. Leia a pergunta novamente.
ChristoKiwi
1
eeehm. Acho que esta é a única resposta certa para a pergunta. Ele perguntou sem cortar a palavra.
Mike Aron
2

Isso exclui a palavra final em vez de incluí-la.

function smartTrim(str, length, delim, appendix) {
    if (str.length <= length) return str;

    var trimmedStr = str.substr(0, length+delim.length);

    var lastDelimIndex = trimmedStr.lastIndexOf(delim);
    if (lastDelimIndex >= 0) trimmedStr = trimmedStr.substr(0, lastDelimIndex);

    if (trimmedStr) trimmedStr += appendix;
    return trimmedStr;
}

Uso:

smartTrim(yourString, 11, ' ', ' ...')
"The quick ..."
clima
fonte
2

Eu fiz uma abordagem diferente. Embora eu precisasse de um resultado semelhante, queria manter meu valor de retorno menor que o comprimento especificado.

function wordTrim(value, length, overflowSuffix) {
    value = value.trim();
    if (value.length <= length) return value;
    var strAry = value.split(' ');
    var retString = strAry[0];
    for (var i = 1; i < strAry.length; i++) {
        if (retString.length >= length || retString.length + strAry[i].length + 1 > length) break;
        retString += " " + strAry[i];
    }
    return retString + (overflowSuffix || '');
}

Editar Refatorei um pouco aqui: Exemplo JSFiddle . Ele reingressa na matriz original em vez de concatenar.

function wordTrim(value, length, overflowSuffix) {
    if (value.length <= length) return value;
    var strAry = value.split(' ');
    var retLen = strAry[0].length;
    for (var i = 1; i < strAry.length; i++) {
        if(retLen == length || retLen + strAry[i].length + 1 > length) break;
        retLen+= strAry[i].length + 1
    }
    return strAry.slice(0,i).join(' ') + (overflowSuffix || '');
}
Pete
fonte
2
function shorten(str,n) {
  return (str.match(RegExp(".{"+n+"}\\S*"))||[str])[0];
}

shorten("Hello World", 3); // "Hello"

Roko C. Buljan
fonte
1

Você pode usar truncateuma linha abaixo:

const text = "The string that I want to truncate!";

const truncate = (str, len) => str.substring(0, (str + ' ').lastIndexOf(' ', len));

console.log(truncate(text, 14));

Viktor Vlasenko
fonte
1
shorten(str, maxLen, appendix, separator = ' ') {
if (str.length <= maxLen) return str;
let strNope = str.substr(0, str.lastIndexOf(separator, maxLen));
return (strNope += appendix);

}

var s = "esta é uma longa string e não consigo explicar tudo"; encurtar (s, 10, '...')

/* "isto é .." */

Vivi Margaretha
fonte
1

Aqui está mais um trecho de código que trunca ao longo dos sinais de pontuação (estava procurando por isso e o Google encontrou essa pergunta aqui). Tive que encontrar uma solução sozinho, então foi isso que eu hackeei em 15 minutos. Encontra todas as ocorrências de. ! ? e trunca em qualquer posição destes que seja <quelen

function pos(str, char) {
    let pos = 0
    const ret = []
    while ( (pos = str.indexOf(char, pos + 1)) != -1) {
        ret.push(pos)
    }
    return ret
}

function truncate(str, len) {
    if (str.length < len)
        return str

    const allPos = [  ...pos(str, '!'), ...pos(str, '.'), ...pos(str, '?')].sort( (a,b) => a-b )
    if (allPos.length === 0) {
        return str.substr(0, len)
    }

    for(let i = 0; i < allPos.length; i++) {
        if (allPos[i] > len) {
            return str.substr(0, allPos[i-1] + 1)
        }
    }
}

module.exports = truncate
Stefan
fonte
1

Texto datilografado e com reticências :)

export const sliceByWord = (phrase: string, length: number, skipEllipses?: boolean): string => {
  if (phrase.length < length) return phrase
  else {
    let trimmed = phrase.slice(0, length)
    trimmed = trimmed.slice(0, Math.min(trimmed.length, trimmed.lastIndexOf(' ')))
    return skipEllipses ? trimmed : trimmed + '…'
  }
}
doublejosh
fonte
0

Pelo que vale a pena, escrevi isso para truncar para o limite da palavra sem deixar pontuação ou espaço em branco no final da string:

function truncateStringToWord(str, length, addEllipsis)
{
    if(str.length <= length)
    {
        // provided string already short enough
        return(str);
    }

    // cut string down but keep 1 extra character so we can check if a non-word character exists beyond the boundary
    str = str.substr(0, length+1);

    // cut any non-whitespace characters off the end of the string
    if (/[^\s]+$/.test(str))
    {
        str = str.replace(/[^\s]+$/, "");
    }

    // cut any remaining non-word characters
    str = str.replace(/[^\w]+$/, "");

    var ellipsis = addEllipsis && str.length > 0 ? '&hellip;' : '';

    return(str + ellipsis);
}

var testString = "hi stack overflow, how are you? Spare";
var i = testString.length;

document.write('<strong>Without ellipsis:</strong><br>');

while(i > 0)
{
  document.write(i+': "'+ truncateStringToWord(testString, i) +'"<br>');
  i--;
}

document.write('<strong>With ellipsis:</strong><br>');

i = testString.length;
while(i > 0)
{
  document.write(i+': "'+ truncateStringToWord(testString, i, true) +'"<br>');
  i--;
}

bbeckford
fonte
0

Não considerou as soluções votadas satisfatórias. Então eu escrevi algo que é genérico e funciona tanto na primeira quanto na última parte do seu texto (algo como substr, mas para palavras). Além disso, você pode definir se deseja que os espaços sejam deixados de fora na contagem de caracteres.

    function chopTxtMinMax(txt, firstChar, lastChar=0){
        var wordsArr = txt.split(" ");
        var newWordsArr = [];

        var totalIteratedChars = 0;
        var inclSpacesCount = true;

        for(var wordIndx in wordsArr){
            totalIteratedChars += wordsArr[wordIndx].length + (inclSpacesCount ? 1 : 0);
            if(totalIteratedChars >= firstChar && (totalIteratedChars <= lastChar || lastChar==0)){
                newWordsArr.push(wordsArr[wordIndx]);
            }
        }

        txt = newWordsArr.join(" ");
        return txt;
    }
Vasili Paspalas
fonte
0

Cheguei atrasado para isso, mas acho que essa função faz exatamente o que o OP pede. Você pode alterar facilmente os valores SENTENCE e LIMIT para resultados diferentes.

function breakSentence(word, limit) {
  const queue = word.split(' ');
  const list = [];

  while (queue.length) {
    const word = queue.shift();

    if (word.length >= limit) {
      list.push(word)
    }
    else {
      let words = word;

      while (true) {
        if (!queue.length ||
            words.length > limit ||
            words.length + queue[0].length + 1 > limit) {
          break;
        }

        words += ' ' + queue.shift();
      }

      list.push(words);
    }
  }

  return list;
}

const SENTENCE = 'the quick brown fox jumped over the lazy dog';
const LIMIT = 11;

// get result
const words = breakSentence(SENTENCE, LIMIT);

// transform the string so the result is easier to understand
const wordsWithLengths = words.map((item) => {
  return `[${item}] has a length of - ${item.length}`;
});

console.log(wordsWithLengths);

A saída deste snippet é onde o LIMIT é 11 é:

[ '[the quick] has a length of - 9',
  '[brown fox] has a length of - 9',
  '[jumped over] has a length of - 11',
  '[the lazy] has a length of - 8',
  '[dog] has a length of - 3' ]
Ian Calderon
fonte
0

Com condições de limite como frase vazia e primeira palavra muito longa. Além disso, ele não usa API / biblioteca de string específica de idioma.

function solution(message, k) {
    if(!message){
        return ""; //when message is empty
    }
    const messageWords = message.split(" ");
    let result = messageWords[0];
    if(result.length>k){
        return ""; //when length of first word itself is greater that k
    }
    for(let i = 1; i<messageWords.length; i++){
        let next = result + " " + messageWords[i];

        if(next.length<=k){
            result = next;
        }else{
            break;
        }
    }
    return result;
}

console.log(solution("this is a long string i cant display", 10));

Shishir Arora
fonte
0

'Macarrão com tomate e espinafre'

se você não quer cortar a palavra pela metade

primeira iteração:

acc: 0 / acc + cur.length = 5 / newTitle = ['Pasta'];

segunda iteração:

acc: 5 / acc + cur.length = 9 / newTitle = ['Pasta', 'com'];

terceira iteração:

acc: 9 / acc + cur.length = 15 / newTitle = ['Macarrão', 'com', 'tomate'];

quarta iteração:

acc: 15 / acc + cur.length = 18 (limite) / newTitle = ['Macarrão', 'com', 'tomate'];

const limitRecipeTitle = (title, limit=17)=>{
    const newTitle = [];
    if(title.length>limit){
        title.split(' ').reduce((acc, cur)=>{
            if(acc+cur.length <= limit){
                newTitle.push(cur);
            }
            return acc+cur.length;
        },0);
    }

    return `${newTitle.join(' ')} ...`
}

saída: Macarrão com tomate ...

senhor
fonte
-1

Você pode cortar espaços com isto:

var trimmedString = flabbyString.replace(/^\s*(.*)\s*$/, '$1');
Pontudo
fonte
-1

Atualizado a partir de @ NT3RP, descobri que se a string atingir um espaço na primeira vez, ela acabará excluindo aquela palavra, tornando a string uma palavra mais curta do que poderia ser. Então, eu apenas coloquei uma instrução if else para verificar se maxLength não cai em um espaço.

codepen.io

var yourString = "The quick brown fox jumps over the lazy dog"; //replace with your string.
var maxLength = 15 // maximum number of characters to extract

if (yourString[maxLength] !== " ") {

//trim the string to the maximum length
var trimmedString = yourString.substr(0, maxLength);

alert(trimmedString)

//re-trim if we are in the middle of a word
trimmedString = trimmedString.substr(0, Math.min(trimmedString.length, trimmedString.lastIndexOf(" ")))
}

else {
  var trimmedString = yourString.substr(0, maxLength);
}

alert(trimmedString)
Landon Call
fonte