Easiest way to create a Regex with multiple scenario - c#

I have function where I have 4 Regex scenario for search tire width..
string sizeWidthRgx = #"(\d{3})[\s\/]\d{2}[\s\/R]?[\s\/]\d{2}";
string sizeWidthRgxSecond = #"(\d{3})[\s\/ZR]?\s?\d{2}";
string sizeWidthRgxThird = #"(\d{2})\s?X?x?\s?\d{1,2}[,.]\d{2}";
string sizeWidthRgxFourth = #"(\d{3})[\s\/]\d{2}[\s\/]?R\d{2}";
var matchSizeWidth = Regex.Match(sizeWidthUpper, sizeWidthRgx);
var matchSizeWidthOther = Regex.Match(sizeWidthUpper, sizeWidthRgxSecond);
var matchSizeWidthThird = Regex.Match(sizeWidthUpper, sizeWidthRgxThird);
var matchSizeWidthFourth = Regex.Match(sizeWidthUpper, sizeWidthRgxFourth);
Someone have idea how i can join this all regex to one or other way to search this parametr.
For example:
(7.50)/80 R16, (305)/35 R24, (31)X10.50R15, (175)/65/14.
Thats all scenario i need to take all number from '()'
#Edit
Here is my code:
var sizeWidthUpper = productName.Trim().ToUpper();
string sizeWidthRgx = #"(\d{3})[\s\/]\d{2}[\s\/R]?[\s\/]\d{2}"; // 1 scenariusz
string sizeWidthRgxSecond = #"(\d{3})[\s\/ZR]?\s?\d{2}"; // 2 scenariusz
string sizeWidthRgxThird = #"(\d{2})\s?X?x?\s?\d{1,2}[,.]\d{2}"; //3 scenariusz, np. BARUM BRAVURIS 4X4 31X10.50R15 109 S FR
string sizeWidthRgxFourth = #"(\d{3})[\s\/]\d{2}[\s\/]?R\d{2}";
var matchSizeWidth = Regex.Match(sizeWidthUpper, sizeWidthRgx);
var matchSizeWidthOther = Regex.Match(sizeWidthUpper, sizeWidthRgxSecond);
var matchSizeWidthThird = Regex.Match(sizeWidthUpper, sizeWidthRgxThird);
var matchSizeWidthFourth = Regex.Match(sizeWidthUpper, sizeWidthRgxFourth);
int outSizeWidth = 0;
if(matchSizeWidth.Success)
{
if (int.TryParse(matchSizeWidth.Groups[1].Value, out outSizeWidth))
{
if ((outSizeWidth >= 125) && (outSizeWidth <= 335))
{
if ((outSizeWidth % 5) == 0) return outSizeWidth.ToString();
}
}
}
if (matchSizeWidthFourth.Success)
{
if (int.TryParse(matchSizeWidthFourth.Groups[1].Value, out outSizeWidth))
{
if ((outSizeWidth >= 125) && (outSizeWidth <= 335))
{
if ((outSizeWidth % 5) == 0) return outSizeWidth.ToString();
}
}
}
if(matchSizeWidthOther.Success)
{
if (int.TryParse(matchSizeWidthOther.Groups[1].Value, out outSizeWidth))
{
if ((outSizeWidth >= 125) && (outSizeWidth <= 335))
{
if ((outSizeWidth % 5) == 0) return outSizeWidth.ToString();
}
}
}
if(matchSizeWidthThird.Success)
{
if (int.TryParse(matchSizeWidthThird.Groups[1].Value, out outSizeWidth))
{
if ((outSizeWidth >= 30) && (outSizeWidth <= 37)) return outSizeWidth.ToString();
}
}
return string.Empty;
It works but I need to know can I shorten this code?

You can wrap each pattern in a named group ((?<name>...)), for instance:
string sizeWidthRgx = #"(?<width>(\d{3})[\s\/]\d{2}[\s\/R]?[\s\/]\d{2})";
Make sure that you assign a unique name to each sub-pattern. Then you can combine them all into one pattern separating them with or pipes:
IEnumerable<Match> matches = Regex.Matches(sizeWidthUpper, sizeWidthRgx + "|" + sizeWidthRgxSecond + "|" + sizeWidthRgxThird + "|" + sizeWidthRgxFourth);
Then you can loop through the matches and look to see if the named group exists in each of the matches (given that pattern, each match will only contain one group):
string matchSizeWidth = null;
string matchSizeWidthOther = null;
string matchSizeWidthThird = null;
string matchSizeWidthFourth = null;
foreach(Match m in matches)
{
Group g = m.Groups("width");
if(g.Success) matchSizeWidth = g.Value;
g = m.Groups("other");
if(g.Success) matchSizeWidthOther = g.Value;
g = m.Groups("third");
if(g.Success) matchSizeWidthThird = g.Value;
g = m.Groups("fourth");
if(g.Success) matchSizeWidthFourth = g.Value;
}

