将没有空格的标题字符串分隔成单词。

32

我希望你能够在没有空格的标题中找到并分离单词。

之前:

ThisIsAnExampleTitleHELLO-WORLD2019T.E.S.T.(Test)"Test"'Test'[Test]

之后:

这是一个示例标题 HELLO-WORLD 2019 T.E.S.T. (测试) [测试] "测试" '测试'


我正在寻找一个正则表达式规则,它能够完成以下任务:
如果一个单词以大写字母开头,则标识出该单词。
但是也要保留所有的大写单词,以免将它们空格成{{A L L U P P E R C A S E}}。
附加规则:
- 如果字母与数字相接触,则在它们之间加上空格:Hello2019World变成Hello 2019 World - 忽略包含句点、连字符或下划线的缩写字母中的空格:T.E.S.T. - 如果在括号、圆括号或引号之间,则忽略空格:[Test] (Test) "Test" 'Test' - 保留连字符:Hello-World

C#

https://rextester.com/GAZJS38767

// Title without spaces
string title = "ThisIsAnExampleTitleHELLO-WORLD2019T.E.S.T.(Test)[Test]\"Test\"'Test'";

// Detect where to space words
string[] split =  Regex.Split(title, "(?<!^)(?=(?<![.\\-'\"([{])[A-Z][\\d+]?)");

// Trim each word of extra spaces before joining
split = (from e in split
         select e.Trim()).ToArray();

// Join into new title
string newtitle = string.Join(" ", split);

// Display
Console.WriteLine(newtitle);

正则表达式

我在数字、括号、圆括号和引号前面的空格上遇到了问题。

https://regex101.com/r/9IIYGX/1

