4

I want to write some Html from c# (html is an example, this might be other languages..)

For example:

    string div = @"<div class=""className"">
                      <span>Mon text</span>
                   </div>";

will produce:

<div class="className">
            <span>Mon text</span>
         </div>

that's not very cool from the Html point of view...

The only way to have a correct HTML indentation will be to indent the C# code like this :

            string div = @"<div class=""className"">
    <span>Mon text</span>
</div>";

We get the correctly indented Html:

<div class="className">
    <span>Mon text</span>
</div>

But indenting the C# like this really broke the readability of the code...

Is there a way to act on the indentation in the C# language ?

If not, does someone have a tip better than :

string div = "<div class=\"className\">" + Environment.NewLine +
             "  <span>Mon text</span>" + Environment.NewLine +
             "</div>";

and better than

var sbDiv = new StringBuilder();
sbDiv.AppendLine("<div class=\"className\">");
sbDiv.AppendLine("    <span>Mon text</span>");
sbDiv.AppendLine("</div>");

What i use as a solution:

Greats thanks to @Yotam for its answer.

I write a little extension to make the alignment "dynamic" :

    /// <summary>
    /// Align a multiline string from the indentation of its first line
    /// </summary>
    /// <remarks>The </remarks>
    /// <param name="source">The string to align</param>
    /// <returns></returns>
    public static string AlignFromFirstLine(this string source)
    {
        if (String.IsNullOrEmpty(source)) {
            return source;
        }

        if (!source.StartsWith(Environment.NewLine)) {
            throw new FormatException("String must start with a NewLine character.");
        }

        int indentationSize = source.Skip(Environment.NewLine.Length)
                                .TakeWhile(Char.IsWhiteSpace)
                                .Count();

        string indentationStr = new string(' ', indentationSize);
        return source.TrimStart().Replace($"\n{indentationStr}", "\n");
    }

Then i can use it like that :

private string GetHtml(string className)
{
    return $@"
            <div class=""{className}"">
                <span>Texte</span>
            </div>".AlignFromFirstLine();
}

That return the correct html :

<div class="myClassName">
    <span>Texte</span>
</div>

One limitation is that it will only work with space indentation...

Any improvement will be welcome !

