需要帮助从 JavaScript 中的字符串中提取数字
Posted
技术标签:
【中文标题】需要帮助从 JavaScript 中的字符串中提取数字【英文标题】:Need help extracting numbers from string in JavaScript 【发布时间】:2020-12-03 12:50:03 【问题描述】:我需要一个坚如磐石的正则表达式来尝试解决 Raphael.js parseStringPath
处理有关 Arc 路径命令和可能的其他命令的一些问题(SnapSVG 也继承了该问题)。你看,arcTo
path 命令接受 7 个坐标和设置,但是某些字符串可能由于极端优化而格式错误,并且浏览器不会标记它们,而是正确呈现它们。检查Raphael.js demo here。
看看这个例子,我使用来自 Raphael.js 的 RegExp 和一个非常简单的例子,我自己的 RegExp 名为 incorrectReg
,试图将 000
之类的字符串分解为 [0
,@987654328 @,0
] 或 011
转换为 [0
,1
,1
]。
let spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029",
pathValues = new RegExp(`(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[$spaces]*,?[$spaces]*`, `ig`),
incorectReg = new RegExp(`([$spaces]*0(?=[a-z0-9])|([$spaces]\\0)*0(?=[a-z0-9]*))`, `ig`); // THIS ONE
function action()
let input = document.getElementById('input'),
output = document.getElementById('output'),
pathValue = input.getAttribute('d'),
segments = pathValue.replace(/([a-z])/gi,'|$1').split('|').filter(x=>x.trim()),
pathArray = []
segments.map(x=>
let pathCommand = x[0],
pathParams = x.replace(pathCommand,'').trim()
pathArray.push( [pathCommand].concat(
pathParams.replace(',',' ')
.replace(pathValues,' $1 ')
.replace(incorectReg,'$1 ')
.split(' '))
.filter(x=>x)
);
)
output.setAttribute('d',pathArray.map(x=>x.join(' ')).join(''))
console.table(pathArray)
svg max-width:49%
<button onclick="action()">Extract</button>
<hr>
<svg viewBox="0 0 16 16">
<path id="input" d="M2,0a2 2 0 00,-2 2a2 2 0 002 2a.5.5 0 011 0z" stroke="red" stroke- fill="none"></path>
</svg>
<svg viewBox="0 0 16 16">
<path id="output" d="M0 0" stroke="green" stroke- fill="none"></path>
</svg>
正如您在浏览器控制台中看到的,我们已经解决了000
组(这显然不是一个有效的数字、布尔值或任何特定的东西),我们只需要解决011
和11
,其中所有这些组实际上都是一串布尔值。
同样,arcTo
路径命令适用于
arcTo -> ['A', rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y]
// str, float, float, float, boolean (0|1), boolean (0|1), float, float
我需要更好的incorrectReg
RegExp 和组合解决方案来正确处理主要是arcTo
和其他类似情况。接受任何建议。
谢谢
【问题讨论】:
您是严格需要正则表达式,还是只想将d
属性拆分为组件?编写一个简单的解析器对 IMO 来说并不困难,您可以将其调整为所有这些“过度优化”(在小数分隔符和减号之前省略空格,省略 lineTo 命令......)
我需要任何 RegExp + 其他解决方案的组合,我知道 fontello/svgpath.js 但那个人正在逐个字符地解析字符串,它没有失败,但我的代码完全支持拉斐尔,所以我需要一个综合解决方案。
“逐字符解析字符串”有什么问题?
也许 incorectReg = new RegExp(`(?<=[01$spaces]+)([01])[$spaces]*`, `g`);
见 regex101.com/r/GVOHEy/1 并替换为完整匹配 .replace(incorectReg,'$& ')
@Thefourthbird 谢谢,这个正则表达式很好,但是这还不够,例如 1.874
是一个布尔值(第 4 个参数)和一个浮点数(第 5 个参数),我可能会为 Raphael 发布一个 PR直接与我们的发现。再次感谢
【参考方案1】:
根据 OP 下面的讨论,我建议不要使用正则表达式,而是使用适当的解析器(或词法分析器或标记器或如何正确调用它)。
你可以
编写自己的解析器(很好的练习) 使用现有的东西,例如我已经成功尝试 svg-path-parser。我什至不确定是否可以创建这样的“超级”正则表达式。无论如何您可以在解析过程中使用“子”正则表达式:-)
【讨论】:
谢谢大佬,已经搞定了。我不能使用该解析器,因为它似乎不是 ES6/ES7,并且它的最后一次更新是 3 年“旧”。完成所有测试后,我正在为 Raphael 准备 PR。谢谢你,祝你好运。【参考方案2】:为了清楚起见并为社区服务,我将发布一个可行的解决方案,它可能会在未来对某人有所帮助。
不幸的是incorrectReg
RegExp,好的或坏的不能工作,因为它也可以改变其他值(例如:M0,11
使用TheFourthBird 提供的正则表达式返回["M",0,1,1]
),所以是的Jan,你是对的!
这是一个可行的解决方案,如果您愿意,请随时编辑或添加更多清晰度。一旦我们都同意一个坚如磐石的解决方案,我会立即向 Raphael 提交 PR。
let spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029",
pathValues = new RegExp(`(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[$spaces]*,?[$spaces]*`, `ig`),
incorrectReg = new RegExp(`(?<=[01$spaces]+)([01])[$spaces]*`, `g`); // FIXED ONE
function action()
let input = document.getElementById('input'),
output = document.getElementById('output'),
pathValue = input.getAttribute('d'),
segments = pathValue.replace(/([a-z])/gi,'|$1').split('|').filter(x=>x.trim()),
pathArray = []
segments.map(x=>
let pathCommand = x[0],
pathParams = x.replace(pathCommand,'').trim();
pathParams = pathParams.replace(',',' ')
.replace(pathValues,' $1 ')
/* .replace(incorrectReg,' $& ') */
.split(' ').filter(x=>x);
if ( pathCommand.toLowerCase() === 'a' && pathParams.length < 7)
for (let i=0, ln = pathParams.length; i<ln; i++)
if ( (i === 3 || i === 4) && pathParams[i].length > 1 )
pathParams = pathParams.slice(0,i) // first part of array
.concat(pathParams[i][0]) // extract largeArcFlag OR sweepFlag
.concat(
pathParams[i].slice(1).replace(/(\-\d|\-\.\d|\.\d*(?=\.))/g,'|$1').split('|'), // get sweepFlag
pathParams.slice(i+1)) // continue after flags
.filter(x=>x) // remove added empty "space" items
ln = pathParams.length // update length
if (pathParams.length === 7)
pathArray.push([pathCommand].concat(pathParams.splice(0, 7)));
else
throw Error(`arcTo requires 7 coordinates, only $pathParams.length + ' given: ['+pathParams.join(',')]`)
else
pathArray.push( [pathCommand].concat(pathParams) );
)
output.setAttribute('d',pathArray.map(x=>x.join(' ')).join(''))
// console.log(pathArray)
svg max-width:49%
<button onclick="action()">Extract</button>
<hr>
<svg viewBox="0 0 16 16">
<path id="input" d="M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2zm7.5 11h-4a.5.5 0 01-.5-.5v-4a.5.5 0 011 0v2.793l4.146-4.147a.5.5 0 01.708.708L6.707 10H9.5a.5.5 0 010 1z" fill="red"></path>
</svg>
<svg viewBox="0 0 16 16">
<path id="output" d="M0 0" fill="green"></path>
</svg>
【讨论】:
考虑添加大量测试以真正“坚如磐石”并发布它,例如在npm 上作为(之一?)参考路径字符串解析库:-)。除了 svg-path-parser,我还尝试了其他几个包,但都失败了。即使像svg.js 这样的“大”库也无法正确解析字符串(我打开了issue) 不,这不过是合规和工作。 1. 如果没有明确使用命令字母,它不提供序列的重复 -A
后面可以跟 7、14、21... 数字,代表 1、2、3 弧段。 2. 指数的e
会被误解为路径命令。 3. 数字可以以+
符号开头,而不仅仅是-
或没有。 4、大范围的无效序列不会被抓到。请参考规范的BNF语法。以上是关于需要帮助从 JavaScript 中的字符串中提取数字的主要内容,如果未能解决你的问题,请参考以下文章
如何从 JavaScript 中的字符串中提取基本 URL?