正则表达式区分ISBN-10和ISBN-13

时间:2012-12-30 23:30:36

标签: php regex validation isbn

我有一个If-else语句,用于检查字符串是否有ISBN-10或ISBN-13(书籍ID)。

我面临的问题是在ISBN-13检查之前发生的ISBN-10检查,ISBN-10检查将匹配10个字符或更多的任何内容,因此可能将ISBN-13误认为是ISBN-10

这是代码......

$str = "ISBN:9780113411436";

if(preg_match("/\d{9}(?:\d|X)/", $str, $matches)){
   echo "ISBN-10 FOUND\n";  
   //isbn returned will be 9780113411
   return 0;
}

else if(preg_match("/\d{12}(?:\d|X)/", $str, $matches)){
   echo "ISBN-13 FOUND\n";
   //isbn returned will be 9780113411436
   return 1;
}

如何确保避免此问题?

5 个答案:

答案 0 :(得分:40)

你真的只需要一个正则表达式。然后执行更有效的strlen()检查以查看哪一个匹配。以下内容将匹配带或不带连字符的字符串中的ISBN-10和ISBN-13值,并且可选地以字符串ISBN:ISBN:(space)ISBN(space)开头。

查找ISBN:

function findIsbn($str)
{
    $regex = '/\b(?:ISBN(?:: ?| ))?((?:97[89])?\d{9}[\dx])\b/i';

    if (preg_match($regex, str_replace('-', '', $str), $matches)) {
        return (10 === strlen($matches[1]))
            ? 1   // ISBN-10
            : 2;  // ISBN-13
    }
    return false; // No valid ISBN found
}

var_dump(findIsbn('ISBN:0-306-40615-2'));     // return 1
var_dump(findIsbn('0-306-40615-2'));          // return 1
var_dump(findIsbn('ISBN:0306406152'));        // return 1
var_dump(findIsbn('0306406152'));             // return 1
var_dump(findIsbn('ISBN:979-1-090-63607-1')); // return 2
var_dump(findIsbn('979-1-090-63607-1'));      // return 2
var_dump(findIsbn('ISBN:9791090636071'));     // return 2
var_dump(findIsbn('9791090636071'));          // return 2
var_dump(findIsbn('ISBN:97811'));             // return false

这将搜索提供的字符串,以查看它是否包含可能的ISBN-10值(返回1)或ISBN-13值(返回2)。如果不是,它将返回false

请参阅上面的DEMO


验证ISBN:

对于严格验证,ISBN的维基百科article具有ISBN-10ISBN-13的一些PHP验证功能。下面是复制,整理和修改的示例,以用于上述函数的略微修改版本。

将退货块更改为:

    return (10 === strlen($matches[1]))
        ? isValidIsbn10($matches[1])  // ISBN-10
        : isValidIsbn13($matches[1]); // ISBN-13

验证ISBN-10:

function isValidIsbn10($isbn)
{
    $check = 0;

    for ($i = 0; $i < 10; $i++) {
        if ('x' === strtolower($isbn[$i])) {
            $check += 10 * (10 - $i);
        } elseif (is_numeric($isbn[$i])) {
            $check += (int)$isbn[$i] * (10 - $i);
        } else {
            return false;
        }
    }

    return (0 === ($check % 11)) ? 1 : false;
}

验证ISBN-13:

function isValidIsbn13($isbn)
{
    $check = 0;

    for ($i = 0; $i < 13; $i += 2) {
        $check += (int)$isbn[$i];
    }

    for ($i = 1; $i < 12; $i += 2) {
        $check += 3 * $isbn[$i];
    }

    return (0 === ($check % 10)) ? 2 : false;
}

请参阅上面的DEMO

答案 1 :(得分:3)

使用^$来匹配字符串的开头和结尾。通过使用字符串分隔符,您测试10位或13位代码的顺序无关紧要。

10位

/^ISBN:(\d{9}(?:\d|X))$/

13位

/^ISBN:(\d{12}(?:\d|X))$/

注意:根据http://en.wikipedia.org/wiki/International_Standard_Book_Number,似乎ISBN中也可以包含-。但根据您使用的$str,看起来您在检查10或13位数之前已删除了连字符。

附加说明:由于ISBN的最后一位用作前一位数的校验和,因此正则表达式无法验证 ISBN是有效的一。它只能检查10或13位格式。


$isbns = array(
  'ISBN:1234567890',       // 10-digit
  'ISBN:123456789X',       // 10-digit ending in X
  'ISBN:1234567890123',    // 13-digit
  'ISBN:123456789012X',    // 13-digit ending in X
  'ISBN:1234'              // invalid
);

function get_isbn($str) {
   if (preg_match('/^ISBN:(\d{9}(?:\d|X))$/', $str, $matches)) {
      echo "found 10-digit ISBN\n";
      return $matches[1];
   }
   elseif (preg_match('/^ISBN:(\d{12}(?:\d|X))$/', $str, $matches)) {
      echo "found 13-digit ISBN\n";
      return $matches[1];
   }
   else {
      echo "invalid ISBN\n";
      return null;
   }
}

foreach ($isbns as $str) {
   $isbn = get_isbn($str);
   echo $isbn."\n\n";
}

输出

found 10-digit ISBN
1234567890

found 10-digit ISBN
123456789X

found 13-digit ISBN
1234567890123

found 13-digit ISBN
123456789012X

invalid ISBN

答案 2 :(得分:1)

切换if else块的顺序,同时删除ISBN中的所有空格,冒号和连字符:

//Replace all the fluff that some companies add to ISBNs
$str = preg_replace('/(\s+|:|-)/', '', $str);

if(preg_match("/^ISBN\d{12}(?:\d|X)$/", $str, $matches)){
   echo "ISBN-13 FOUND\n";
   //isbn returned will be 9780113411436
   return 1;
}

else if(preg_match("/^ISBN\d{9}(?:\d|X)$/", $str, $matches)){
   echo "ISBN-10 FOUND\n";  
   //isbn returned will be 9780113411
   return 0;
}

答案 3 :(得分:1)

在ISBN-10检查之前进行ISBN-13检查?这假设您希望将它们作为任何字符串的一部分进行匹配,即(您的示例在开头有一个额外的“ISBN:”,因此匹配字符串中的任何位置似乎是某种要求)

答案 4 :(得分:0)

ISBN10_REGEX = /^(?:\d[\ |-]?){9}[\d|X]$/i
ISBN13_REGEX = /^(?:\d[\ |-]?){13}$/i