使用 RegEx 解析具有复杂分隔符的字符串

Posted

技术标签:

【中文标题】使用 RegEx 解析具有复杂分隔符的字符串【英文标题】:Use RegEx to parse a string with complicated delimiting 【发布时间】:2013-06-10 06:08:09 【问题描述】:

这是一个正则表达式问题。

感谢您的帮助,请耐心等待,因为 RegEx 绝对不是我的强项!

完全作为背景...我问的原因是我想使用 RegEx 来解析类似于 SVG 路径数据段的字符串。我一直在寻找解析段及其段属性的先前答案,但没有找到正确处理后者的答案。

以下是一些示例字符串,例如我需要解析的字符串:

M-11.11,-22
L.33-44  
ac55         66 
h77  
M88 .99  
Z 

我需要将字符串解析成这样的数组:

["M", -11.11, -22]
["L", .33, -44]
["ac", 55, 66]
["h", 77]
["M", 88, .99]
["Z"]

到目前为止,我在这个答案上找到了这段代码:Parsing SVG "path" elements with C# - are there libraries out there to do this? 帖子是 C#,但正则表达式在 javascript 中很有用:

var argsRX = /[\s,]|(?=-)/; 
var args = segment.split(argsRX);

这是我得到的:

 [ "M", -11.11, -22, <empty element>  ]
 [ "L.33", -44, <empty>, <empty> ]
 [ "ac55", <empty>, <empty>, <empty>, 66 <empty>  ]
 [ "h77", <empty>, <empty>  
 [ "M88", .99, <empty>, <empty> ]
 [ "Z", <empty> ]

使用此正则表达式时的问题:

一个不需要的空数组元素被放置在每个字符串数组的末尾。 如果多个空格是分隔符,则会为每个额外的空格创建一个不需要的空数组元素。 如果数字紧跟在开头字母之后,则该数字将附加到字母上,但应成为单独的数组元素。

以下是传入字符串的更完整定义:

每个字符串以 1 个或多个字母开头(大小写混合)。 接下来是零个或多个数字。 数字可能带有减号(总是在前面)。 这些数字可能在数字的任意位置(末尾除外)有一个小数点。 可能的分隔符有:逗号、空格、空格、减号。 前面或后面有空格的逗号也是可能的分隔符。 即使减号是分隔符,它们也必须与数字保持一致。 数字可能紧跟在开头字母之后(没有空格),并且该数字应该是分开的。

这是我一直在使用的测试代码:

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body background-color: ivory; 
</style>

<script>
    $(function()


var pathData = "M-11.11,-22 L.33-44  ac55    66 h77  M88 .99  Z" 

// separate pathData into segments
var segmentRX = /[a-z]+[^a-z]*/ig;
var segments = pathData.match(segmentRX);

for(var i=0;i<segments.length;i++)
    var segment=segments[i];
    //console.log(segment);

    var argsRX = /[\s,]|(?=-)/; 
    var args = segment.split(argsRX);
    for(var j=0;j<args.length;j++)
        var arg=args[j];
        console.log(arg.length+": "+arg);
    



    ); // end $(function());
</script>

</head>

<body>
</body>
</html>

【问题讨论】:

["M", 88 .99] 应该是 ["M", 88, .99] 吗? 糟糕,实际上是一个错字!我的意思是输入一个包含 3 个元素的数组:“M”、88 和 .99——抱歉。 【参考方案1】: ^([a-z]+)(?:(-?\d*.?\d+)[^\d\n\r.-]*(-?\d*.?\d+)?)?

解释

^ # 字符串开头 ([a-z]+) # 任意数量的字符,匹配到第 1 组 (?: # 非捕获组 (-?\d*.?\d+) # 第一个数字(可选单数和小数点,数字) [^\d\n\r.-]* # 分隔字符(除了这些) (-?\d*.?\d+)? # 第二个数字 )? # 结束非捕获组,设为可选

与“不区分大小写”标志一起使用。

http://rubular.com/r/EyUNmoONJ7 https://regex101.com/r/gTczcD/1

【讨论】:

谢谢,看起来不错,除了一些不需要的额外元素(由于额外的空格分隔符)。我可以处理这些额外的元素。 在您的链接中 Match4 有 1 个空匹配,而 Match6 有 2 个空匹配。 @markE 是的,因为这些行上没有匹配的数字?我不确定你在说什么。 可能是我解释的错。当有超过 1 个空格分隔数字时,我希望这些额外空格不会返回匹配项。也许我误解了......正则表达式不是我的强项。 :) 我的正则表达式为您的输入返回 'ac55 66'* 数组 ['ac55 66', 'ac', '55', '66']。没有返回额外的空格。我错过了什么? *(请注意,本站的 cmets 中会折叠多个空格)【参考方案2】:
function parsePathData(pathData)

    var tokenizer = /([a-z]+)|([+-]?(?:\d+\.?\d*|\.\d+))/gi,
        match,
        current,
        commands = [];

    tokenizer.lastIndex = 0;
    while (match = tokenizer.exec(pathData))
    
        if (match[1])
        
            if (current) commands.push(current);
            current = [ match[1] ];
        
        else
        
            if (!current) current = [];
            current.push(match[2]);
        
    
    if (current) commands.push(current);
    return commands;


