views:

461

answers:

8

What would an implementation of 'MagicFunction' look like to make the following (nunit) test pass?

public MagicFunction_Should_Prepend_Given_String_To_Each_Line()
{
    var str = @"line1
line2
line3";

    var result = MagicFunction(str, "-- ");

    var expected = @"-- line1
-- line2
-- line3";

    Assert.AreEqual(expected, result);
}
+1  A: 
var result = "-- " + str.Replace(Environment.NewLine, Environment.NewLine + "-- ");

if you want it cope with either Windows (\r\n) NewLines or Unix ones (\n) then:

var result = "-- " + str.Replace("\n", "\n-- ");

No need to touch the \r as it is to be left where it was before. If however you want to cross between Unix and Windows then:

var result = "-- " + str.Replace("\r","").Replace("\n", Enviornment.NewLine + "-- ");

Will do it and return the result in the local OS's format

JDunkerley
Will only work with Windows-style new-line markers ("\r\n")
Thomas Levesque
@Thomas: The Environment.NewLine will work with whatever is the OS's NewLine string. The only issue is if parsing windows files on unix or vice versa
JDunkerley
Yes, that's the issue I'm talking about... It's very common to manipulate UNIX file on Windows or vice versa
Thomas Levesque
+6  A: 
string MagicFunction(string str, string prepend)
{
   str = str.replace("\n", "\n" + prepend);
   str = prepend + str;
   return str;
}

EDIT:
As others have pointed out, the newline characters vary between environments. If you're only planning to use this function on files that were created in the same environment then System.Environment will work fine. However, if you create a file on a Linux box and then transfer it over to a Windows box you'll want to specify a different type of newline. Since Linux uses \n and Windows uses \r\n this piece of code will work for both Windows and Linux files. If you're throwing Macs into the mix (\r) you'll have to come up with something a little more involved.

Spencer Ruport
What about the "\r" character ?
Thomas Levesque
Or even better, how about Environment.NewLine. I'm pretty sure that's what it's there for. :)
Esteban Araya
Okidokie. Edited.
Spencer Ruport
+1  A: 

You could do it like that :

public string MagicFunction2(string str, string prefix)
{
    bool first = true;
    using(StringWriter writer = new StringWriter())
    using(StringReader reader = new StringReader(str))
    {
        string line;
        while((line = reader.ReadLine()) != null)
        {
            if (!first)
                writer.WriteLine();
            writer.Write(prefix + line);
            first = false;
        }
        return writer.ToString();
    }
}
Thomas Levesque
This will work with all style new-line markers?
TheDeeno
I just tried with "\r\n" (Windows), "\n" (Linux) and "\r" (Mac), it works in all cases. However it doesn't preserve the original new-line markers, it always uses Environment.NewLine in the output string
Thomas Levesque
Hmm, slight bug. It always adds a newline to the end of the string even if the original string didn't have one.
TheDeeno
@TheDeeno: you're right, it's fixed now
Thomas Levesque
+2  A: 
private static string MagicFunction(string str, string prefix)
{
    string[] lines = str.Split(new[] { '\n' });
    return string.Join("\n", lines.Select(s => prefix + s).ToArray());
}
Fredrik Mörk
was wondering how no one LINQed it yet :)
Gishu
You probably need to factor in \r\n - `var lines = str.Split(new char[] {'\r','\n'}, StringSplitOptions.RemoveEmptyEntries);`
Gishu
@Gishu: I tested the method both with `\r\n` and `\n` linebreaks and it works with both. It does not work with only `\r`. One upside with splitting on `\n` only is that the resulting string contains the same style linebreaks as the input (even if there is a mixture between `\r\n` and `\n` linebreaks in the string.
Fredrik Mörk
A: 

You could split the string by Environment.NewLine, and then add the prefix to each of those string, and then join them by Environment.NewLine.

string MagicFunction(string prefix, string orignalString)
{
    List<string> prefixed = new List<string>();
    foreach (string s in orignalString.Split(new[]{Environment.NewLine}, StringSplitOptions.None))
    {
        prefixed.Add(prefix + s);
    }

    return String.Join(Environment.NewLine, prefixed.ToArray());
}
jrummell
A: 

How about:

string MagicFunction(string InputText) {
    public static Regex regex = new Regex(
          "(^|\\r\\n)",
        RegexOptions.IgnoreCase
        | RegexOptions.CultureInvariant
        | RegexOptions.IgnorePatternWhitespace
        | RegexOptions.Compiled
        );

    // This is the replacement string
    public static string regexReplace = 
          "$1-- ";

    // Replace the matched text in the InputText using the replacement pattern
    string result = regex.Replace(InputText,regexReplace);

    return result;
}
Lazarus
A: 

How about this. It uses StringBuilder in case you are planning on prepending a lot of lines.

string MagicFunction(string input)
{
  StringBuilder sb = new StringBuilder();
  StringReader sr = new StringReader(input);
  string line = null;

  using(StringReader sr = new StringReader(input))
  {
    while((line = sr.ReadLine()) != null)
    {
      sb.Append(String.Concat("-- ", line, System.Environment.NewLine));
    }
  }
  return sb.ToString();
}
scottm
A: 

Thanks all for your answers. I implemented the MagicFunction as an extension method. It leverages Thomas Levesque's answer but is enhanced to handle all major environments AND assumes you want the output string to use the same newline terminator of the input string.

I favored Thomas Levesque's answer (over Spencer Ruport's, Fredrik Mork's, Lazarus, and JDunkerley) because it was the best performing. I'll post performance results on my blog and link here later for those interested.

(Obviously, the function name of 'MagicFunctionIO' should be changed. I went with 'PrependEachLineWith')

public static string MagicFunctionIO(this string self, string prefix)
{
  string terminator = self.GetLineTerminator();
  using (StringWriter writer = new StringWriter())
  {
    using (StringReader reader = new StringReader(self))
    {
      bool first = true;
      string line;
      while ((line = reader.ReadLine()) != null)
      {
        if (!first)
          writer.Write(terminator);
        writer.Write(prefix + line);
        first = false;
      }
      return writer.ToString();
    }
  }
}

public static string GetLineTerminator(this string self)
{
  if (self.Contains("\r\n")) // windows
    return "\r\n";
  else if (self.Contains("\n")) // unix
    return "\n";
  else if (self.Contains("\r")) // mac
    return "\r";
  else // default, unknown env or no line terminators
    return Environment.NewLine;
}
TheDeeno