Gus Nando
Gus Nando

Reputation: 416

Custom Uppercase on String

hi i was trying to make a program that modified a word in a string to a uppercase word.

the uppercase word is in a tag like this :

the <upcase>weather</upcase> is very <upcase>hot</upcase>

the result :

the WEATHER is very HOT

my code is like this :

string upKey = "<upcase>";
string lowKey = "</upcase>";


string quote = "the lazy <upcase>fox jump over</upcase> the dog <upcase> something here </upcase>";
        int index = quote.IndexOf(upKey);
        int indexEnd = quote.IndexOf(lowKey);


       while(index!=-1)
        { 

        for (int a = 0; a < index; a++)
        {
            Console.Write(quote[a]);
        }

        string upperQuote = "";

        for (int b = index + 8; b < indexEnd; b++)
        {

            upperQuote += quote[b];
        }

        upperQuote = upperQuote.ToUpper().ToString();
        Console.Write(upperQuote);

        for (int c = indexEnd+9;c<quote.Length;c++)
        {
            if (quote[c]=='<')
            {

                break;

            }


            Console.Write(quote[c]);
        }
        index = quote.IndexOf(upKey, index + 1);
        indexEnd = quote.IndexOf(lowKey, index + 1);   

        }

        Console.WriteLine();

        }  

i have been trying using this code,and a while(while (indexEnd != -1)) :

index = quote.IndexOf(upKey, index + 1);
indexEnd = quote.IndexOf(lowKey, index + 1);   

but that not work, the program run into unlimited loop, btw i'm a noob so please give a answer that i can understand :)

Upvotes: 2

Views: 127

Answers (4)

InBetween
InBetween

Reputation: 32750

The way to go here is to probably use Regex but these easy parsing excercises are always fun to do manually. This can be easily solved using a very simple state machine.

What states can we have when dealing with strings of this nature? I can think of 4:

  • We are either parsing normal text
  • Or we are parsing an opening format tag '<...>'
  • Or we are parsing a closing format tag '</...>'
  • Or we are parsing text to be formatted between tags

I can't think of any other states. Now we need to think about the normal flow / transition between states. What should happen when we a parse string with the correct format?

  • Parser starts up expecting normal text. That is easy to understand.
  • If expecting normal text we encounter a '<' then the parser should switch to parsing opening format tag state. There is no other valid state transition.
  • If in parsing opening format tag state we encounter a '>' then the parser should switch to parsing text to be formatted. There is no other valid state transition.
  • If in parsing text to be formatted we encounter a '<' then the parser should switch to parsing closing tag. Again, there is no other valid state transition.
  • If in parsing closing tag we encounter a '>' then the parser should switch to normal text. Once more, there is no other valid transition. Note that we are disallowing nested tags.

Ok, so that seems pretty easy to understand. What do we need to implement this?

First we'll need something to represent the parsing states. A good old enum will do:

 private enum ParsingState
 {
     UnformattedText,
     OpenTag,
     CloseTag,
     FormattedText,
 }

Now we need some string buffers to keep track of the final formatted string, the current format tag we are parsing and finally the substring we need to format. We will use several StringBuilder's for these as we don't know how long these buffers are and how many concatenations will be performed:

var formattedStringBuffer = new StringBuilder();
var formatBuffer = new StringBuilder();
var tagBuffer = new StringBuilder();

We will also need to keep track of the parser's state and the current active tag if any (so we can make sure that the parsed closing tag matches the current active tag):

 var state = ParsingState.UnformattedText;
 var activeFormatTag = string.Empty;

And now we are good to go, but before we do, can we generalize this so it works with any format tag?

Yes we can, we just need to tell the parser what to do for each supported tag. We can do this easily just passing a along a Dictionary that ties each tag with the action it should perform. We do this the following way:

var formatter = new Dictionary<string, Func<string, string>>();
formatter.Add("upcase", s => s.ToUpperInvariant());
formatter.Add("lcase", s => s.ToLowerInvariant());