(?<!^)(?=(?<![.\-'"([{])(?<![A-Z])[A-Z][\d+?]?)

(?<!^)          // Negative look behind

(?=             // Positive look ahead

(?<![.\-'"([{]) // Ignore if starts with punctuation
(?<![A-Z])      // Ignore if starts with double Uppercase letter
[A-Z]           // Space after each Uppercase letter
[\d+]?          // Space after number

)

解决方案

感谢您所有人的答案。这是一个正则表达式示例。我将其应用于文件名,并排除特殊字符\/:*?"<>|

https://rextester.com/FYEVE73725

https://regex101.com/r/xi8L4z/1


11
我赞同这篇帖子,因为它是我数小时以来看到的第一篇内容信息、研究和努力程度都相当适当的帖子。 - TheGeneral
2
@MichaelRandall,可悲的是,在大多数周末,我看到网站上的情况比这个记录要好。 - Tim Biegeleisen
4个回答

19

以下是一个正则表达式,对于您的示例输入至少似乎可以工作:

(?<=[a-z])(?=[A-Z])|(?<=[0-9])(?=[A-Za-z])|(?<=[A-Za-z])(?=[0-9])|(?<=\W)(?=\W)

此模式指定在以下条件之一的边界上进行拆分:

  • 前面是小写字母,后面是大写字母(或反之)
  • 前面是数字,后面是字母(或反之)
  • 前面和后面都是非单词字符(例如引号、括号等)


string title = "ThisIsAnExampleTitleHELLO-WORLD2019T.E.S.T.(Test)[Test]\"Test\"'Test'";
string[] split =  Regex.Split(title, "(?<=[a-z])(?=[A-Z])|(?<=[0-9])(?=[A-Za-z])|(?<=[A-Za-z])(?=[0-9])|(?<=\\W)(?=\\W)"); 
split = (from e in split select e.Trim()).ToArray();
string newtitle = string.Join(" ", split);

This Is An Example Title HELLO-WORLD 2019 T.E.S.T. (Test) [Test] "Test" 'Test'

注意:您可能还想将此断言添加到正则表达式的替代方案中:

(?<=\W)(?=\w)|(?<=\w)(?=\W)

我们在这里逃脱了,因为该边界条件从未发生过。但您可能需要用于其他输入。


我遇到了一个问题,当涉及到像 AI 这样的单个字母时,它们不会被分开,因为它们使用了 ALL UPPERCASE 规则(两个相邻的大写字母)。ATitleExample 变成了 ATitle Example - Matt McManis
1
@MattMcManis 这是一个边缘情况,可能会破坏这里给出的所有答案。你需要做更多的工作来覆盖这样的情况。 - Tim Biegeleisen
也许我可以将此输出通过第二个正则表达式来修复。 - Matt McManis

10

前几部分与 @revo回答 相似: (?<!^|[A-Z\p{P}])[A-Z]|(?<=\p{P})\p{P},此外我加入了以下正则表达式来在数字和字母之间添加空格: (?<=[a-z])(?=\d)|(?<=\d)(?=[a-z])|(?<=[A-Z])(?=\d)|(?<=\d)(?=[A-Z]),并且为检测到的 OTPIsADevice ,使用先行断言和后行断言查找大写字母和小写字母: (((?<!^)[A-Z](?=[a-z]))|((?<=[a-z])[A-Z]))

请注意,| 是或运算符,允许执行所有正则表达式。

正则表达式: (?<!^|[A-Z\p{P}])[A-Z]|(?<=\p{P})\p{P}|(?<=[a-z])(?=\d)|(?<=\d)(?=[a-z])|(?<=[A-Z])(?=\d)|(?<=\d)(?=[A-Z])|(((?<!^)[A-Z](?=[a-z]))|((?<=[a-z])[A-Z]))

演示

更新

稍作改进:

从: (?<!^|[A-Z\p{P}])[A-Z]|(?<=\p{P})\p{P}|(?<=[a-z])(?=\d)|(?<=\d)(?=[a-z])|(?<=[A-Z])(?=\d)|(?<=\d)(?=[A-Z])

到: (?<!^|[A-Z\p{P}])[A-Z]|(?<=\p{P})\p{P}|(?<=\p{L})\d 这个表达式可以做同样的事情。

(((?<!^)(?<!\p{P})[A-Z](?=[a-z]))|((?<=[a-z])[A-Z]))|(?<!^)(?=[[({&])|(?<=[)\]}!&}])是从OP评论中改进的正则表达式,针对某些标点符号添加了例外:(((?<!^)(?<!['([{])[A-Z](?=[a-z]))|((?<=[a-z])[A-Z]))|(?<!^)(?=[[({&])|(?<=[)\\]}!&}])

最终的正则表达式为:(?<!^|[A-Z\p{P}])[A-Z]|(?<=\p{P})\p{P}|(?<=\p{L})\d|(((?<!^)(?<!\p{P})[A-Z](?=[a-z]))|((?<=[a-z])[A-Z]))|(?<!^)(?=[[({&])|(?<=[)\]}!&}])

演示


这几乎完美地运作了。有一个问题,在最后一部分中 |(((?<!^)[A-Z](?=[a-z]))|((?<=[a-z])[A-Z])) 没有保留括号、方括号和引号。https://rextester.com/BTA83734 - Matt McManis
谢谢,你的正则表达式已经解决了单个字母的问题。我在结尾添加了一些额外的规则来处理其他问题。https://rextester.com/FYEVE73725 - Matt McManis

9

为了追求简单而不是庞大的正则表达式,我推荐使用此代码,其中包含小而简单的模式(代码中有解释的注释):

string str = "ThisIsAnExampleTitleHELLO-WORLD2019T.E.S.T.(Test)\"Test\"'Test'[Test]";
// insert space when there is small letter followed by upercase letter
str = Regex.Replace(str, "(?<=[a-z])(?=[A-Z])", " ");
// insert space whenever there's digit followed by a ltter
str = Regex.Replace(str, @"(?<=\d)(?=[A-Za-z])", " ");
// insert space when there's letter followed by digit
str = Regex.Replace(str, @"(?<=[A-Za-z])(?=\d)", " ");
// insert space when there's one of characters ("'[ followed by letter or digit
str = Regex.Replace(str, @"(?=[(\[""'][a-zA-Z0-9])", " ");
// insert space when what preceeds is on of characters ])"'
str = Regex.Replace(str, @"(?<=[)\]""'])", " ");

如果你的主要关注点是评论,你可以启用x模式或使用内联注释,例如(?#当字母后跟数字时插入空格) - revo
2
@revo 我使用了标准的C#注释 :) 我认为这样更易读。 - Michał Turczyn
2
你也可以通过设置standard x修饰符来编写这种可读注释,从而编写多行、缩进完美的注释。顺便说一句,这并不简单,只是需要分割。 - revo

8

您可以通过采用不同的解释方式来减少正则表达式的要求,从而缩短步骤。例如,第一个要求可以理解为:“在大写字母前没有标点或大写字母的情况下保留大写字母”。

以下正则表达式适用于几乎所有提到的要求,并可以扩展以包括或排除其他情况:

(?<!^|[A-Z\p{P}])[A-Z]|(?<=\p{P})\p{P}

你需要使用 Replace() 方法,并将 $0 作为替换字符串。
在此处查看实时演示
.NET(在此执行):
string input = @"ThisIsAnExample.TitleHELLO-WORLD2019T.E.S.T.(Test)""Test""'Test'[Test]";
Regex regex = new Regex(@"(?<!^|[A-Z\p{P}])[A-Z]|(?<=\p{P})\p{P}", RegexOptions.Multiline);
Console.WriteLine(regex.Replace(input, @" $0"));

这是一个有趣的方法。可以添加哪个规则来通过在 2019 之间加入空格来修复 HELLO-WORLD2019 - Matt McManis
1
在交替项中添加 (?<=\p{L})\d(?<!^|[A-Z\p{P}])[A-Z]|(?<=\p{P})\p{P}|(?<=\p{L})\d - revo
我还有一个问题,像 AI 这样的单个字母单词不会空格。ATitleExample 变成了 ATitle Example - Matt McManis
OTPIsADevice 这样的名称怎么样? - revo
它开始变得复杂了。OTPIs ADevice 或许我可以将输出通过第二个过滤器。规则:如果一个单词以2个大写字母开头 ADevice,则在第一个字母后添加一个空格 A Device。并且如果一个全大写的单词以一个小写字母结尾 OTPIs,则在最后两个字母前添加一个空格 OTP Is - Matt McManis

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