Обрезать строку для целых слов в .NET С#

Я пытаюсь обрезать длинный текст на С#, но я не хочу, чтобы моя строка обрезалась наполовину через слово. У кого-нибудь есть функция, которую я могу использовать для усечения строки в конце слова?

E.g:

"This was a long string..."

Нет:

"This was a long st..."

person TimS    schedule 23.10.2009    source источник
comment
Не могли бы вы дать свое текущее решение для усечения?   -  person pyrocumulus    schedule 23.10.2009
comment
@Cloud Just .Substring(0, ‹количество символов›)   -  person TimS    schedule 23.10.2009
comment
Что ж, если ‹количество символов› больше фактической строки, подстрока вызовет исключение, требующее дополнительной проверки.   -  person Oskar Duveborn    schedule 28.06.2010


Ответы (10)


Попробуйте следующее. Это довольно элементарно. Просто находит первый пробел, начиная с нужной длины.

public static string TruncateAtWord(this string value, int length) {
    if (value == null || value.Length < length || value.IndexOf(" ", length) == -1)
        return value;

    return value.Substring(0, value.IndexOf(" ", length));
}
person Dave    schedule 23.10.2009
comment
Идеально! И не регулярное выражение в поле зрения :) - person TimS; 23.10.2009
comment
Может быть, имеет смысл найти первый пробел ПЕРЕД нужной длиной? Иначе придется гадать, на какой желаемой длине? - person mlsteeves; 23.10.2009
comment
Также должно быть -1, чтобы не использовать регулярное выражение;) - person Goran; 23.10.2009
comment
@mlseeves Хороший вопрос, но в этом случае я не слишком беспокоюсь, так как это просто функция тщеславия, поэтому фиксированная длина не обрезается. - person TimS; 23.10.2009
comment
Использование @string неуместно: в данном случае оно не нужно и сбивает с толку. С таким же успехом параметр можно было бы назвать str. Если бы не это, я бы проголосовал за этот ответ. - person LBushkin; 23.10.2009
comment
Я не согласен с использованием аббревиатуры str — я думаю, что аббревиатуры всегда делают код менее читаемым. +1 за то, что в комментарии вместо этого используется значение. - person Jon Rea; 16.12.2013

Спасибо за ваш ответ Дэйв. Я немного подправил функцию, и это то, что я использую ... если нет больше комментариев;)

public static string TruncateAtWord(this string input, int length)
{
    if (input == null || input.Length < length)
        return input;
    int iNextSpace = input.LastIndexOf(" ", length, StringComparison.Ordinal);
    return string.Format("{0}…", input.Substring(0, (iNextSpace > 0) ? iNextSpace : length).Trim());
}
person TimS    schedule 23.10.2009
comment
Вдобавок к этому я теперь также вызываю из этой функции еще одну строковую служебную функцию, которая удаляет все HTML-теги (используя RegEx). Это сводит к минимуму риск поломки HTML в результате усечения, так как вся строка будет в виде обычного текста. - person TimS; 27.10.2009
comment
Обратите внимание, что этот метод ищет первый пробел ПОСЛЕ указанного значения длины, что почти всегда приводит к тому, что результирующая строка оказывается длиннее значения. Чтобы найти последний пробел перед длиной, просто подставьте input.LastIndexOf(" ", length) при вычислении iNextSpace. - person CBono; 14.02.2010
comment
+100 за комментарий CBono - это должно быть раньше! В случае такого длинного слова, как это, у вас будет очень длинная строка, которая намного превышает желаемую длину! - person Jason; 23.06.2010
comment
Также обратите внимание, что многоточие (три точки), добавленное к концу усеченной строки, в некоторых случаях увеличивает максимальную длину строки. - person dthrasher; 22.11.2010
comment
Вместо этого вы можете добавить символ эллипса. '…' - person Örjan Jämte; 08.07.2011
comment
В строке 5 я бы предложил использовать: int iNextSpace = input.LastIndexOf(" ", length, System.StringComparison.Ordinal); для символов, специфичных для языка - person Goran Žuri; 05.12.2012
comment
блестящий, потрясающий, великолепный - person Chris Hawkes; 23.10.2014
comment
как рекомендовал @GoranŽuri (и Resharper также дал мне предложение / предупреждение), я добавил StringComparison.Ordinal в строку 5, а также проверил его, чтобы убедиться, что он работает должным образом. - person Shiva; 15.05.2017

Мой вклад:

public static string TruncateAtWord(string text, int maxCharacters, string trailingStringIfTextCut = "&hellip;")
{
    if (text == null || (text = text.Trim()).Length <= maxCharacters) 
      return text;

    int trailLength = trailingStringIfTextCut.StartsWith("&") ? 1 
                                                              : trailingStringIfTextCut.Length; 
    maxCharacters = maxCharacters - trailLength >= 0 ? maxCharacters - trailLength 
                                                     : 0;
    int pos = text.LastIndexOf(" ", maxCharacters);
    if (pos >= 0)
        return text.Substring(0, pos) + trailingStringIfTextCut;

    return string.Empty;
}

Это то, что я использую в своих проектах, с необязательным трейлингом. Текст никогда не будет превышать длину maxCharacters + завершающий текст.

person Contra    schedule 12.09.2012

Если вы используете формы Windows, в методе Graphics.DrawString есть опция в StringFormat, чтобы указать, следует ли обрезать строку, если она не помещается в указанную область. Это позволит добавить многоточие по мере необходимости.

http://msdn.microsoft.com/en-us/library/system.drawing.stringtrimming.aspx

