【问题标题】:Determining chapter number in different types of text确定不同类型文本中的章节编号
【发布时间】:2018-12-24 01:37:18
【问题描述】:

我正在从与小说相关的帖子中提取标题。目的是通过使用正则表达式来确定帖子是关于哪一章的。每个站点使用不同的方式来识别章节。以下是最常见的情况:

$title = 'text chapter 25.6 text'; // c25.6
$title = 'text chapters 23, 24, 25 text'; // c23-25
$title = 'text chapters 23+24+25 text'; // c23-25
$title = 'text chapter 23, 25 text'; // c23 & 25
$title = 'text chapter 23 & 24 & 25 text'; // c23-25
$title = 'text c25.5-30 text'; // c25.5-30
$title = 'text c99-c102 text'; // c99-102
$title = 'text chapter 99 - chapter 102 text'; // c99-102
$title = 'text chapter 1 - 3 text'; // c1-3
$title = '33 text chapter 1, 2 text 3'; // c1-2
$title = 'text v2c5-10 text'; // c5-10
$title = 'text chapters 23, 24, 25, 29, 31, 32 text'; // c23-25 & 29 & 31-32

章节编号总是列在标题中,只是在上面显示的不同变体中。

到目前为止我所拥有的

到目前为止,我有一个正则表达式来确定章节的单个案例,例如:

$title = '9 text chapter 25.6 text'; // c25.6

使用此代码(尝试ideone):

function get_chapter($text, $terms) {

    if (empty($text)) return;
    if (empty($terms) || !is_array($terms)) return;

    $values = false;

    $terms_quoted = array();
    foreach ($terms as $term)
        $terms_quoted[] = preg_quote($term, '/');

    // search for matches in $text
    // matches with lowercase, and ignores white spaces...
    if (preg_match('/('.implode('|', $terms_quoted).')\s*(\d+(\.\d+)?)/i', $text, $matches)) {
        if (!empty($matches[2]) && is_numeric($matches[2])) {
            $values = array(
                'term' => $matches[1],
                'value' => $matches[2]
            );
        }
    }

    return $values;
}

$text = '9 text chapter 25.6 text'; // c25.6
$terms = array('chapter', 'chapters');
$chapter = get_chapter($text, $terms);

print_r($chapter);

if ($chapter) {
    echo 'Chapter is: c'. $chapter['value'];
}

如何使用上面列出的其他示例进行此操作?鉴于这个问题的复杂性,如果符合条件,我将奖励 200 分。

【问题讨论】:

  • 不错!每个问题都应该带有一个我到目前为止尝试过的内容 标题。 +1
  • 考虑到这个问题的复杂性,如果符合条件,我会悬赏 200 分。
  • 如果您没有一套明确的规则来定义您可能的输入,那么真的没有办法想出一刀切的解决方案。
  • @JorgeCampos 正如我强调的那样,这些都是可能的规则。我处理了数千个标题以确定这些规则。唯一可能不同的是chapter 被称为别的东西......但正如你在我的代码中看到的那样,我已经有了一个解决方案。我完全理解可能会错过千分之一,但对于这种类型的脚本来说,这是一个很好的比例。
  • 标题中是否同时存在 'chapter' 或 'c' 之一?

标签: php regex


【解决方案1】:

我认为在不抛出一些误报的情况下构建这样的东西是非常复杂的,因为某些模式可能包含在标题中,在这种情况下,它们会被代码检测到。

无论如何,我将介绍一种您可能会感兴趣的解决方案,您有时间可以尝试一下。我没有对它进行深入测试,因此,如果您发现此实现有任何问题,请告诉我,我会尝试找到解决方案。

看看你的模式,它们都可以分为两大类:

  • 从一个数字到另一个数字 (G1)
  • 一个或多个数字,用逗号、加号或 & 号 (G2) 分隔

因此,如果我们可以将这两个群体分开,我们就可以区别对待它们。从接下来的标题中,我将尝试以这种方式获取章节编号:

