Méthode idéale pour tronquer une chaîne avec des ellipses

je suis sûr que nous avons tous vu ellipsis' sur les statuts Facebook (ou ailleurs), et cliqué sur" Show more " et il n'y a que 2 autres caractères ou ainsi. Je suppose que c'est à cause de la programmation paresseuse, parce qu'il y a sûrement une méthode idéale.

Mine compte des caractères minces [iIl1] comme "demi-caractères", mais cela ne va pas autour de ellipsis l'air stupide quand ils cachent à peine des caractères.

Existe-t-il une méthode idéale? Voici la mienne:

/**
 * Return a string with a maximum length of <code>length</code> characters.
 * If there are more than <code>length</code> characters, then string ends with an ellipsis ("...").
 *
 * @param text
 * @param length
 * @return
 */
public static String ellipsis(final String text, int length)
{
    // The letters [iIl1] are slim enough to only count as half a character.
    length += Math.ceil(text.replaceAll("[^iIl]", "").length() / 2.0d);

    if (text.length() > length)
    {
        return text.substring(0, length - 3) + "...";
    }

    return text;
}

le langage n'a pas vraiment d'importance, mais étiqueté comme Java parce que c'est ce que je suis surtout intéressé à voir.

50
demandé sur Amy B 2010-08-30 06:46:00

11 réponses

j'aime l'idée de laisser "mince" caractères compte comme un demi-caractère. Simple et une bonne approximation.

