Как разбить строку списка аргументов sql? - PullRequest
0 голосов
/ 26 марта 2012

Вопрос:

У меня есть функция, которая ищет escape-последовательности ODBC, а затем рекурсивно заменяет их на их собственный эквивалент.

Например, учитывая escape-последовательность ODBC, например: {fn concat(column1, column2)}

Я заменяю его его собственным эквивалентом SQL, подобным этому

if (StringComparer.OrdinalIgnoreCase.Equals("concat", strFunctionName)) {
    strArguments = strArguments;

    string[] astrArguments = strArguments.Split(',');
    string strTerm = astrArguments[0] + " || " + astrArguments[1];

    return strTerm;
}

, который приводит к column1 ||column2 (синтаксис PostGreSql)

Вообще говоря, мне нужно разрешить строку "arglist_comma_separated" в {fn what (arglist_comma_separated)}

в ее компоненты

Само собой разумеется,что с моей текущей версией это не сработает, если аргумент 1 в arglist будет выглядеть примерно так:

'hello, world'

Еще хуже, если я, например, сопоставлю фамилию и имя, это может быть {fn независимо от того, '(d'' Amato ',' Alberto ')}

Как бы я лучше разделил аргументы?

Как я могу сделать это с помощью регулярных выражений или с помощью синтаксического анализа?

Edit: Хм, подумал еще раз, и у него была великолепная идея (замените '' на escape-символ).При этом сложность проблемы снизилась со среднего до тривиального для решения без регулярных выражений:Приложение: На самом деле, только почти тривиально, я забыл рассмотреть вложение функций при разбиении - глупо.

    protected static string[] GetArguments(string strAllArguments)
    {
        string EscapeCharacter = System.Convert.ToChar(8).ToString();

        strAllArguments = strAllArguments.Replace("''", EscapeCharacter);

        bool bInString = false;
        int iLastSplitAt = 0;
        int iInFunction = 0;

        System.Collections.Generic.List<string> lsArguments = new System.Collections.Generic.List<string>();

        for (int i = 0; i < strAllArguments.Length; i++)
        {
            char strCurrentChar = strAllArguments[i];

            if (strCurrentChar == '\'')
                bInString = !bInString;

            if (bInString)
                continue;


            if (strCurrentChar == '(')
                 iInFunction++;

            if (strCurrentChar == ')')
                 iInFunction--;

            if (strCurrentChar == ',')
            {
                if(iInFunction == 0)
                {
                    string strExtract = strAllArguments.Substring(iLastSplitAt, i - iLastSplitAt);
                    strExtract = strExtract.Replace(EscapeCharacter, "''");
                    lsArguments.Add(strExtract);
                    iLastSplitAt = i;
                }
            }

        }


        string strExtractLast = strAllArguments.Substring(iLastSplitAt + 1);
        strExtractLast = strExtractLast.Replace(EscapeCharacter, "''");
        lsArguments.Add(strExtractLast);

        string[] astrResult = lsArguments.ToArray();
        lsArguments.Clear();
        lsArguments = null;

        return astrResult;
    }

1 Ответ

1 голос
/ 26 марта 2012

( Редактировать: Я много исправил этот ответ, так как много итераций обратной связи; ниже приведены мои выводы по этому вопросу)

Разбор сложного языка является сложной задачей, поэтомуЯ предполагаю, что вы сузили свою проблему до обработки разделенного запятыми списка значений токенов (таких как строки, числа, простые идентификаторы и т. Д., А не сложных выражений).Если я ошибаюсь, у вас, вероятно, есть большая проблема в ваших руках, чем вы думаете.В этом случае я бы предложил этот вопрос в качестве отправной точки.

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

В большинстве языков, еслистрока ограничена ', вы можете избежать кавычки в ней, используя \'.SQL интерпретирует '' внутри строки как экранированную кавычку.Если вы знаете, что будет присутствовать только одна из этих форм, вы можете игнорировать другую.В моем ответе ниже я решил включить оба.

Кроме того, некоторые языки допускают использование одинарных кавычек (') и двойных кавычек (") для разделения строки.Применяются те же наблюдения о сбежавших персонажах.Мое решение также работает с обеими формами.

Помимо строк, также важно указать, какие символы являются допустимыми для аргумента.Для простоты я предположил, что это будет «все, что не является запятой».По той же причине мое предлагаемое решение будет принимать любое количество строк и нестроковых значений и сгруппирует их вместе, возвращая их как единый объект (повторяя, что если ожидаются сложные выражения, вместо этого следует использовать более общую технику синтаксического анализаэтого простого решения).

Одним из способов реализации этого было бы циклически проходить символы, применяя приведенную выше логику, как вы делали в своем недавнем обновлении.Другой будет использовать регулярное выражение.Регулярное выражение имеет как плюсы лучшую производительность (обычно) и более чистый код, менее подверженный ошибкам.Основным недостатком является сложность самого регулярного выражения, поскольку «плотный» формат может быть сложнее для понимания / поддержки.

Тогда будет предложено мое предлагаемое регулярное выражение (для удобства чтения добавлены пробелы / новые строки):

(
    (?:  \'   (?: ['\\]\' | [^'] )*   \'  |
         \"   (?: ["\\]\" | [^"] )*   \"  |
         [^,'"]
    )+
)
(?: \, | $)

В кратком формате:

((?:\'(?:['\\]\'|[^'])*\'|\"(?:["\\]\"|[^"])*\"|[^,'"])+)(?:\,|$)

Каждая строка принимает в качестве «символов» либо экранированные кавычки (' или \, сопровождаемые '), либо все, что не является кавычкой.За совпадением (большая группа захвата) должен следовать либо ,, либо конец ввода.

Живой пример приведенного выше регулярного выражения можно увидеть здесь (примериспользует Ruby, но должен одинаково работать в C #).Пока все входные данные совпадают (т. Е. Не существует несоответствующей подстроки), каждое совпадение будет правильно захватывать аргумент. Предупреждение: искаженные входы будут давать неправильные выходы, поэтому приведенное выше регулярное выражение должно не использоваться для проверки.

Чтобы использовать это решение в своем коде C #, вы можете использоватьRegex.Matches:

MatchCollection matches = Regex.Matches(strArguments, "((?:\'(?:['\\]\'|[^'])*\'|\"(?:["\\]\"|[^"])*\"|[^,'"])+)(?:\,|$)");
string[] arguments = from m in matches select m.Captures[1].Value;

Как отмечено выше, вы также должны убедиться, что совпадения охватывают весь ввод.Я оставляю это как упражнение для читателя ...;)

Примечания:

  1. Я предполагаю, что результаты Matches не являютсяперекрытия;если я ошибаюсь, приведенный выше код должен быть адаптирован для каждого совпадения, начиная с индекса, который заканчивается в предыдущем;
  2. Я также, как обычно, предполагаю, что группа захвата # 0 будет целикомсовпадение, и # 1 будет первой группой захвата.
...