+-------------------------------------------+-------+------------------------+
| TITLE                                     | GROUP | EXTRACT                |
+-------------------------------------------+-------+------------------------+
| text chapter 25.6 text                    |  G2   | 25.6                   |
| text chapters 23, 24, 25 text             |  G2   | 23, 24, 25             |
| text chapters 23+24+25 text               |  G2   | 23, 24, 25             |
| text chapter 23, 25 text                  |  G2   | 23, 25                 |
| text chapter 23 & 24 & 25 text            |  G2   | 23, 24, 25             |
| text c25.5-30 text                        |  G1   | 25.5 - 30              |
| text c99-c102 text                        |  G1   | 99 - 102               |
| text chapter 99 - chapter 102 text        |  G1   | 99 - 102               |
| text chapter 1 - 3 text                   |  G1   | 1 - 3                  |
| 33 text chapter 1, 2 text 3               |  G2   | 1, 2                   |
| text v2c5-10 text                         |  G1   | 5 - 10                 |
| text chapters 23, 24, 25, 29, 31, 32 text |  G2   | 23, 24, 25, 29, 31, 32 |
| text chapters 23 and 24 and 25 text       |  G2   | 23, 24, 25             | 
| text chapters 23 and chapter 30 text      |  G2   | 23, 30                 | 
+-------------------------------------------+-------+------------------------+

要仅提取章节数并区分它们,一种解决方案可能是构建一个正则表达式,该表达式捕获两个章节范围 (G1) 组和一个由字符分隔的数字组 (G2)。章节编号提取后,我们可以对结果进行处理以显示正确格式的章节。

代码如下:

我看到您仍在问题中未包含的 cmets 中添加更多案例。如果要添加新案例,只需创建一个新的匹配模式并将其添加到最终的正则表达式中。只需遵循两个匹配组的范围和一个由字符分隔的数字的匹配组的规则。另外,要考虑到最冗长的模式应该位于较少的模式之前。例如,ccc N - ccc N 应位于 cc N - cc N 之前,最后一个应位于 c N - c N 之前。

$model = ['chapters?', 'chap', 'c']; // different type of chapter names
$c = '(?:' . implode('|', $model) . ')'; // non-capturing group for chapter names
$n = '\d+\.?\d*'; // chapter number
$s = '(?:[\&\+,]|and)'; // non-capturing group of valid separators
$e = '[ $]'; // end of a match (a space or an end of a line)

// Different patterns to match each case
$g1 = "$c *($n) *\- *$c *($n)$e"; // match chapter number - chapter number in all its variants (G1)
$g2 = "$c *($n) *\- *($n)$e"; // match chapter number - number in all its variants (G1)
$g3 = "$c *((?:(?:$n) *$s *)+(?:$n))$e"; // match chapter numbers separated by something in all its variants (G2) 
$g4 = "((?:$c *$n *$s *)+$c *$n)$e"; // match chapter number and chater number ... and chapter numberin all its variants (G2)
$g5 = "$c *($n)$e"; // match chapter number in all its variants (G2)

// Build a big non-capturing group with all the patterns
$reg = "/(?:$g1|$g2|$g3|$g4|$g5)/";

// Function to process each title
function getChapters ($title) {

    global $n, $reg;
    // Store the matches in one flatten array
    // arrays with three indexes correspond to G1
    // arrays with two indexes correspond to G2
    if (!preg_match($reg, $title, $matches)) return '';
    $numbers = array_values(array_filter($matches));

    // Show the formatted chapters for G1
    if (count($numbers) == 3) return "c{$numbers[1]}-{$numbers[2]}";

    // Show the formatted chapters for G2        
    if(!preg_match_all("/$n/", $numbers[1], $nmatches, PREG_PATTERN_ORDER)) return '';
    $m = $nmatches[0];
    $t = count($m);
    $str = "c{$m[0]}";
    foreach($m as $i => $mn) {
        if ($i == 0) continue;
        if ($mn == $m[$i - 1] + 1) {
            if (substr($str, -1) != '-') $str .= '-';
            if ($i == $t - 1 || $mn != $m[$i + 1] - 1) $str .= $mn;
        } else {
            if ($i < $t) $str .= ' & ';
            $str .= $mn;
        }
        return $str;
    }

}

您可以查看the code working on Ideone。

【讨论】:

  • 谢谢!一旦我有一个广泛的标题列表,我将尝试这个。
【解决方案2】:

逻辑