person mlsteeves    schedule 23.10.2009
comment
Это для страницы ASP.Net, но я делаю некоторые вещи Win Forms, которые так полезно знать! - person TimS; 23.10.2009

Я пошел немного дальше вашего подхода:

public string TruncateAtWord(string value, int length)
{
    if (value == null || value.Trim().Length <= length)
        return value;

    int index = value.Trim().LastIndexOf(" ");

    while ((index + 3) > length)
        index = value.Substring(0, index).Trim().LastIndexOf(" ");

    if (index > 0)
        return value.Substring(0, index) + "...";

    return value.Substring(0, length - 3) + "...";
}

Я использую это, чтобы обрезать твиты.

person Douglas Ludlow    schedule 24.02.2012
comment
Я бы подумал об извлечении ... в константу, потому что, если вы решите изменить его, вам придется обновить его в 4 местах (если вы включите число 3) - person Max Carroll; 01.04.2020

Это решение тоже работает (берет первые 10 слов из myString):

String.Join(" ", myString.Split(' ').Take(10))
person user1795248    schedule 14.03.2016
comment
Это на самом деле довольно аккуратно. Есть некоторые сценарии, для которых он не подходит (например, word.), но в целом это хороший читаемый подход. - person ; 02.05.2018

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

    public static string GetMaxWords(this string input, int maxWords, string truncateWith = "...", string additionalSeparators = ",-_:")
    {
        int words = 1;
        bool IsSeparator(char c) => Char.IsSeparator(c) || additionalSeparators.Contains(c);

        IEnumerable<char> IterateChars()
        {
            yield return input[0];

            for (int i = 1; i < input.Length; i++)
            {
                if (IsSeparator(input[i]) && !IsSeparator(input[i - 1]))
                    if (words == maxWords)
                    {
                        foreach (char c in truncateWith)
                            yield return c;

                        break;
                    }
                    else
                        words++;

                yield return input[i];
            }
        }

        return !input.IsNullOrEmpty()
            ? new String(IterateChars().ToArray())
            : String.Empty;
    }
person SysCafe    schedule 25.05.2020
comment
Это единственная версия, которая решила эту проблему для меня, обрабатывая кавычки... например. Пользователь обновил «Дату назначения» до 2020-10-28T12:00:00.000Z с 2020-10-28T04:00:00.000Z - person Dwain Browne; 24.10.2020

упростили, добавили опцию транкингового символа и сделали его расширением.

    public static string TruncateAtWord(this string value, int maxLength)
    {
        if (value == null || value.Trim().Length <= maxLength)
            return value;

        string ellipse = "...";
        char[] truncateChars = new char[] { ' ', ',' };
        int index = value.Trim().LastIndexOfAny(truncateChars);

        while ((index + ellipse.Length) > maxLength)
            index = value.Substring(0, index).Trim().LastIndexOfAny(truncateChars);

        if (index > 0)
            return value.Substring(0, index) + ellipse;

        return value.Substring(0, maxLength - ellipse.Length) + ellipse;
    }
person Leon van Wyk    schedule 24.07.2014
comment
это просто не работает так, как задумано. Пожалуйста, хотя бы проверьте себя на вменяемость, прежде чем что-либо публиковать здесь. - person avs099; 02.04.2017

Вот что я придумал. Это нужно, чтобы остальная часть предложения также была разбита на куски.

public static List<string> SplitTheSentenceAtWord(this string originalString, int length)
    {
        try
        {
            List<string> truncatedStrings = new List<string>();
            if (originalString == null || originalString.Trim().Length <= length)
            {
                truncatedStrings.Add(originalString);
                return truncatedStrings;
            }
            int index = originalString.Trim().LastIndexOf(" ");

            while ((index + 3) > length)
                index = originalString.Substring(0, index).Trim().LastIndexOf(" ");

            if (index > 0)
            {
                string retValue = originalString.Substring(0, index) + "...";
                truncatedStrings.Add(retValue);

                string shortWord2 = originalString;
                if (retValue.EndsWith("..."))
                {
                    shortWord2 = retValue.Replace("...", "");
                }
                shortWord2 = originalString.Substring(shortWord2.Length);

                if (shortWord2.Length > length) //truncate it further
                {
                    List<string> retValues = SplitTheSentenceAtWord(shortWord2.TrimStart(), length);
                    truncatedStrings.AddRange(retValues);
                }
                else
                {
                    truncatedStrings.Add(shortWord2.TrimStart());
                }
                return truncatedStrings;
            }
            var retVal_Last = originalString.Substring(0, length - 3);
            truncatedStrings.Add(retVal_Last + "...");
            if (originalString.Length > length)//truncate it further
            {
                string shortWord3 = originalString;
                if (originalString.EndsWith("..."))
                {
                    shortWord3 = originalString.Replace("...", "");
                }
                shortWord3 = originalString.Substring(retVal_Last.Length);
                List<string> retValues = SplitTheSentenceAtWord(shortWord3.TrimStart(), length);

                truncatedStrings.AddRange(retValues);
            }
            else
            {
                truncatedStrings.Add(retVal_Last + "...");
            }
            return truncatedStrings;
        }
        catch
        {
            return new List<string> { originalString };
        }
    }
person Rahul Sonthalia    schedule 25.07.2015

я использую это

public string Truncate(string content, int length)
    {
        try
        {
            return content.Substring(0,content.IndexOf(" ",length)) + "...";
        }
        catch
        {
            return content;
        }
    }
person Zhou Hai    schedule 10.08.2014