Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Algorithm for expanding a character set?

Tags:

c#

Are there any ready-made functions for expanding a C# regex-style character set?

For example, expand("a-z1") would return a string containing all the characters a to z, followed by the number 1.


Here's what I've got so far:

public static string ExpandCharacterSet(string set)
{
    var sb = new StringBuilder();
    int start = 0;
    while (start < set.Length - 1)
    {
        int dash = set.IndexOf('-', start + 1);

        if (dash <= 0 || dash >= set.Length - 1)
            break;

        sb.Append(set.Substring(start, dash - start - 1));

        char a = set[dash - 1];
        char z = set[dash + 1];

        for (var i = a; i <= z; ++i)
            sb.Append(i);

        start = dash + 2;
    }

    sb.Append(set.Substring(start));
    return sb.ToString();
}

Is there anything I'm overlooking?

PS: Let's ignore negative character sets for now.


Thought my example was quite clear... let's try that again. This is what I want:

ExpandCharacterSet("a-fA-F0-9") == "abcdefABCDEF0123456789"
like image 305
mpen Avatar asked Aug 22 '26 12:08

mpen


1 Answers

It took a bit of work to get this but here's what I was able to muster. Of course this is not going to be portable since I'm messing with internals. But it works well enough for simple test cases. It will accept any regex character class but will not work for negated classes. The range of values is way too broad without any restrictions. I don't know if it will be correct for all cases and it doesn't handle repetition at all but it's a start. At least you won't have to roll out your own parser. As of .NET Framework 4.0:

public static class RegexHelper
{
    public static string ExpandCharClass(string charClass)
    {
        var regexParser = new RegexParser(CultureInfo.CurrentCulture);
        regexParser.SetPattern(charClass);
        var regexCharClass = regexParser.ScanCharClass(false);
        int count = regexCharClass.RangeCount();
        List<string> ranges = new List<string>();
        // range 0 can be skipped
        for (int i = 1; i < count; i++)
        {
            var range = regexCharClass.GetRangeAt(i);
            ranges.Add(ExpandRange(range));
        }
        return String.Concat(ranges);
    }

    static string ExpandRange(SingleRange range)
    {
        char first = range._first;
        char last = range._last;
        return String.Concat(Enumerable.Range(first, last - first + 1).Select(i => (char)i));
    }

    internal class RegexParser
    {
        static readonly Type RegexParserType;
        static readonly ConstructorInfo RegexParser_Ctor;
        static readonly MethodInfo RegexParser_SetPattern;
        static readonly MethodInfo RegexParser_ScanCharClass;

        static RegexParser()
        {
            RegexParserType = Assembly.GetAssembly(typeof(Regex)).GetType("System.Text.RegularExpressions.RegexParser");

            var flags = BindingFlags.NonPublic | BindingFlags.Instance;
            RegexParser_Ctor = RegexParserType.GetConstructor(flags, null, new[] { typeof(CultureInfo) }, null);
            RegexParser_SetPattern = RegexParserType.GetMethod("SetPattern", flags, null, new[] { typeof(String) }, null);
            RegexParser_ScanCharClass = RegexParserType.GetMethod("ScanCharClass", flags, null, new[] { typeof(Boolean) }, null);
        }

        private readonly object instance;

        internal RegexParser(CultureInfo culture)
        {
            instance = RegexParser_Ctor.Invoke(new object[] { culture });
        }

        internal void SetPattern(string pattern)
        {
            RegexParser_SetPattern.Invoke(instance, new object[] { pattern });
        }

        internal RegexCharClass ScanCharClass(bool caseInsensitive)
        {
            return new RegexCharClass(RegexParser_ScanCharClass.Invoke(instance, new object[] { caseInsensitive }));
        }
    }

    internal class RegexCharClass
    {
        static readonly Type RegexCharClassType;
        static readonly MethodInfo RegexCharClass_RangeCount;
        static readonly MethodInfo RegexCharClass_GetRangeAt;

        static RegexCharClass()
        {
            RegexCharClassType = Assembly.GetAssembly(typeof(Regex)).GetType("System.Text.RegularExpressions.RegexCharClass");

            var flags = BindingFlags.NonPublic | BindingFlags.Instance;
            RegexCharClass_RangeCount = RegexCharClassType.GetMethod("RangeCount", flags, null, new Type[] { }, null);
            RegexCharClass_GetRangeAt = RegexCharClassType.GetMethod("GetRangeAt", flags, null, new[] { typeof(Int32) }, null);
        }

        private readonly object instance;

        internal RegexCharClass(object regexCharClass)
        {
            if (regexCharClass == null)
                throw new ArgumentNullException("regexCharClass");
            if (regexCharClass.GetType() != RegexCharClassType)
                throw new ArgumentException("not an instance of a RegexCharClass object", "regexCharClass");
            instance = regexCharClass;
        }

        internal int RangeCount()
        {
            return (int)RegexCharClass_RangeCount.Invoke(instance, new object[] { });
        }

        internal SingleRange GetRangeAt(int i)
        {
            return new SingleRange(RegexCharClass_GetRangeAt.Invoke(instance, new object[] { i }));
        }
    }

    internal struct SingleRange
    {
        static readonly Type RegexCharClassSingleRangeType;
        static readonly FieldInfo SingleRange_first;
        static readonly FieldInfo SingleRange_last;

        static SingleRange()
        {
            RegexCharClassSingleRangeType = Assembly.GetAssembly(typeof(Regex)).GetType("System.Text.RegularExpressions.RegexCharClass+SingleRange");

            var flags = BindingFlags.NonPublic | BindingFlags.Instance;
            SingleRange_first = RegexCharClassSingleRangeType.GetField("_first", flags);
            SingleRange_last = RegexCharClassSingleRangeType.GetField("_last", flags);
        }

        internal char _first;
        internal char _last;

        internal SingleRange(object singleRange)
        {
            if (singleRange == null)
                throw new ArgumentNullException("singleRange");
            if (singleRange.GetType() != RegexCharClassSingleRangeType)
                throw new ArgumentException("not an instance of a SingleRange object", "singleRange");
            _first = (char)SingleRange_first.GetValue(singleRange);
            _last = (char)SingleRange_last.GetValue(singleRange);
        }
    }
}

// usage:
RegexHelper.ExpandCharClass(@"[\-a-zA-F1 5-9]");
// "-abcdefghijklmnopqrstuvwxyzABCDEF1 56789"
like image 70
Jeff Mercado Avatar answered Aug 24 '26 03:08

Jeff Mercado