Modify casing of marked letters
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.