7

I have a long string and I want to fit that in a small field. To achieve that, I break the string into lines on whitespace. The algorithm goes like this:

    public static string BreakLine(string text, int maxCharsInLine)
    {
        int charsInLine = 0;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < text.Length; i++)
        {
            char c = text[i];
            builder.Append(c);
            charsInLine++;

            if (charsInLine >= maxCharsInLine && char.IsWhiteSpace(c))
            {
                builder.AppendLine();
                charsInLine = 0;
            }
        }
        return builder.ToString();
    }

But this breaks when there's a short word, followed by a longer word. "foo howcomputerwork" with a max length of 16 doesn't break, but I want it to. One thought I has was looking forward to see where the next whitespace occurs, but I'm not sure whether that would result in the fewest lines possible.

John NoCookies
  • 1,041
  • 3
  • 17
  • 28
  • Do you want *at least* n characters in a line or *at most*? Because if you want *at most* n characters you need to work your way back from the nth character to a whitespace. And if at any time there is a word with more than n characters, you're screwed. ^_^; – Corak Mar 08 '13 at 12:54
  • @Corak "Splitting text into lines with **maximum** length", doesn't that imply *at most* ? – Nolonar Mar 08 '13 at 12:56
  • @Nolonar yes, but the code implies *at least*, so I wasn't sure. – Corak Mar 08 '13 at 12:58

3 Answers3

6

Enjoy!

public static string SplitToLines(string text, char[] splitOnCharacters, int maxStringLength)
{
    var sb = new StringBuilder();
    var index = 0;

    while (text.Length > index)
    {
        // start a new line, unless we've just started
        if (index != 0)
            sb.AppendLine();

        // get the next substring, else the rest of the string if remainder is shorter than `maxStringLength`
        var splitAt = index + maxStringLength <= text.Length
            ? text.Substring(index, maxStringLength).LastIndexOfAny(splitOnCharacters)
            : text.Length - index;

        // if can't find split location, take `maxStringLength` characters
        splitAt = (splitAt == -1) ? maxStringLength : splitAt;

        // add result to collection & increment index
        sb.Append(text.Substring(index, splitAt).Trim());
        index += splitAt;
    }

    return sb.ToString();
}

Note that splitOnCharacters and maxStringLength could be saved in user settings area of the app.

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
Dead.Rabit
  • 1,965
  • 1
  • 28
  • 46
  • Original Post was tested & working, unsure if @DIMI had the chance to test his/her edits but I have a feeling IIRC (from 2 years ago =P) you need to inject 1 character on every line using this method. – Dead.Rabit Feb 25 '16 at 14:20
  • 1
    Hey :) I use the same code. I just implemented it like an extension becouse it is most popular sort of usage for methods like split to array or split to lines. And your answer is great so I did +1, man. – NoWar Feb 25 '16 at 14:56
1

Check the contents of the character before writing to the string builder and or it with the current count:

    public static string BreakLine(string text, int maxCharsInLine)
    {
        int charsInLine = 0;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < text.Length; i++)
        {
            char c = text[i];
            if (char.IsWhiteSpace(c) || charsInLine >= maxCharsInLine)
            {
                builder.AppendLine();
                charsInLine = 0;
            }
            else 
            {
                builder.Append(c);
                charsInLine++;                    
            }
        }
        return builder.ToString();
    }
Moop
  • 3,414
  • 2
  • 23
  • 37
  • great use of string builder, it's required when concatinating this many strings together. looks like it will add a new line for every word in the text however. – Dead.Rabit Sep 10 '14 at 13:37
  • This code put each word on new line, regardless is end of line (based on maxCharsInLine) or not. – P R Oct 03 '14 at 12:10
  • @PR According to the question asked: 'I break the string into lines on whitespace' implies that he breaks the line up at whitespaces (and character count) – Moop Oct 03 '14 at 16:53
  • For one word per line it's easier to do `string.Join(Environment.NewLine, text.Split(' '))`. Also, appending text character by character will be slower than doing it in chunks, as @Dead.Rabit does. – Drew Noakes Apr 22 '16 at 11:05
1

update a code a bit, the @dead.rabit goes to loop sometime.

 public static string SplitToLines(string text,char[] splitanyOf, int maxStringLength)
    {               
        var sb = new System.Text.StringBuilder();
        var index = 0;
        var loop = 0;
        while (text.Length > index)
        {
            // start a new line, unless we've just started
            if (loop != 0)
            {
                sb.AppendLine();
            }

            // get the next substring, else the rest of the string if remainder is shorter than `maxStringLength`
            var splitAt = 0;
            if (index + maxStringLength <= text.Length)
            {
                splitAt = text.Substring(index, maxStringLength).LastIndexOfAny(splitanyOf);
            }
            else
            {
                splitAt = text.Length - index;
            }

            // if can't find split location, take `maxStringLength` characters
            if (splitAt == -1 || splitAt == 0)
            {
                splitAt = text.IndexOfAny(splitanyOf, maxStringLength);
            }

            // add result to collection & increment index
            sb.Append(text.Substring(index, splitAt).Trim());
            if(text.Length > splitAt)
            {
                text = text.Substring(splitAt + 1).Trim();
            }
            else
            {
                text = string.Empty;
            }
            loop = loop + 1;
        }

        return sb.ToString();
    }
nitin157
  • 51
  • 3