我建议采用以下结合正则表达式和通用字符串处理逻辑的方法:

  • 使用preg_match 和适当的正则表达式来匹配整个文本块的第一次出现,从$terms 数组中的关键字开始,直到与该术语相关的最后一个数字(+ 可选部分字母)
  • 获得匹配后,创建一个包含输入字符串、匹配值和后处理匹配的数组
  • 可以通过删除连字符数字之间的空格并在数字与+、&amp; 或, 字符连接的情况下重建数字范围来完成后处理。这需要一个多步骤操作:1)匹配前一个整体匹配中的连字符分隔的子字符串并修剪掉不必要的零和空格,2)将数字块拆分为单独的项目并将它们传递给将生成数字的单独函数范围
  • buildNumChain($arr) 函数将创建数字范围,如果数字后面有字母,则将其转换为 section X 后缀。

解决方案

你可以使用

$strs = ['c0', 'c0-3', 'c0+3', 'c0 & 9', 'c0001, 2, 03', 'c01-03', 'c1.0 - 2.0', 'chapter 2A Hello', 'chapter 2AHello', 'chapter 10.4c', 'chapter 2B', 'episode 23.000 & 00024', 'episode 23 & 24', 'e23 & 24', 'text c25.6 text', '001 & 2 & 5 & 8-20 & 100 text chapter 25.6 text 98', 'hello 23 & 24', 'ep 1 - 2', 'chapter 1 - chapter 2', 'text chapter 25.6 text', 'text chapters 23, 24, 25 text','text chapter 23, 25 text', 'text chapter 23 & 24 & 25 text','text c25.5-30 text', 'text c99-c102 text', 'text chapter 1 - 3 text', '33 text chapter 1, 2 text 3','text chapters 23, 24, 25, 29, 31, 32 text', 'c19 & c20', 'chapter 25.6 & chapter 29', 'chapter 25+c26', 'chapter 25 + 26 + 27'];
$terms = ['episode', 'chapter', 'ch', 'ep', 'c', 'e', ''];

usort($terms, function($a, $b) {
    return strlen($b) - strlen($a);
});
 
$chapter_main_rx = "\b(?|" . implode("|", array_map(function ($term) {
    return strlen($term) > 0 ? "(" . substr($term, 0, 1) . ")(" . substr($term, 1) . "s?)": "()()" ;},
  $terms)) . ")\s*";
$chapter_aux_rx = "\b(?:" . implode("|", array_map(function ($term) {
    return strlen($term) > 0 ? substr($term, 0, 1) . "(?:" . substr($term, 1) . "s?)": "" ;},
  $terms)) . ")\s*";

$reg = "~$chapter_main_rx((\d+(?:\.\d+)?(?:[A-Z]\b)?)(?:\s*(?:[,&+-]|and)\s*(?:$chapter_aux_rx)?(?4))*)~ui";

foreach ($strs as $s) {
    if (preg_match($reg, $s, $m)) {
        $p3 = preg_replace_callback(
            "~(\d*(?:\.\d+)?)([A-Z]?)\s*-\s*(?:$chapter_aux_rx)?|(\d+(?:\.\d+)?(?:[A-Z]\b)?)(?:\s*(?:[,&+]|and)\s*(?:$chapter_aux_rx)?(?1))*~ui", function($x) use ($chapter_aux_rx) {
                return (isset($x[3]) && strlen($x[3])) ? buildNumChain(preg_split("~\s*(?:[,&+]|and)\s*(?:$chapter_aux_rx)?~ui", $x[0])) 
                : ((isset($x[1]) && strlen($x[1])) ? ($x[1] + 0) : "") . ((isset($x[2]) && strlen($x[2])) ? ord(strtolower($x[2])) - 96 : "") . "-";
            }, $m[3]);
        print_r(["original" => $s, "found_match" => trim($m[0]), "converted" => $m[1] . $p3]);
        echo "\n";
    } else {
        echo "No match for '$s'!\n";
    
    }
}

