【问题标题】:Can I use the same substring as part of different captures?我可以使用相同的子字符串作为不同捕获的一部分吗?
【发布时间】:2016-03-07 19:08:40
【问题描述】:

我想创建一个函数,允许我将 CamelCase 转换为 Title Case。对于正则表达式来说,这似乎是一项不错的任务,但如果您有更好的解决方案,我不承诺使用正则表达式。

这是我的第一次尝试,在大多数情况下都有效,但我将在几行内解决一些问题:

private static Regex camelSplitRegex = new Regex(@"(\S)([A-Z])");
private static String camelReplacement = "$1 $2";

public String SplitCamel(String text){
    return camelSplitRegex.Replace(text, camelReplacement);
}

正则表达式模式查找非空白字符(第一次捕获)后跟大写字母(第二次捕获)。在函数中,Regex.Replace 用于在第 1 次和第 2 次捕获之间插入一个空格。

这适用于许多示例:

  • SplitCamel("privateField") 返回"private Field"
  • SplitCamel("PublicMethod") 返回"Public Method"
  • SplitCamel(" LeadingSpace") 返回 " Leading Space",而不根据需要在“前导”之前插入额外的空格。

我遇到的问题是在处理多个连续的大写字母时。

  • SplitCamel("NASA") 返回“N AS A”而不是“N A S A”
  • SplitCamel("C3PO") 返回“C3 PO”而不是“C3 P O”
  • SplitCamel("CAPS LOCK FEVER") 返回“C AP S L OC K F EV E R”而不是“C A P S L O C K F E V E R”

在这些情况下,我认为问题在于每个大写字母仅被捕获为\S 或[A-Z],但不能在一场比赛中为\S,在下一场比赛中为[A-Z]。


我的主要问题是,“.NET 正则表达式引擎是否有某种方式支持将相同的子字符串用作连续匹配的不同捕获?”其次,有没有更好的分割骆驼案的方法?

【问题讨论】:

  • 澄清一下,您肯定希望连续的大写字母仍然被拆分(即“NASA”应该转到“N A S A”)或者是否倾向于将大写字母块保留为一个块?
  • 结果不应包含两个感人的大写字母。

标签: c# regex


【解决方案1】:
private static Regex camelSplitRegex = new Regex(@"(?<=\w)(?=[A-Z])");
private static String camelReplacement = " ";

完成这项工作。

您的模式的问题是,当您有字符串“ABCD”时,\S 匹配 A,([A-Z]) 匹配 B,并且您获得“A BCD”,但是对于下一个替换,B 已经被模式消耗并且不能再使用了。

方法是使用不消耗字符的lookarounds (一个lookbehind (?&lt;=...)和一个lookahead (?=...)),它们只是测试字符串中的当前位置,这就是为什么替换字符串中不需要任何引用,只需要在当前位置放一个空格即可。

\w 字符类包含 unicode 字母、unicode 数字和下划线。如果要将搜索限制为 ASCII 数字和字母,请改用 [0-9a-zA-Z]。

更准确地说:

  • 对于 unicode,请使用 (?&lt;=[\p{L}\p{N}])(?=\p{Lu}),它适用于重音字母和其他字母和数字。
  • 对于 ASCII 使用 (?&lt;=[a-zA-Z0-9])(?=[A-Z])

【讨论】:

  • 非常简单。我总是忽略前瞻和后视。谢谢。
【解决方案2】:

这是一种非常规的表达方式。

public static string SplitCamel(this string stuff)
{
    var builder = new StringBuilder();
    char? prev = null;
    foreach (char c in stuff)
    {
        if (prev.HasValue && !char.IsWhiteSpace(prev.Value) && 'A' <= c && c <= 'Z') 
            builder.Append(' ');
        builder.Append(c);
        prev = c;
    }

    return builder.ToString();
}

以下

Console.WriteLine("'{0}'", "privateField".SplitCamel());
Console.WriteLine("'{0}'", "PublicMethod".SplitCamel());
Console.WriteLine("'{0}'", " LeadingSpace".SplitCamel());
Console.WriteLine("'{0}'", "NASA".SplitCamel());
Console.WriteLine("'{0}'", "C3PO".SplitCamel());
Console.WriteLine("'{0}'", "CAPS LOCK FEVER".SplitCamel());

打印

'私人领域'

'公共方法'

'领先空间'

'N A S A'

'C3 P O'

'C A P S L O C K F E V E R'

【讨论】:

  • 感谢您提供非正则表达式替代方案,但我将采用 Casimir 的先行/后行解决方案。
【解决方案3】:

请考虑切换到值类型字符串而不是字符串类。对此进行更新。

 private static Regex camelSplitRegex = new Regex(@"(^\S)?([A-Z])");

【讨论】:

  • 我不确定您的评论的确切含义,而且我认为您的正则表达式模式在这种情况下不会起作用。前面的非空白字符不应该是可选的,这将允许以大写字母开头的匹配项。
  • 关于字符串 stackoverflow.com/questions/7074/… 。请根据您的示例对其进行测试或提供一个不起作用的示例。请用一个具体的例子证明它不起作用。
  • 虽然我知道出于某种原因它违反了 Microsoft 风格指南,但我通常使用 CLR 原始类型名称“String”或“Int32”而不是“string”或“int”,这样 1) 全部我的类型名称在源代码中的颜色相同,2)我的变量声明中使用的类型名称与调用这些类型的静态方法必须使用的类型名称匹配(“Int32.Parse(text)”),3)我在处理多种 .NET 语言时可以使用相同的原始类型名称,并且 4) 不会混淆“int”或“long”的大小,这在非 .NET 语言之间会有所不同。
  • 我想不出使用 C# 原始类型关键字的任何优势,除了约定和一些需要 C# 关键字的领域,例如 VS2015 之前的 Enum 类型声明(“enum MyEnum : byte {}” )。
  • 我实际上是从 Jeffrey Richter 的“CLR via C#”一书中得到这个想法的。他更喜欢 CLR 类型名称还有其他一些原因。查看第 5 章的开头,该章节位于此 PDF 的 pg117。 sd.blackball.lv/library/…
猜你喜欢
  • 2013-09-24
  • 2021-07-17
  • 2021-03-12
  • 2019-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多