C#排序并放回Regex.matches

3
有没有办法使用RegEx.Matches来查找并将匹配的值以不同(字母)顺序写回?目前我有以下代码:
var pattern = @"(KEY `[\w]+?` \(`.*`*\))";
var keys = Regex.Matches(line, pattern);

Console.WriteLine("\n\n");
foreach (Match match in keys)
{
    Console.WriteLine(match.Index + " = " + match.Value.Replace("\n", "").Trim());
}

但是我真正需要的是获取table.sql备份,并按字母顺序排序现有的索引,例如代码:

line = "...PRIMARY KEY (`communication_auto`),\n  KEY `idx_current` (`current`),\n  KEY `idx_communication` (`communication_id`,`current`),\n  KEY `idx_volunteer` (`volunteer_id`,`current`),\n  KEY `idx_template` (`template_id`,`current`)\n);"

感谢您 J


更新: 感谢m.buettner的解决方案,它为我提供了基础,让我可以继续前进。不幸的是,我并不擅长正则表达式,但最终我得到了一段代码,我相信它仍有改进空间:

...
//sort INDEXES definitions alphabetically
if (line.Contains("  KEY `")) line = Regex.Replace(
    line,
    @"[ ]+(KEY `[\w]+` \([\w`,]+\),?\s*)+",
    ReplaceCallbackLinq
);

static string ReplaceCallbackLinq(Match match) 
{
    var result = String.Join(",\n  ",
        from Capture item in match.Groups[1].Captures
        orderby item.Value.Trim()
        select item.Value.Trim().Replace("),", ")")
    );
    return "  " + result + "\n";
}


更新:还有一种情况,当索引字段长度超过255个字符时,MySQL会将索引截短至255个字符,并以以下方式写入:

KEY `idx3` (`app_property_definition_id`,`value`(255),`audit_current`),

所以,为了适应这种情况,我不得不更改一些代码: 在ReplaceCallbackLinq中:
select item.Value.Trim().Replace("`),", "`)")

并将正则表达式定义为:
@"[ ]+(KEY `[\w]+` \([\w`(\(255\)),]+\),?\s*)+",
2个回答

2
这不能仅通过正则表达式完成。但是,您可以使用回调函数,并利用.NET捕获同一捕获组中多个内容的独特能力。这样,您就避免了使用Matches并自己编写所有内容的情况。相反,您可以使用内置的Replace函数。我的下面的示例只是对KEY短语进行排序并将它们放回原处(因此除了对SQL语句中的短语进行排序外,什么也没做)。如果您想要不同的输出,您可以通过捕获模式的不同部分和在最后进行Join操作来轻松实现。
首先,我们需要一个匹配评估器来传递回调函数:
MatchEvaluator evaluator = new MatchEvaluator(ReplaceCallback);

接下来,我们编写一个正则表达式,一次性匹配整个索引集合,并在捕获组中捕获索引名称。然后将其放入带有评估器参数的Replace重载方法中:

output = Regex.Replace(
    input,
    @"(KEY `([\w]+)` \(`[^`]*`(?:,`[^`]*`)*\),?\s*)+",
    evaluator
);

现在,在大多数语言中,这并不是有用的,因为由于重复捕获,捕获组1始终只包含第一个或最后一个被捕获的内容(与捕获组2相同)。但幸运的是,您正在使用C#,.NET的正则表达式引擎非常强大。因此,让我们来看看回调函数以及如何使用多个捕获:

static string ReplaceCallback(Match match)
{
    int captureCount = match.Groups[1].Captures.Count;
    string[] indexNameArray = new string[captureCount];
    string[] keyBlockArray = new string[captureCount];
    for (int i = 0; i < captureCount; i++)
    {
        keyBlockArray[i] = match.Groups[1].Captures[i].Value;
        indexNameArray[i] = match.Groups[2].Captures[i].Value;
    }
    Array.Sort(indexNameArray, keyBlockArray);
    return String.Join("\n  ", keyBlockArray);
}
match.Groups[i].Captures允许我们访问单个组的多个捕获。由于这些是Capture对象,目前似乎并不是很有用,因此我们从它们的值中构建了两个字符串数组。然后我们使用Array.Sort根据其中一个数组的值(被视为键)对两个数组进行排序。作为“键”,我们使用表名的捕获。作为“值”,我们使用一个完整的KEY ...,块的全部捕获。这将按名称对完整的块进行排序。然后我们可以简单地将块连接在一起,添加之前使用的空格分隔符并返回它们。

谢谢,这个解决方案给了我基础知识,让我可以继续前进。可惜我不太擅长正则表达式,但最终我得到了一段代码,我相信仍然可以改进: - Jakub Pawlinski

1

不确定我是否完全理解问题,但将foreach更改为:

foreach (Match match in keys.Cast<Match>().OrderBy(m => m.Value))

你想做什么?


谢谢,也许我没有表达清楚。你的代码解决了一半的问题,另一半是如何按照这个新顺序将其写回。 - Jakub Pawlinski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接