C# Console Word Wrap - c#

I have a string with newline characters and I want to wrap the words. I want to keep the newline characters so that when I display the text it looks like separate paragraphs. Anyone have a good function to do this? Current function and code below.(not my own function). The WordWrap function seems to be stripping out \n characters.
static void Main(string[] args){
StreamReader streamReader = new StreamReader("E:/Adventure Story/Intro.txt");
string intro = "";
string line;
while ((line = streamReader.ReadLine()) != null)
{
intro += line;
if(line == "")
{
intro += "\n\n";
}
}
WordWrap(intro);
public static void WordWrap(string paragraph)
{
paragraph = new Regex(#" {2,}").Replace(paragraph.Trim(), #" ");
var left = Console.CursorLeft; var top = Console.CursorTop; var lines = new List<string>();
for (var i = 0; paragraph.Length > 0; i++)
{
lines.Add(paragraph.Substring(0, Math.Min(Console.WindowWidth, paragraph.Length)));
var length = lines[i].LastIndexOf(" ", StringComparison.Ordinal);
if (length > 0) lines[i] = lines[i].Remove(length);
paragraph = paragraph.Substring(Math.Min(lines[i].Length + 1, paragraph.Length));
Console.SetCursorPosition(left, top + i); Console.WriteLine(lines[i]);
}
}

Here is a word wrap function that works by using regular expressions to find the places that it's ok to break and places where it must break. Then it returns pieces of the original text based on the "break zones". It even allows for breaks at hyphens (and other characters) without removing the hyphens (since the regex uses a zero-width positive lookbehind assertion).
IEnumerable<string> WordWrap(string text, int width)
{
const string forcedBreakZonePattern = #"\n";
const string normalBreakZonePattern = #"\s+|(?<=[-,.;])|$";
var forcedZones = Regex.Matches(text, forcedBreakZonePattern).Cast<Match>().ToList();
var normalZones = Regex.Matches(text, normalBreakZonePattern).Cast<Match>().ToList();
int start = 0;
while (start < text.Length)
{
var zone =
forcedZones.Find(z => z.Index >= start && z.Index <= start + width) ??
normalZones.FindLast(z => z.Index >= start && z.Index <= start + width);
if (zone == null)
{
yield return text.Substring(start, width);
start += width;
}
else
{
yield return text.Substring(start, zone.Index - start);
start = zone.Index + zone.Length;
}
}
}

If you want another newline to make text look-like paragraphs, just use Replace method of your String object.
var str =
"Line 1\n" +
"Line 2\n" +
"Line 3\n";
Console.WriteLine("Before:\n" + str);
str = str.Replace("\n", "\n\n");
Console.WriteLine("After:\n" + str);

Recently I've been working on creating some abstractions that imitate window-like features in a performance- and memory-sensitive console context.
To this end I had to implement word-wrapping functionality without any unnecessary string allocations.
The following is what I managed to simplify it into. This method:
preserves new-lines in the input string,
allows you to specify what characters it should break on (space, hyphen, etc.),
returns the start indices and lengths of the lines via Microsoft.Extensions.Primitives.StringSegment struct instances (but it's very simple to replace this struct with your own, or append directly to a StringBuilder).
public static IEnumerable<StringSegment> WordWrap(string input, int maxLineLength, char[] breakableCharacters)
{
int lastBreakIndex = 0;
while (true)
{
var nextForcedLineBreak = lastBreakIndex + maxLineLength;
// If the remainder is shorter than the allowed line-length, return the remainder. Short-circuits instantly for strings shorter than line-length.
if (nextForcedLineBreak >= input.Length)
{
yield return new StringSegment(input, lastBreakIndex, input.Length - lastBreakIndex);
yield break;
}
// If there are native new lines before the next forced break position, use the last native new line as the starting position of our next line.
int nativeNewlineIndex = input.LastIndexOf(Environment.NewLine, nextForcedLineBreak, maxLineLength);
if (nativeNewlineIndex > -1)
{
nextForcedLineBreak = nativeNewlineIndex + Environment.NewLine.Length + maxLineLength;
}
// Find the last breakable point preceding the next forced break position (and include the breakable character, which might be a hypen).
var nextBreakIndex = input.LastIndexOfAny(breakableCharacters, nextForcedLineBreak, maxLineLength) + 1;
// If there is no breakable point, which means a word is longer than line length, force-break it.
if (nextBreakIndex == 0)
{
nextBreakIndex = nextForcedLineBreak;
}
yield return new StringSegment(input, lastBreakIndex, nextBreakIndex - lastBreakIndex);
lastBreakIndex = nextBreakIndex;
}
}

Related

C#: Need to split a string into a string[] and keeping the delimiter (also a string) at the beginning of the string

I think I am too dumb to solve this problem...
I have some formulas which need to be "translated" from one syntax to another.
Let's say I have a formula that goes like that (it's a simple one, others have many "Ceilings" in it):
string formulaString = "If([Param1] = 0, 1, Ceiling([Param2] / 0.55) * [Param3])";
I need to replace "Ceiling()" with "Ceiling(; 1)" (basically, insert "; 1" before the ")").
My attempt is to split the fomulaString at "Ceiling(" so I am able to iterate through the string array and insert my string at the correct index (counting every "(" and ")" to get the right index)
What I have so far:
//splits correct, but loses "CEILING("
string[] parts = formulaString.Split(new[] { "CEILING(" }, StringSplitOptions.None);
//splits almost correct, "CEILING(" is in another group
string[] parts = Regex.Split(formulaString, #"(CEILING\()");
//splits almost every letter
string[] parts = Regex.Split(formulaString, #"(?=[(CEILING\()])");
When everything is done, I concat the string so I have my complete formula again.
What do I have to set as Regex pattern to achieve this sample? (Or any other method that will help me)
part1 = "If([Param1] = 0, 1, ";
part2 = "Ceiling([Param2] / 0.55) * [Param3])";
//part3 = next "CEILING(" in a longer formula and so on...
As I mention in a comment, you almost got it: (?=Ceiling). This is incomplete for your use case unfortunately.
I need to replace "Ceiling()" with "Ceiling(; 1)" (basically, insert "; 1" before the ")").
Depending on your regex engine (for example JS) this works:
string[] parts = Regex.Split(formulaString, #"(?<=Ceiling\([^)]*(?=\)))");
string modifiedFormula = String.join("; 1", parts);
The regex
(?<=Ceiling\([^)]*(?=\)))
(?<= ) Positive lookbehind
Ceiling\( Search for literal "Ceiling("
[^)] Match any char which is not ")" ..
* .. 0 or more times
(?=\)) Positive lookahead for ")", effectively making us stop before the ")"
This regex is a zero-assertion, therefore nothing is lost and it will cut your strings before the last ")" in every "Ceiling()".
This solution would break whenever you have nested "Ceiling()". Then your only solution would be writing your own parser for the same reasons why you can't parse markup with regex.
Regex.Replace(formulaString, #"(?<=Ceiling\()(.*?)(?=\))","$1; 1");
Note: This will not work for nested "Ceilings", but it does for Ceiling(), It will also not work fir Ceiling(AnotherFunc(x)). For that you need something like:
Regex.Replace(formulaString, #"(?<=Ceiling\()((.*\((?>[^()]+|(?1))*\))*|[^\)]*)(\))","$1; 1$3");
but I could not get that to work with .NET, only in JavaScript.
This is my solution:
private string ConvertCeiling(string formula)
{
int ceilingsCount = formula.CountOccurences("Ceiling(");
int startIndex = 0;
int bracketCounter;
for (int i = 0; i < ceilingsCount; i++)
{
startIndex = formula.IndexOf("Ceiling(", startIndex);
bracketCounter = 0;
for (int j = 0; j < formula.Length; j++)
{
if (j < startIndex) continue;
var c = formula[j];
if (c == '(')
{
bracketCounter++;
}
if (c == ')')
{
bracketCounter--;
if (bracketCounter == 0)
{
// found end
formula = formula.Insert(j, "; 1");
startIndex++;
break;
}
}
}
}
return formula;
}
And CountOccurence:
public static int CountOccurences(this string value, string parameter)
{
int counter = 0;
int startIndex = 0;
int indexOfCeiling;
do
{
indexOfCeiling = value.IndexOf(parameter, startIndex);
if (indexOfCeiling < 0)
{
break;
}
else
{
startIndex = indexOfCeiling + 1;
counter++;
}
} while (true);
return counter;
}

Remove all but specified substring from string

I have a string that contains several instances of a substring, as well as other text. The substring is specified by beginning with a given sequence of letters (e.g. CNTY) and ending with a double slash (//). How could I efficiently remove all text that does not fall inside the specified substring? Thanks for the help. I found that this Regex will return the result needed:
string result = Regex.Matches(text, "CNTY(.*)//").Cast<Match>().Aggregate("", (s, t) => s + t.Value, s => s);
But, I have another more complicated substring, which begins with WEATHLOC, then contains wildcard text across several lines, and ends with a line beginning RMKS, more wildcard text, and then //. Here is an example:
WEATHLOC/ICAO:KCOS//
OBSTIME/052005Z//
WIND/360/10//
VSBY/10/SM//
CLDLYR/-/LYR:BKN//
TEMP/MAXTEMP:15/MINTEMP:18//
ALTSTG/HG:29.92//
RMKS/SAMPLE//
Everything from WEATHLOC to the final // needs to be captured, and I can only rely on its beginning with WEATHLOC and ending with RMKS*//. Is there a way to express that in a Regex match?
This should work:
string text = "hiCNTYhello//content What /-CNTYworld//12texCNTY!//That's it";
string search = "CNTY(.*?)//";
MatchCollection matches = Regex.Matches(text, search);
Will match "hello", "world" and "!"
This little code segment works. The RegEx method was too difficult for me, but this does work. We are trying to check if we are in bounds of CNTY // and to output that text to the StringBuilder.
static void Main(string[] args)
{
var input = #"CNTYTestingTesting//This is some more test CNTY1234//And some moreCNTYWhat is this?//";
var sb = new StringBuilder();
int inCnty = -1;
for (int i = 0; i < input.Length; i ++)
{
// Test for start
if (i < input.Length - 4)
{
if (input.Substring(i, 4) == "CNTY")
{
inCnty = i + 4; // Index of first CNTY
}
}
// Test for end
if (i < input.Length - 1)
{
if (input.Substring(i, 2) == #"//")
{
inCnty = -1; // Reset
}
}
// Test if we are in the segment
if (i >= inCnty && inCnty > 0)
{
// Outside string
sb.Append(input[i]);
}
}
var output = sb.ToString();
Console.WriteLine(output);
Console.Read();
}

C# How to generate a new string based on multiple ranged index

Let's say I have a string like this one, left part is a word, right part is a collection of indices (single or range) used to reference furigana (phonetics) for kanjis in my word:
string myString = "子で子にならぬ時鳥,0:こ;2:こ;7-8:ほととぎす"
The pattern in detail:
word,<startIndex>(-<endIndex>):<furigana>
What would be the best way to achieve something like this (with a space in front of the kanji to mark which part is linked to the [furigana]):
子[こ]で 子[こ]にならぬ 時鳥[ほととぎす]
Edit: (thanks for your comments guys)
Here is what I wrote so far:
static void Main(string[] args)
{
string myString = "ABCDEF,1:test;3:test2";
//Split Kanjis / Indices
string[] tokens = myString.Split(',');
//Extract furigana indices
string[] indices = tokens[1].Split(';');
//Dictionnary to store furigana indices
Dictionary<string, string> furiganaIndices = new Dictionary<string, string>();
//Collect
foreach (string index in indices)
{
string[] splitIndex = index.Split(':');
furiganaIndices.Add(splitIndex[0], splitIndex[1]);
}
//Processing
string result = tokens[0] + ",";
for (int i = 0; i < tokens[0].Length; i++)
{
string currentIndex = i.ToString();
if (furiganaIndices.ContainsKey(currentIndex)) //add [furigana]
{
string currentFurigana = furiganaIndices[currentIndex].ToString();
result = result + " " + tokens[0].ElementAt(i) + string.Format("[{0}]", currentFurigana);
}
else //nothing to add
{
result = result + tokens[0].ElementAt(i);
}
}
File.AppendAllText(#"D:\test.txt", result + Environment.NewLine);
}
Result:
ABCDEF,A B[test]C D[test2]EF
I struggle to find a way to process ranged indices:
string myString = "ABCDEF,1:test;2-3:test2";
Result : ABCDEF,A B[test] CD[test2]EF
I don't have anything against manually manipulating strings per se. But given that you seem to have a regular pattern describing the inputs, it seems to me that a solution that uses regex would be more maintainable and readable. So with that in mind, here's an example program that takes that approach:
class Program
{
private const string _kinvalidFormatException = "Invalid format for edit specification";
private static readonly Regex
regex1 = new Regex(#"(?<word>[^,]+),(?<edit>(?:\d+)(?:-(?:\d+))?:(?:[^;]+);?)+", RegexOptions.Compiled),
regex2 = new Regex(#"(?<start>\d+)(?:-(?<end>\d+))?:(?<furigana>[^;]+);?", RegexOptions.Compiled);
static void Main(string[] args)
{
string myString = "子で子にならぬ時鳥,0:こ;2:こ;7-8:ほととぎす";
string result = EditString(myString);
}
private static string EditString(string myString)
{
Match editsMatch = regex1.Match(myString);
if (!editsMatch.Success)
{
throw new ArgumentException(_kinvalidFormatException);
}
int ichCur = 0;
string input = editsMatch.Groups["word"].Value;
StringBuilder text = new StringBuilder();
foreach (Capture capture in editsMatch.Groups["edit"].Captures)
{
Match oneEditMatch = regex2.Match(capture.Value);
if (!oneEditMatch.Success)
{
throw new ArgumentException(_kinvalidFormatException);
}
int start, end;
if (!int.TryParse(oneEditMatch.Groups["start"].Value, out start))
{
throw new ArgumentException(_kinvalidFormatException);
}
Group endGroup = oneEditMatch.Groups["end"];
if (endGroup.Success)
{
if (!int.TryParse(endGroup.Value, out end))
{
throw new ArgumentException(_kinvalidFormatException);
}
}
else
{
end = start;
}
text.Append(input.Substring(ichCur, start - ichCur));
if (text.Length > 0)
{
text.Append(' ');
}
ichCur = end + 1;
text.Append(input.Substring(start, ichCur - start));
text.Append(string.Format("[{0}]", oneEditMatch.Groups["furigana"]));
}
if (ichCur < input.Length)
{
text.Append(input.Substring(ichCur));
}
return text.ToString();
}
}
Notes:
This implementation assumes that the edit specifications will be listed in order and won't overlap. It makes no attempt to validate that part of the input; depending on where you are getting your input from you may want to add that. If it's valid for the specifications to be listed out of order, you can also extend the above to first store the edits in a list and sort the list by the start index before actually editing the string. (In similar fashion to the way the other proposed answer works; though, why they are using a dictionary instead of a simple list to store the individual edits, I have no idea…that seems arbitrarily complicated to me.)
I included basic input validation, throwing exceptions where failures occur in the pattern matching. A more user-friendly implementation would add more specific information to each exception, describing what part of the input actually was invalid.
The Regex class actually has a Replace() method, which allows for complete customization. The above could have been implemented that way, using Replace() and a MatchEvaluator to provide the replacement text, instead of just appending text to a StringBuilder. Which way to do it is mostly a matter of preference, though the MatchEvaluator might be preferred if you have a need for more flexible implementation options (i.e. if the exact format of the result can vary).
If you do choose to use the other proposed answer, I strongly recommend you use StringBuilder instead of simply concatenating onto the results variable. For short strings it won't matter much, but you should get into the habit of always using StringBuilder when you have a loop that is incrementally adding onto a string value, because for long string the performance implications of using concatenation can be very negative.
This should do it (and even handle ranged indices), based on the formatting of the input string you have-
using System;
using System.Collections.Generic;
public class stringParser
{
private struct IndexElements
{
public int start;
public int end;
public string value;
}
public static void Main()
{
//input string
string myString = "子で子にならぬ時鳥,0:こ;2:こ;7-8:ほととぎす";
int wordIndexSplit = myString.IndexOf(',');
string word = myString.Substring(0,wordIndexSplit);
string indices = myString.Substring(wordIndexSplit + 1);
string[] eachIndex = indices.Split(';');
Dictionary<int,IndexElements> index = new Dictionary<int,IndexElements>();
string[] elements;
IndexElements e;
int dash;
int n = 0;
int last = -1;
string results = "";
foreach (string s in eachIndex)
{
e = new IndexElements();
elements = s.Split(':');
if (elements[0].Contains("-"))
{
dash = elements[0].IndexOf('-');
e.start = int.Parse(elements[0].Substring(0,dash));
e.end = int.Parse(elements[0].Substring(dash + 1));
}
else
{
e.start = int.Parse(elements[0]);
e.end = e.start;
}
e.value = elements[1];
index.Add(n,e);
n++;
}
//this is the part that takes the "setup" from the parts above and forms the result string
//loop through each of the "indices" parsed above
for (int i = 0; i < index.Count; i++)
{
//if this is the first iteration through the loop, and the first "index" does not start
//at position 0, add the beginning characters before its start
if (last == -1 && index[i].start > 0)
{
results += word.Substring(0,index[i].start);
}
//if this is not the first iteration through the loop, and the previous iteration did
//not stop at the position directly before the start of the current iteration, add
//the intermediary chracters
else if (last != -1 && last + 1 != index[i].start)
{
results += word.Substring(last + 1,index[i].start - (last + 1));
}
//add the space before the "index" match, the actual match, and then the formatted "index"
results += " " + word.Substring(index[i].start,(index[i].end - index[i].start) + 1)
+ "[" + index[i].value + "]";
//remember the position of the ending for the next iteration
last = index[i].end;
}
//if the last "index" did not stop at the end of the input string, add the remaining characters
if (index[index.Keys.Count - 1].end + 1 < word.Length)
{
results += word.Substring(index[index.Keys.Count-1].end + 1);
}
//trimming spaces that may be left behind
results = results.Trim();
Console.WriteLine("INPUT - " + myString);
Console.WriteLine("OUTPUT - " + results);
Console.Read();
}
}
input - 子で子にならぬ時鳥,0:こ;2:こ;7-8:ほととぎす
output - 子[こ]で 子[こ]にならぬ 時鳥[ほととぎす]
Note that this should also work with characters the English alphabet if you wanted to use English instead-
input - iliketocodeverymuch,2:A;4-6:B;9-12:CDEFG
output - il i[A]k eto[B]co deve[CDEFG]rymuch

Wrap text to the next line when it exceeds a certain length?

I need to write different paragraphs of text within a certain area. For instance, I have drawn a box to the console that looks like this:
/----------------------\
| |
| |
| |
| |
\----------------------/
How would I write text within it, but wrap it to the next line if it gets too long?
Split on last space before your row length?
int myLimit = 10;
string sentence = "this is a long sentence that needs splitting to fit";
string[] words = sentence.Split(new char[] { ' ' });
IList<string> sentenceParts = new List<string>();
sentenceParts.Add(string.Empty);
int partCounter = 0;
foreach (string word in words)
{
if ((sentenceParts[partCounter] + word).Length > myLimit)
{
partCounter++;
sentenceParts.Add(string.Empty);
}
sentenceParts[partCounter] += word + " ";
}
foreach (string x in sentenceParts)
Console.WriteLine(x);
UPDATE (the solution above lost the last word in some cases):
int myLimit = 10;
string sentence = "this is a long sentence that needs splitting to fit";
string[] words = sentence.Split(' ');
StringBuilder newSentence = new StringBuilder();
string line = "";
foreach (string word in words)
{
if ((line + word).Length > myLimit)
{
newSentence.AppendLine(line);
line = "";
}
line += string.Format("{0} ", word);
}
if (line.Length > 0)
newSentence.AppendLine(line);
Console.WriteLine(newSentence.ToString());
Here's one that is lightly tested and uses LastIndexOf to speed things along (a guess):
private static string Wrap(string v, int size)
{
v = v.TrimStart();
if (v.Length <= size) return v;
var nextspace = v.LastIndexOf(' ', size);
if (-1 == nextspace) nextspace = Math.Min(v.Length, size);
return v.Substring(0, nextspace) + ((nextspace >= v.Length) ?
"" : "\n" + Wrap(v.Substring(nextspace), size));
}
I started with Jim H.'s solution and end up with this method. Only problem is if text has any word that longer than limit. But works well.
public static List<string> GetWordGroups(string text, int limit)
{
var words = text.Split(new string[] { " ", "\r\n", "\n" }, StringSplitOptions.None);
List<string> wordList = new List<string>();
string line = "";
foreach (string word in words)
{
if (!string.IsNullOrWhiteSpace(word))
{
var newLine = string.Join(" ", line, word).Trim();
if (newLine.Length >= limit)
{
wordList.Add(line);
line = word;
}
else
{
line = newLine;
}
}
}
if (line.Length > 0)
wordList.Add(line);
return wordList;
}
I modified the version of Jim H such that it supports some special cases.
For example the case when the sentence does not contain any whitespace character; I also noted that there is a problem when a line has a space at the last position; then the space is added at the end and you end up with one character too much.
Here is my version just in case someone is interested:
public static List<string> WordWrap(string input, int maxCharacters)
{
List<string> lines = new List<string>();
if (!input.Contains(" "))
{
int start = 0;
while (start < input.Length)
{
lines.Add(input.Substring(start, Math.Min(maxCharacters, input.Length - start)));
start += maxCharacters;
}
}
else
{
string[] words = input.Split(' ');
string line = "";
foreach (string word in words)
{
if ((line + word).Length > maxCharacters)
{
lines.Add(line.Trim());
line = "";
}
line += string.Format("{0} ", word);
}
if (line.Length > 0)
{
lines.Add(line.Trim());
}
}
return lines;
}
This is a more complete and tested solution.
The bool overflow parameter specifies, whether long words are chunked in addition to splitting up by spaces.
Consecutive whitespaces, as well as \r, \n, are ignored and collapsed into one space.
Edge cases are throughfully tested
public static string WrapText(string text, int width, bool overflow)
{
StringBuilder result = new StringBuilder();
int index = 0;
int column = 0;
while (index < text.Length)
{
int spaceIndex = text.IndexOfAny(new[] { ' ', '\t', '\r', '\n' }, index);
if (spaceIndex == -1)
{
break;
}
else if (spaceIndex == index)
{
index++;
}
else
{
AddWord(text.Substring(index, spaceIndex - index));
index = spaceIndex + 1;
}
}
if (index < text.Length) AddWord(text.Substring(index));
void AddWord(string word)
{
if (!overflow && word.Length > width)
{
int wordIndex = 0;
while (wordIndex < word.Length)
{
string subWord = word.Substring(wordIndex, Math.Min(width, word.Length - wordIndex));
AddWord(subWord);
wordIndex += subWord.Length;
}
}
else
{
if (column + word.Length >= width)
{
if (column > 0)
{
result.AppendLine();
column = 0;
}
}
else if (column > 0)
{
result.Append(" ");
column++;
}
result.Append(word);
column += word.Length;
}
}
return result.ToString();
}
I modified Manfred's version. If you put a string with the '\n' character in it, it will wrap the text strangely because it will count it as another character. With this minor change all will go smoothly.
public static List<string> WordWrap(string input, int maxCharacters)
{
List<string> lines = new List<string>();
if (!input.Contains(" ") && !input.Contains("\n"))
{
int start = 0;
while (start < input.Length)
{
lines.Add(input.Substring(start, Math.Min(maxCharacters, input.Length - start)));
start += maxCharacters;
}
}
else
{
string[] paragraphs = input.Split('\n');
foreach (string paragraph in paragraphs)
{
string[] words = paragraph.Split(' ');
string line = "";
foreach (string word in words)
{
if ((line + word).Length > maxCharacters)
{
lines.Add(line.Trim());
line = "";
}
line += string.Format("{0} ", word);
}
if (line.Length > 0)
{
lines.Add(line.Trim());
}
}
}
return lines;
}
Other answers didn't consider East Asian languages, which don't use space to break words.
In general, a sentence in East Asian languages can be wrapped in any position between characters, except certain punctuations (it is not a big problem even if ignore punctuation rules). It is much simpler than European languages but when consider mixing different languages, you have to detect the language of each character by checking the Unicode table, and then apply the break lines by space algorithm only for European languages parts.
References:
https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap
https://en.wikipedia.org/wiki/Line_breaking_rules_in_East_Asian_languages
https://en.wikibooks.org/wiki/Unicode/Character_reference/0000-0FFF
This code will wrap the paragraph text. It will break the paragraph text into lines. If it encounters any word which is even larger than the line length, it will break the word into multiple lines too.
private const int max_line_length = 25;
private string wrapLinesToFormattedText(string p_actual_string) {
string formatted_string = "";
int available_length = max_line_length;
string[] word_arr = p_actual_string.Trim().Split(' ');
foreach (string w in word_arr) {
string word = w;
if (word == "") {
continue;
}
int word_length = word.Length;
//if the word is even longer than the length that the line can have
//the large word will get break down into lines following by the successive words
if (word_length >= max_line_length)
{
if (available_length > 0)
{
formatted_string += word.Substring(0, available_length) + "\n";
word = word.Substring(available_length);
}
else
{
formatted_string += "\n";
}
word = word + " ";
available_length = max_line_length;
for (var count = 0;count<word.Length;count++) {
char ch = word.ElementAt(count);
if (available_length==0) {
formatted_string += "\n";
available_length = max_line_length;
}
formatted_string += ch;
available_length--;
}
continue;
}
if ((word_length+1) <= available_length)
{
formatted_string += word+" ";
available_length -= (word_length+1);
continue;
}
else {
available_length = max_line_length;
formatted_string += "\n"+word+" " ;
available_length -= (word_length + 1);
continue;
}
}//end of foreach loop
return formatted_string;
}
//end of function wrapLinesToFormattedText
Blockquote
Here is a small piece of optimized code for wrapping text according to float sentence length limit written in Visual Basic9.
Dim stringString = "Great code! I wish I could found that when searching for Print Word Wrap VB.Net and other variations when searching on google. I’d never heard of MeasureString until you guys mentioned it. In my defense, I’m not a UI programmer either, so I don’t feel bad for not knowing"
Dim newstring = ""
Dim t As Integer = 1
Dim f As Integer = 0
Dim z As Integer = 0
Dim p As Integer = stringString.Length
Dim myArray As New ArrayList
Dim endOfText As Boolean = False REM to exit loop after finding the last words
Dim segmentLimit As Integer = 45
For t = z To p Step segmentLimit REM you can adjust this variable to fit your needs
newstring = String.Empty
newstring += Strings.Mid(stringString, 1, 45)
If Strings.Left(newstring, 1) = " " Then REM Chr(13) doesn't work, that's why I have put a physical space
newstring = Strings.Right(newstring, newstring.Length - 1)
End If
If stringString.Length < 45 Then
endOfText = True
newstring = stringString
myArray.Add(newstring) REM fills the last entry then exits
myArray.TrimToSize()
Exit For
Else
stringString = Strings.Right(stringString, stringString.Length - 45)
End If
z += 44 + f
If Not Strings.Right(newstring, 1) = Chr(32) Then REM to detect space
Do Until Strings.Right(newstring, z + 1) = " "
If Strings.Right(newstring, z + f) = " " OrElse Strings.Left(stringString, 1) = " " Then
Exit Do
End If
newstring += Strings.Left(stringString, 1)
stringString = Strings.Right(stringString, stringString.Length - 1) REM crops the original
p = stringString.Length REM string from left by 45 characters and additional characters
t += f
f += 1
Loop
myArray.Add(newstring) REM puts the resulting segments of text in an array
myArray.TrimToSize()
newstring = String.Empty REM empties the string to load the next 45 characters
End If
t = 1
f = 1
Next
For Each item In myArray
MsgBox(item)
'txtSegmentedText.Text &= vbCrLf & item
Next
I know I am a bit late, But I managed to get a solution going by using recursion.
I think its one of the cleanest solutions proposed here.
Recursive Function:
public StringBuilder TextArea { get; set; } = new StringBuilder();
public void GenerateMultiLineTextArea(string value, int length)
{
// first call - first length values -> append first length values, remove first length values from value, make second call
// second call - second length values -> append second length values, remove first length values from value, make third call
// third call - value length is less then length just append as it is
if (value.Length <= length && value.Length != 0)
{
TextArea.Append($"|{value.PadRight(length)}" + "|");
}
else
{
TextArea.Append($"|{value.Substring(0, length).ToString()}".PadLeft(length) + "|\r\n");
value = value.Substring(length, (value.Length) - (length));
GenerateMultiLineTextArea(value, length);
}
}
Usage:
string LongString =
"This is a really long string that needs to break after it reaches a certain limit. " +
"This is a really long string that needs to break after it reaches a certain limit." + "This is a really long string that needs to break after it reaches a certain limit.";
GenerateMultiLineTextArea(LongString, 22);
Console.WriteLine("/----------------------\\");
Console.WriteLine(TextArea.ToString());
Console.WriteLine("\\----------------------/");
Outputs:
/----------------------\
|This is a really long |
|string that needs to b|
|reak after it reaches |
|a certain limit. This |
|is a really long strin|
|g that needs to break |
|after it reaches a cer|
|tain limit.This is a r|
|eally long string that|
| needs to break after |
|it reaches a certain l|
|imit. |
\----------------------/

Divide long string into 60 character long lines but don't break words

There has to be a better way to do this.
I just want to split long string into 60 character lines but do not break words. So it doesn't have to add up to 60 characters just has to be less than 60.
The code below is what I have and it works but I'm thinking there's a better way. Anybody?
Modified to use StringBuilder and fixed the problem of removing a repeating word.
Also don't want to use regex because I think that would be less efficient than what I have now.
public static List<String> FormatMe(String Message)
{
Int32 MAX_WIDTH = 60;
List<String> Line = new List<String>();
String[] Words;
Message = Message.Trim();
Words = Message.Split(" ".ToCharArray());
StringBuilder s = new StringBuilder();
foreach (String Word in Words)
{
s.Append(Word + " ");
if (s.Length > MAX_WIDTH)
{
s.Replace(Word, "", 0, s.Length - Word.Length);
Line.Add(s.ToString().Trim());
s = new StringBuilder(Word + " ");
}
}
if (s.Length > 0)
Line.Add(s.ToString().Trim());
return Line;
}
Thanks
Another (now TESTED) sample, very similiar to Keith approach:
static void Main(string[] args)
{
const Int32 MAX_WIDTH = 60;
int offset = 0;
string text = Regex.Replace(File.ReadAllText("oneline.txt"), #"\s{2,}", " ");
List<string> lines = new List<string>();
while (offset < text.Length)
{
int index = text.LastIndexOf(" ",
Math.Min(text.Length, offset + MAX_WIDTH));
string line = text.Substring(offset,
(index - offset <= 0 ? text.Length : index) - offset );
offset += line.Length + 1;
lines.Add(line);
}
}
I ran that on this file with all line breaks manually replaced by " ".
Try this:
const Int32 MAX_WIDTH = 60;
string text = "...";
List<string> lines = new List<string>();
StringBuilder line = new StringBuilder();
foreach(Match word in Regex.Matches(text, #"\S+", RegexOptions.ECMAScript))
{
if (word.Value.Length + line.Length + 1 > MAX_WIDTH)
{
lines.Add(line.ToString());
line.Length = 0;
}
line.Append(String.Format("{0} ", word.Value));
}
if (line.Length > 0)
line.Append(word.Value);
Please, also check this out: How do I use a regular expression to add linefeeds?
Inside a Regular expression, the Match Evaluator function (an anonymous method) does the grunt work and stores the newly sized lines into a StringBuilder. We don't use the return value of Regex.Replace method because we're just using its Match Evaluator function as a feature to accomplish line breaking from inside the regular expression call - just for the heck of it, because I think it's cool.
using System;
using System.Text;
using System.Text.RegularExpressions;
strInput is what you want to convert the lines of.
int MAX_LEN = 60;
StringBuilder sb = new StringBuilder();
int bmark = 0; //bookmark position
Regex.Replace(strInput, #".*?\b\w+\b.*?",
delegate(Match m) {
if (m.Index - bmark + m.Length + m.NextMatch().Length > MAX_LEN
|| m.Index == bmark && m.Length >= MAX_LEN) {
sb.Append(strInput.Substring(bmark, m.Index - bmark + m.Length).Trim() + Environment.NewLine);
bmark = m.Index + m.Length;
} return null;
}, RegexOptions.Singleline);
if (bmark != strInput.Length) // last portion
sb.Append(strInput.Substring(bmark));
string strModified = sb.ToString(); // get the real string from builder
It's also worth noting the second condition in the if expression in the Match Evaluator m.Index == bmark && m.Length >= MAX_LEN is meant as an exceptional condition in case there is a word longer than 60 chars (or longer than the set max length) - it will not be broken down here but just stored on one line by itself - I guess you might want to create a second formula for that condition in the real world to hyphenate it or something.
An other one ...
public static string SplitLongWords(string text, int maxWordLength)
{
var reg = new Regex(#"\S{" + (maxWordLength + 1) + ",}");
bool replaced;
do
{
replaced = false;
text = reg.Replace(text, (m) =>
{
replaced = true;
return m.Value.Insert(maxWordLength, " ");
});
} while (replaced);
return text;
}
I would start with saving the length of the original string. Then, start backwards, and just subtract, since odds are that I would get below 60 faster by starting at the last word and going back than building up.
Once I know how long, then just use StringBuilder and build up the string for the new string.
List<string> lines = new List<string>();
while (message.Length > 60) {
int idx = message.LastIndexOf(' ', 60);
lines.Add(message.Substring(0, idx));
message = message.Substring(idx + 1, message.Length - (idx + 1));
}
lines.Add(message);
You might need to modify a bit to handle multiple spaces, words with >60 chars in them, ...
I tried the original solution and found that it didn't quite work. I've modified it slightly to make it work. It now works for me and solves a problem I had. Thanks.
Jim.
public static List<String> FormatMe(String message)
{
int maxLength = 10;
List<String> Line = new List<String>();
String[] words;
message = message.Trim();
words = message.Split(" ".ToCharArray());
StringBuilder sentence = new StringBuilder();
foreach (String word in words)
{
if((sentence.Length + word.Length) <= maxLength)
{
sentence.Append(word + " ");
}
else
{
Line.Add(sentence.ToString().Trim());
sentence = new StringBuilder(word + " ");
}
}
if (sentence.Length > 0)
Line.Add(sentence.ToString().Trim());
return Line;
}
private void btnSplitText_Click(object sender, EventArgs e)
{
List<String> Line = new List<string>();
string message = "The quick brown fox jumps over the lazy dog.";
Line = FormatMe(message);
}

Categories