Май 122013
 

Привет.
Для разработки нового проекта, а также для написания новых статей написал класс генерации строк, с которым хочу поделиться с читателями блога.
Класс позволяет генерировать строку в соответствии с определенным форматом, заданным в стиле регулярных выражений.

Например, так выглядит генерация GUID (http://ru.wikipedia.org/wiki/GUID):

var g = new StringGenerator(@"{$[:xdigit:]*8-$[:xdigit:]*4-$[:xdigit:]*4-$[:xdigit:]*4-$[:xdigit:]*12}");
MessageBox.Show(g.Process()); // {9caea44a-01fc-a2a7-6b99-3f7fba5a0aef}

Так — хеши формата ВК:

var g = new StringGenerator(@"$[a-z0-9]*16");
MessageBox.Show(g.Process()); // hjcpvnw10swl9ral

Также есть поддержка любых вставок между группами:

var g = new StringGenerator(@"Username: $[\w]*5; Password: $[\w]*8");
MessageBox.Show(g.Process()); // Username: NBFxQ; Password: H8xA74g6

При использовании символов ‘$’, ‘[‘, ‘]’, ‘*’, ‘-‘, не забывайте их экранировать следующим образом:
var s = "\\$\\[a\\-b\\]"; или var s = @"\$\[a\-b\]";

В качестве примера использования можете взглянуть на простейший генератор паролей, который выглядит следующим образом:
генератор паролей

Скачать проект можно тут: ссылка

Исходный код класса:

// Simple string generator
// Author: bafoed
// Web-page: https://blog.bafoed.ru
 
using System;
using System.Collections.Generic;
using System.Linq;
 
class StringGenerator
{
    private readonly Dictionary<string, string> _replaces = new Dictionary<string, string>
        {
            {":digit:", @"0-9"},
            {"\\d", @"0-9"},
            {":alnum:", @"0-9A-Za-z"},
            {"\\w", @"0-9A-Za-z"},
            {":alpha:", @"A-Za-z"},
            {":blank:", @"  "},
            {"\\W", @"  "},
            {":xdigit:", @"0-9a-f"},
            {":punct:", @".,""'?!;:#\$%&()*+-/<>[email protected]\[\]\\^_{}|~"},
            {":upper:", @"A-Z"},
            {":lower:", @"a-z"}
        };
 
    private string _pattern;
    private readonly Random _random = new Random(Environment.TickCount);
 
 
    public StringGenerator(string pattern)
    {
        if (
            (pattern.Split('[').Length - pattern.Split(new[] { @"\[" }, StringSplitOptions.None).Length != pattern.Split(']').Length - pattern.Split(new[] { @"\]" }, StringSplitOptions.None).Length)
            ||
            (pattern.Split('[').Length - pattern.Split(new[] { @"\[" }, StringSplitOptions.None).Length != pattern.Split('$').Length - pattern.Split(new[] { @"\$" }, StringSplitOptions.None).Length)
          )
        {
            throw new ArgumentException("Number of non escaped characters '[', ']' and '$' must be the same.");
        }
 
        _pattern = pattern;
        PreparePattern();
 
    }
 
 
    private string PreparePattern()
    {
        for (var i = 0; i < _pattern.Length; i++)
        {
            char currentChar = _pattern[i];
            char prevChar = (i == 0) ? '\0' : _pattern[i - 1];
            char nextChar = (_pattern.Length - 1 == i) ? '\0' : _pattern[i + 1];
            if (currentChar == '*' && prevChar != '\\')
            {
                var scan = i + 1;
                var countString = "";
                while (_pattern.Length != scan && char.IsDigit(_pattern[scan]))
                {
                    countString += _pattern[scan];
                    scan++;
                }
                if (countString == string.Empty)
                {
                    throw new ArgumentException("Invalid number after unescaped '*'");
                }
 
                int count = Convert.ToInt32(countString);
                var group = _pattern.Substring(0, i);
                group = group.Substring(group.LastIndexOf("$["));
                string replacement = "";
                for (int r = 0; r < count; r++)
                {
                    replacement += group;
                }
 
                _pattern = _pattern.Replace(group + "*" + count, replacement);
                return PreparePattern();
            }
        }
 
        return _pattern;
    }
 
    public string Process()
    {
        string output = "";
        for (var i = 0; i < _pattern.Length; i++)
        {
            char currentChar = _pattern[i];
            char prevChar = (i == 0) ? '\0' : _pattern[i - 1];
            char nextChar = (_pattern.Length - 1 == i) ? '\0' : _pattern[i + 1];
 
            if (currentChar == '\\' && (nextChar == '$' || nextChar == '[' || nextChar == ']'))
            {
                output += nextChar;
                i++;
                continue;
            }
 
            if (currentChar == '$')
            {
                if (nextChar != '[')
                {
                    throw new ArgumentException("Invalid format, no '[' after '$' at position " + i);
                }
 
                var atBrackets = _pattern.Substring(i, _pattern.IndexOf(']', i) - i).Substring("$[".Length);
                output += ProcessBracket(atBrackets);
                i += ("[" + atBrackets + "]").Length;
                continue;
            }
 
            output += currentChar;
        }
 
        return output;
    }
 
    private char ProcessBracket(string bracketInner)
    {
        if (string.Empty == bracketInner) return '\0';
        bracketInner = _replaces.Aggregate(bracketInner, (current, replacement) => current.Replace(replacement.Key, replacement.Value));
        var validChars = new List<int>();
        for (var i = 0; i < bracketInner.Length; i++)
        {
            char currentChar = bracketInner[i];
            char prevChar = (i == 0) ? '\0' : bracketInner[i - 1];
            char nextChar = (bracketInner.Length - 1 == i) ? '\0' : bracketInner[i + 1];
 
            if (currentChar == '\\')
            {
                validChars.Add(nextChar);
                i++;
                continue;
            }
 
            if (currentChar == '-')
            {
                Enumerable.Range(prevChar, nextChar - prevChar + 1).ToList().ForEach(validChars.Add);
                i++;
                continue;
            }
 
            validChars.Add(currentChar);
        }
 
        return (char)validChars[_random.Next(0, validChars.Count)];
    }
}

Код может быть кривоват, но со всеми генерируемыми мною строками справлялся.
Статья получилась небольшая, потому что класс написан для другой, которая еще в процессе написания.
Вопросы как обычно можно задавать в ЛС или в комментарии.

P.S. Поблагодарить меня можно с помощью нажатия на «Мне нравится» ниже, не зря ведь я это писал, ну.