| File: Language\HtmlConventions.cs | Web Access |
| Project: src\roslyn\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Razor.PooledObjects; namespace Microsoft.AspNetCore.Razor.Language; public static class HtmlConventions { private static readonly char[] InvalidNonWhitespaceHtmlCharacters = ['@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*']; internal static bool IsInvalidNonWhitespaceHtmlCharacters(char testChar) { foreach (var c in InvalidNonWhitespaceHtmlCharacters) { if (c == testChar) { return true; } } return false; } /// <summary> /// Converts from pascal/camel case to lower kebab-case. /// </summary> /// <example> /// SomeThing => some-thing /// capsONInside => caps-on-inside /// CAPSOnOUTSIDE => caps-on-outside /// ALLCAPS => allcaps /// One1Two2Three3 => one1-two2-three3 /// ONE1TWO2THREE3 => one1two2three3 /// First_Second_ThirdHi => first_second_third-hi /// </example> public static string ToHtmlCase(string input) { if (string.IsNullOrEmpty(input)) { return input; } return TryGetKebabCaseString(input, out var result) ? result : input; } private static bool TryGetKebabCaseString(ReadOnlySpan<char> input, [NotNullWhen(true)] out string? result) { using var _ = StringBuilderPool.GetPooledObject(out var builder); var allLower = true; var i = 0; foreach (var c in input) { if (char.IsUpper(c)) { allLower = false; if (ShouldInsertHyphenBeforeUppercase(input, i)) { builder.Append('-'); } builder.Append(char.ToLowerInvariant(c)); } else { builder.Append(c); } i++; } if (allLower) { // If the input is all lowercase, we don't need to realize the builder, // it will just be cleared when the pooled object is disposed. result = null; return false; } result = builder.ToString(); return true; } private static bool ShouldInsertHyphenBeforeUppercase(ReadOnlySpan<char> input, int i) { Debug.Assert(char.IsUpper(input[i])); if (i == 0) { // First character is uppercase, no hyphen needed (e.g. This → this) return false; } var prev = input[i - 1]; if (char.IsLower(prev)) { // Lowercase followed by uppercase (e.g. someThing → some-thing) return true; } if ((char.IsUpper(prev) || char.IsDigit(prev)) && (i + 1 < input.Length) && char.IsLower(input[i + 1])) { // Uppercase or digit followed by uppercase, followed by lowercase (e.g. CAPSOn → caps-on or ONE1Two → ONE1-Two) return true; } return false; } }