Raccourcir la chaîne sans couper les mots en JavaScript

102

Je ne suis pas très doué pour la manipulation de chaînes en JavaScript, et je me demandais comment vous feriez pour raccourcir une chaîne sans couper aucun mot. Je sais comment utiliser substring, mais pas indexOf ou quoi que ce soit vraiment bien.

Disons que j'avais la chaîne suivante:

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

Je veux le réduire à 10 caractères, mais s'il ne se termine pas par un espace, terminez le mot. Je ne veux pas que la variable de chaîne ressemble à ceci:

"c'est une longue chaîne que je ne peux pas dis"

Je veux qu'il termine le mot jusqu'à ce qu'un espace apparaisse.

Josh Bedo
la source
vous voulez dire couper une chaîne? essayer" too many spaces ".trim()
Anurag
1
Quelques exemples d'entrée et de sortie attendue aideraient beaucoup à répondre à cette question.
deceze
d'accord, désolé, dis que j'ai eu la chaîne text = "c'est une longue chaîne que je ne peux pas afficher" je veux la réduire à 10 caractères mais si elle ne se termine pas par un espace, terminer le mot je ne veux pas que la variable chaîne ressemble ce "c'est une longue chaîne que je ne peux pas dis"
Josh Bedo

Réponses:

180

Si je comprends bien, vous voulez raccourcir une chaîne à une certaine longueur (par exemple raccourcir "The quick brown fox jumps over the lazy dog"à, disons, 6 caractères sans couper aucun mot).

Si tel est le cas, vous pouvez essayer quelque chose comme ce qui suit:

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
la source
9
@josh ce n'est absolument pas vrai que ".replace" ne fonctionne pas dans les "fonctions jQuery". Il n'y a même pas de "fonction jQuery".
Pointy
3
n'est-ce pas "maxLength + 1". Et si maxLength est supérieur ou égal à la longueur totale de la phrase, alors le dernier mot n'est pas inclus. mais merci pour la solution.
Beytan Kurt
4
Si vous l'utilisez sur une chaîne plus courte que maxLength, le dernier mot est coupé. Peut-être que @AndrewJuniorHoward a déjà indiqué le correctif pour cela ( maxLength + 1), mais je l'ai corrigé en ajoutant simplement cette ligne en haut:var yourString += " ";
tylerl
3
Malheureusement, si vous en retirez une fox jumps over the lazy dogpartie, le résultat sera The quick brown , quand il devrait l'être The quick brown fox.
Andrey Gordeev
2
Cela coupe toujours le dernier mot.
Chris Cinelli
109

Il existe de nombreuses façons de le faire, mais une expression régulière est une méthode d'une ligne utile:

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

Cette expression renvoie les 11 premiers caractères (tous) plus tous les caractères non espace suivants.

Exemple de script:

<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>

Production:

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
la source
Génial, j'ai littéralement cherché sur Google cette question d'un million de façons et je n'ai pu trouver qu'une version de travail pour php, rien de comparable et impliquant des boucles.
Josh Bedo
1
Il fait référence à la première (et seule, dans ce cas) correspondance de sous-expression - les éléments entre crochets. $ 0 ferait référence à la correspondance entière, qui dans ce cas est la chaîne entière.
Hamish
3
@josh Vous devriez être capable de faire de la longueur maximale une variable en utilisant un objet regexp:t.replace(new RegExp("^(.{"+length+"}[^\s]*).*"), "$1")
rjmackay
1
@Hamish votre option fonctionne bien, mais elle inclut également le dernier mot si la longueur dépasse. J'ai essayé de modifier l'expression regex pour exclure le dernier mot si la limite maximale de mots dépasse mais ne l'a pas fait fonctionner. Comment pouvons-nous y parvenir?
Shashank Agrawal du
1
Eh bien, cela ne fonctionne pas vraiment correctement, parfois je passe la valeur maximale comme par exemple si le dernier mot était déjà de 30 caractères, il aura déjà une longueur de plus de 60! même si la longueur est définie sur{30}
Al-Mothafar
65

Je suis un peu surpris que pour un problème simple comme celui-ci, il y ait tant de réponses difficiles à lire et certaines, y compris celle choisie, ne fonctionnent pas.

Je veux généralement que la chaîne de résultat soit au maximum des maxLen caractères. J'utilise également cette même fonction pour raccourcir les slugs dans les URL.

str.lastIndexOf(searchValue[, fromIndex]) prend un deuxième paramètre qui est l'index à partir duquel commencer la recherche en arrière dans la chaîne, ce qui rend les choses efficaces et simples.

// 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));
}

Ceci est un exemple de sortie:

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"

Et pour la limace:

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
la source
1
J'ai complètement oublié le lastIndexOf (). Bonne prise!
Tici
2
Cette plante si pour une raison quelconque strest undefined. J'ai ajoutéif (!str || str.length <= maxLen) return str;
Silvain
cela ne gère pas le cas de bord où le séparateur ne se produit pas dans la chaîne
shrewquest
@shrewquest Cela fonctionne. Si le séparateur n'est pas dans la chaîne, il renvoie la chaîne elle-même si str.length <= maxLen. Sinon, il renvoie une chaîne vide.
Chris Cinelli
20

Tout le monde semble oublier que indexOf prend deux arguments: la chaîne à rechercher et l'index de caractère à partir duquel commencer la recherche. Vous pouvez couper la chaîne au premier espace après 10 caractères.

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
la source
Notez que indexOf peut être remplacé par lastIndexOf si des limites strictes sont nécessaires.
Scheintod
14

