如何拆分sql参数列表字符串?

时间:2012-03-26 07:09:27

标签: c# sql regex parsing split

问题:

我有一个搜索ODBC转义序列的函数, 然后用它们的原生等价物递归替换它们。

例如,给定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 whatever(arglist_comma_separated)}

进入其组件

毋庸置疑,对于我目前的版本,它不起作用 如果arglist中的参数1类似于

'hello, world'

更糟糕的是,如果我以家庭和名字为例,它可能是 {fn what('d'Amato','Alberto')}

我如何最好地分割论点?

如何使用正则表达式或解析?

编辑:
嗯,再想一想,并有一个出色的想法(用一个逃脱角色代替'') 有了这个,对于非正则表达式解决方案,问题的复杂性从中高到降级为:
附录:实际上,只是几乎无足轻重,我忘了考虑功能嵌套在分裂 - 愚蠢。

    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 个答案:

答案 0 :(得分:1)

编辑:我清理过这个答案很多,因为很多反馈迭代;下面是我对这个主题的结论)

解析复杂的语言是一项艰巨的任务,因此我假设您将问题缩小到处理以逗号分隔的标记值列表(例如字符串,数字,简单标识符等 - 而不是复杂的表达式)。如果我弄错了,你手中的问题可能比你想象的要大。在这种情况下,我建议this question作为起点。

最简单的解决方案 - 在,上拆分 - 主要不是因为字符串,因为逗号可以出现在字符串中。解析字符串是一项简单的任务,假设您正确处理转义字符:它以引号开头,具有零个或多个字符,并以另一个引号结束。

在大多数语言中,如果字符串由'分隔,则可以使用\'转义其中的引号。 SQL将字符串中的''解释为转义引用。如果您知道只有其中一种形式存在,您可以忽略另一种形式。在下面的答案中,我决定将两者都包括在内。

此外,某些语言接受单引号(')和双引号(")来分隔字符串。关于转义字符的相同观察结果适用。我的解决方案也涉及两种形式。

除了字符串之外,指定参数的有效字符也很重要。为简单起见,我认为它将是“任何不是逗号”。出于同样的原因,我提出的解决方案将接受任意数量的字符串和非字符串,并将它们组合在一起,将它们作为单个实体返回(重申,如果需要复杂表达式,则应采用更通用的解析技术这个简单的解决方案)。

实现这一点的一种方法是在应用上述逻辑时循环查看字符,就像在最近的更新中所做的那样。另一种是使用正则表达式。正则表达式具有更好的性能(通常)和更清晰的代码,更不容易出错。主要的骗局是正则表达式本身的复杂性,因为“密集”格式可能更难理解/维护。

我建议的正则表达式将是(为了便于阅读而添加空格/换行符):

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

简短格式:

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

每个字符串都接受转义引号('\后跟')或任何非引号的“字符”。匹配(大捕获组)必须后跟,或输入结束。

上面的正则表达式的实例可以看出here(该示例使用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将首先捕获组。