Подтвердить что ты не робот

Идеальный метод усечения строки с многоточием

Я уверен, что все мы видели эллипсис на статусах Facebook (или в другом месте), и нажали "Показать больше", и есть только еще 2 символа или около того. Я думаю, это из-за ленивого программирования, потому что, безусловно, есть идеальный метод.

Mine подсчитывает тонкие символы [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, потому что это меня больше всего интересует.

4b9b3361

Ответ 1

Мне нравится идея позволить "тонким" символам считать половину персонажа. Простое и хорошее приближение.

Основная проблема с большинством эллипсинга: (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

Я шокирован, никто не упоминал Commons Lang StringUtils # abbreviate().

Обновление: да, это не относится к тонким персонажам, но я не согласен с тем, что каждый из них имеет разные настройки экранов и шрифтов, и большая часть людей, которые приземляются здесь на этой странице, вероятно, ищет поддерживаемой библиотеки, как указано выше.

Ответ 3

Похоже, вы можете получить более точную геометрию из графического контекста Java FontMetrics.

Приложение: При приближении к этой проблеме это может помочь провести различие между моделью и представлением. Модель представляет собой String, конечную последовательность кодовых точек UTF-16, в то время как представление представляет собой серию глифов, отображаемых некоторым шрифтом на каком-либо устройстве.

В частном случае Java можно использовать SwingUtilities.layoutCompoundLabel() для осуществления перевода. Приведенный ниже пример перехватывает вызов макета в BasicLabelUI, чтобы продемонстрировать эффект. Возможно, можно использовать метод утилиты в других контекстах, но соответствующий FontMetrics должен быть определен эмпирически.

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

Если вы говорите о веб-сайте, то есть выводите HTML/JS/CSS, вы можете выбросить все эти решения, потому что есть чистое решение CSS.

text-overflow:ellipsis;

Это не так просто, как просто добавить этот стиль к вашему CSS, потому что он втягивается с другим CSS; например, требуется, чтобы элемент имел переполнение: hidden; и если вы хотите, чтобы ваш текст находился в одной строке, white-space:nowrap; тоже хорош.

У меня есть таблица стилей, которая выглядит так:

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

У вас может даже быть кнопка "читать больше", которая просто запускает функцию javascript для изменения стилей и бинго, поле будет изменяться и полный текст будет виден. (в моем случае, однако, я склонен использовать атрибут заголовка html для полного текста, если только он не будет очень длинным)

Надеюсь, что это поможет. Это гораздо более простое решение, которое пытается беспорядочно вычислить размер текста и усекать его, и все такое. (конечно, если вы пишете приложение, отличное от веб-сайта, вам все равно нужно это сделать)

Существует одна сторона этого решения: Firefox не поддерживает стиль многоточия. Раздражает, но я не думаю, что это важно. Он все равно урезает текст правильно, так как это происходит путем переполнения: скрытый, он просто не отображает многоточие. Он работает во всех других браузерах (включая IE, вплоть до IE5.5!), Так что это немного раздражает, что Firefox этого еще не делает. Надеюсь, новая версия Firefox скоро решит эту проблему.

[EDIT]
Люди по-прежнему голосуют за этот ответ, поэтому я должен отредактировать его, отметив, что Firefox теперь поддерживает стиль многоточия. Эта функция была добавлена ​​в Firefox 7. Если вы используете более раннюю версию (у FF3.6 и FF4 все еще есть некоторые пользователи), вам не повезло, но большинство пользователей FF теперь в порядке. Здесь гораздо больше деталей об этом: text-overflow: эллипсис в Firefox 4? (и FF5)

Ответ 5

Для меня это было бы идеально -

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

Я бы не стал беспокоиться о размере каждого персонажа, если я действительно не знаю, где и в каком шрифте он будет отображаться. Многие шрифты представляют собой шрифты с фиксированной шириной, каждый из которых имеет одинаковый размер.

Даже если это шрифт переменной ширины, и если вы считаете 'i', 'l', чтобы взять половину ширины, то почему бы не считать 'w' 'm', чтобы взять двойную ширину? Сочетание таких символов в строке обычно усредняет эффект их размера, и я предпочел бы игнорировать такие детали. Выбор мудрости мудрости был бы самым важным.

Ответ 6

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

Ответ 7

Как насчет этого (чтобы получить строку из 50 символов):

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

Ответ 8

Если вы беспокоитесь о том, что многоточие скрывает очень небольшое количество символов, почему бы просто не проверить это условие?

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

Я бы пошел с чем-то похожим на стандартную модель, которая у вас есть. Я бы не стал беспокоиться о том, что касается символов, поскольку @Gopi сказал, что это, вероятно, конец всему балансу. То, что я делаю, является новым, имеет другой параметр, называемый "minNumberOfhiddenCharacters" (возможно, немного менее подробный). Затем, когда вы установите эллипсис, я сделаю что-то вроде:

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

Что это будет означать, так это то, что если длина вашего текста равна 35, ваша длина будет равна 30, а минимальное число символов, которое нужно скрыть, равно 10, тогда вы получите полную строку. Если ваш минимальный номер символа, который нужно скрыть, равен 3, вы должны получить многоточие вместо этих трех символов.

Главное, что нужно знать, это то, что я исказил значение "длина", чтобы оно больше не было максимальной длины. Длина выводимой строки теперь может составлять от 30 символов (если длина текстa > 40) до 40 символов (если длина текста составляет 40 символов). Эффективно наша максимальная длина становится длиной + minNumberOfhiddenCharacters. Строка, конечно, может быть короче 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, длина равна 30 и minNumberOfhiddenCharacters = 10, тогда мы войдем во вторую часть внутреннего if и получим 27 символов +..., чтобы сделать 30. Это фактически так же, как если бы мы вошли в первую часть цикла (что является признаком, у нас есть наши граничные условия справа). Если длина текста была 36, мы получили бы 26 символов + многоточие, давая нам 29 символов с 10 скрытыми.

Я обсуждал, может ли перестройка некоторой логики сравнения сделать ее более интуитивной, но в конце концов решила оставить ее такой, какой она есть. Вы можете обнаружить, что text.length() - minNumberOfhiddenCharacters < length-3 делает более очевидным то, что вы делаете, хотя.

Ответ 10

В моих глазах вы не можете получить хорошие результаты без математики пикселей.

Таким образом, Java, вероятно, является неправильным решением для устранения этой проблемы, когда вы находитесь в контексте веб-приложения (например, facebook).

Я бы пошел на javascript. Поскольку Javascript не является моей основной областью интереса, я не могу судить, является ли это хорошим решением, но оно может дать вам указатель.