【问题标题】:regex differentiating between ISBN-10 and ISBN-13区分 ISBN-10 和 ISBN-13 的正则表达式
【发布时间】:2012-12-15 06:27:56
【问题描述】:

我有一个 If-else 语句,它检查字符串以查看是否存在 ISBN-10 或 ISBN-13(图书 ID)。

我面临的问题是 ISBN-10 检查发生在 ISBN-13 检查之前,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;
}

如何确保避免此问题?

【问题讨论】:

  • 嗯...交换订单?
  • $str 变量不是有效的 ISBN 编号,并且不会匹配您提供的任何一个正则表达式。你想搭配什么?像 $str 之类的东西,还是一个实际的 ISBN?
  • @WillC。你是什​​么意思它不是一个有效的 ISBN?这是一本真正的书,可以在亚马逊上找到
  • 我的错误,我没有正确阅读 ISBN 文档。你是对的。 isbn.org/standards/home/isbn/us/isbnqa.asp#Q3 注意,':' 似乎不是唯一的分隔符:isbn.org/standards/home/isbn/international/html/usm4.htm

标签: php regex validation isbn


【解决方案1】:
ISBN10_REGEX = /^(?:\d[\ |-]?){9}[\d|X]$/i
ISBN13_REGEX = /^(?:\d[\ |-]?){13}$/i

【讨论】:

【解决方案2】:

你真的只需要一个正则表达式。然后进行更高效的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:

对于 strict 验证,用于 ISBN 的 Wikipedia 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

【讨论】:

  • 我刚刚意识到这就是为什么我删除了我之前的评论,我有一个请求,请你从正则表达式中删除“ISBN:”,我只想匹配一个 ISBN,我给出的是一个例子,我可能得到的其他字符串例子可能是end. 9780113411142 -
  • 那么 ISBN: 是可选的吗?所以你想匹配有和没有它的字符串吗?
  • @mk_89, string of different lengths,你这是什么意思?你的意思是说匹配ISBN里面说这个字符串:some text ISBN:1234567890 more text
  • 是的,我想从一段文本中匹配一个 ISBN,例如你给我的例子。
  • 已更新以允许检测和验证979 前缀的 ISBN。
【解决方案3】:

切换 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;
}

【讨论】:

  • 测试的顺序无关紧要。即,一个 13 位的 ISBN 永远不应该返回一个 10 位的 ISBN。使用这种逻辑,我们可以说我们有一个有效的社会安全号码,因为它与驾驶执照号码中的一段数字相匹配。
  • 鉴于 OP 的正则表达式,顺序确实很重要。我同意应该更改正则表达式以匹配整个字符串,因为额外的 ISBN 匹配(如 ISBN-11,如果存在)需要按特定顺序进行。
  • 这就是我的意思,您正在处理错误逻辑的症状,而不是仅仅通过编写良好的逻辑测试来解决问题。
【解决方案4】:

使用^$ 匹配字符串的开头和结尾。通过使用字符串分隔符,测试 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

【讨论】:

  • 您提供的正则表达式与已发布问题中的正则表达式不匹配相同的字符串。
  • @WillC.,这就是问题所在; OP 的正则表达式写得不好,这就是逻辑首先失败的原因。
  • 让我澄清一下,ISBN-10 和 ISBN-13 格式可以以“X”字符结尾,这是您的正则表达式无法匹配的。
  • @maček 写得不好?哎呀,你的表达失败了,isbn 可以是数字和字母的混合,我对匹配 "ISBN:" 不感兴趣进行正则表达式比较
  • @mk_89,我无意冒犯你。维基百科似乎并没有暗示“数字和字母的混合”是有效的。请注意,检查 10 位和 13 位格式与 验证 ISBN 本身不同。有关详细信息,请参阅我更新的帖子。
【解决方案5】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-27
    • 1970-01-01
    • 2020-03-01
    相关资源
    最近更新 更多