Modify casing of marked letters Trying to learn coding in my 30s.

Could anybody review my code and give me some feedback if possible? It works, but I am sure there are better ways to solve this problem.

The problem:

A text is given. Write a program that modifies the casing of letters to uppercase at all places in the text surrounded by &lt;upcase&gt; and &lt;/upcase&gt; tags. Tags cannot be nested.

Here is what I've come up with:

namespace Task6Casing
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Text: ");
            string text = Console.ReadLine();

            char[] textNew = new char[text.Length];

            int i = 0, j = 0, k = 0;

            while (i &lt; text.Length)
            {
                if (text.IndexOf("&lt;upcase&gt;", k) &lt; 0)            // check if there is any &lt;upcase&gt; tag
                {                                               // if no, copy everyting
                    while (i &lt; text.Length)
                    {
                        textNew[j++] = text[i++];
                    }
                }
                else                                  
                {
                    while (i &lt; text.IndexOf("&lt;upcase&gt;", k))     // if there is an &lt;upcase&gt; tag, copy letters until the tag
                    {
                        textNew[j++] = text[i++];
                    }

                    i += 8;                                     // move index i to the position right next to the &lt;upcase&gt; tag
                    k = i;
                }

                if (text.IndexOf("&lt;/upcase&gt;", k) &lt; 0)           // check if there is any &lt;/upcase&gt; tag
                {                                               // if no, copy everyting in CAPITAL letters
                    while (i &lt; text.Length)
                    {
                        textNew[j++] = Char.ToUpper(text[i++]);
                    }
                }
                else
                {
                    while (i &lt; text.IndexOf("&lt;/upcase&gt;", k))    // if there is an &lt;/upcase&gt; tag, copy letters in CAP letters until the tag
                    {
                        textNew[j++] = Char.ToUpper(text[i++]);
                    }

                    i += 9;                                     // move index i to the position right next to the &lt;/upcase&gt; tag
                }
            }

            foreach(char c in textNew)
            {
                Console.Write(c);
            }
            Console.WriteLine();

        }
    }
}
Your code is easy to understand and very performant.

Some improvements:


String for start / end tag could be stored as constant. That has the advantage that it can be changed on one central location and the length of the string can be accessed like i += START_TAG.Length
When using a StringBuilder instead of of the char array newText, the running variable 'j' can be dropped.
The 2 code parts



            if (text.IndexOf("&lt;upcase&gt;", k) &lt; 0)            // check if there is
            any &lt;upcase&gt; tag
            {                                               // if no, copy everyting
                while (i &lt; text.Length)
                {
                    textNew[j++] = text[i++];
                }
            }
            else                                  
            {
                while (i &lt; text.IndexOf("&lt;upcase&gt;", k))     // if there is an &lt;upcase&gt; tag, copy letters until the tag
                {
                    textNew[j++] = text[i++];
                }

                i += 8;                                     // move index i to the position right next to the &lt;upcase&gt; tag
                k = i;
            }



and


            if (text.IndexOf("&lt;/upcase&gt;", k) &lt; 0)           // check if there is any &lt;/upcase&gt; tag
            {                                               // if no, copy everyting in CAPITAL letters
                while (i &lt; text.Length)
                {
                    textNew[j++] = Char.ToUpper(text[i++]);
                }
            }
            else
            {
                while (i &lt; text.IndexOf("&lt;/upcase&gt;", k))    // if there is an &lt;/upcase&gt; tag, copy letters in CAP letters until the tag
                {
                    textNew[j++] = Char.ToUpper(text[i++]);
                }

                i += 9;                                     // move index i to the position right next to the &lt;/upcase&gt; tag
            }



are very simlar. Probably it is possible to create one more generic code fragment that coveres both cases.



Since your solution is still understandable for such a simple use case, it will fast become unmaintainable if the use case becomes more complex. Therefore, it makes sense to think about a more abstract OOP concepts to model the solution.

One alternative impl. (that is probably over engineered for the given problem) gives an idea how a more object oriented design could look like:

        public class Tag
        {
            private readonly Func&lt;char, char&gt; map;
            public Tag(string start, string end, Func&lt;char, char&gt; map)
            {
                this.Start = start;
                this.End = end;
                this.map = map;
            }

            public string Start { get; }
            public string End { get; }
            public char Map(char input) =&gt; this.map(input);
        }

        public class TagProcessor
        {
            private readonly Tag tag;
            private readonly StringBuilder output = new StringBuilder();

            private string input;
            private bool isTagOpen;
            private int index;

            public TagProcessor(Tag tag)
            {
                this.tag = tag;
            }

            public string Process(string input)
            {
                this.input = input;
                this.index = 0;
                this.isTagOpen = false;
                this.output.Clear();

                do
                {
                    var tagProcessed = this.TryOpenTag() || this.TryCloseTag();
                    if (!tagProcessed)
                    {
                        this.ApplyCurrentChar();
                    }
                }
                while (this.MoveNext());

                return output.ToString();
            }

            private bool IsEndTag() =&gt; input.IndexOf(tag.End, this.index) == this.index;

            private bool IsStartTag() =&gt; input.IndexOf(tag.Start, this.index) == this.index;

            private bool MoveNext()
            {
                index++;
                return index &lt; this.input.Length;
            }

            private void ApplyCurrentChar()
            {
                var inputChar = this.input[this.index];
                var transfomed = this.isTagOpen ? tag.Map(inputChar) : inputChar;
                this.output.Append(transfomed);
            }

            private bool TryOpenTag()
            {
                if (!isTagOpen &amp;&amp; IsStartTag())
                {
                    this.index += this.tag.Start.Length - 1;
                    this.isTagOpen = true;
                    return true;
                }

                return false;
            }

            private bool TryCloseTag()
            {
                if (isTagOpen &amp;&amp; IsEndTag())
                {
                    this.index += this.tag.End.Length - 1;
                    this.isTagOpen = false;
                    return true;
                }

                return false;
            }
        }

        public static void Main(string[] args)
        {
            var processor = new TagProcessor(new Tag("&lt;upcase&gt;", "&lt;/upcase&gt;", char.ToUpper));

            var test = new[]
            {
                "abc&lt;upcase&gt;test&lt;/upcase&gt;",
                "abc&lt;upcase&gt;test",
                "abc&lt;upcase&gt;&lt;/upcase&gt;test",
                "abc&lt;upcase&gt;test&lt;/upcase&gt;test",
                "abc&lt;upcase&gt;te&lt;upcase&gt;st&lt;/upcase&gt;test",
                "a&lt;/upcase&gt;bc&lt;upcase&gt;te&lt;upcase&gt;st&lt;/upcase&gt;te&lt;/upcase&gt;st",
            };

            foreach (var t in test)
                Console.WriteLine(t + ": " + processor.Process(t));
            Console.ReadLine();
        }


The advantages are, that this solution remains readable if the complexity grows (e.g. more tags were added) and it allows to change / extend the logic without understanding the whole parsing logic. Further more, each method has a single pupose which increases comprehensibleness.