用省略号截断string的理想方法

我相信我们所有人都看过Facebook上的省略号(或其他地方),点击“显示更多”,只有另外两个字符。 我猜这是因为懒惰的编程,因为肯定有一个理想的方法。

我把瘦身人物[iIl1]当作“半angular”来计算,但是这并不能解决省略者在几乎隐藏任何angular色时[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,因为这是我最感兴趣的看到。

我喜欢让“瘦”字符算作半个字符的想法。 简单和很好的近似。

然而,大多数省略号的主要问题在于, 他们在中间切断了单词 (imho)。 这是一个考虑到字界的解决scheme(但不涉及像素math和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) + "..."; } 

algorithm的testing如下所示:

在这里输入图像描述

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

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

看起来你可能会从Javagraphics上下文的FontMetrics获得更准确的几何graphics。

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

在Java的特定情况下,可以使用SwingUtilities.layoutCompoundLabel()来实现翻译。 下面的例子拦截了BasicLabelUI的布局调用来演示效果。 在其他情况下使用效用方法也许是可能的,但是适当的FontMetrics必须由经验确定。

替代文字

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

如果你正在谈论一个网站 – 即输出HTML / JS / CSS,你可以抛弃所有这些解决scheme,因为有一个纯粹的CSS解决scheme。

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

你甚至可以有一个“阅读更多”button,只是运行一个JavaScript函数来改变样式,宾果,框将重新大小,全文将是可见的。 (在我的情况下,我倾向于使用HTML标题属性的全文,除非它可能会很长)

希望有所帮助。 这是一个更简单的解决scheme,试图混乱计算文本的大小和截断,以及所有这一切。 (当然,如果你正在编写一个非基于web的应用程序,你可能仍然需要这样做)

这个解决scheme有一个缺点:Firefox不支持省略号样式。 讨厌,但我不认为批评 – 它仍然正确截断文本,因为这是由溢出处理:隐藏,它只是不显示省略号。 它在所有其他浏览器(包括IE浏览器,一直回到IE5.5!)都能正常工作,所以Firefox还没有做到这一点有些恼人。 希望Firefox的新版本能尽快解决这个问题。

[编辑]
人们仍然在这个答案投票,所以我应该编辑它,注意Firefox现在支持省略号样式。 该function是在Firefox 7中添加的。如果您使用的是早期版本(FF3.6和FF4仍然有一些用户),那么您运气不好,但大多数FF用户现在可以。 这里有更多的细节: text-overflow:Firefox 4中的省略号? (和FF5)

对我来说这将是理想的 –

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

我不会担心每个angular色的大小,除非我真的知道将要显示的字体和字体。 许多字体是固定宽度的字体,其中每个字符具有相同的尺寸。

即使它是一个可变宽度的字体,并且如果您计算'i','l'取一半的宽度,那么为什么不计算'w''m'取两倍的宽度呢? string中的这些字符的组合通常会平均出其大小的效果,并且我宁愿忽略这样的细节。 明智地select“长度”的价值最重要。

怎么样(得到一串50个字符):

 text.replaceAll("(?<=^.{47}).*$", "..."); 

如果你担心省略号只能隐藏很less的字符,为什么不检查这种情况呢?

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

我会select类似于你所拥有的标准模型。 我不打扰字符宽度的东西 – 因为@Gopi说,它可能会最终平衡。 我会做的是新的是有另一个名为“minNumberOfhiddenCharacters”(可能有点不详细)的参数。 然后,当省略号检查我会做这样的事情:

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

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

要注意的主要是我已经颠覆了“长度”的含义,使它不再是最大长度。 输出string的长度现在可以是从30个字符(当文本长度大于40时)到40个字符(当文本长度是40个字符长度)之间的任何值。 有效地,我们的最大长度变为长度+ minNumberOfhiddenCharacters。 当原始string小于30时,string当然可以less于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,长度是30,minNumberOfhiddenCharacters = 10,那么我们将进入内部的第二部分,如果得到27个字符+ …使得30.这实际上是相同的就好像我们进入了循环的第一部分(这是一个符号,我们有我们的边界条件)。 如果文本长度是36,我们会得到26个字符+省略号给我们29个字符,10个隐藏。

我在辩论是否重新安排一些比较逻辑会使它更直观,但最终决定保持原样。 你可能会发现text.length() - minNumberOfhiddenCharacters < length-3使得它更清楚你正在做什么。

在我看来,没有像素math就无法得到好的结果。

因此,当您处于Web应用程序上下文(如Facebook)时,Java可能是解决这个问题的错误的结果。

我会去的JavaScript。 由于Javascript并不是我感兴趣的主要领域,我不能真正判断这是否是一个好的解决scheme,但它可能会给你一个指针。

  public static String getTruncated(String str, int maxSize){ int limit = maxSize - 3; return (str.length() > maxSize) ? str.substring(0, limit) + "..." : str; } 

使用Guava的com.google.common.base.Ascii.truncate(CharSequence,int,String)方法:

 Ascii.truncate("foobar", 7, "..."); // returns "foobar" Ascii.truncate("foobar", 5, "..."); // returns "fo..."