需要帮助从 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 组(这显然不是一个有效的数字、布尔值或任何特定的东西),我们只需要解决01111,其中所有这些组实际上都是一串布尔值。

同样,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(`(?&lt;=[01$spaces]+)([01])[$spaces]*`, `g`); 见 regex101.com/r/GVOHEy/1 并替换为完整匹配 .replace(incorectReg,'$&amp; ') @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中提取一部分[重复]

如何从 JavaScript 中的字符串中提取基本 URL?

从javascript中的字符串中提取数字[重复]

如何从javascript中的rgb字符串中提取颜色值[重复]

JavaScript中如何提取字符串?

需要帮助以在 Google Maps API Javascript 代码中提取 XML 和多个位置