Acortar cadena sin cortar palabras en JavaScript

102

No soy muy bueno con la manipulación de cadenas en JavaScript, y me preguntaba cómo haría para acortar una cadena sin cortar ninguna palabra. Sé cómo usar la subcadena, pero no indexOf ni nada realmente bien.

Digamos que tenía la siguiente cadena:

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

Quiero recortarlo a 10 caracteres, pero si no termina con un espacio, termine la palabra. No quiero que la variable de cadena se vea así:

"esta es una cadena larga que no puedo deshacer"

Quiero que termine la palabra hasta que aparezca un espacio.

Josh Bedo
fuente
te refieres a recortar una cuerda? probar" too many spaces ".trim()
Anurag
1
Algunos ejemplos de entrada y salida esperada ayudarían mucho a responder esta pregunta.
diciembre
bien, lo siento, digo que tenía la cadena text = "esta es una cadena larga que no puedo mostrar" quiero recortarla a 10 caracteres, pero si no termina con un espacio, termine la palabra no quiero que la variable de cadena se vea así este "esta es una cadena larga no puedo dis"
Josh Bedo

Respuestas:

180

Si lo he entendido correctamente, desea acortar una cadena a una cierta longitud (por ejemplo, acortar "The quick brown fox jumps over the lazy dog" a, digamos, 6 caracteres sin cortar ninguna palabra).

Si este es el caso, puede intentar algo como lo siguiente:

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
fuente
9
@josh es absolutamente falso que ".replace" no funcione en "funciones jQuery". Ni siquiera existe una "función jQuery".
Puntiagudo
3
no debería ser "maxLength + 1". Y si maxLength es mayor o igual que la longitud completa de la oración, no se incluye la última palabra. pero gracias por la solución.
Beytan Kurt
4
Si usa esto en una cadena que es más corta que maxLength, la última palabra se corta. Tal vez @AndrewJuniorHoward ya indicó la solución para esto ( maxLength + 1), pero lo arreglé simplemente agregando esta línea en la parte superior:var yourString += " ";
tylerl
3
Desafortunadamente, si quitas fox jumps over the lazy dogparte, el resultado será The quick brown , cuando debería ser The quick brown fox.
Andrey Gordeev
2
Esto siempre corta la última palabra.
Chris Cinelli
108

Hay muchas formas de hacerlo, pero una expresión regular es un método útil de una línea:

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

Esta expresión devuelve los primeros 11 caracteres (cualquiera) más los caracteres posteriores que no sean espacios.

Script de ejemplo:

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

Salida:

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
fuente
Impresionante, literalmente busqué en Google esta pregunta un millón de formas y solo pude encontrar una versión funcional para php, nada parecido a esto y que involucre bucles.
Josh Bedo
1
Se refiere a la primera (y única, en este caso) coincidencia de subexpresión: las cosas entre paréntesis. $ 0 se referiría a la coincidencia completa, que en este caso es la cadena completa.
Hamish
3
@josh Debería poder hacer que la longitud máxima sea una variable usando un objeto regexp:t.replace(new RegExp("^(.{"+length+"}[^\s]*).*"), "$1")
rjmackay
1
@Hamish su opción funciona bien, pero también incluye la última palabra si la longitud excede. Intenté alterar la expresión de expresiones regulares para excluir la última palabra si se excede el límite máximo de palabras, pero no funcionó. ¿Cómo podemos lograrlo?
Shashank Agrawal
1
Bueno, esto realmente no funciona correctamente, a veces paso el valor máximo como por ejemplo, si la última palabra ya tenía 30 caracteres, ¡ya tendrá una longitud de más de 60! incluso si establece la duración en{30}
Al-Mothafar
65

Me sorprende un poco que para un problema simple como este haya tantas respuestas que son difíciles de leer y algunas, incluida la elegida, no funcionan.

Por lo general, quiero que la cadena de resultado tenga como máximo maxLen caracteres. También uso esta misma función para acortar las babosas en las URL.

str.lastIndexOf(searchValue[, fromIndex]) toma un segundo parámetro que es el índice en el que comenzar a buscar hacia atrás en la cadena, lo que hace que las cosas sean eficientes y 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));
}

Esta es una salida de muestra:

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"

Y para la babosa:

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
fuente
1
Me olvidé por completo de lastIndexOf (). ¡Buena atrapada!
Tici
2
Esto se bloquea si por alguna razón stres undefined. Añadíif (!str || str.length <= maxLen) return str;
Silvain
esto no maneja el caso de borde donde el separador no ocurre en la cadena
shrewquest
@shrewquest Funciona. Si el separador no está en la cadena, devuelve la propia cadena si str.length <= maxLen. De lo contrario, devuelve una cadena vacía.
Chris Cinelli
20