aprovent
  • 941
  • 6
  • 20
  • Consider building up your HTML using an object model, and let the object model do the conversion to string for you. – Eric Lippert Jul 22 '16 at 14:48
  • 1
    @EricLippert My example show some Html but i'm writing dynamically different code format like cshtml view, cs and js class,... I would have liked to use the relatively clear syntax of C# string interpolation to order my ouput code skeleton. I think my requirements are so specific that i must use a custom model for each output language... but time is missing to write them :-) – aprovent Jul 22 '16 at 15:00
  • See also [Controlling Output Indentation in ASP.Net MVC](http://stackoverflow.com/q/1593057/18192) – Brian Jul 22 '16 at 15:42
  • 1
    Please see (and upvote) my suggestion for change in the Visual Studio IDE: [Indent multi-line verbatim strings](https://developercommunity.visualstudio.com/idea/602807/indent-multi-line-verbatim-strings.html). – Olivier Jacot-Descombes Aug 11 '19 at 17:55
  • 1
    @OlivierJacot-Descombes Upvote done. Great suggestion,I hope it will be implemented soon... – aprovent Aug 12 '19 at 21:36

4 Answers4

3

You could wrap the string to the next line to get the desired indentation:

    string div = 
@"
<div class=""className"">
    <span>Mon text</span>
</div>"
.TrimStart(); // to remove the additional new-line at the beginning

Another nice solution (disadvantage: depends on the indentation level!)

        string div = @"
        <div class=""className"">
        <span>Mon text</span>
        </div>".TrimStart().Replace("\n            ", "\n");

It just removes the indentation out of the string. make sure the number of spaces in the first string of the Replace is the same amount of spaces your indentation has.

Yotam Salmon
  • 2,400
  • 22
  • 36
  • 1
    Use `a=a.TrimStart()` instead (there's no additional at the end) – Tim Schmelter Jul 22 '16 at 09:42
  • @TimSchmelter Good idea, thanks! Btw, how does the `Trim()` function works when not provided with arguments? Because it seems like when giving it a '\n' it misses something at the beginning, but with no parameters it works like a charm! – Yotam Salmon Jul 22 '16 at 09:45
  • 1
    It will remove all characters that are [white-spaces](https://msdn.microsoft.com/en-us/library/system.char.iswhitespace(v=vs.110).aspx). That includes spaces, new-lines and tab characters. – Tim Schmelter Jul 22 '16 at 09:46
  • @TimSchmelter Cool, seems very useful! Learning something new every day ;-) – Yotam Salmon Jul 22 '16 at 09:47
  • @TimSchmelter about the edit of the 1st comment, there is an additional '\n' at the end, so `Trim()` is the best – Yotam Salmon Jul 22 '16 at 09:49
  • No, there is none. I have edited your answer a little bit. Feel free to undo it if you don't agree – Tim Schmelter Jul 22 '16 at 09:53
  • @TimSchmelter +Yotam it works very well when your c# code is not indented, but if it is in a method of a class of a namespace, it will break the c# indentation and impact too much the readability... – aprovent Jul 22 '16 at 09:54
  • @aprovent Added another possible solution – Yotam Salmon Jul 22 '16 at 09:59
  • @YotamSalmon Thanks. It seems to be the only way it can be done with c# mulitline strings... So sorry not to have a specific syntax to enable this like `@!" ....."` to remove all starting spaces of all next lines relatively from the first line... – aprovent Jul 22 '16 at 10:10
2

I like this solution more, but how about:

string div = "<div class='className'>\n"
           + "    <span>Mon text</span>\n"
           + "</div>";

This gets rid of some clutter:

  • Replace " inside strings with ' so that you don't need to escape the quote. (Single quotes in HTML appear to be legal.)
  • You can then also use regular "" string literals instead of @"".
  • Use \n instead of Environment.NewLine.

Note that the string concatenation is performed during compilation, by the compiler. (See also this and this blog post on the subject by Eric Lippert, who previously worked on the C# compiler.) There is no runtime performance penalty.

Community
  • 1
  • 1
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • 1
    @Rick: Can you back up your claim? The C# compiler will do the string concatenation. You end up with one monolithic string literal in your assembly. If there is any performance penalty, if happens at compile time. See http://stackoverflow.com/questions/288794/does-c-sharp-optimize-the-concatenation-of-string-literals. – stakx - no longer contributing Jul 22 '16 at 10:00
  • 1
    @aprovent: What performance hits are you referring to? There are none that matter. See my above comment. – stakx - no longer contributing Jul 22 '16 at 10:01
  • @stakx Thanks, i learn something ! So the only problem of this technique is the repetition of `+ "` and `\n"` – aprovent Jul 22 '16 at 10:04
  • I used the + concatenation in a project where I was concatenating 1000's of strings. It ran like a dog! I changed to use `StringBuilder` and the performance improvement was massive - I've no figures to back this up - just know from experience. for the example there will be no difference between the two methods as it's so small but as I said, with a long string it will be noticeable. In your example, what is happening is a new string is being created every time you use + which is where the performance hit comes from. – Percy Jul 22 '16 at 10:13
  • @Rick: Back then, you were probably not concatenating string literals (essentially compile time constants) which the compiler can optimize, but strings you read from somewhere. The two situations are not directly comparable. I agree that in the latter situation using a `StringBuilder` is the more suitable choice. – stakx - no longer contributing Jul 22 '16 at 10:16
  • Check out this StackOverflow post - it backs up my argument http://stackoverflow.com/a/1532483/1768592 "6.51min vs 11secs" for a "big string" – Percy Jul 22 '16 at 10:17
  • @Rick: Why are you referring me to a Java-related question? The present question is about C# / .NET. You are now comparing apples and oranges. – stakx - no longer contributing Jul 22 '16 at 10:19
  • My Bad - here you go then - I've just run it as a test - change the loop to 50000 and you see a large difference http://stackoverflow.com/a/1612819/1768592 – Percy Jul 22 '16 at 10:28
  • @Rick: I suggest you study [my comment above](http://stackoverflow.com/questions/38523090/acting-on-the-indentation-of-a-c-sharp-multiline-string/38523187?noredirect=1#comment64442385_38523187), and read the articles I linked to in my answer. There is a difference between concatenating string literal constants (e.g. `"Hello world"`, `"N"`) and strings built dynamically at run-time (e.g. `number.ToString("N")`, `Console.ReadLine(…)`, `new string(…)`). The compiler can optimize the former, but not the latter. Enough said. – stakx - no longer contributing Jul 22 '16 at 10:32
2

Inspired by trimIndent() in Kotlin.

This code:

    var x = @"
       anything
         you
       want
    ".TrimIndent();

will produce a string:


anything
  you
want

or "\nanything\n you\nwant\n"

Implementation:

public static string TrimIndent(this string s)
{
    string[] lines = s.Split('\n');

    IEnumerable<int> firstNonWhitespaceIndices = lines
        .Skip(1)
        .Where(it => it.Trim().Length > 0)
        .Select(IndexOfFirstNonWhitespace);

    int firstNonWhitespaceIndex;

    if (firstNonWhitespaceIndices.Any()) firstNonWhitespaceIndex = firstNonWhitespaceIndices.Min();
    else firstNonWhitespaceIndex = -1;

    if (firstNonWhitespaceIndex == -1) return s;

    IEnumerable<string> unindentedLines = lines.Select(it => UnindentLine(it, firstNonWhitespaceIndex));
    return String.Join("\n", unindentedLines);
}

private static string UnindentLine(string line, int firstNonWhitespaceIndex)
{
    if (firstNonWhitespaceIndex < line.Length)
    {
        if (line.Substring(0, firstNonWhitespaceIndex).Trim().Length != 0)
        {
            return line;
        }

        return line.Substring(firstNonWhitespaceIndex, line.Length - firstNonWhitespaceIndex);
    }

    return line.Trim().Length == 0 ? "" : line;
}

private static int IndexOfFirstNonWhitespace(string s)
{
    char[] chars = s.ToCharArray();
    for (int i = 0; i < chars.Length; i++)
    {
        if (chars[i] != ' ' && chars[i] != '\t') return i;
    }

    return -1;
}
Max Farsikov
  • 2,451
  • 19
  • 25
0

If it is one long string then you can always keep the string in a text file and read it into your variable, e.g.

string text = File.ReadAllText(@"c:\file.txt", Encoding.UTF8);

This way you can format it anyway you want using a text editor and it won't negatively effect the look of your code.

If you're changing parts of the string on the fly then StringBuilder is your best option. - or if you did decide to read the string in from a text file, you could include {0} elements in your string and then use string.format(text, "text1","text2", etc) to change the required parts.

Percy
  • 2,855
  • 2
  • 33
  • 56