For your point:
Thats all scenario i need to take all number from '()'
You can just use this regex as well:
\((\d+(?:\.\d+)?)\)
Demo
To combine all regexes you can use | token which works as an OR condition:
regexpattern1|regexpattern2|regexpattern3

You could likely combine the patterns you've got into:
\(([0-9.]+)\)[X\/]([0-9.]+)[R\/\s]+([0-9]+)
This divides each numerical value into a group, which will then result in three groups.
Results:
7.50/80 R16
305/35 R24
31/10.50 R15
175/65 R14
Code:
string s = "(7.50)/80 R16, (305)/35 R24, (31)X10.50R15, (175)/65/14.";
string p = "\\(([0-9.]+)\\)[X\\/]([0-9.]+)[R\\/\\s]+([0-9]+)";
MatchCollection mc = Regex.Matches(s, p);
foreach (Match m in mc) {
Console.WriteLine("{0}/{1} R{2}", m.Groups[1], m.Groups[2], m.Groups[3]);
}
Example:
https://regex101.com/r/bF1rH0/1

Related

How to parse nested parenthesis only in first level in C#

I would like to write C# code that parses nested parenthesis to array elements, but only on first level. An example is needed for sure:
I want this string:
"(example (to (parsing nested paren) but) (first lvl only))"
tp be parsed into:
["example", "(to (parsing nested paren) but)", "(first lvl only)"]
I was thinking about using regex but can't figure out how to properly use them without implementing this behaviour from scratch.
In the case of malformed inputs I would like to return an empty array, or an array ["error"]
I developed a parser for your example. I also checked some other examples which you can see in the code.
using System;
using System.Collections;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
string str = "(example (to (parsing nested paren) but) (first lvl only))"; // => [example , (to (parsing nested paren) but) , (first lvl only)]
//string str = "(first)(second)(third)"; // => [first , second , third]
//string str = "(first(second)third)"; // => [first , (second) , third]
//string str = "(first(second)(third)fourth)"; // => [first , (second) , (third) , fourth]
//string str = "(first((second)(third))fourth)"; // => [first , ((second)(third)) , fourth]
//string str = "just Text"; // => [ERROR]
//string str = "start with Text (first , second)"; // => [ERROR]
//string str = "(first , second) end with text"; // => [ERROR]
//string str = ""; // => [ERROR]
//string str = "("; // => [ERROR]
//string str = "(first()(second)(third))fourth)"; // => [ERROR]
//string str = "(((extra close pareanthese))))"; // => [ERROR]
var res = Parser.parse(str);
showRes(res);
}
static void showRes(ArrayList res)
{
var strings = res.ToArray();
var theString = string.Join(" , ", strings);
Console.WriteLine("[" + theString + "]");
}
}
public class Parser
{
static Dictionary<TokenType, TokenType> getRules()
{
var rules = new Dictionary<TokenType, TokenType>();
rules.Add(TokenType.OPEN_PARENTHESE, TokenType.START | TokenType.OPEN_PARENTHESE | TokenType.CLOSE_PARENTHESE | TokenType.SIMPLE_TEXT);
rules.Add(TokenType.CLOSE_PARENTHESE, TokenType.SIMPLE_TEXT | TokenType.CLOSE_PARENTHESE);
rules.Add(TokenType.SIMPLE_TEXT, TokenType.SIMPLE_TEXT | TokenType.CLOSE_PARENTHESE | TokenType.OPEN_PARENTHESE);
rules.Add(TokenType.END, TokenType.CLOSE_PARENTHESE);
return rules;
}
static bool isValid(Token prev, Token cur)
{
var rules = Parser.getRules();
return rules.ContainsKey(cur.type) && ((prev.type & rules[cur.type]) == prev.type);
}
public static ArrayList parse(string sourceText)
{
ArrayList result = new ArrayList();
int openParenthesesCount = 0;
Lexer lexer = new Lexer(sourceText);
Token prevToken = lexer.getStartToken();
Token currentToken = lexer.readNextToken();
string tmpText = "";
while (currentToken.type != TokenType.END)
{
if (currentToken.type == TokenType.OPEN_PARENTHESE)
{
openParenthesesCount++;
if (openParenthesesCount > 1)
{
tmpText += currentToken.token;
}
}
else if (currentToken.type == TokenType.CLOSE_PARENTHESE)
{
openParenthesesCount--;
if (openParenthesesCount < 0)
{
return Parser.Error();
}
if (openParenthesesCount > 0)
{
tmpText += currentToken.token;
}
}
else if (currentToken.type == TokenType.SIMPLE_TEXT)
{
tmpText += currentToken.token;
}
if (!Parser.isValid(prevToken, currentToken))
{
return Parser.Error();
}
if (openParenthesesCount == 1 && tmpText.Trim() != "")
{
result.Add(tmpText);
tmpText = "";
}
prevToken = currentToken;
currentToken = lexer.readNextToken();
}
if (openParenthesesCount != 0)
{
return Parser.Error();
}
if (!Parser.isValid(prevToken, currentToken))
{
return Parser.Error();
}
if (tmpText.Trim() != "")
{
result.Add(tmpText);
}
return result;
}
static ArrayList Error()
{
var er = new ArrayList();
er.Add("ERROR");
return er;
}
}
class Lexer
{
string _txt;
int _index;
public Lexer(string text)
{
this._index = 0;
this._txt = text;
}
public Token getStartToken()
{
return new Token(-1, TokenType.START, "");
}
public Token readNextToken()
{
if (this._index >= this._txt.Length)
{
return new Token(-1, TokenType.END, "");
}
Token t = null;
string txt = "";
if (this._txt[this._index] == '(')
{
txt = "(";
t = new Token(this._index, TokenType.OPEN_PARENTHESE, txt);
}
else if (this._txt[this._index] == ')')
{
txt = ")";
t = new Token(this._index, TokenType.CLOSE_PARENTHESE, txt);
}
else
{
txt = this._readText();
t = new Token(this._index, TokenType.SIMPLE_TEXT, txt);
}
this._index += txt.Length;
return t;
}
private string _readText()
{
string txt = "";
int i = this._index;
while (i < this._txt.Length && this._txt[i] != '(' && this._txt[i] != ')')
{
txt = txt + this._txt[i];
i++;
}
return txt;
}
}
class Token
{
public int position
{
get;
private set;
}
public TokenType type
{
get;
private set;
}
public string token
{
get;
private set;
}
public Token(int position, TokenType type, string token)
{
this.position = position;
this.type = type;
this.token = token;
}
}
[Flags]
enum TokenType
{
START = 1,
OPEN_PARENTHESE = 2,
SIMPLE_TEXT = 4,
CLOSE_PARENTHESE = 8,
END = 16
}
well, regex will do the job:
var text = #"(example (to (parsing nested paren) but) (first lvl only))";
var pattern = #"\(([\w\s]+) (\([\w\s]+ \([\w\s]+\) [\w\s]+\)) (\([\w\s]+\))\)*";
try
{
Regex r = new Regex(pattern, RegexOptions.IgnoreCase);
Match m = r.Match(text);
string group_1 = m.Groups[1].Value; //example
string group_2 = m.Groups[2].Value; //(to (parsing nested paren) but)
string group_3 = m.Groups[3].Value; //(first lvl only)
return new string[]{group_1,group_2,group_3};
}
catch(Exception ex){
return new string[]{"error"};
}
hopefully this helps, tested here in dotnetfiddle
Edit:
this might get you started into building the right expression according to whatever patterns you are falling into and maybe build a recursive function to parse the rest into the desired output :)
RegEx is not recursive. You either count bracket level, or recurse.
An non-recursive parser loop I tested for the example you show is..
string SplitFirstLevel(string s)
{
List<string> result = new List<string>();
int p = 0, level = 0;
for (int i = 0; i < s.Length; i++)
{
if (s[i] == '(')
{
level++;
if (level == 1) p = i + 1;
if (level == 2)
{
result.Add('"' + s.Substring(p, i - p) + '"');
p = i;
}
}
if (s[i] == ')')
if (--level == 0)
result.Add('"' + s.Substring(p, i - p) + '"');
}
return "[" + String.Join(",", result) + "]";
}
Note: after some more testing, I see your specification is unclear. How to delimit orphaned level 1 terms, that is terms without bracketing ?
For example, my parser translates
(example (to (parsing nested paren) but) (first lvl only))
to:
["example ","(to (parsing nested paren) but) ","(first lvl only)"]
and
(example (to (parsing nested paren)) but (first lvl only))
to:
["example ","(to (parsing nested paren)) but ","(first lvl only)"]
In either case, "example" gets a separate term, while "but" is grouped with the first term. In the first example this is logical, it is in the bracketing, but it may be unwanted behaviour in the second case, where "but" should be separated, like "example", which also has no bracketing (?)

