Me preguntaba cómo recortar un nombre de archivo en JS para mostrar "..." o cualquier apéndice después de un cierto número de caracteres, la forma más eficiente de manejar todos los casos de prueba posibles.

Reglas

  1. Muestra la extensión real del archivo y no el último carácter después de dividir el nombre de la cadena con "."
  2. La función debe tomar el nombre del archivo de entrada (cadena), el número de caracteres para recortar (entero) y el apéndice (cadena) como parámetro.
  3. Por eficiente, quiero decir que espero escribir menos líneas de código y manejar todos los casos extremos posibles.

Entradas de muestra

  1. myAwesomeFile.min.css
  2. my Awesome File.tar.gz
  3. file.png

Salida esperada (digamos que quiero recortar después de 5 caracteres)

  1. myAwe .... min.css
  2. mi Aw .... tar.gz
  3. file.png

Editar la pregunta para mostrar mi intento

function trimFileName(str, noOfChars, appendix) {
  let nameArray = str.split(".");
  let fileType = `.${nameArray.pop()}`;
  let fileName = nameArray.join(" ");

  if (fileName.length >= noOfChars) {
    fileName = fileName.substr(0, noOfChars) + appendix;
  };

  return (fileName + fileType);
}

console.log(trimFileName("myAwesomeFile.min.css", 5, "..."));
console.log(trimFileName("my Awesome File.tar.gz", 5, "..."));
console.log(trimFileName("file.png", 5, "..."));

Editar # 2: Siéntase libre de seguir adelante y editar la pregunta si cree que no es la expectativa estándar y agregar más casos extremos a las entradas de muestra y salidas esperadas.

Editar # 3: se agregaron algunos detalles más a la pregunta después de los nuevos comentarios. Sé que mi intento no cumple con los resultados esperados (y no estoy seguro de si el resultado que he enumerado anteriormente es una expectativa estándar o no).

Editar # 4 (Final): eliminó la regla de no romper una palabra en el medio después de una reacción continua en los comentarios y cambió las reglas para atender casos de uso más realistas y prácticos.

3
Sibasish Mohanty 30 oct. 2019 a las 10:01

5 respuestas

La mejor respuesta

Si tratamos el carácter de punto . como un separador para las extensiones de archivo, lo que solicite se puede resolver con una sola línea de JavaScript:

name.replace(new RegExp('(^[^\\.]{' + chars + '})[^\\.]+'), '$1' + subst);

Código de demostración en el siguiente fragmento:

function f(name, chars, subst) {
  return name.replace(
    new RegExp('(^[^\\.]{' + chars + '})[^\\.]+'), '$1' + subst);
}


test('myAwesomeFile.min.css', 5, '...', 'myAwe....min.css');
test('my Awesome File.tar.gz', 5, '...', 'my Aw....tar.gz');
test('file.png', 5, '...', 'file.png');

function test(filename, length, subst, expected) {
  let actual = f(filename, length, subst);
  console.log(actual, actual === expected ? 'OK' : 'expected: ' + expected);
}

En Windows, AFAIK, la extensión del archivo es solo la que sigue al último punto. Por lo tanto, técnicamente, la extensión de archivo de "myAwesomeFile.min.css" es solo css, y la extensión de archivo de "my Awesome File.tar.gz" es solo gz.

En este caso, lo que pides aún se puede resolver con una línea de JavaScript:

name.replace(new RegExp('(^.{' + chars + '}).+(\\.[^\\.]*$)'), '$1' + subst + '$2');

Código de demostración en el siguiente fragmento:

function f(name, chars, subst) {
  return name.replace(
    new RegExp('(^.{' + chars + '}).+(\\.[^\\.]*$)'), '$1' + subst + '$2');
}


test('myAwesomeFile.min.css', 5, '...', 'myAwe....css');
test('my Awesome File.tar.gz', 5, '...', 'my Aw....gz');
test('file.png', 5, '...', 'file.png');

function test(filename, length, subst, expected) {
  let actual = f(filename, length, subst);
  console.log(actual, actual === expected ? 'OK' : 'expected: ' + expected);
}

Si realmente desea permitir casos extremos con extensiones múltiples específicas, probablemente necesite definir una lista completa de todas las extensiones múltiples permitidas para saber cómo tratar casos como "my.awesome.file.min.css". Debería proporcionar una lista de todos los casos que desea incluir antes de que sea posible determinar qué tan eficiente podría ser una solución.