Great! Now our implementation could be the following:

 public static string Parse(this string str, Dictionary<string, Func<string,string>> formatter)
 {
     var formattedStringBuffer = new StringBuilder();
     var formatBuffer = new StringBuilder();
     var tagBuffer = new StringBuilder();
     var state = ParsingState.UnformattedText;
     var activeFormatTag = string.Empty;

     foreach (var c in str)
     {
        switch (state)
         {
             case ParsingState.UnformattedText:
                 {
                     if (c != '<')
                     {
                         formattedStringBuffer.Append(c);
                     }
                     else
                     {
                         state = ParsingState.OpenTag;
                     }

                     break;
                 }
             case ParsingState.OpenTag:
                 {
                     if (c != '>')
                     {
                         tagBuffer.Append(c);
                     }
                     else
                     {
                         state = ParsingState.FormattedText;
                         activeFormatTag = tagBuffer.ToString();
                         tagBuffer.Clear();
                     }

                     break;

                 }
             case ParsingState.FormattedText:
                 {
                     if (c != '<')
                     {
                         formatBuffer.Append(c);
                     }
                     else
                     {
                         state = ParsingState.CloseTag;
                     }

                     break;
                 }
             case ParsingState.CloseTag:
                 {
                     if (c!='>')
                     {
                         tagBuffer.Append(c);
                     }
                     else
                     {
                         var expectedTag = $"/{activeFormatTag}";
                         var tag = tagBuffer.ToString();

                         if (tag != expectedTag)
                             throw new FormatException($"Expected closing tag not found: <{expectedTag}>.");

                         if (formatter.ContainsKey(activeFormatTag))
                         {
                             var formatted = formatter[activeFormatTag](formatBuffer.ToString());
                             formattedStringBuffer.Append(formatted);
                             tagBuffer.Clear();
                             formatBuffer.Clear();
                             state = ParsingState.UnformattedText;
                         }
                         else
                             throw new FormatException($"Format tag <{activeFormatTag}> not recognized.");
                     }

                     break;
                 }
         }
     }

     if (state != ParsingState.UnformattedText)
         throw new FormatException($"Bad format in specified string '{str}'");

     return formattedStringBuffer.ToString();
 }

Is it the most elegant solution? No, Regex will do a much better job, but being a beginner I would not recommend you start solving these kind of problems that way, you'll learn a whole lot more solving them manualy. You'll have plenty of time to learn Regex later on.

Upvotes: 0

juharr
juharr

Reputation: 32266

The following will work as long as there are only matching tags and none of them are nested.

public static string Upper(string str)
{
    const string start = "<upcase>";
    const string end = "</upcase>";

    var builder = new StringBuilder();

    // Find the first start tag
    int startIndex = str.IndexOf(start);

    // If no start tag found then return the original
    if (startIndex == -1)
        return str;

    // Append the part before the first tag as is
    builder.Append(str.Substring(0, startIndex));

    // Continue as long as we find another start tag.
    while (startIndex != -1)
    {
        // Find the end tag for the current start tag
        var endIndex = str.IndexOf(end, startIndex);

        // Append the text between the start and end as upper case.
        builder.Append(
            str.Substring(
                startIndex + start.Length, 
                endIndex - startIndex - start.Length).ToUpper());

        // Find the next start tag.
        startIndex = str.IndexOf(start, endIndex);

        // Append the part after the end tag, but before the next start as is
        builder.Append(
            str.Substring(
                endIndex + end.Length, 
                (startIndex == -1 ? str.Length : startIndex) - endIndex - end.Length));
    }

    return builder.ToString();
}

Upvotes: 1

Nasreddine
Nasreddine

Reputation: 37808

You can use a regular expression for this:

string input = "the <upcase>weather</upcase> is very <upcase>hot</upcase>";

var regex = new Regex("<upcase>(?<theMatch>.*?)</upcase>");
var result = regex.Replace(input, match => match.Groups["theMatch"].Value.ToUpper());
// result will be: "the WEATHER is very HOT"

Here's an explanation taken from here for the regular expression used above:

  • <upcase> matches the characters <upcase> literally (case sensitive)
  • (?&lt;theMatch&gt;.\*?) Named capturing group theMatch
  • .*? matches any character (except newline)
  • Quantifier: *? Between zero and unlimited times, as few times as possible, expanding as needed [lazy]
  • < matches the characters < literally
  • / matches the character / literally
  • upcase> matches the characters upcase> literally (case sensitive)

Upvotes: 5

ispiro
ispiro

Reputation: 27673

I'm not rewriting your code. Just answering your (main) question:

You need to keep a variable of the index you're at, and check for IndexOf from there only (See MSDN). Something like this:

int index = 0;
while (quote.IndexOf(upKey, index) != -1)
{
   //Your code, including updating the value of index.
}

(I didn't check this on Visual Studio. This is just to point you in the direction that I think you're looking for.)

The reason for the infinite loop is that you're always testing IndexOf of the same index. Perhaps you mean to have quote.IndexOf(upKey, index += 1); which would change the value of index?

Upvotes: 0

Related Questions