function buildNumChain($arr) {
    $ret = "";
    $rngnum = "";
    for ($i=0; $i < count($arr); $i++) {
        $val = $arr[$i];
        $part = "";
        if (preg_match('~^(\d+(?:\.\d+)?)([A-Z]?)$~i', $val, $ms)) {
            $val = $ms[1];
            if (!empty($ms[2])) {
                $part = ' part ' . (ord(strtolower($ms[2])) - 96);
            }
        }
        $val = $val + 0;
        if (($i < count($arr) - 1) && $val == ($arr[$i+1] + 0) - 1) {
            if (empty($rngnum))  {
                $ret .= ($i == 0 ? "" : " & ") . $val;
            }
            $rngnum = $val;
        } else if (!empty($rngnum) || $i == count($arr)) {
            $ret .= '-' . $val;
            $rngnum = "";
        } else {
            $ret .= ($i == 0 ? "" : " & ") . $val . $part;
        }
    }
    return $ret;
}

请参阅PHP demo。

要点

  • 将c 或chapter/chapters 与后面的数字匹配,仅捕获c 和数字
  • 找到匹配项后,处理包含数字序列的组 2
  • 所有&lt;number&gt;-c?&lt;number&gt; 子字符串都应去除空格和c 在数字之前/之间和之间
  • 所有,/&amp; 分隔的数字都应使用buildNumChain 进行后处理,该buildNumChain 生成连续数字的范围(假定为整数)。

主正则表达式看起来像$terms = ['episode', 'chapter', 'ch', 'ep', 'c', 'e', '']:

'~(?|(e)(pisodes?)|(c)(hapters?)|(c)(hs?)|(e)(ps?)|(c)(s?)|(e)(s?)|()())\s*((\d+(?:\.\d+)?(?:[A-Z]\b)?)(?:\s*(?:[,&+-]|and)\s*(?:(?:e(?:pisodes?)|c(?:hapters?)|c(?:hs?)|e(?:ps?)|c(?:s?)|e(?:s?)|)\s*)?(?4))*)~ui'

请参阅regex demo。

模式详情

  • (?|(e)(pisodes?)|(c)(hapters?)|(c)(hs?)|(e)(ps?)|(c)(s?)|(e)(s?)|()()) - 一个分支重置组,它捕获搜索词的第一个字母并将该词的其余部分捕获到强制组 2。如果有一个空词,则添加 ()() 以确保在group 包含相同数量的组
  • \s* - 0+ 个空格
  • ((\d+(?:\.\d+)?(?:[A-Z]\b)?)(?:\s*(?:[,&amp;+-]|and)\s*c?(?3))*) - 第 2 组:
    • (\d+(?:\.\d+)?(?:[A-Z]\b)?) - 第 3 组:1+ 位,后跟. 的可选序列,1+ 位数字,然后是可选的 ASCII 字母,后跟非单词字符或字符串结尾(注意不区分大小写的修饰符将使[A-Z] 也匹配小写ASCII 字母)
    • (?:\s*(?:[,&amp;+-]|and)\s*(?:(?:e(?:pisodes?)|c(?:hapters?)|c(?:hs?)|e(?:ps?)|c(?:s?)|e(?:s?)|)\s*)?(?4))* - 零个或多个序列
      • \s*(?:[,&amp;+-]|and)\s* - 一个 ,、&amp;、+、- 或 and,包含可选的 0+ 个空格
      • (?:e(?:pisodes?)|c(?:hapters?)|c(?:hs?)|e(?:ps?)|c(?:s?)|e(?:s?)|) - 添加可选复数结尾的任何术语s
      • (?4) - 第 4 组模式递归/重复

当正则表达式匹配时,Group 1 的值为c,因此它将是结果的第一部分。那么,

 "~(\d*(?:\.\d+)?)([A-Z]?)\s*-\s*(?:$chapter_aux_rx)?|(\d+(?:\.\d+)?(?:[A-Z]\b)?)(?:\s*(?:[,&+]|and)\s*(?:$chapter_aux_rx)?(?1))*~ui"

在preg_replace_callback 中使用,以删除-(如果有)和术语(如果有)之间的空格,后面跟0+ 个空格字符,如果第1 组匹配,则匹配以

"~\s*(?:[,&+]|and)\s*(?:$chapter_aux_rx)?~ui"

