3

我需要根据这些规则验证一个字符串:

  1. 值不是s
  2. 值至少 1 个字符长
  3. 值仅包含a-z0-9-_/
  4. 价值不以/
  5. 价值不以/
  6. 值不包含/s/
  7. 值不包含//
  8. 价值不以s/
  9. 价值不以/s

(更简单地说,我正在寻找类似于 UNIX 样式路径的东西,带有斜杠分隔符,其中文件/文件夹名称只允许a-z0-9-_,没有文件/文件夹被命名s,并且它没有开头或结尾的斜杠。)

我需要通过 JavaScript 在客户端执行此操作,并使用 PHP 在服务器端执行此操作。

我知道最优雅的解决方案是通过复杂的正则表达式。但是,尝试写一个值得挑战吗?还是我应该只使用条件?

现在,我的解决方案是:http: //jsfiddle.net/cKfnW/

JavaScript:

(function ($) {
    var test = function (val) {
        return
            val != 's' &&
            /^[a-z0-9-_\/]+$/.test(val) &&
            val.substr(0, 1) != '/' &&
            val.substr(val.length-1) != '/' &&
            val.search('/s/') == -1 &&
            val.search('//') == -1 &&
            val.substr(0, 2) != 's/' &&
            val.substr(val.length-2) != '/s';
    };
    $('#test')
        .keyup(function () {
            if (test($(this).val())) {
                $(this).removeClass('fail').addClass('pass');
            }
            else {
                $(this).removeClass('pass').addClass('fail');
            }
        )
        .keyup();
})(jQuery);

PHP:

<?php
function test ($val) {
    return
        $val != 's' &&
        preg_match('/^[a-z0-9-_\/]+$/', $val) &&
        substr($val, 0, 1) != '/' &&
        substr($val, -1) != '/' &&
        strpos($val, '/s/') === false &&
        strpos($val, '//') === false &&
        substr($val, 0, 2) != 's/' &&
        substr($val, -2) != '/s';
}

die (test($_GET['test']) ? 'pass' : 'fail');
?>

这是可接受的做法吗?我不太擅长正则表达式,而且我不知道如何为此编写一个——但我不禁觉得这更像是一种破解而不是一种解决方案。

你怎么看?

谢谢。

4

3 回答 3

2

即使有您的检查,您当然应该通过将它们全部合并为一个 if 来摆脱嵌套的 IF。这是带有 2 个正则表达式的更简单的变体(首先限制您的边缘情况,其次检查允许的字符):

if (
    $val != 's' 
    && !preg_match('!(^/|/s|s/|//|/$)!', $val) 
    && preg_match('!^[a-z0-9-_/]+$!', $val)
) {
  // ...
}

UPD:哦,当我输入答案时,你已经删除了嵌套的 IF :) 好,好!

于 2013-08-19T20:21:30.480 回答
1

针对多个AND'ed 要求的单个正则表达式解决方案

这是一个符合您要求的注释 php 正则表达式:(总是以这种方式编写非平凡的正则表达式)

$re = '% # Validate *nix-like path w/multiple specs.
    ^          # Anchor to start of string.
    (?!s$)     # Value is not s
    (?=.)      # Value is at least 1 character long
    (?!/)      # Value does not begin with /
    (?!.*/$)   # Value does not end with /
    (?!.*/s/)  # Value does not contain /s/
    (?!.*//)   # Value does not contain //
    (?!s/)     # Value does not begin with s/
    (?!.*/s$)  # Value does not end with /s
    [\w\-/]+   # Value contains only a-z0-9-_/
    $          # Anchor to end of string.
    %ix';

这是等效的 JavaScript 版本:

var re = /^(?!s$)(?=.)(?!\/)(?!.*\/$)(?!.*\/s\/)(?!.*\/\/)(?!s\/)(?!.*\/s$)[\w\-\/]+$/i;

此解决方案假定您的要求不区分大小写。如果不是这种情况,则删除iignorecase 修饰符(并将[\w\-/]+表达式更改为[a-z0-9_\-/]+)。

为了描述清楚起见,我为您的每个要求编写了每行一个断言的注释版本。与^开始时的锚点一起,每个前瞻断言都以合乎逻辑的AND方式工作。请注意,(?=.)自上一个表达式以来,断言(确保存在一个字符)是多余且不必要的:[\w\-/]+还确保长度至少为一个。请注意,此操作需要the^和锚点。$

该解决方案演示了如何在一个易于阅读和维护的正则表达式中实现多个需求。但是,出于其他原因,您可能希望将其拆分为单独的检查 - 例如,以便您的代码可以为每个需求生成单独的有意义的错误消息。

于 2013-08-20T03:28:48.657 回答
1

显然为此使用正则表达式:

if (preg_match('~^(?!s?/|s$)(?>[a-z0-9_-]++|/(?!s?/|s?$))++$~', $val)) {
    // do that
}

图案细节:

~                 # pattern delimiter
^                 # start of the string
(?!s?/|s$)        # negative lookahead (not followed by "s$", "/", "s/")
(?>               # open an atomic group (can be replaced by "(?:")
    [a-z0-9_-]++  # allowed characters except "/", one or more times
  |               # OR
    /(?!s?/|s?$)  # "/" not followed by "s/" or "/" or "$" or "s$" 
)++               # close the group and repeat one or more times
$                 # end of the string
~                 # pattern delimiter

单个正则表达式相对于多个小正则表达式有什么优势?

您只遍历测试字符串一次,并且模式在第一个坏字符处失败。

对于futur调试,可以使用verbose模式和nowdoc使其更清晰,例如:

$pattern = <<<'LOD'
~
^                 
(?!s?/|s$)        # not followed by "s$", "/", "s/"

(?>  [a-z0-9_-]++ | / (?!s?/|s?$)  )++

$                 
~x
LOD;                 

对于客户端,您可以在 javascript 中使用此模式:

/^(?!s?\/|s$)(?:[a-z0-9_-]|\/(?!s?\/|s?$))+$/

注意:当你想把一个字面-量放在一个字符类中时,你必须总是把它写在类的开头或结尾,因为它是一个特殊的字符,用来定义一个字符范围。

于 2013-08-19T20:16:40.947 回答