String formatting in C#?

I have some problems to format strings from a List<string>
Here's a picture of the List values:
Now I managed to manipulate some of the values but others not, here's what I used to manipulate:
string prepareStr(string itemToPrepare) {
string first = string.Empty;
string second = string.Empty;
if (itemToPrepare.Contains("\"")) {
first = itemToPrepare.Replace("\"", "");
}
if (first.Contains("-")) {
int beginIndex = first.IndexOf("-");
second = first.Remove(beginIndex, first.Length - beginIndex);
}
return second;
}
Here's a picture of the Result:
I need to get the clear Path without the (-startup , -minimzed , MSRun , double apostrophes).
What am I doing wrong here?
EDIT my updated code:
void getStartUpEntries() {
var startEntries = StartUp.getStartUp();
if (startEntries != null && startEntries.Count != 0) {
for (int i = 0; i < startEntries.Count; i++) {
var splitEntry = startEntries[i].Split(new string[] { "||" }, StringSplitOptions.None);
var str = splitEntry[1];
var match = Regex.Match(str, #"\|\|""(?<path>(?:\""|[^""])*)""");
var finishedPath = match.Groups["path"].ToString();
if (!string.IsNullOrEmpty(finishedPath)) {
if (File.Exists(finishedPath) || Directory.Exists(finishedPath)) {
var _startUpObj = new StartUp(splitEntry[0], finishedPath,
"Aktiviert: ", new Uri("/Images/inWatch.avOK.png", UriKind.RelativeOrAbsolute),
StartUp.getIcon(finishedPath));
_startUpList.Add(_startUpObj);
}
else {
var _startUpObjNo = new StartUp(splitEntry[0], finishedPath,
"Aktiviert: ", new Uri("/Images/inWatch.avOK.png", UriKind.RelativeOrAbsolute),
StartUp.getIcon(string.Empty));
_startUpList.Add(_startUpObjNo);
}
}
var _startUpObjLast = new StartUp(splitEntry[0], splitEntry[1],
"Aktiviert: ", new Uri("/Images/inWatch.avOK.png", UriKind.RelativeOrAbsolute),
StartUp.getIcon(string.Empty));
_startUpList.Add(_startUpObjLast);
}
lstStartUp.ItemsSource = _startUpList.OrderBy(item => item.Name).ToList();
}
You could use a regex to extract the path:
var str = #"0Raptr||""C:\Program Files (x86)\Raptr\raptrstub.exe"" --startup"
var match = Regex.Match(str, #"\|\|""(?<path>(?:\""|[^""])*)""");
Console.WriteLine(match.Groups["path"]);
This will match any (even empty) text (either an escaped quote, or any character which is not a quote) between two quote characters preceeded by two pipe characters.
Similarly, you could simply split on the double quotes as I see that's a repeating occurrence in your examples and take the second item in the split array:
var path = new Regex("\"").Split(s)[1];
This is and update to your logic without using any Regex:
private string prepareStr(string itemToPrepare)
{
string result = null;
string startString = #"\""";
string endString = #"\""";
int startPoint = itemToPrepare.IndexOf(startString);
if (startPoint >= 0)
{
startPoint = startPoint + startString.Length;
int EndPoint = itemToPrepare.IndexOf(endString, startPoint);
if (EndPoint >= 0)
{
result = itemToPrepare.Substring(startPoint, EndPoint - startPoint);
}
}
return result;
}

How to Get Data from index of String

I'm new in c#. and I have some Question...
I have String following this code
string taxNumber = "1222233333445";
I want to get data from This string like that
string a = "1"
string b = "2222"
string c = "33333"
string d = "44"
string e = "5"
Please Tell me about Method for get Data From String.
Thank You Very Much ^^
Use the String.Substring(int index, int length) method
string a = taxNumber.Substring(0, 1);
string b = taxNumber.Substring(1, 4);
// etc
Oh well, the best I can come up with is this:
IEnumerable<string> numbers
= taxNumber.ToCharArray()
.Distinct()
.Select(c => new string(c, taxNumber.Count(t => t == c)));
foreach (string numberGroup in numbers)
{
Console.WriteLine(numberGroup);
}
Outputs:
1
2222
33333
44
5
This can also do , you dont need to fix the no of characters, you can check by changing the no of 1's , 2's etc
string taxNumber = "1222233333445";
string s1 = taxNumber.Substring(taxNumber.IndexOf("1"), ((taxNumber.Length - taxNumber.IndexOf("1")) - (taxNumber.Length - taxNumber.LastIndexOf("1"))) + 1);
string s2 = taxNumber.Substring(taxNumber.IndexOf("2"), ((taxNumber.Length - taxNumber.IndexOf("2")) - (taxNumber.Length - taxNumber.LastIndexOf("2"))) + 1);
string s3 = taxNumber.Substring(taxNumber.IndexOf("3"), ((taxNumber.Length - taxNumber.IndexOf("3")) - (taxNumber.Length - taxNumber.LastIndexOf("3"))) + 1);
You can use Char.IsDigit to identify digits out of string, and may apply further logic as follows:
for (int i=0; i< taxNumber.Length; i++)
{
if (Char.IsDigit(taxNumber[i]))
{
if(taxNumber[i-1]==taxNumber[i])
{
/*Further assign values*/
}
}
Try this Code
string taxNumber = "1222233333445";
char[] aa = taxNumber.ToCharArray();
List<string> finals = new List<string>();
string temp = string.Empty;
for (int i = 0; i < aa.Length; i++)
{
if (i == 0)
{
temp = aa[i].ToString();
}
else
{
if (aa[i].ToString() == aa[i - 1].ToString())
{
temp += aa[i];
}
else
{
if (temp != string.Empty)
{
finals.Add(temp);
temp = aa[i].ToString();
}
}
if (i == aa.Length - 1)
{
if (aa[i].ToString() != aa[i - 1].ToString())
{
temp = aa[i].ToString();
finals.Add(temp);
}
else
{
finals.Add(temp);
}
}
}
}
and check value of finals string list
you may use regex:
string strRegex = #"(1+|2+|3+|4+|5+|6+|7+|8+|9+|0+)";
RegexOptions myRegexOptions = RegexOptions.None;
Regex myRegex = new Regex(strRegex, myRegexOptions);
string strTargetString = #"1222233333445";
return myRegex.Split(strTargetString);

c# regex groups

How convert string to groups
STRING:
TableStart:Name="valueName";ClassName="valueClassName";MasterDatasource="valueMasterDatasource";Condition="valueCondition";ConditionParams="valueConditionParams";OrderBy="valueOrderBy"
OUTPUT GROUPS:
Name = "valueName"
ClassName = "valueClassName"
MasterDatasource = "valueMasterDatasource"
Condition = "valueCondition"
ConditionParams = "valueConditionParams"
OrderBy = "valueOrderBy"
Regex?
TableStart:Name=(?<Name>".*");ClassName=(?<ClassName>".*");MasterDatasource=(?<MasterDatasource>".*");Condition=(?<Condition>".*");ConditionParams=(?<ConditionParams>".*");OrderBy=(?<OrderBy>".*")
Try to use this snippet:
var s = "TableStart:Name=\"valueName\";ClassName=\"valueClassName\";MasterDatasource=\"valueMas" +
"terDatasource\";Condition=\"valueCondition\";ConditionParams=\"valueConditionParams\"" +
";OrderBy=\"valueOrderBy\"";
var reg = new Regex("TableStart:Name=(?<Name>\".*\");ClassName=(?<ClassName>\".*\");MasterDatasource=(?<Ma" +
"sterDatasource>\".*\");Condition=(?<Condition>\".*\");ConditionParams=(?<ConditionPa" +
"rams>\".*\");OrderBy=(?<OrderBy>\".*\")");
Match match = reg.Match(s);
GroupCollection groups = match.Groups;
for (int i = 1; i < groups.Count; i++)
{
Console.WriteLine("{0} = {1}", reg.GroupNameFromNumber(i), groups[i].Value);
}
prints:
Name = "valueName"
ClassName = "valueClassName"
MasterDatasource = "valueMasterDatasource"
Condition = "valueCondition"
ConditionParams = "valueConditionParams"
OrderBy = "valueOrderBy"
Edit: or you can use a much concise and shorter regexp
var reg = new Regex("\\w+\\=\"\\w+\"");
foreach (Match match in reg.Matches(s))
{
var values = match.Value.Split('=');
Console.WriteLine ("{0} = {1}",values[0], values[1]);
}
prints the same.
A regex-less alternative:
string input = "TableStart:Name=\"valueName\";ClassName=\"valueClassName\";MasterDatasource=\"valueMasterDatasource\";Condition=\"valueCondition\";ConditionParams=\"valueConditionParams\";OrderBy=\"valueOrderBy\"";
input = input.Replace("TableStart:", "");
string[] nameValues = input.Split(';');
foreach (string nameValue in nameValues){
string[] couple = nameValue.Split('=');
string name = couple[0];
string value = couple[1].Trim('"');
Console.WriteLine("{0} = {1}", name, value);
}

Is there a better way to implement Shift+Tab or Decrease Indent?

this is how i implemented Shift-Tab or decrease indent... the result on screenr
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift && e.Key == Key.Tab)
{
// Shift+Tab
int selStart = txtEditor.SelectionStart;
int selLength = txtEditor.SelectionLength;
string selText = txtEditor.SelectedText;
string text = txtEditor.Text;
// find new lines that are followed by 1 or more spaces
Regex regex = new Regex(Environment.NewLine + #"(\s+)");
Match m = regex.Match(selText);
string spaces;
while (m.Success)
{
GroupCollection grps = m.Groups;
spaces = grps[1].Value;
int i = 0;
// remove 1 space on each loop to a max of 4 spaces
while (i < 4 && spaces.Length > 0)
{
spaces = spaces.Remove(0, 1);
i++;
}
// update spaces in selText
selText = selText.Remove(grps[1].Index, grps[1].Length).Insert(grps[1].Index, spaces);
m = regex.Match(selText, grps[1].Index + spaces.Length);
}
// commit changes to selText to text
text = text.Remove(selStart, selLength).Insert(selStart, selText);
// decrease indent of 1st line
// - find 1st character of selection
regex = new Regex(#"\w");
m = regex.Match(text, selStart);
int start = selStart;
if (m.Success) {
start = m.Index;
}
// - start search for spaces
regex = new Regex(Environment.NewLine + #"(\s+)", RegexOptions.RightToLeft);
m = regex.Match(text, start);
if (m.Success) {
spaces = m.Groups[1].Value;
int i = 0;
while (i < 4 && spaces.Length > 0) {
spaces = spaces.Remove(0, 1); // remove 1 space
i++;
}
text = text.Remove(m.Groups[1].Index, m.Groups[1].Length).Insert(m.Groups[1].Index, spaces);
selStart = m.Groups[1].Index;
}
txtEditor.Text = text;
txtEditor.SelectionStart = selStart;
txtEditor.SelectionLength = selText.Length;
e.Handled = true;
}
the code looks messy and i wonder if theres a better way.
Personally, I wouldn't use Regex for this.
Untested, probably needs modification:
public static class StringExtensions
{
// Removes leading white-spaces in a string up to a maximum
// of 'level' characters
public static string ReduceIndent(this string line, int level)
{
// Produces an IEnumerable<char> with the characters
// of the string verbatim, other than leading white-spaces
var unindentedChars = line.SkipWhile((c, index) => char.IsWhiteSpace(c) && index < level);
return new string(unindentedChars.ToArray());
}
// Applies a transformation to each line of a string and returns the
// transformed string
public static string LineTransform(this string text, Func<string,string> transform)
{
//Splits the string into an array of lines
var lines = text.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
//Applies the transformation to each line
var transformedLines = lines.Select(transform);
//Joins the transformed lines into a new string
return string.Join(Environment.NewLine, transformedLines.ToArray());
}
}
...
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift && e.Key == Key.Tab)
{
// Reduces the indent level of the selected text by applying the
// 'ReduceIndent' transformation to each line of the text.
string replacement = txtEditor.SelectedText
.LineTransform(line => line.ReduceIndent(4));
int selStart = txtEditor.SelectionStart;
int selLength = txtEditor.SelectionLength;
txtEditor.Text = txtEditor.Text
.Remove(selStart, selLength)
.Insert(selStart, replacement);
txtEditor.SelectionStart = selStart;
txtEditor.SelectionLength = replacement.Length;
e.Handled = true;
}
EDIT:
Added comments to the code as per the request of the OP.
For more info:
Extension Methods
Func<T, TResult> delegate
Enumerable.SkipWhile extension method
Lambda Expressions
I'm thinking freely as I have never implemented a text editor.
What if you represent each line by an object with an indentation property, which is reflected in the rendering of the line. Then it would be easy to increase and decrease the indent.

Categories