I´m planning to write a description of the parameters of my console-app in a formated way similar to the follwoing:
The following options are possible:
myOption: Text do describe the option, but that should be splitted
to several lines if too big. Text should automatically
align by a fixed offset.
I already got a method to split the text at the right positions (assuming we do not care if we split in the midlle of any word, we would cimplicate things only if we´d care if we actually split at word-boundaries). However I am stuck on aligning the options explanation.
This is the code so far:
public void DisplayHelpEx()
{
var offset = this._options.Max(x => x.ParameterName.Length) + 6;
Console.WriteLine("The following options are possible:");
foreach (var option in this._corrections)
{
Console.Write((option.ParameterName + ": ").PadLeft(offset));
WriteOffset(offset, option.Explanation);
}
}
public void WriteOffset(int offset, string text)
{
var numChars = TOTAL_NUMBER_CHARS_PER_LINE - offset;
string line;
while ((line = new String(text.Take(numChars).ToArray())).Any())
{
var s = line.PadLeft(numChars);
Console.Write(s);
Console.WriteLine();
text= new String(text.Skip(numChars).ToArray());
}
}
I have tried many combinations of .PadLeft and .PadRight but can´t get it to work.
With the approach above I get the following output:
The following options are possible:
myOption: Text do describe the option, but that should be splitted
to several lines if too big. Text should automatically
align by a fixed offset.
PadLeft takes the text and adds some spaces left or right so that the full text will have a defined width, see https://msdn.microsoft.com/en-us/library/system.string.padleft(v=vs.110).aspx .
However, in your case, you don't want to have the whole text to have a fixed width (especially not if you in future want to split nicely at word boundaries), but rather the offset at the beginning. So why don't you just add the offset spaces at the beginning of each line, like so?
private const string optionParameterName = "myOption";
private const string optionText =
"Text do describe the option, but that should be splitted to several lines if too big.Text should automatically align by a fixed offset.";
private const int TOTAL_NUMBER_CHARS_PER_LINE = 60;
public void DisplayHelpEx()
{
var offset = optionParameterName.Length + 6;
Console.WriteLine("The following options are possible:");
WriteOffset(offset, optionParameterName + ": ", optionText);
}
public void WriteOffset(int offset, string label, string text)
{
var numChars = TOTAL_NUMBER_CHARS_PER_LINE - offset;
string offsetString = new string(' ', offset);
string line;
bool firstLine = true;
while ((line = new String(text.Take(numChars).ToArray())).Any())
{
if (firstLine)
{
Console.Write(label.PadRight(offset));
}
else
{
Console.Write(offsetString);
}
firstLine = false;
Console.Write(line);
Console.WriteLine();
text = new String(text.Skip(numChars).ToArray());
}
}
// output:
// The following options are possible:
// myOption: Text do describe the option, but that should b
// e splitted to several lines if too big.Text sh
// ould automatically align by a fixed offset.
Note that I used label.PadRight(offset) in the first line to make sure that the string with the label is padded to the correct length -- here the padding is useful because it allows us to make the label string have exactly the same width as the other offsets.
in WriteOffset method you do weird things with text and it is hard to follow its modifications
test with a fiddle modified program
public class Program
{
static int TOTAL_NUMBER_CHARS_PER_LINE = 64;
public static void Main()
{
DisplayHelpEx();
}
// i only set test params here
public static void DisplayHelpEx()
{
string ParameterName = "myOption";
string Explanation = "Text do describe the option, but that should be splitted to several lines if too big. Text should automatically align by a fixed offset";
int offset = ParameterName.Length + 6;
Console.WriteLine("The following options are possible:");
// print caption
Console.Write((ParameterName + ": ").PadLeft(offset));
// print help
WriteOffset(offset, TOTAL_NUMBER_CHARS_PER_LINE - offset, Explanation);
}
private static void WriteOffset(int offset, int width, string text)
{
string pad = new String(' ', offset);
int i = 0;
while (i < text.Length)
{
// print text by 1 symbol
Console.Write(text[i]);
i++;
if (i%width == 0)
{
// when line end reached, go to next
Console.WriteLine();
// make offset for new line
Console.Write(pad);
}
}
}
}
Related
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;
}
}
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
Using Console.WriteLine(), this it output:
I want it to look like this automatically, instead of manually putting in \n wherever needed:
Is this possible? If so, how?
Here is a solution that will work with tabs, newlines, and other whitespace.
using System;
using System.Collections.Generic;
/// <summary>
/// Writes the specified data, followed by the current line terminator, to the standard output stream, while wrapping lines that would otherwise break words.
/// </summary>
/// <param name="paragraph">The value to write.</param>
/// <param name="tabSize">The value that indicates the column width of tab characters.</param>
public static void WriteLineWordWrap(string paragraph, int tabSize = 8)
{
string[] lines = paragraph
.Replace("\t", new String(' ', tabSize))
.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
for (int i = 0; i < lines.Length; i++) {
string process = lines[i];
List<String> wrapped = new List<string>();
while (process.Length > Console.WindowWidth) {
int wrapAt = process.LastIndexOf(' ', Math.Min(Console.WindowWidth - 1, process.Length));
if (wrapAt <= 0) break;
wrapped.Add(process.Substring(0, wrapAt));
process = process.Remove(0, wrapAt + 1);
}
foreach (string wrap in wrapped) {
Console.WriteLine(wrap);
}
Console.WriteLine(process);
}
}
This will take a string and return a list of strings each no longer than 80 characters):
var words = text.Split(' ');
var lines = words.Skip(1).Aggregate(words.Take(1).ToList(), (l, w) =>
{
if (l.Last().Length + w.Length >= 80)
l.Add(w);
else
l[l.Count - 1] += " " + w;
return l;
});
Starting with this text:
var text = "Hundreds of South Australians will come out to cosplay when Oz Comic Con hits town this weekend with guest stars including the actor who played Freddy Krueger (A Nightmare on Elm Street) and others from shows such as Game of Thrones and Buffy the Vampire Slayer.";
I get this result:
Hundreds of South Australians will come out to cosplay when Oz Comic Con hits
town this weekend with guest stars including the actor who played Freddy Krueger
(A Nightmare on Elm Street) and others from shows such as Game of Thrones and
Buffy the Vampire Slayer.
Coded in a couple of minutes, it will actually break with words that have more than 80 characters and doesn't take into consideration the Console.WindowWidth
private static void EpicWriteLine(String text)
{
String[] words = text.Split(' ');
StringBuilder buffer = new StringBuilder();
foreach (String word in words)
{
buffer.Append(word);
if (buffer.Length >= 80)
{
String line = buffer.ToString().Substring(0, buffer.Length - word.Length);
Console.WriteLine(line);
buffer.Clear();
buffer.Append(word);
}
buffer.Append(" ");
}
Console.WriteLine(buffer.ToString());
}
It's also not very optimized on both CPU and Memory. I wouldn't use this in any serious context.
This should work, although it can probably be shortened some more:
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]);
}
}
It may be hard to understand, so basically what this does:
Trim() removes spaces at the start and the end.
The Regex() replaces multiple spaces with one space.
The for loop takes the first (Console.WindowWidth - 1) characters from the paragraph and sets it as a new line.
The `LastIndexOf()1 tries to find the last space in the line. If there isn't one, it leaves the line as it is.
This line is removed from the paragraph, and the loop repeats.
Note: The regex was taken from here.
Note 2: I don't think it replaces tabs.
You can use CsConsoleFormat† to write strings to console with word wrap. It's actually the default text wrapping mode (but it can be changed to character wrap or no wrap).
var doc = new Document().AddChildren(
"2. I have bugtested this quite a bit however if something "
+ "goes wrong and the program crashes just restart it."
);
ConsoleRenderer.RenderDocument(doc);
You can also have an actual list with margin for numbers:
var docList = new Document().AddChildren(
new List().AddChildren(
new Div("I have not bugtested this enough so if something "
+ "goes wrong and the program crashes good luck with it."),
new Div("I have bugtested this quite a bit however if something "
+ "goes wrong and the program crashes just restart it.")
)
);
ConsoleRenderer.RenderDocument(docList);
Here's what it looks like:
† CsConsoleFormat was developed by me.
If you have a word (like a path) greater than screen width, you also needs word wrap.
using System;
using System.Text;
namespace Colorify.Terminal
{
public static class Wrapper
{
static int _screenWidth { get; set; }
public static void Text(string text)
{
StringBuilder line = new StringBuilder();
string[] words = text.Split(' ');
_screenWidth = (Console.WindowWidth - 3);
foreach (var item in words)
{
Line(ref line, item);
Item(ref line, item);
}
if (!String.IsNullOrEmpty(line.ToString().Trim()))
{
Out.WriteLine($"{line.ToString().TrimEnd()}");
}
}
static void Line(ref StringBuilder line, string item)
{
if (
((line.Length + item.Length) >= _screenWidth) ||
(line.ToString().Contains(Environment.NewLine))
)
{
Out.WriteLine($"{line.ToString().TrimEnd()}");
line.Clear();
}
}
static void Item(ref StringBuilder line, string item)
{
if (item.Length >= _screenWidth)
{
if (line.Length > 0)
{
Out.WriteLine($" {line.ToString().TrimEnd()}");
line.Clear();
}
int chunkSize = item.Length - _screenWidth;
string chunk = item.Substring(0, _screenWidth);
line.Append($"{chunk} ");
Line(ref line, item);
item = item.Substring(_screenWidth, chunkSize);
Item(ref line, item);
}
else
{
line.Append($"{item} ");
}
}
}
}
Its already implemented on Colorify - C# NETCore Console Library with Text Format: colors, alignment and lot more [ for Win, Mac & Linux ]
You should probably be able to utilize Console.WindowWidth with some look-ahead newline logic to make this happen.
I have a problem while trying to replace all text matching a particular word in a rich text box. This is the code i use
public static void ReplaceAll(RichTextBox myRtb, string word, string replacer)
{
int index = 0;
while (index < myRtb.Text.LastIndexOf(word))
{
int location = myRtb.Find(word, index, RichTextBoxFinds.None);
myRtb.Select(location, word.Length);
myRtb.SelectedText = replacer;
index++;
}
MessageBox.Show(index.ToString());
}
private void btnReplaceAll_Click(object sender, EventArgs e)
{
Form1 text = (Form1)Application.OpenForms["Form1"];
ReplaceAll(text.Current, txtFind2.Text, txtReplace.Text);
}
This works well but i have noticed a little malfunction when i try to replace a letter with itself and another letter.
For example i want to replace all the e in Welcome to Nigeria with ea.
This is what i get Weaalcomeaaaaaaa to Nigeaaaaaaaaaaaaaaria.
And the message box shows 23 when there are only three e. Pls what am i doing wrong and how can i correct it
Simply do this:
yourRichTextBox.Text = yourRichTextBox.Text.Replace("e","ea");
If you want to report the number of matches (which are replaced), you can try using Regex like this:
MessageBox.Show(Regex.Matches(yourRichTextBox.Text, "e").Count.ToString());
UPDATE
Of course, using the method above has expensive cost in memory, you can use some loop in combination with Regex to achieve some kind of advanced replacing engine like this:
public void ReplaceAll(RichTextBox myRtb, string word, string replacement){
int i = 0;
int n = 0;
int a = replacement.Length - word.Length;
foreach(Match m in Regex.Matches(myRtb.Text, word)){
myRtb.Select(m.Index + i, word.Length);
i += a;
myRtb.SelectedText = replacement;
n++;
}
MessageBox.Show("Replaced " + n + " matches!");
}
Question
How do I convert the string "Européen" to the RTF-formatted string "Europ\'e9en"?
[TestMethod]
public void Convert_A_Word_To_Rtf()
{
// Arrange
string word = "Européen";
string expected = "Europ\'e9en";
string actual = string.Empty;
// Act
// actual = ... // How?
// Assert
Assert.AreEqual(expected, actual);
}
What I have found so far
RichTextBox
RichTextBox can be used for certain things. Example:
RichTextBox richTextBox = new RichTextBox();
richTextBox.Text = "Européen";
string rtfFormattedString = richTextBox.Rtf;
But then rtfFormattedString turns out to be the entire RTF-formatted document, not just the string "Europ\'e9en".
Stackoverflow
Insert string with special characters into RTF
How to output unicode string to RTF (using C#)
Output RTF special characters to Unicode
Convert Special Characters for RTF (iPhone)
Google
I've also found a bunch of other resources on the web, but nothing quite solved my problem.
Answer
Brad Christie's answer
Had to add Trim() to remove the preceeding space in result. Other than that, Brad Christie's solution seems to work.
I'll run with this solution for now even though I have a bad gut feeling since we have to SubString and Trim the heck out of RichTextBox to get a RTF-formatted string.
Test case:
[TestMethod]
public void Test_To_Verify_Brad_Christies_Stackoverflow_Answer()
{
Assert.AreEqual(#"Europ\'e9en", "Européen".ConvertToRtf());
Assert.AreEqual(#"d\'e9finitif", "définitif".ConvertToRtf());
Assert.AreEqual(#"\'e0", "à".ConvertToRtf());
Assert.AreEqual(#"H\'e4user", "Häuser".ConvertToRtf());
Assert.AreEqual(#"T\'fcren", "Türen".ConvertToRtf());
Assert.AreEqual(#"B\'f6den", "Böden".ConvertToRtf());
}
Logic as an extension method:
public static class StringExtensions
{
public static string ConvertToRtf(this string value)
{
RichTextBox richTextBox = new RichTextBox();
richTextBox.Text = value;
int offset = richTextBox.Rtf.IndexOf(#"\f0\fs17") + 8; // offset = 118;
int len = richTextBox.Rtf.LastIndexOf(#"\par") - offset;
string result = richTextBox.Rtf.Substring(offset, len).Trim();
return result;
}
}
Doesn't RichTextBox always have the same header/footer? You could just read the content based on off-set location, and continue using it to parse. (I think? please correct me if I'm wrong)
There are libraries available, but I've never had good luck with them personally (though always just found another method before fully exhausting the possibilities). In addition, most of the better ones are usually include a nominal fee.
EDIT
Kind of a hack, but this should get you through what you need to get through (I hope):
RichTextBox rich = new RichTextBox();
Console.Write(rich.Rtf);
String[] words = { "Européen", "Apple", "Carrot", "Touché", "Résumé", "A Européen eating an apple while writing his Résumé, Touché!" };
foreach (String word in words)
{
rich.Text = word;
Int32 offset = rich.Rtf.IndexOf(#"\f0\fs17") + 8;
Int32 len = rich.Rtf.LastIndexOf(#"\par") - offset;
Console.WriteLine("{0,-15} : {1}", word, rich.Rtf.Substring(offset, len).Trim());
}
EDIT 2
The breakdown of the codes RTF control code are as follows:
Header
\f0 - Use the 0-index font (first font in the list, which is typically Microsoft Sans Serif (noted in the font table in the header: {\fonttbl{\f0\fnil\fcharset0 Microsoft Sans Serif;}}))
\fs17 - Font formatting, specify the size is 17 (17 being in half-points)
Footer
\par is specifying that it's the end of a paragraph.
Hopefully that clears some things up. ;-)
I found a nice solution that actually uses the RichTextBox itself to do the conversion:
private static string FormatAsRTF(string DirtyText)
{
System.Windows.Forms.RichTextBox rtf = new System.Windows.Forms.RichTextBox();
rtf.Text = DirtyText;
return rtf.Rtf;
}
http://www.baltimoreconsulting.com/blog/development/easily-convert-a-string-to-rtf-in-net/
This is how I went:
private string ConvertString2RTF(string input)
{
//first take care of special RTF chars
StringBuilder backslashed = new StringBuilder(input);
backslashed.Replace(#"\", #"\\");
backslashed.Replace(#"{", #"\{");
backslashed.Replace(#"}", #"\}");
//then convert the string char by char
StringBuilder sb = new StringBuilder();
foreach (char character in backslashed.ToString())
{
if (character <= 0x7f)
sb.Append(character);
else
sb.Append("\\u" + Convert.ToUInt32(character) + "?");
}
return sb.ToString();
}
I think using a RichTextBox is:
1) overkill
2) I don't like RichTextBox after spending days of trying to make it work with an RTF document created in Word.
Below is an ugly example of converting a string to an RTF string:
class Program
{
static RichTextBox generalRTF = new RichTextBox();
static void Main()
{
string foo = #"Européen";
string output = ToRtf(foo);
Trace.WriteLine(output);
}
private static string ToRtf(string foo)
{
string bar = string.Format("!!##!!{0}!!##!!", foo);
generalRTF.Text = bar;
int pos1 = generalRTF.Rtf.IndexOf("!!##!!");
int pos2 = generalRTF.Rtf.LastIndexOf("!!##!!");
if (pos1 != -1 && pos2 != -1 && pos2 > pos1 + "!!##!!".Length)
{
pos1 += "!!##!!".Length;
return generalRTF.Rtf.Substring(pos1, pos2 - pos1);
}
throw new Exception("Not sure how this happened...");
}
}
I know it has been a while, hope this helps..
This code is working for me after trying every conversion code I could put my hands on:
titleText and contentText are simple text filled in a regular TextBox
var rtb = new RichTextBox();
rtb.AppendText(titleText)
rtb.AppendText(Environment.NewLine);
rtb.AppendText(contentText)
rtb.Refresh();
rtb.rtf now holds the rtf text.
The following code will save the rtf text and allow you to open the file, edit it and than load it back into a RichTextBox back again:
rtb.SaveFile(path, RichTextBoxStreamType.RichText);
Here's improved #Vladislav Zalesak's answer:
public static string ConvertToRtf(string text)
{
// using default template from wiki
StringBuilder sb = new StringBuilder(#"{\rtf1\ansi\ansicpg1250\deff0{\fonttbl\f0\fswiss Helvetica;}\f0\pard ");
foreach (char character in text)
{
if (character <= 0x7f)
{
// escaping rtf characters
switch (character)
{
case '\\':
case '{':
case '}':
sb.Append('\\');
break;
case '\r':
sb.Append("\\par");
break;
}
sb.Append(character);
}
// converting special characters
else
{
sb.Append("\\u" + Convert.ToUInt32(character) + "?");
}
}
sb.Append("}");
return sb.ToString();
}
Not the most elegant, but quite optimal and fast method:
public static string PlainTextToRtf(string plainText)
{
if (string.IsNullOrEmpty(plainText))
return "";
string escapedPlainText = plainText.Replace(#"\", #"\\").Replace("{", #"\{").Replace("}", #"\}");
escapedPlainText = EncodeCharacters(escapedPlainText);
string rtf = #"{\rtf1\ansi\ansicpg1250\deff0{\fonttbl\f0\fswiss Helvetica;}\f0\pard ";
rtf += escapedPlainText.Replace(Environment.NewLine, "\\par\r\n ") + ;
rtf += " }";
return rtf;
}
.
Encode characters (Polish ones) method:
private static string EncodeCharacters(string text)
{
if (string.IsNullOrEmpty(text))
return "";
return text
.Replace("ą", #"\'b9")
.Replace("ć", #"\'e6")
.Replace("ę", #"\'ea")
.Replace("ł", #"\'b3")
.Replace("ń", #"\'f1")
.Replace("ó", #"\'f3")
.Replace("ś", #"\'9c")
.Replace("ź", #"\'9f")
.Replace("ż", #"\'bf")
.Replace("Ą", #"\'a5")
.Replace("Ć", #"\'c6")
.Replace("Ę", #"\'ca")
.Replace("Ł", #"\'a3")
.Replace("Ń", #"\'d1")
.Replace("Ó", #"\'d3")
.Replace("Ś", #"\'8c")
.Replace("Ź", #"\'8f")
.Replace("Ż", #"\'af");
}