61 votes

Méthode idéale pour tronquer une chaîne de caractères avec des ellipses

Je suis sûr que nous avons tous déjà vu des "ellipses" dans des statuts Facebook (ou ailleurs), et cliqué sur "Montrer plus" pour constater qu'il n'y avait que deux caractères de plus ou moins. Je suppose que c'est dû à une programmation paresseuse, car il existe sûrement une méthode idéale.

Le mien compte des caractères minces [iIl1] comme "demi-caractères", mais cela ne permet pas de contourner le fait que les ellipses ont l'air stupides lorsqu'elles ne cachent presque aucun caractère.

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

La langue n'a pas vraiment d'importance, mais je l'ai étiquetée comme étant Java parce que c'est ce que je suis le plus intéressé à voir.

3 votes

Bien que je sois trop paresseux pour apporter une vraie solution maintenant, voici un conseil pour améliorer les liens "show more" : changez-les en "show more (xyz caractères supplémentaires)". Ainsi, je saurai à l'avance si cela en vaut la peine...

86voto

aioobe Points 158466

J'aime l'idée de laisser les personnages "minces" compter pour un demi-caractère. C'est simple et c'est une bonne approximation.

Cependant, le principal problème avec la plupart des ellipsoïdes est (imho) que ils coupent les mots au milieu . Voici une solution qui prend en compte les limites des mots (mais qui ne tient pas compte de la mathématique des pixels et de 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

1 votes

Vous voudrez probablement utiliser le caractère ellipsis au lieu de trois points, car la ligne peut s'interrompre précisément entre les points. Lorsque vous apportez cette modification au code ci-dessus, changez toutes les occurrences de 3 a 1 .

0 votes

Je suppose qu'il devrait probablement utiliser un BreakIterator au lieu de chercher un espace ASCII.

73voto

Adam Gent Points 15055

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

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

3 votes

Cela ne répond pas à ma question.

4 votes

Je suppose que oui. J'ai raté ta référence aux personnages minces, mais personnellement, je pense que c'est ridicule et que ça ne prend pas i18n en compte. Son site no le site idéal et maintenant les gens vont copier et coller le code ci-dessus alors qu'il existe une bibliothèque qui fait déjà cela de manière déterministe... BTW vous avez manqué t parce que "t" est mince sur mon écran.

1 votes

Merci pour la réponse Adam ! StringUtils.abbreviate a bien fonctionné pour mon cas d'utilisation.

28voto

trashgod Points 136305

Il semble que vous pourriez obtenir une géométrie plus précise à partir du contexte graphique Java. FontMetrics .

Addendum : Pour aborder ce problème, il peut être utile de faire la distinction entre le modèle et la vue. Le modèle est un String La vue est une séquence finie de points de code UTF-16, tandis que la vue est une série de glyphes, rendue dans une certaine police sur un certain dispositif.

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 en BasicLabelUI pour démontrer l'effet. Il peut être possible d'utiliser la méthode de l'utilité dans d'autres contextes, mais la méthode de l'utilité appropriée doit être utilisée. FontMetrics devrait être déterminée de manière empirique.

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 http://stackoverflow.com/questions/3597550 */
public class LayoutTest extends JPanel {

    private static final String text =
        "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();
            }
        });
    }
}

1 votes

Donc, si je comprends bien, vous créez une étiquette, vous définissez la police, puis vous déterminez la longueur du texte en fonction du rendu de l'étiquette, c'est-à-dire que Swing calcule l'ellipse pour vous ? Donc, en supposant qu'ils gèrent la situation où l'ellipse elle-même n'est pas plus courte que l'original, cela ne fonctionne que si vous pouvez dupliquer exactement les métriques de la police.

0 votes

M. Shiny and New : Je pense que c'est un bon résumé. Le site FontMetrics et la géométrie de la vue définissent le résultat. Notez que la méthode (éventuellement raccourcie) String renvoyée (indirectement) par layoutCompoundLabel() inclut les ellipses.

0 votes

C'est une belle réponse, mais elle ne répond pas vraiment à la question. Bien que le PO ne spécifie pas explicitement le contexte, on peut supposer que le but est de construire un raccourcisseur de texte pour raccourcir le texte pour l'affichage d'un extrait sur un site web.

11voto

Spudley Points 85371