Todo el mundo parece olvidar que indexOf toma dos argumentos: la cadena para coincidir y el índice de caracteres desde el que comenzar a buscar. Puede romper la cadena en el primer espacio después de 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
fuente
Tenga en cuenta que indexOf se puede reemplazar por lastIndexOf si se necesitan límites estrictos.
Scheintod
14

Lodash tiene una función escrita específicamente para esto: _.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
fuente
7

Basado en la respuesta de NT3RP que no maneja algunos casos de esquina, hice este código. Garantiza no devolver un texto con un evento size> maxLength y ...se agregó una elipsis al final.

Esto también maneja algunos casos de esquina como un texto que tiene una sola palabra que es> 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;
}

Supongo que puede eliminar fácilmente la dependencia de jquery si esto le molesta.

Sebastien Lorber
fuente
3
Me gusta esta solución, pero no debería los argumentos pasado a $.extendser invertida?
JKesMc9tqIQe9M
6

Aquí hay una solución en una línea.

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

Llego tarde a la fiesta, pero aquí hay una solución pequeña y fácil que se me ocurrió para devolver una cantidad de palabras.

No está directamente relacionado con sus requisitos de personajes , pero tiene el mismo resultado que creo que buscaba.

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

Vea el ejemplo de trabajo aquí: https://jsfiddle.net/bx7rojgL/

Michael Giovanni Pumo
fuente
Escribiste una función JS que trunca una cadena a varias palabras. Vuelve a leer la pregunta.
ChristoKiwi
1
eeehm. Creo que esta es la única respuesta correcta a la pregunta. Preguntó sin cortar la palabra.
Mike Aron
2

Esto excluye la última palabra en lugar de incluirla.

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

Adopté un enfoque diferente. Si bien necesitaba un resultado similar, quería mantener mi valor de retorno menor que la longitud especificada.

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 Lo refactoricé un poco aquí: Ejemplo JSFiddle . Vuelve a unir la matriz original en lugar 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
fuente
2
function shorten(str,n) {
  return (str.match(RegExp(".{"+n+"}\\S*"))||[str])[0];
}

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

Roko C. Buljan
fuente
1

Puede utilizar truncateuna sola línea a continuación:

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
fuente
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 es una cadena larga y no puedo explicar todo"; acortar (s, 10, '...')

/* "esto es .." */

vivi margaretha
fuente
1

Aquí hay otra pieza de código que se trunca a lo largo de los signos de puntuación (estaba buscando esto y Google encontró esta pregunta aquí). Tuve que encontrar una solución por mi cuenta, así que esto es lo que pirateé en 15 minutos. Busca todas las apariciones de. ! ? y trunca en cualquier posición de estos que sea <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
fuente
1

Mecanografiado y con puntos suspensivos :)

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

Por lo que vale, escribí esto para truncar al límite de la palabra sin dejar signos de puntuación o espacios en blanco al final de la cadena:

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

No encontré satisfactorias las soluciones votadas. Así que escribí algo que es un poco genérico y funciona tanto en la primera como en la última parte de tu texto (algo así como substr pero para palabras). También puede establecer si desea que los espacios se omitan en el recuento 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
fuente
0

Llegué tarde para esto, pero creo que esta función hace exactamente lo que solicita OP. Puede cambiar fácilmente los valores de SENTENCE y LIMIT para obtener diferentes resultados.

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 salida de este fragmento es donde el LÍMITE es 11 es:

[ '[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
fuente
0

Con condiciones de contorno como una oración vacía y una primera palabra muy larga. Además, no utiliza api / biblioteca de cadenas específicas de un 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
fuente
0

'Pasta con tomate y espinacas'

si no quieres cortar la palabra por la mitad

primera iteración:

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

segunda iteración:

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

tercera iteración:

acc: 9 / acc + cur.length = 15 / newTitle = ['Pasta', 'with', 'tomato'];

cuarta iteración:

acc: 15 / acc + cur.length = 18 (límite límite) / newTitle = ['Pasta', 'with', 'tomato'];

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(' ')} ...`
}

salida: Pasta con tomate ...

Señor
fuente
-1

Puede recortar espacios con esto:

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

Actualizado desde @ NT3RP Descubrí que si la cadena golpea un espacio la primera vez, terminará borrando esa palabra, haciendo que la cadena sea una palabra más corta de lo que puede ser. Así que lancé una declaración if else para verificar que maxLength no cae en un espacio.

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
fuente