le principal problème avec la plupart des ellipses cependant, sont (imho) que ils coup de mots Au milieu . Voici une solution qui prend en compte les limites des mots (mais ne plonge pas dans pixel-math et L'API Swing).

private final static String NON_THIN = "[^iIl1\.,']";

private static int textWidth(String str) {
    return (int) (str.length() - str.replaceAll(NON_THIN, "").length() / 2);
}

public static String ellipsize(String text, int max) {

    if (textWidth(text) <= max)
        return text;

    // Start by chopping off at the word before max
    // This is an over-approximation due to thin-characters...
    int end = text.lastIndexOf(' ', max - 3);

    // Just one long word. Chop it off.
    if (end == -1)
        return text.substring(0, max-3) + "...";

    // Step forward as long as textWidth allows.
    int newEnd = end;
    do {
        end = newEnd;
        newEnd = text.indexOf(' ', end + 1);

        // No more spaces.
        if (newEnd == -1)
            newEnd = text.length();

    } while (textWidth(text.substring(0, newEnd) + "...") < max);

    return text.substring(0, end) + "...";
}

Un test de l'algorithme ressemble à ceci:

enter image description here

70
répondu aioobe 2015-07-14 09:30:58

je suis choqué que personne n'ait mentionné Commons Lang StringUtils#abbreviate () .

mise à jour: oui, il ne prend pas en compte les caractères maigres, mais je ne suis pas d'accord avec le fait que tout le monde a des écrans et des polices de caractères différents et une grande partie des gens qui atterrissent ici sur cette page sont probablement à la recherche d'une bibliothèque entretenue comme celle ci-dessus.

40
répondu Adam Gent 2016-01-13 14:12:29

il semble que vous pourriez obtenir une géométrie plus précise de La Java graphics context FontMetrics .

Addendum: En abordant ce problème, il peut aider à faire la distinction entre le modèle et la vue. Le modèle est un String , une séquence finie de points de code UTF-16, tandis que la vue est une série de glyphes, rendus dans une police sur un certain appareil.

dans le cas particulier de Java, on peut utiliser SwingUtilities.layoutCompoundLabel() pour effectuer la traduction. L'exemple ci-dessous intercepte l'appel de mise en page dans BasicLabelUI pour démontrer l'effet. Il peut être possible d'utiliser la méthode de l'utilité dans d'autres contextes, mais le FontMetrics approprié devrait être déterminé empiriquement.

alt text

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.basic.BasicLabelUI;

/** @see /q/ideal-method-to-truncate-a-string-with-ellipsis-37215/"A damsel with a dulcimer in a vision once I saw.";
    private final JLabel sizeLabel = new JLabel();
    private final JLabel textLabel = new JLabel(text);
    private final MyLabelUI myUI = new MyLabelUI();

    public LayoutTest() {
        super(new GridLayout(0, 1));
        this.setBorder(BorderFactory.createCompoundBorder(
            new LineBorder(Color.blue), new EmptyBorder(5, 5, 5, 5)));
        textLabel.setUI(myUI);
        textLabel.setFont(new Font("Serif", Font.ITALIC, 24));
        this.add(sizeLabel);
        this.add(textLabel);
        this.addComponentListener(new ComponentAdapter() {

            @Override
            public void componentResized(ComponentEvent e) {
                sizeLabel.setText(
                    "Before: " + myUI.before + " after: " + myUI.after);
            }
        });
    }

    private static class MyLabelUI extends BasicLabelUI {

        int before, after;

        @Override
        protected String layoutCL(
            JLabel label, FontMetrics fontMetrics, String text, Icon icon,
            Rectangle viewR, Rectangle iconR, Rectangle textR) {
            before = text.length();
            String s = super.layoutCL(
                label, fontMetrics, text, icon, viewR, iconR, textR);
            after = s.length();
            System.out.println(s);
            return s;
        }
    }

    private void display() {
        JFrame f = new JFrame("LayoutTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new LayoutTest().display();
            }
        });
    }
}
25
répondu trashgod 2010-09-04 18:44:09

si vous parlez d'un site web - c.-à-d. outputing HTML/JS/CSS, vous pouvez jeter toutes ces solutions parce qu'il ya une solution CSS pur.

text-overflow:ellipsis;

ce n'est pas aussi simple que d'ajouter simplement ce style à votre CSS, parce qu'il interagit avec d'autres CSS; par exemple, il exige que l'élément déborde:caché; et si vous voulez votre texte sur une seule ligne, white-space:nowrap; est bon aussi.

j'ai une feuille de style qui ressemble à ceci:

.myelement {
  word-wrap:normal;
  white-space:nowrap;
  overflow:hidden;
  -o-text-overflow:ellipsis;
  text-overflow:ellipsis;
  width: 120px;
}

vous pouvez même avoir un bouton" Lire plus " qui exécute simplement une fonction javascript pour changer les styles, et bingo, la boîte va re-size et le texte complet sera visible. (dans mon cas, cependant, j'ai tendance à utiliser l'attribut html title pour le texte complet, sauf s'il est susceptible d'être très long)

Espère que ça aide. C'est une solution beaucoup plus simple que d'essayer de mess calculer la taille du texte et le tronquer, et tout ça. (bien sûr, si vous êtes l'écriture d'un non-application web, vous pouvez encore avoir besoin de le faire)

il y a un inconvénient à cette solution: Firefox ne supporte pas le style elliptique. Ennuyeux, mais je ne pense pas critique - il ne tronque toujours le texte correctement, comme cela est traité par overflow:caché, il n'affiche tout simplement pas l'ellipse. Il fonctionne dans tous les autres navigateurs (y compris IE, tout le chemin du retour à IE5.5!), il est donc un peu ennuyeux que Firefox ne le fasse pas encore. Espérons-le, un nouveau version de Firefox va résoudre ce problème bientôt.

[MODIFIER]

Les gens votent encore sur cette réponse, donc je devrais l'éditer pour noter que Firefox soutient maintenant le style elliptique. La fonctionnalité a été ajoutée dans Firefox 7. Si vous utilisez une version antérieure (FF3.6 et FF4 ont encore quelques utilisateurs) alors vous n'avez pas de chance, mais la plupart des utilisateurs FF sont maintenant OK. Il y a beaucoup plus de détails à ce sujet ici: text-overflow:ellipsis dans Firefox 4? (et FF5)

10
répondu Spudley 2017-05-23 12:09:56

pour moi ce serait idéal -

 public static String ellipsis(final String text, int length)
 {
     return text.substring(0, length - 3) + "...";
 }

Je ne m'inquiéterais pas de la taille de chaque caractère à moins de savoir où et dans quelle police il sera affiché. Beaucoup de polices sont des polices de largeur fixes où chaque caractère a la même dimension.

même si c'est une police de largeur variable, et si vous comptez 'i', 'l' pour prendre la moitié de la largeur, alors pourquoi ne pas compter 'w' 'm' pour prendre le double de la largeur? Un mélange de tels caractères dans une chaîne en général, l'effet de leur taille est moyen, et je préférerais ignorer ces détails. Choisir la valeur de "longueur" avec sagesse serait le plus important.

4
répondu Gopi 2010-09-02 06:28:02
 public static String getTruncated(String str, int maxSize){
    int limit = maxSize - 3;
    return (str.length() > maxSize) ? str.substring(0, limit) + "..." : str;
 }
4
répondu gayavat 2012-05-31 12:48:26

Que Diriez-vous de ceci (pour obtenir une chaîne de 50 caractères):

text.replaceAll("(?<=^.{47}).*$", "...");
4
répondu yegor256 2015-03-28 00:22:34

si vous craignez que les ellipses ne cachent qu'un très petit nombre de caractères, pourquoi ne pas simplement vérifier cette condition?

public static String ellipsis(final String text, int length)
{
    // The letters [iIl1] are slim enough to only count as half a character.
    length += Math.ceil(text.replaceAll("[^iIl]", "").length() / 2.0d);

    if (text.length() > length + 20)
    {
        return text.substring(0, length - 3) + "...";
    }

    return text;
}
3
répondu davmac 2010-09-02 04:52:01

je dirais quelque chose qui ressemble au modèle standard que vous avez. Je ne me soucierais pas de la largeur de caractère chose - comme @Gopi a dit qu'il est probablement bon de tout équilibrer à la fin. Ce que je ferais qui est nouveau est d'avoir un autre paramter appelé quelque chose comme "minNumberOfhiddenCharacters" (peut-être un peu moins verbeux). Puis quand daign the ellipsis check je ferais quelque chose comme:

if (text.length() > length+minNumberOfhiddenCharacters)
{
    return text.substring(0, length - 3) + "...";
}

Ce que cela signifie est que si votre texte longueur est de 35, votre "longueur" est 30 et votre min nombre de caractères à cacher est de 10 alors, vous obtiendrez votre chaîne en plein. Si votre nombre minimum de caractère à cacher était 3 alors vous obtiendriez l'ellipse au lieu de ces trois caractères.

La principale chose à être conscient, c'est que j'ai détourné le sens de "longueur", de sorte qu'il n'est plus une longueur maximale. La longueur de la chaîne outputted peut maintenant être n'importe quoi de 30 caractères (quand la longueur du texte est >40) à 40 caractères (quand la longueur du texte est 40 caractères). Effectivement notre longueur maximale devient longueur + minNumberOfhiddenCharacters. La chaîne pourrait bien sûr être plus courte que 30 caractères lorsque la chaîne originale est inférieure à 30, mais c'est un cas ennuyeux que nous devrions ignorer.

si vous voulez que la longueur soit un maximum dur et rapide alors vous voulez quelque chose comme:

if (text.length() > length)
{
    if (text.length() - length < minNumberOfhiddenCharacters-3)
    {
        return text.substring(0, text.length() - minNumberOfhiddenCharacters) + "...";
    }
    else
    {
        return text.substring(0, length - 3) + "...";
    }
}

ainsi dans cet exemple si texte.la longueur () est de 37, La longueur est de 30 et minNumberOfhiddenCharacters = 10 alors nous allons aller dans la deuxième partie de l'intérieur si et obtenir 27 caractères + ... 30. C'est en fait la même chose que si nous étions entrés dans la première partie de la boucle (ce qui est un signe que nous avons nos Conditions limites). Si la longueur du texte était de 36, nous aurions 26 caractères + l'ellipse nous donnant 29 caractères Avec 10 cachés.

je me demandais si le fait de réorganiser une partie de la logique de comparaison la rendrait plus intuitive, mais à la fin j'ai décidé de la laisser telle qu'elle est. Vous pourriez trouver text.length() - minNumberOfhiddenCharacters < length-3 rend plus évidente de ce que vous faites.

3
répondu Chris 2010-09-02 16:02:52

à mes yeux, vous ne pouvez pas obtenir de bons résultats sans les maths pixel.

ainsi, Java est probablement la mauvaise fin pour corriger ce problème lorsque vous êtes dans un contexte d'application web (comme facebook).

j'utiliserais javascript. Puisque Javascript n'est pas mon principal domaine d'intérêt, Je ne peux pas vraiment juger si ce est une bonne solution, mais il pourrait vous donner un pointeur.

3
répondu rompetroll 2010-09-03 15:30:08

à l'Aide de Goyave com.Google.commun.base.ASCII.truncate (CharSequence, int, String) method:

Ascii.truncate("foobar", 7, "..."); // returns "foobar"
Ascii.truncate("foobar", 5, "..."); // returns "fo..."
2
répondu Adrian Baker 2017-02-22 06:40:26