var pathData = "M-11.11,-22 L.33-44  ac55    66 h77  M88 .99  Z";
var commands = parsePathData(pathData);
console.log(commands);

输出:

[ [ "M", "-11.11", "-22" ],
  [ "L", ".33", "-44" ],
  [ "ac", "55", "66" ],
  [ "h", "77" ],
  [ "M", "88", ".99" ],
  [ "Z" ] ]

【讨论】:

不应该将ac解析为一个元素吗? 谢谢!优点:似乎可以解析出所有的字母和数字。但是还有额外的元素,如“未定义”、单数逗号和额外的空元素。我想我以后可以用 javascript 过滤掉这些。 好的,现在输出看起来不错!我最初的“额外”元素可能是由于我在正则表达式方面的弱点。【参考方案3】:

我必须对数据进行非常相似的解析,才能在全国最大的田径比赛中报告实时结果。 http://ksathletics.com/2013/statetf/liveresults.js 虽然涉及到很多客户端和服务器端代码,但原理是一样的。事实上,数据的种类实际上是相同的。

我建议您不要使用一个“巨型”正则表达式,而是使用一个分隔数据片段的表达式和另一个将每个数据片段分解为其主要标识符和以下值的表达式。这通过允许二级正则表达式匹配数据值的定义而不必区分分隔符,从而解决了各种分隔符的问题。 (这也比将所有逻辑放入单个正则表达式更有效。)

这是一个经过测试可以处理您提供的输入的解决方案。

<script>
var pathData = "M-11.11,-22 L.33-44  ac55    66 h77  M88 .99  Z" 

function parseData(pathData) 
    var pieces = pathData.match(/([a-z]+[-.,\d ]*)/gi), i;
    /* now parse each piece into its own array */
    for (i=0; i<pieces.length; i++)
        pieces[i] = pieces[i].match(/([a-z]+|-?[.\d]*\d)/gi);
    return pieces;


pathPieces = parseData(pathData);
document.write(pathPieces.join('<br />'));
console.log(pathPieces);
</script>

http://dropoff.us/private/1370846040-1-test-path-data.html

更新:结果完全等同于您想要的指定输出。然而,我想到的一个想法是,您是否还想要或需要从字符串到数字的类型转换。你也需要那个吗?我只是在考虑解析数据之外的下一步。

【讨论】:

@markE 澄清一下,我的代码中唯一需要的部分是parseData 函数。您可以将其粘贴在您的代码中并在您的数据字符串上调用它以将其转换为您想要的数组。【参考方案4】:

您的“模式”由一个或多个字母组成,后跟一个十进制数字,然后是另一个以逗号或空格分隔的数字。

正则表达式:/([a-z]+)(-?(?:\d*\.)?\d+)(?:[,\s]+|(?=-))(-?(?:\d*\.)?\d+)/i

【讨论】:

我相信这将无法正确解析L.33-44[,\s]+ 需要更改为由单个逗号、一个或多个空白字符或 - 的前瞻组成的非捕获组。 非常感谢。这个正则表达式更好,但一些前缀字母和数字仍然一起运行。归结起来,该模式是一个或多个字母后跟可能是十进制或负数的数字。似乎问题在于分隔符可能是逗号、一个或多个空格、一个逗号加空格、下一个数字的减号或下一个数字的小数点。 抱歉,将此答案发布在我的小型笔记本电脑上,所以我看不到要仔细检查所有内容的问题!编辑答案以实现L.33-44 的前瞻并添加 CI 标志。 svgs 的数字可以是浮点字面量,因此它们可以是 +- 前缀(不仅仅是 -),并且可以包含 e 或 E 作为指数,这也会导致您的 a-z catch 出现问题。【参考方案5】:

你可以试试这个模式:

/([a-z]+)(-?(?:\d*\.)?\d+)?(?:\s+|,|(-(?:\d*\.)?\d+))?(-?(?:\d*\.)?\d+)?/

(有点长,但似乎可以)

注意最后一个数字可以在捕获组\3或\4中

【讨论】:

以上是关于使用 RegEx 解析具有复杂分隔符的字符串的主要内容,如果未能解决你的问题,请参考以下文章

对分隔符具有特定约束的拆分字符串

解析器:分隔符指导

使用 RegEx 忽略分隔符前的特定字符

C# 使用 Regex.Split 拆分大字符串。必须保留分隔符

如何保留 Regex.Split 的分隔符?

如何将分隔符数组传递给 Regex.Split 函数? [关闭]