Lodash a une fonction spécialement écrite pour cela: _.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...'
Léon Li
la source
7

Basé sur la réponse NT3RP qui ne gère pas certains cas de coin, j'ai créé ce code. Il garantit de ne pas renvoyer un texte avec un événement size> maxLength, une ellipse a ...été ajoutée à la fin.

Cela gère également certains cas d'angle comme un texte dont un seul mot est> 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;
}

Je suppose que vous pouvez facilement supprimer la dépendance jquery si cela vous dérange.

Sébastien Lorber
la source
3
J'aime cette solution, mais les arguments ne devraient-ils pas $.extendêtre passés pour être inversés?
JKesMc9tqIQe9M
6

Voici une solution en une ligne.

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
la source
3

Je suis en retard à la fête, mais voici une petite solution simple que j'ai trouvée pour renvoyer un certain nombre de mots.

Ce n'est pas directement lié à votre exigence de personnages , mais cela sert le même résultat que je crois que vous recherchiez.

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, '...'));

Voir l'exemple de travail ici: https://jsfiddle.net/bx7rojgL/

Michael Giovanni Pumo
la source
Vous avez écrit une fonction JS qui tronque une chaîne en un certain nombre de mots. Relisez la question.
ChristoKiwi
1
eeehm. je pense que c'est la seule bonne réponse à la question. Il a demandé sans couper le mot.
Mike Aron
2

Cela exclut le dernier mot au lieu de l'inclure.

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;
}

Usage:

smartTrim(yourString, 11, ' ', ' ...')
"The quick ..."
climat
la source
2

J'ai adopté une approche différente. Alors que j'avais besoin d'un résultat similaire, je voulais garder ma valeur de retour inférieure à la longueur spécifiée.

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 || '');
}

Edit Je l'ai un peu remanié ici: Exemple JSFiddle . Il rejoint le tableau d'origine au lieu de concaténer.

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
la source
2
function shorten(str,n) {
  return (str.match(RegExp(".{"+n+"}\\S*"))||[str])[0];
}

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

Roko C. Buljan
la source
1

Vous pouvez utiliser truncateune seule ligne ci-dessous:

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
la source
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 = "c'est une longue chaîne et je ne peux pas tout expliquer"; raccourcir (s, 10, '...')

/* "c'est .." */

vivi margaretha
la source
1

Voici encore un autre morceau de code qui tronque le long des signes de ponctuation (recherchait ceci et Google a trouvé cette question ici). J'ai dû trouver une solution par moi-même, c'est donc ce que j'ai piraté en 15 minutes. Recherche toutes les occurrences de. ! ? et tronque à n'importe quelle position de ceux-ci qui est <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
la source
1

Typographie, et avec des ellipses :)

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
la source
1

'Pâtes aux tomates et épinards'

si vous ne voulez pas couper le mot en deux

première itération:

acc: 0 / acc + cur.length = 5 / newTitle = ['Pâtes'];

deuxième itération:

acc: 5 / acc + cur.length = 9 / newTitle = ['Pâtes', 'avec'];

troisième itération:

acc: 9 / acc + cur.length = 15 / newTitle = ['Pâtes', 'avec', 'tomate'];

quatrième itération:

acc: 15 / acc + cur.length = 18 (limite limite) / newTitle = ['Pâtes', 'avec', '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(' ')} ...`
}

sortie: Pâtes à la tomate ...

Seigneur
la source
Cela ne tient pas compte des caractères 'join (' '), qui peuvent rendre la chaîne plus longue que la limite. Si vous changez la fonction de reduction () en (acc, cur, idx) et le if en (acc + cur.length <= limit - idx), cela prendra en compte les espaces supplémentaires lorsque les mots seront réunis. Si être strictement dans la limite est requis.
PSaul
0

Pour ce que ça vaut, j'ai écrit ceci pour tronquer à la limite du mot sans laisser de ponctuation ou d'espaces à la fin de la chaîne:

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
la source
0

Je n'ai pas trouvé les solutions votées satisfaisantes. J'ai donc écrit quelque chose qui est un peu générique et fonctionne à la fois la première et la dernière partie de votre texte (quelque chose comme substr mais pour les mots). Vous pouvez également définir si vous souhaitez que les espaces soient omis dans le nombre de caractères.

    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
la source
0

Je suis arrivé en retard pour cela mais je pense que cette fonction fait exactement ce que l'OP demande. Vous pouvez facilement modifier les valeurs SENTENCE et LIMIT pour différents résultats.

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);

La sortie de cet extrait de code est où la LIMITE est 11 est:

[ '[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
la source
0

Avec des conditions aux limites comme une phrase vide et un premier mot très long. En outre, il n'utilise aucune chaîne api / bibliothèque spécifique au langage.

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
la source
-1

Vous pouvez couper les espaces avec ceci:

var trimmedString = flabbyString.replace(/^\s*(.*)\s*$/, '$1');
Pointu
la source
-1

Mis à jour à partir de @ NT3RP, j'ai trouvé que si la chaîne frappe un espace pour la première fois, elle finira par supprimer ce mot, ce qui rendra votre chaîne un mot plus courte qu'elle ne peut l'être. J'ai donc simplement ajouté une instruction if else pour vérifier que maxLength ne tombe pas sur un espace.

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)
Appel Landon
la source