代码之家  ›  专栏  ›  技术社区  ›  Amy B

用省略号截断字符串的理想方法

  •  53
  • Amy B  · 技术社区  · 14 年前

    我敢肯定,我们所有人都在Facebook的状态(或其他地方)上看到了省略号,然后点击了“显示更多”,只有大约2个字符。我想这是因为懒惰的编程,因为肯定有一个理想的方法。

    我的数苗条的字符 [iIl1] 作为“半字符”,但这并不能避免省略号在几乎不隐藏任何字符时看起来很傻。

    有没有理想的方法?这是我的:

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

    语言并不重要,但被标记为Java,因为这是我最感兴趣的。

    12 回复  |  直到 6 年前
        1
  •  74
  •   aioobe    9 年前

    我喜欢让“瘦”字算作半个字。简单而好的近似值。

    然而,大多数省略号的主要问题是(imho),即在中间切掉单词。这里有一个考虑单词边界的解决方案(但不深入到像素数学和Swing API)。

    private final static string non-thin=“[^IIL1\\,'”]“;
    
    private static int textwidth(字符串str){
    返回(int)(str.length()-str.replaceall(non-thin,“”).length()/2);
    }
    
    公共静态字符串椭圆化(字符串文本,int max){
    
    如果(textWidth(text)<=max)
    返回文本;
    
    //从max前面的单词开始
    //由于字符太细,这是一个过度近似值…
    int end=text.lastindexof('',最大值-3);
    
    //只有一个长词。把它砍掉。
    如果(结束==1)
    返回文本。子字符串(0,max-3)+“..”;
    
    //只要textwidth允许,就向前一步。
    int newend=结束;
    做{
    结束= NeNead;
    newend=text.indexof('',end+1);
    
    //没有更多的空间。
    如果(newend=-1)
    newend=text.length();
    
    }while(textwidth(text.substring(0,newend))+“…”)<max);
    
    返回文本。子字符串(0,结束)+“..”;
    }
    < /代码> 
    
    

    算法测试如下:

    好的近似值。

    然而,大多数省略的主要问题是(imho)他们在中间拼字. 这里有一个考虑单词边界的解决方案(但不深入到像素数学和Swing API)。

    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) + "...";
    }
    

    算法测试如下:

    enter image description here

        2
  •  45
  •   Adam Gent    9 年前

    我很震惊没人提到 Commons Lang StringUtils#abbreviate() .

    更新:是的,它没有考虑到苗条的字符,但我不同意,考虑到每个人都有不同的屏幕和字体设置,以及在这个页面上的大部分人可能正在寻找一个维护的库,如上述。

        3
  •  26
  •   trashgod    14 年前
    看起来,您可能会从Java图形上下文的 代码> FontMetrics < /COD> < /A>

    附录:在解决这个问题时,区分模型和视图可能会有所帮助。模型是一个有限的UTF-16码位序列,而视图是一系列字形,在某些设备上以某种字体呈现。

    在Java的特定情况下,可以使用“a HeRF=”“http://dass/java/Sava/Swing,SavigUntual.html,HTML,FontMetrics,%20Java.Lang.Stand,%20Javax .Swing,图标,%20It,%20It,%20It,%20It,%20Java.AWT.矩形,%20Java.AWT.矩形,%20java. swingutilities.layoutCompoundLabel(). to effect the translation.下面的示例截取 basiclabelui >a>中的布局调用以演示效果。可能会在其他环境中使用实用程序方法,但相应的 fontmetrics would have to be determined emperially.

    import java.awt.color;
    导入java.awt.eventqueue;
    导入java.awt.font;
    导入java.awt.fontmetrics;
    导入java.awt.gridlayout;
    导入java.awt.rectangle;
    导入java.awt.event.componentadapter;
    导入java.awt.event.componentEvent;
    进口javax.swing.borderfactory;
    导入javax.swing.icon;
    导入javax.swing.jframe;
    导入javax.swing.jlabel;
    导入javax.swing.jpanel;
    导入javax.swing.border.emptyOrder;
    导入javax.swing.border.lineborder;
    导入javax.swing.plaf.basic.basiclabelui;
    
    /**@参见http://stackoverflow.com/questions/3597550*/
    公共类布局测试扩展了jpanel{
    
    私有静态最终字符串文本=
    “有一次我看见一个带着扬琴的少女在幻象中。”
    private final jlabel sizelabel=new jlabel();
    private final jLabel textLabel=新jLabel(文本);
    private final mylabelui myui=new mylabelui();
    
    公共布局测试()。{
    super(新网格布局(0,1));
    this.setborder(borderFactory.createCompoundBorder(
    new lineborder(color.blue),new emptyOrder(5,5,5,5));
    textLabel.setui(myui);
    textLabel.setFont(新字体(“serif”,font.italic,24));
    添加(sizelabel);
    this.add(文本标签);
    this.addcomponentListener(new componentadapter()){
    
    @重写
    公共void组件化(组件事件e){
    SizeLabel.设置文本(
    “之前:”+myui.before+“之后:”+myui.after);
    }
    (});
    }
    
    私有静态类MyLabelUI扩展basicLabelUI{
    
    int之前,之后;
    
    @重写
    受保护的字符串布局(
    jLabel标签,fontMetrics fontMetrics,字符串文本,图标图标,
    矩形视图,矩形ICONR,矩形文本框){
    before=text.length();
    字符串S=super.layoutcl(
    label、fontmetrics、text、icon、viewer、iconr、textr);
    之后=s.长度();
    系统.out.println(s);
    返回S;
    }
    }
    
    私有void display()。{
    jframe f=新jframe(“布局测试”);
    f.setdefaultcloseoperation(jframe.exit在关闭时退出);
    F.加法(这个);
    F.();
    f.setLocationRelativeTo(空);
    f.设置可见(真);
    }
    
    公共静态void main(string[]args){
    eventQueue.invokelater(new runnable()){
    
    @重写
    public void run()。{
    新布局测试().display();
    }
    (});
    }
    }
    < /代码> 
    Java图形上下文FontMetrics.

    附录:在解决这个问题时,区分模型和视图可能会有所帮助。该模型是String,UTF-16代码点的有限序列,而视图是一系列字形,在某些设备上以某种字体呈现。

    在Java的特定情况下,可以使用SwingUtilities.layoutCompoundLabel()使翻译生效。下面的示例截获布局调用BasicLabelUI以证明效果。在其他情况下也可以使用效用方法,但是FunT度量必须根据经验来确定。

    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();
                }
            });
        }
    }
    
        4
  •  10
  •   Community CDub    8 年前

    如果你说的是一个网站,比如输出html/js/css,你可以扔掉所有这些解决方案,因为这里有一个纯粹的css解决方案。

    text-overflow:ellipsis;
    

    它并没有简单到只将样式添加到CSS中,因为它与其他CSS交错;例如,它要求元素溢出:隐藏;如果您希望文本在一行上, white-space:nowrap; 也是好的。

    我有一个这样的样式表:

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

    你甚至可以有一个“读取更多”按钮,只需运行一个javascript函数来更改样式,然后宾果游戏,框将重新调整大小,全文将可见。(在我的例子中,我倾向于使用HTML标题属性作为全文,除非它可能会变得很长)

    希望有帮助。这是一个简单得多的解决方案,尝试混乱计算文本大小和截断它,等等。(当然,如果您正在编写一个非基于Web的应用程序,您可能仍然需要这样做)

    这个解决方案有一个缺点:火狐不支持省略号样式。很烦人,但我不认为它是关键的——它仍然正确地截断文本,正如溢出处理的那样:隐藏,它只是不显示省略号。它在所有其他浏览器中都有效(包括IE,一直到IE5.5!)所以,火狐还没有做到这一点有点让人恼火。希望新版的火狐能很快解决这个问题。

    [编辑]
    人们仍然对这个答案进行投票,所以我应该编辑它,以注意火狐现在支持省略号样式。该功能已添加到Firefox 7中。如果你使用的是早期版本(ff3.6和ff4还有一些用户),那么你就走运了,但是大多数ff用户现在都没事了。这里有更多的细节: text-overflow:ellipsis in Firefox 4? (and FF5)

        5
  •  4
  •   Gopi    14 年前

    对我来说这是理想的-

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

    我不会担心每个字符的大小,除非我真的知道它将在哪里以什么字体显示。许多字体都是固定宽度的字体,每个字符都有相同的尺寸。

    即使它是一个可变宽度的字体,如果你把“i”、“l”计算为宽度的一半,那么为什么不把“w”、“m”计算为宽度的两倍呢?在一个字符串中混合这样的字符通常会平均出它们大小的影响,我宁愿忽略这些细节。明智地选择“长度”的值最重要。

        6
  •  4
  •   gayavat    13 年前
     public static String getTruncated(String str, int maxSize){
        int limit = maxSize - 3;
        return (str.length() > maxSize) ? str.substring(0, limit) + "..." : str;
     }
    
        7
  •  4
  •   yegor256    10 年前

    这个怎么样(要得到50个字符的字符串):

    text.replaceAll("(?<=^.{47}).*$", "...");
    
        8
  •  3
  •   davmac    14 年前

    如果你担心省略号只隐藏了很少的字符,为什么不检查一下这个条件呢?

    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;
    }
    
        9
  •  3
  •   Chris    14 年前

    我想买一些和你的标准型号类似的东西。我不想为字符宽度的问题操心——正如@gopi所说,最终可能会达到平衡。我想做的是,有另一个参数叫做“minnumberofhiddencharacters”(可能不那么详细)。然后在执行省略号检查时,我会做如下操作:

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

    这意味着,如果文本长度为35,则“长度”为30,要隐藏的最小字符数为10,则会得到完整的字符串。如果要隐藏的最小字符数是3,那么您将得到省略号而不是这三个字符。

    最重要的是我已经颠覆了“长度”的含义,所以它不再是最大长度。输出字符串的长度现在可以是30个字符(当文本长度为>40时)到40个字符(当文本长度为40个字符时)。实际上,我们的最大长度变为长度+最小隐藏字符数。当原始字符串小于30时,字符串当然可以短于30个字符,但这是一个我们应该忽略的无聊情况。

    如果你想让长度成为一个硬性和快速的最大值,那么你需要的是:

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

    所以在这个例子中,如果text.length()是37,length是30,minNumberOfHiddencharacters=10,那么我们将进入内部if的第二部分,得到27个字符+…使30。这实际上就像我们进入了循环的第一部分(这是一个标志,表明我们的边界条件是正确的)。如果文本长度是36,我们会得到26个字符+省略号,它给我们29个字符,10个隐藏字符。

    我正在讨论重新排列一些比较逻辑是否会使它更直观,但最终决定还是保持原样。你可能会发现 text.length() - minNumberOfhiddenCharacters < length-3 使你所做的更加明显。

        10
  •  3
  •   rompetroll    14 年前

    在我看来,如果没有像素数学,你就不能得到好的结果。

    因此,当您处于Web应用程序上下文(如脸谱网)时,Java可能是解决此问题的错误端。

    我会选择javascript。因为javascript不是我感兴趣的主要领域,所以我不能判断 this 是一个很好的解决方案,但它可能会给您一个指针。

        11
  •  2
  •   Adrian Baker    8 年前

    番石榴 com.google.common.base.Ascii.truncate(CharSequence, int, String) 方法:

    Ascii.truncate("foobar", 7, "..."); // returns "foobar"
    Ascii.truncate("foobar", 5, "..."); // returns "fo..."
    
        12
  •  0
  •   Beowolve    6 年前

    大多数解决方案不考虑字体度量,这是一个非常简单但工作的解决方案JavaSwing,我已经使用了很多年了。

    private String ellipsisText(String text, FontMetrics metrics, Graphics2D g2, int targetWidth) {
       String shortText = text;
       int activeIndex = text.length() - 1;
    
       Rectangle2D textBounds = metrics.getStringBounds(shortText, g2);
       while (textBounds.getWidth() > targetWidth) {
          shortText = text.substring(0, activeIndex--);
          textBounds = metrics.getStringBounds(shortText + "...", g2);
       }
       return activeIndex != text.length() - 1 ? shortText + "..." : text;
    }