4
Tomas Langkaas 22 nov. 2019 a las 21:01

Puede extraer de manera bastante sencilla la condición de su extensión (se puede reemplazar fácilmente con una lista de extensiones válidas) y la expresión regular extrae la última parte. Luego solo agrega una marca en el nombre del archivo (comenzando al principio del nombre del archivo) para recortar el resultado.

  const trim = (string, x) => {
      // We assume that up to last two . delimited substrings of length are the extension
      const extensionRegex = /(?:(\.[a-zA-Z0-9]+){0,2})$/g;
      const { index } = extensionRegex.exec(string);
      // No point in trimming since result string would be longer than input
      if (index + 2 < x) {
        return string;
      }
      return string.substr(0, x) + ".." + string.substr(index);
    };

  /* Assert that we keep the extension */
  console.log("cat.tar.gz", trim("cat.tar.gz", 100) == "cat.tar.gz");
  console.log("cat.zip", trim("cat.zip", 100) == "cat.zip");
  /* Assert that we keep x characters before trim */
  console.log("1234567890cat.tar.gz",!trim("1234567890cat.tar.gz",10).includes("cat"));
  console.log("1234567890cat.zip", !trim("1234567890cat.zip", 10).includes("cat"));
0
swistak 26 nov. 2019 a las 11:20

Es realmente difícil tener en cuenta todas las extensiones (incluidos los casos límite). Consulte esta lista, por ejemplo, para extensiones comunes: https://www.computerhope.com/issues/ch001789 .htm. Evento con tantas extensiones, la lista es exhaustiva de todas las extensiones.

Su función está bien, pero para tener en cuenta más casos, podría reescribirse en esto:

function trimFileName(filename, limit = 5, spacer = '.') {
  const split = filename.indexOf(".");
  const name = filename.substring(0, split);
  const ext = filename.substring(split);

  let trimName = name.substring(0, limit);

  if (name.length > trimName.length)
    trimName = trimName.padEnd(limit + 3, spacer);

  return trimName + ext;
}

console.log(trimFileName("myAwesomeFile.min.css"));
console.log(trimFileName("my Awesome File.tar.gz"));
console.log(trimFileName("file.png"));
3
Kalimah 19 nov. 2019 a las 13:16

A continuación se muestra un enfoque bastante simple para lograr el acortamiento de la manera que desee. Los comentarios están en el código, pero avíseme si algo necesita una explicación adicional:

//A simple object to hold some configs about how we want to shorten the file names
const config = {
  charsBeforeTrim: 5,
  seperator: '.',
  replacementText: '....'
};

//Given file names to shorten
const files = ['myAwesomeFile.min.css', 'my Awesome File.tar.gz', 'file.png'];

//Function to do the actual file name shortening
const shorten = s =>
  s.length > config.charsBeforeTrim ? `${s.substring(0, config.charsBeforeTrim)}${config.replacementText}` : s;

//Function to generate a short file name with extension(s)
const generateShortName = (file, config) => {
  //ES6 Array destructuring makes it easy to get the file name in a unique variable while keeping the remaining elements (the extensions) in their own array:
  const [name, ...extensions] = file.split(config.seperator);

  //Simply append all remaining extension chunks to the shortName
  return extensions.reduce((accum, extension) => {
    accum += `${config.seperator}${extension}`;
    return accum;
  }, shorten(name));
};

//Demonstrate usage
const shortFileNames = files.map(file => generateShortName(file, config));

console.log(shortFileNames);
2
Tom O. 21 nov. 2019 a las 14:45
const parse = (filename, extIdx = filename.lastIndexOf('.')) => ({
  name: filename.substring(0, extIdx),
  extension: filename.substring(extIdx + 1),
})

const trimFileName = (
  filename, size = 5, fill = '...', 
  file = parse(filename),
  head = file.name.substring(0, size)
) => file.name.length >= size ? `${head}${fill}${file.extension}` : filename

/* - - - - - - - - - - - - - - - - - - - - - - - - - */

;[
  'myAwesomeFile.min.css', 
  'my.Awesome.File.min.css',
  'my Awesome File.tar.gz',
  'file.png',
].forEach(f => console.log(trimFileName(f)))
1
kornieff 26 nov. 2019 a las 21:00