正则表达式(它匹配 &amp;、,、+ 或 and 在可选的 0+ 空格后跟 0+ 空格,然后是可选字符串,术语后跟 0+ 空格)并且数组是传递给构建结果字符串的buildNumChain 函数。

【讨论】:

  • 经过进一步思考,为2-1 添加part 案例有时可能根本不准确,因为,他们将如何以这种格式显示第 3 部分或第 2 章...2-3将是第 2 章到第 3 章......所以我想不出一个合理的逻辑,所以最好将它简单地显示为 2-1 (目前是这样)。但是,chapter 2A 是很常见的情况。
  • 同意。并且不区分大小写,这意味着 chapter 2a 也是一个匹配项。
  • 看起来它正在工作!正如我们所说,通过测试运行它。谢谢!如果您有机会,请将此更改添加到答案中,以便其他人也可以清楚地看到它。
  • 我明天要做最后的测试,但它看起来非常好。你告诉我当它按预期工作时通知你......我相信它是现在!
  • @HenrikPetterson 是的,我有,但是当值为0 时,我的捕获被评估为空这一事实让我感到困惑。我将!empty 更改为strlen... &gt; 0,现在似乎可行。请参阅this update(我也更改了$x[2] 的处理方式,但我怀疑这很关键)。
【解决方案3】:

我对您的示例进行了分支,添加了一些内容,例如“chapter”并匹配“c”和“chapter”,然后从字符串中提取所有匹配的表达式,提取单个数字,展平找到的任何范围,并返回一个格式化的字符串,就像你在 cmets 中为每个字符串一样:

这里是链接:ideone

函数本身(稍微修改了你的):

function get_chapter($text, $terms) {

    if (empty($text)) return;
    if (empty($terms) || !is_array($terms)) return;

    $values = false;

    $terms_quoted = array();
    //make e.g. "chapters" match either "c" OR "Chapters" 
    foreach ($terms as $term)
        //revert this to your previous one if you want the "terms" provided explicitly
        $terms_quoted[] = $term[0].'('.preg_quote(substr($term,1), '/').')?';

    $matcher = '/(('.implode('|', $terms_quoted).')\s*(\d+(?:\s*[&+,.-]*\s*?)*)+)+/i';

    //match the "chapter" expressions you provided
    if (preg_match($matcher, $text, $matches)) {
        if (!empty($matches[0])) {

            //extract the numbers, in order, paying attention to existing hyphen/range identifiers
            if (preg_match_all('/\d+(?:\.\d+)?|-+/', $matches[0], $numbers)) {
                $bot = NULL;
                $top = NULL;
                $nextIsTop = false;
                $results = array();
                $setv = function(&$b,&$t,$v){$b=$v;$t=$v;};
                $flatten = function(&$b,&$t,$n,&$r){$x=$b;if($b!=$t)$x=$x.'-'.$t;array_push($r,$x);$b=$n;$t=$n;return$r;};
                foreach ($numbers[0] as $num) {
                    if ($num == '-') $nextIsTop = true;
                    elseif ($nextIsTop) {
                        $top = $num;
                        $nextIsTop = false;
                    }
                    elseif (is_null($bot)) $setv($bot,$top,$num);
                    elseif ($num - $top > 1) $flatten($bot,$top,$num,$results);
                    else $top = $num;
                }
                return implode(' & ', $flatten ($bot,$top,$num,$results));
            }
        }
    }
}

还有调用块:

$text = array(
'9 text chapter 25.6 text', // c25.6
'text chapter 25.6 text', // c25.6
'text chapters 23, 24, 25 text', // c23-25
'chapters 23+24+25 text', // c23-25
'chapter 23, 25 text', // c23 & 25
'text chapter 23 & 24 & 25 text', // c23-25
'text c25.5-30 text', // c25.5-30
'text c99-c102 text', // c99-102
'text chapter 99 - chapter 102 text', // c99-102
'text chapter 1 - 3 text', // c1-3
'33 text chapter 1, 2 text 3', // c1-2
'text v2c5-10 text', // c5-10
'text chapters 23, 24, 25, 29, 31, 32 text', // c23-25 & 29 & 31-32
);
$terms = array('chapter', 'chapters');
foreach ($text as $snippet)
{
    $chapter = get_chapter($snippet, $terms);
    print("Chapter is: c".$chapter."\n");
}

