File: Forms\FieldIdGenerator.cs
Web Access
Project: src\src\Components\Web\src\Microsoft.AspNetCore.Components.Web.csproj (Microsoft.AspNetCore.Components.Web)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Text;
 
namespace Microsoft.AspNetCore.Components.Forms;
 
/// <summary>
/// Provides methods for generating valid HTML id attribute values from field names.
/// </summary>
internal static class FieldIdGenerator
{
    // Valid characters for HTML 4.01 id attributes (excluding '.' to avoid CSS selector conflicts)
    // See: https://www.w3.org/TR/html401/types.html#type-id
    private static readonly SearchValues<char> ValidIdChars =
        SearchValues.Create("-0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz");
 
    /// <summary>
    /// Sanitizes a field name to create a valid HTML id attribute value.
    /// </summary>
    /// <param name="fieldName">The field name to sanitize.</param>
    /// <returns>A valid HTML id attribute value, or an empty string if the input is null or empty.</returns>
    /// <remarks>
    /// This method follows HTML 4.01 id attribute rules:
    /// - The first character must be a letter (A-Z, a-z)
    /// - Subsequent characters can be letters, digits, hyphens, underscores, colons, or periods
    /// - Periods are replaced with underscores to avoid CSS selector conflicts
    /// </remarks>
    public static string SanitizeHtmlId(string? fieldName)
    {
        if (string.IsNullOrEmpty(fieldName))
        {
            return string.Empty;
        }
 
        // Fast path: check if sanitization is needed
        var firstChar = fieldName[0];
        var startsWithLetter = char.IsAsciiLetter(firstChar);
        var indexOfInvalidChar = fieldName.AsSpan(1).IndexOfAnyExcept(ValidIdChars);
 
        if (startsWithLetter && indexOfInvalidChar < 0)
        {
            return fieldName;
        }
 
        // Slow path: build sanitized string
        var result = new StringBuilder(fieldName.Length);
 
        // First character must be a letter
        if (startsWithLetter)
        {
            result.Append(firstChar);
        }
        else
        {
            result.Append('z');
            if (IsValidIdChar(firstChar))
            {
                result.Append(firstChar);
            }
            else
            {
                result.Append('_');
            }
        }
 
        // Process remaining characters
        for (var i = 1; i < fieldName.Length; i++)
        {
            var c = fieldName[i];
            result.Append(IsValidIdChar(c) ? c : '_');
        }
 
        return result.ToString();
    }
 
    private static bool IsValidIdChar(char c)
        => ValidIdChars.Contains(c);
}