Code/TailBox/Domain/Text/TailBoxText.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Sandbox.TailBox;
internal static class TailBoxText
{
public static List<string> Segment( string input, char separator )
{
var result = new List<string>();
var start = 0;
var square = 0;
var round = 0;
var curly = 0;
var quote = '\0';
var escaped = false;
for ( var i = 0; i < input.Length; i++ )
{
var c = input[i];
if ( escaped )
{
escaped = false;
continue;
}
if ( quote != '\0' )
{
if ( c == '\\' )
{
escaped = true;
continue;
}
if ( c == quote )
quote = '\0';
continue;
}
if ( c == separator && square == 0 && round == 0 && curly == 0 )
{
result.Add( input[start..i] );
start = i + 1;
continue;
}
if ( c == '\\' )
{
escaped = true;
continue;
}
if ( c is '"' or '\'' )
{
quote = c;
continue;
}
switch ( c )
{
case '[':
square++;
continue;
case ']':
if ( square > 0 ) square--;
continue;
case '(':
round++;
continue;
case ')':
if ( round > 0 ) round--;
continue;
case '{':
curly++;
continue;
case '}':
if ( curly > 0 ) curly--;
continue;
}
}
result.Add( input[start..] );
return result;
}
public static string DecodeArbitraryValue( string input )
{
if ( string.IsNullOrEmpty( input ) )
return input;
var builder = new StringBuilder();
var escaped = false;
var functionStack = new List<FunctionFrame>();
var currentIdentifier = new StringBuilder();
for ( var i = 0; i < input.Length; i++ )
{
var c = input[i];
if ( escaped )
{
builder.Append( c );
escaped = false;
currentIdentifier.Clear();
continue;
}
if ( c == '\\' )
{
escaped = true;
continue;
}
if ( char.IsLetterOrDigit( c ) || c == '-' )
{
currentIdentifier.Append( c );
}
else if ( c == '(' )
{
functionStack.Add( new FunctionFrame( currentIdentifier.ToString(), 0 ) );
currentIdentifier.Clear();
}
else
{
currentIdentifier.Clear();
if ( c == ')' && functionStack.Count > 0 )
functionStack.RemoveAt( functionStack.Count - 1 );
else if ( c == ',' && functionStack.Count > 0 )
{
var frame = functionStack[^1];
functionStack[^1] = frame with { ArgumentIndex = frame.ArgumentIndex + 1 };
}
}
if ( c == '_' )
{
var frame = functionStack.Count > 0 ? functionStack[^1] : default;
builder.Append( ShouldPreserveUnderscore( frame ) ? '_' : ' ' );
currentIdentifier.Clear();
continue;
}
builder.Append( c );
}
if ( escaped )
builder.Append( '\\' );
return builder.ToString();
}
private static bool ShouldPreserveUnderscore( FunctionFrame frame )
{
if ( string.IsNullOrEmpty( frame.Name ) )
return false;
if ( frame.Name.Equals( "url", StringComparison.OrdinalIgnoreCase ) || frame.Name.EndsWith( "_url", StringComparison.OrdinalIgnoreCase ) )
return true;
if ( frame.ArgumentIndex == 0
&& (frame.Name.Equals( "var", StringComparison.OrdinalIgnoreCase )
|| frame.Name.EndsWith( "_var", StringComparison.OrdinalIgnoreCase )
|| frame.Name.Equals( "theme", StringComparison.OrdinalIgnoreCase )
|| frame.Name.EndsWith( "_theme", StringComparison.OrdinalIgnoreCase )) )
{
return true;
}
return false;
}
public static string EscapeIdentifier( string value )
{
if ( value is null )
throw new ArgumentNullException( nameof( value ) );
var length = value.Length;
if ( length == 0 )
return "";
var result = new StringBuilder();
var firstCodeUnit = value[0];
if ( length == 1 && firstCodeUnit == '-' )
return "\\" + value;
for ( var index = 0; index < length; index++ )
{
var codeUnit = value[index];
if ( codeUnit == 0 )
{
result.Append( '\uFFFD' );
continue;
}
if ( (codeUnit >= 0x0001 && codeUnit <= 0x001f)
|| codeUnit == 0x007f
|| (index == 0 && codeUnit >= '0' && codeUnit <= '9')
|| (index == 1 && codeUnit >= '0' && codeUnit <= '9' && firstCodeUnit == '-') )
{
result.Append( '\\' ).Append( ((int)codeUnit).ToString( "x" ) ).Append( ' ' );
continue;
}
if ( codeUnit >= 0x0080
|| codeUnit == '-'
|| codeUnit == '_'
|| (codeUnit >= '0' && codeUnit <= '9')
|| (codeUnit >= 'A' && codeUnit <= 'Z')
|| (codeUnit >= 'a' && codeUnit <= 'z') )
{
result.Append( codeUnit );
continue;
}
result.Append( '\\' ).Append( codeUnit );
}
return result.ToString();
}
public static string EscapeIdentifierForSboxSelector( string value )
{
if ( value is null )
throw new ArgumentNullException( nameof( value ) );
if ( value.Length == 0 )
return "";
var result = new StringBuilder();
for ( var index = 0; index < value.Length; index++ )
{
var codeUnit = value[index];
if ( IsIdentifierSafe( codeUnit, index, value ) )
{
result.Append( codeUnit );
continue;
}
result.Append( '\\' ).Append( ((int)codeUnit).ToString( "x6" ) ).Append( ' ' );
}
return result.ToString();
}
private static bool IsIdentifierSafe( char codeUnit, int index, string value )
{
if ( codeUnit >= 0x0080 || codeUnit == '_' )
return true;
if ( codeUnit == '-' )
return index > 0;
if ( index == 1 && value[0] == '-' && codeUnit >= '0' && codeUnit <= '9' )
return false;
if ( codeUnit >= '0' && codeUnit <= '9' )
return index > 0;
if ( codeUnit >= 'A' && codeUnit <= 'Z' )
return true;
if ( codeUnit >= 'a' && codeUnit <= 'z' )
return true;
return false;
}
private readonly record struct FunctionFrame( string Name, int ArgumentIndex );
}