S'il s'agit d'un site web, c'est-à-dire d'un site qui produit du HTML/JS/CSS, vous pouvez vous débarrasser de toutes ces solutions car il existe une solution purement CSS.

text-overflow:ellipsis;

Ce n'est pas aussi simple que d'ajouter ce style à votre CSS, car il interagit avec d'autres CSS ; par exemple, il faut que l'élément ait la propriété overflow:hidden ; et si vous voulez que votre texte soit 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 la suite" qui exécute simplement une fonction javascript pour modifier les styles, et bingo, la boîte se redimensionne et le texte complet est visible. (Dans mon cas, cependant, j'ai tendance à utiliser l'attribut html title pour le texte complet, sauf s'il risque d'être très long).

J'espère que cela vous aidera. C'est une solution beaucoup plus simple que d'essayer de calculer la taille du texte, de le tronquer et tout le reste. (Bien sûr, si vous écrivez une application non basée sur le Web, vous devrez peut-être encore le faire).

Il y a un inconvénient à cette solution : Firefox ne supporte pas le style ellipsis. C'est ennuyeux, mais je ne pense pas que ce soit critique -- Il tronque toujours le texte correctement, puisque cela est géré par overflow:hidden, mais il n'affiche pas l'ellipse. Cela fonctionne dans tous les autres navigateurs (y compris IE, jusqu'à IE5.5 !), c'est donc un peu ennuyeux que Firefox ne le fasse pas encore. Espérons qu'une nouvelle version de Firefox résoudra bientôt ce problème.

[EDIT]
Les gens votent encore sur cette réponse, je dois donc la modifier pour indiquer que Firefox prend désormais en charge le style ellipse. Cette fonctionnalité a été ajoutée dans Firefox 7. Si vous utilisez une version antérieure (FF3.6 et FF4 ont encore quelques utilisateurs), vous n'avez pas de chance, mais la plupart des utilisateurs de FF sont maintenant satisfaits. Il y a beaucoup plus de détails à ce sujet ici : text-overflow:ellipsis dans Firefox 4 ? (et FF5)

0 votes

J'aime aussi cette réponse. Malheureusement, le PDG de l'entreprise où je travaille actuellement utilise Firefox et se plaint quand il ne peut pas voir les choses correctement, même en ignorant tous les autres navigateurs... :( Mais j'espère que Firefox supportera cela bientôt !

0 votes

Oui, c'est ennuyeux quand on vous le dit. Nous avons adopté l'approche pragmatique selon laquelle nous pouvons nous passer de l'ellipse dans Firefox, étant donné que le reste de la fonctionnalité fonctionne bien (c'est-à-dire qu'elle est correctement tronquée, le lien "Lire la suite" fonctionne, etc.) Vous pouvez contourner le problème en créant un bloc semi-transparent qui passe au blanc et couvre les derniers caractères de votre élément de texte, de sorte que si le texte le couvre, il semble disparaître. Ce n'est pas une ellipse, mais cela peut constituer une alternative appropriée.

6voto

Gopi Points 5317

Pour moi, ce serait l'idéal -

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

Je ne me préoccuperais pas de la taille de chaque caractère à moins de savoir vraiment où et dans quelle police il sera affiché. De nombreuses polices sont des polices à largeur fixe où chaque caractère a la même dimension.

Même s'il s'agit d'une police à largeur variable, et si vous comptez que 'i', 'l' prennent la moitié de la largeur, alors pourquoi ne pas compter que 'w', 'm' prennent le double de la largeur ? Un mélange de ces caractères dans une chaîne de caractères compensera généralement l'effet de leur taille, et je préfère ignorer ces détails. Le choix judicieux de la valeur de "longueur" est le plus important.

0 votes

Ayant utilisé à la fois l'algorithme de l'OP (et quelques dérivations) et celui-ci dans un code de production, je peux dire que, au moins dans mon contexte (développement Android), cette seule ligne est BEAUCOUP plus cohérente. L'approche de l'OP varie radicalement à travers différents blocs de texte. Je n'ai pas exploré les causes profondes de ce phénomène, je ne fais que rapporter ce que j'ai vu.

1 votes

Cela pourrait provoquer une exception IndexOutOfBoundsException. Vous devriez tester la longueur de la chaîne avant d'utiliser substring.

1 votes

Et ça fait trois points dans ton extrait, no une ellipse

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X