输出结果:

Chapter is: c25.6
Chapter is: c25.6
Chapter is: c23-25
Chapter is: c23-25
Chapter is: c23 & 25
Chapter is: c23-25
Chapter is: c25.5-30
Chapter is: c99-102
Chapter is: c99-102
Chapter is: c1-3
Chapter is: c1-2
Chapter is: c5-10
Chapter is: c23-25 & 29 & 31-32

【讨论】:

    【解决方案4】:

    使用捕获章节信息的通用正则表达式。

    '~text\s+(?|chapters?\s+(\d+(?:\.\d+)?(?:\s*[-+,&]\s*\d+(?:\.\d+)?)*)|(?:v\d+)?((?:c\s*)?\d+(?:\.\d+)?(?:\s*[-]\s*(?:c\s*)?\d+(?:\.\d+)?)*)|(chapters?\s+\d+(?:\.\d+)?(?:\s*[-+,&]\s*chapter\s+\d+(?:\.\d+)?)*))\s+text~'
    

    然后用此查找 '~[^-.\d+,&amp;\r\n]+~' 清理组 1,替换为空 ''。

    然后用这个查找'~[+&amp;]~' 替换为逗号','

    更新
    下面的 php 代码包含一个合并各个章节序列的功能
    到章节范围。

    主正则表达式,可读版本

     text
     \s+ 
     (?|
          chapters?
          \s+ 
          (                             # (1 start)
               \d+ 
               (?: \. \d+ )?
               (?:
                    \s* [-+,&] \s* 
                    \d+ 
                    (?: \. \d+ )?
               )*
          )                             # (1 end)
       |  
          (?: v \d+ )?
          (                             # (1 start)
               (?: c \s* )?
               \d+ 
               (?: \. \d+ )?
               (?:
                    \s* [-] \s* 
                    (?: c \s* )?
                    \d+ 
                    (?: \. \d+ )?
               )*
          )                             # (1 end)
       |  
          (                             # (1 start)
               chapters?
               \s+ 
               \d+ 
               (?: \. \d+ )?
               (?:
                    \s* [-+,&] \s* 
                    chapter
                    \s+ 
                    \d+ 
                    (?: \. \d+ )?
               )*
          )                             # (1 end)
    
    
     )
     \s+ 
     text 
    

    php代码示例

    http://sandbox.onlinephpfunctions.com/code/128cab887b2a586879e9735c56c35800b07adbb5

     $array = array(
     'text chapter 25.6 text',
     'text chapters 23, 24, 25 text',
     'text chapters 23+24+25 text',
     'text chapter 23, 25 text',
     'text chapter 23 & 24 & 25 text',
     'text c25.5-30 text',
     'text c99-c102 text',
     'text chapter 99 - chapter 102 text',
     'text chapter 1 - 3 text',
     '33 text chapter 1, 2 text 3',
     'text v2c5-10 text',
     'text chapters 23, 24, 25, 29, 31, 32 text');
    
     foreach( $array as $input ){
         if ( preg_match( '~text\s+(?|chapters?\s+(\d+(?:\.\d+)?(?:\s*[-+,&]\s*\d+(?:\.\d+)?)*)|(?:v\d+)?((?:c\s*)?\d+(?:\.\d+)?(?:\s*[-]\s*(?:c\s*)?\d+(?:\.\d+)?)*)|(chapters?\s+\d+(?:\.\d+)?(?:\s*[-+,&]\s*chapter\s+\d+(?:\.\d+)?)*))\s+text~',
                          $input, $groups ))
         {
             $chapters_verbose = $groups[1];
             $cleaned = preg_replace( '~[^-.\d+,&\r\n]+~', '',  $chapters_verbose );
             $cleaned = preg_replace( '~[+&]~',            ',', $cleaned   );
    
             $cleaned_and_condensed = CondnseChaptersToRanges( $cleaned );
    
             echo "\$title = '" . $input . "';  // c$cleaned_and_condensed\n";
    
         }        
     }
    
     function CondnseChaptersToRanges( $cleaned_chapters )
     {
             ///////////////////////////////////////
             // Combine chapter ranges.
             // Explode on comma's.
             //
             $parts = explode( ',', $cleaned_chapters );
             $size = count( $parts );
             $chapter_condensed = '';
    
             for ( $i = 0; $i < $size; $i++ )
             {
                 //echo "'$parts[$i]' ";
                 if ( preg_match( '~^\d+$~', $parts[$i] ) )
                 {
                     $first_num = (int) $parts[$i];
                     $last_num  = (int) $parts[$i];
                     $j = $i + 1;
    
                     while ( $j < $size && preg_match( '~^\d+$~', $parts[$j] ) && 
                             (int) $parts[$j] == ($last_num + 1) )
                     {
                         $last_num = (int) $parts[$j];
                         $i = $j;
                         ++$j ;
                     }
                     $chapter_condensed .= ",$first_num";
                     if ( $first_num != $last_num )
                         $chapter_condensed .= "-$last_num";
                 }
                 else
                     $chapter_condensed .= ",$parts[$i]";
             }
              $chapter_condensed = ltrim( $chapter_condensed, ',' );
    
             return $chapter_condensed;
     }
    

    输出

     $title = 'text chapter 25.6 text';  // c25.6
     $title = 'text chapters 23, 24, 25 text';  // c23-25
     $title = 'text chapters 23+24+25 text';  // c23-25
     $title = 'text chapter 23, 25 text';  // c23,25
     $title = 'text chapter 23 & 24 & 25 text';  // c23-25
     $title = 'text c25.5-30 text';  // c25.5-30
     $title = 'text c99-c102 text';  // c99-102
     $title = 'text chapter 99 - chapter 102 text';  // c99-102
     $title = 'text chapter 1 - 3 text';  // c1-3
     $title = '33 text chapter 1, 2 text 3';  // c1-2
     $title = 'text v2c5-10 text';  // c5-10
     $title = 'text chapters 23, 24, 25, 29, 31, 32 text';  // c23-25,29,31-32
    

    【讨论】:

      【解决方案5】:

      试试这个。似乎适用于给定的示例以及更多示例:

      <?php
      
      $title[] = 'c005 - c009'; // c5-9
      $title[] = 'c5.00 & c009'; // c5 & 9
      $title[] = 'text c19 & c20 text'; //c19-20
      $title[] = 'c19 & c20'; // c19-20
      $title[] = 'text chapter 19 and chapter 25 text'; // c19 & 25
      $title[] = 'text chapter 19 - chapter 23 and chapter 25 text'; // c19-23 & 25 (c19 for termless)
      $title[] = 'text chapter 19 - chapter 23, chapter 25 text'; // c19-23 & 25 (c19 for termless)
      $title[] = 'text chapter 23 text'; // c23
      $title[] = 'text chapter 23, chapter 25-29 text'; // c23 & 25-29
      $title[] = 'text chapters 23-26, 28, 29 + 30 + 32-39 text'; // c23-26 & c28-30 & c32-39
      $title[] = 'text chapter 25.6 text'; // c25.6
      $title[] = 'text chapters 23, 24, 25 text'; // c23-25
      $title[] = 'text chapters 23+24+25 text'; // c23-25
      $title[] = 'text chapter 23, 25 text'; // c23 & 25
      $title[] = 'text chapter 23 & 24 & 25 text'; // c23-25
      $title[] = 'text c25.5-30 text'; // c25.5-30
      $title[] = 'text c99-c102 text'; // c99-102 (c99 for termless)
      $title[] = 'text chapter 1 - 3 text'; // c1-3
      $title[] = 'sometext 33 text chapter 1, 2 text 3'; // c1-2 or c33 if no terms
      $title[] = 'text v2c5-10 text'; // c5-10 or c2 if no terms
      $title[] = 'text cccc5-10 text'; // c5-10
      $title[] = 'text chapters 23, 24, 25, 29, 31, 32 text'; // c23-25 & 29 & 31-32
      $title[] = 'chapter 19 - chapter 23'; // c19-23 or c19 for termless
      $title[] = 'chapter 12 part 2'; // c12
      
      function get_chapter($text, $terms) {
        $rterms = sprintf('(?:%s)', implode('|', $terms));
      
        $and = '(?:  [,&+]|\band\b  )';
        $isrange = "(?:  \s*-\s*  $rterms?  \s*\d+  )";
        $isdotnum = '(?:\.\d+)';
        $the_regexp = "/(
          $rterms \s*  \d+  $isdotnum?  $isrange?   
          (  \s*  $and  \s*  $rterms?  \s*  \d+  $isrange?  )*
        )/mix";
      
        $result = array();
        $result['orignal'] = $text;
        if (preg_match($the_regexp, $text, $matches)) {
          $result['found_match'] = $tmp = $matches[1];
          $tmp = preg_replace("/$rterms\s*/i", '', $tmp);
          $tmp = preg_replace('/\s*-\s*/', '-', $tmp);
          $chapters = preg_split("/\s* $and \s*/ix", $tmp);
          $chapters = array_map(function($x) {
              return preg_replace('/\d\K\.0+/', '',
                     preg_replace('/(?|\b0+(\d)|-\K0+(\d))/', '\1', $x
              ));
          }, $chapters);
          $chapters = merge_chapters($chapters);
          $result['converted'] = join_chapters($chapters);
        }
        else {
          $result['found_match'] = '';
          $result['converted'] = $text;
        }
        return $result;
      }
      
      function merge_chapters($chapters) {
        $i = 0;
        $begin = $end = -1;
        $rtchapters = array();
        foreach ($chapters as $chapter) {
          // Fetch next chapter
          $next = isset($chapters[$i+1]) ? $chapters[$i+1] : -1;
          // If not set, set begin chapter
          if ($begin == -1) {$begin = $chapter;}
          if (preg_match('/-/', $chapter)) {
            // It is a range, we reset begin/end and store the range
            $begin = $end = -1;
            array_push($rtchapters, $chapter);
          }
          else if ($chapter+1 == $next) {
            // next is current + 1, update end
            $end = $next;
          }
          else {
            // store result (if no end, then store current chapter, else store the range
            array_push($rtchapters, sprintf('%s', $end == -1 ? $chapter : "$begin-$end"));
            $begin = $end = -1; // reset, since we stored results
          }
          $i++; // needed for $next
        }
        return $rtchapters;
      }
      
      function join_chapters($chapters) {
        return 'c' . implode(' & ', $chapters) . "\n";
      }
      
      print "\nTERMS LEGEND:\n";
      print "Case 1. = ['chapters', 'chapter', 'ch', 'c']\n";
      print "Case 2. = []\n\n\n\n";
      foreach ($title as $t) {
        // If some patterns start by same letters, use longest first.
        print "Original: $t\n";
        print 'Case 1. = ';
        $result = get_chapter($t, ['chapters', 'chapter', 'ch', 'c']);
        print_r ($result);
        print 'Case 2. = ';
        $result = get_chapter($t, []);
        print_r ($result);
        print "--------------------------\n";
      }
      

      输出:参见:https://ideone.com/Ebzr9R

      【讨论】:

      • 感谢您发布替代方案。它可以与'chapter 23 and 33'; // c23 &amp; 33 和'chapters 23 and chapter 33'; // c23 &amp; 33 一起使用吗?
      • 只是尝试一下@Julio - 到目前为止以下内容不起作用:c19 &amp; c20。此外,它并不总是v1c2,它可以是xc2 或任何东西。
      • @HenrikPetterson 这两种情况都应该适用于正则表达式的最新版本。我刚刚更新了我的答案。
      • 出色的答案。您为 Wiktor 的解决方案提供了另一种方法(他基本上是正则宇宙中的 上帝) - 太棒了!虽然我可以完全阅读您的代码,但最好将 cmets 添加到其中以便其他人可以阅读。另外,如果您查看我的原始(不完整)代码,我会通过自定义 $terms 来测试标题。是否可以调整您的代码,以便我们设置条款$terms = ['chapter', 'ch', 'episode'...]?而且,是否可以传递一个空的$terms = [''];,然后我们使用当前算法匹配(任何数字)?意思是,hello 23 &amp; 24 将是 c23-24?
      • @HenrikPetterson 我为“005”和“5.00”之类的数字添加了清理
      猜你喜欢
      • 1970-01-01
      • 2021-08-26
      • 1970-01-01
      • 2018-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多