在Javascript中防止正则表达式回溯SI前缀

时间:2014-10-17 10:47:08

标签: javascript regex

在Javascript中,我使用正则表达式捕获SI单位(以及一些非SI单位),并使用乘法点正确格式化它们。

例如:“Js”变为“J⋅s”,“mΩm”变为“mΩ·m”等。

问题是,有一些前缀也是SI单位(例如“m”),所以我所写的将错误地将“mΩ”转换为“m⋅Ω”。

var $dot = "\u22c5";
var $minus = "\u2212";

var $prefix = "(p|n|\u00B5|m|c|d|k|M|G|T)";
var $si_unit = "(m|g|l|L|s|A|K|mol|cd|Hz|rad|sr|N|Pa|J|W|C|V|F|\u03A9|S|Wb|T|H|\u00B0C|\u00B0F|lm|lx|Bq|Gy|Sv|kat|eV|\u0025)";
var $power = "([+" + $minus + "]?\d+)";

var $unit = "(" + $prefix + "?" + $si_unit + $power + "?)";
var $multiplied = $unit + "(" + $dot + $unit + ")*";
var $denominator = $multiplied + "(\/" + $multiplied + ")?";

var $corrections= [
    {
        // corrects dot products
        pattern: new RegExp( $unit + "(?=" + $unit + ")", "g" ),
        correction: "$1" + $dot
    }
    // more correction patterns later
];

function correct( $string ) {

    var $corrected = $string;
    $corrections.forEach( function( corrector ) {
        $corrected = $corrected.replace( corrector.pattern, corrector.correction );
    });
    return $corrected;
}

correct( "m" ); // m - CORRECT
correct( "mΩ" ); // m⋅Ω - INCORRECT, should be mΩ
correct( "Ωm" ); // Ω⋅m - CORRECT
correct( "mΩm" ); // mΩ⋅m - CORRECT
@nhahtdh指出

更新,问题在于前瞻性回溯,因为在Ω之后没有找到$unit,因此回溯并将m和Ω标识为{{ 1}}而不是$unit + $unit。在这种情况下,我需要防止回溯。

2 个答案:

答案 0 :(得分:2)

我认为每个案例都没有独特的解决方案。

你想把点放在哪里例如mmΩ? 它应该是mm⋅Ω还是m·mΩ? 在我看来,这两个答案都是有效的。

修改

或者,如果我们看一个更荒谬的例子:(虽然他们心智正常的人会像这样写出来)

mmmmol

我们可以通过以下方式分割它:

  • m·m·m·mol·m ^ 3·mol
  • m·m·mmol = m ^ 2·mmol
  • m·mm·mol
  • mm⋅mmol
  • mm·m·mol

因此,除非您指定一些假设,否则您可能无法完全按照您希望的方式获得每个边缘情况。

编辑2:

防止这些不一致的一种方法是在人们进入单位时强制执行here所示的指导方针,并在他们犯错时显示警告。

编辑3:

如果你想要贪婪的行为,你必须匹配endOfLine。否则,最后一场比赛不会以贪婪的方式对待。

您还必须确保具有最长stringlength的单位位于匹配列表的前面。如果你不这样做,它会在 mol 之前与 m 单位匹配并退出那里,从而永远不会达到 mol

有关操作中的代码,请参阅this jsfiddle

var $dot = "\u22c5";
var $minus = "\u2212";

var $prefix = "(?:\u00B5|c|d|G|k|m|M|n|p|T)";
var $si_unit = "(?:kat|mol|rad|\u00B0C|\u00B0F|Bq|cd|eV|Gy|Hz|lm|lx|Pa|sr|Sv|Wb|\u0025|\u03A9|A|C|F|g|H|J|K|l|L|m|N|s|S|T|V|W)";
var $power = "(?:[+" + $minus + "]?\\d+)";

var $unit = "("+ $prefix + "?"+ $si_unit + $power + "?)";

var $multiplied = $unit + "(" + $dot + $unit + ")*";
var $denominator = $multiplied + "(\/" + $multiplied + ")?";

var $corrections= [
    {
        // corrects dot products
        pattern: new RegExp( $unit + "(?=($|" + $unit + "))", "g" ),
        correction: "$1" + $dot
    }
    // more correction patterns later
];

$(document).ready(function() {


    var resultsElem = $("#results");

    addToResults(resultsElem, correct( "m" )); // m - CORRECT
    addToResults(resultsElem, correct( "m\u03A9" )); // mΩ - CORRECT, should be mΩ
    addToResults(resultsElem, correct( "\u03A9m" )); // Ω⋅m - CORRECT
    addToResults(resultsElem, correct( "m\u03A9m" )); // mΩ⋅m - CORRECT
    addToResults(resultsElem, correct( "mmmmol" )); // mm⋅mmol - CORRECT
});

function correct( $string ) {

    var $corrected = $string;
    $corrections.forEach( function( corrector ) {
        $corrected = $corrected.replace( corrector.pattern, corrector.correction );
    });

    //if you want greedy behaviour, you will have to match for the end of the line too.
    //The replace function will put a dot at the end too. Remove it if it's there.
    if($corrected.charAt($corrected.length -1 ) == $dot){
        $corrected = $corrected.substring(0,$corrected.length -1);
    }
    return $corrected;
}

function addToResults(elem, theResult){
    elem.append(theResult).append("<br>");
}

答案 1 :(得分:1)

((?:p|n|m){0,1}(?:m|g|l|L|s|A))((?:p|n|m){0,1}(?:m|g|l|L|s|A))

编辑:现在理解你想要的更好一点,我认为这应该有用,前缀+单位(或唯一前缀)的每一面都保存在捕获组1和2中

(?:p|n|m) being the prefixes 

(?:m|g|l|L|s|A) being the units

对于添加点,您只需在结果中进行迭代,并在每个结果后放置点