una titulos titulo tesis subtitulos separar poner numeracion nomenclatura nombre niveles insertar indice encabezado como capitulos capitulo php regex

php - titulos - Determinación del número de capítulo en diferentes tipos de texto.



nomenclatura en word (5)

Lógica

Sugiero el siguiente enfoque que combina una expresión regular y una lógica común de procesamiento de cadenas:

  • use preg_match con la expresión regular apropiada para que coincida con la primera aparición de todo el fragmento de texto que comienza con la palabra clave de la matriz $terms hasta el último número (+ letra de sección opcional) relacionada con el término
  • una vez que se obtiene la coincidencia, cree una matriz que incluya la cadena de entrada, el valor de coincidencia y la coincidencia postprocesada
  • el procesamiento posterior puede realizarse eliminando espacios entre los números con guión y reconstruyendo los rangos numéricos en el caso de números unidos con los caracteres + , & o,. Esto requiere una operación de varios pasos: 1) hacer coincidir las subcadenas separadas por guiones en la coincidencia general anterior y recortar los ceros y espacios en blanco innecesarios, 2) dividir los fragmentos de números en elementos separados y pasarlos a una función separada que generará el número rangos
  • la función buildNumChain($arr) creará los rangos de números y si una letra sigue a un número, lo convertirá en un sufijo de la section X

Solución

Puedes utilizar

$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; }

Vea la demostración de PHP .

Puntos principales

  • Une c o chapter / chapters con los números que los siguen, captura solo c y los números
  • Después de encontrar las coincidencias, procese el Grupo 2 que contiene las secuencias numéricas
  • Todas las subcadenas <number>-c?<number> deben ser eliminadas de espacios en blanco c antes / entre números y
  • Todos los números separados & / & deben procesarse buildNumChain con buildNumChain que genera rangos de números consecutivos (se suponen números enteros).

El regex principal se verá como si $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''

Vea la demostración de expresiones regulares .

Detalles del patrón

  • (?|(e)(pisodes?)|(c)(hapters?)|(c)(hs?)|(e)(ps?)|(c)(s?)|(e)(s?)|()()) - un grupo de reinicio de rama que captura la primera letra del término de búsqueda y captura el resto del término en un Grupo 2 obligatorio. Si hay un término vacío, se agrega ()() para asegurarse Las ramas en el grupo contienen el mismo número de grupos.
  • /s* - 0+ espacios en blanco
  • ((/d+(?:/./d+)?(?:[AZ]/b)?)(?:/s*(?:[,&+-]|and)/s*c?(?3))*) - Grupo 2:
    • (/d+(?:/./d+)?(?:[AZ]/b)?) - Grupo 3: dígitos de 1+, seguidos de una secuencia opcional de . , 1+ dígitos y luego una letra ASCII opcional que debe seguirse con un carácter sin palabra o al final de la cadena (tenga en cuenta que el modificador que no distingue entre mayúsculas y minúsculas hará que [AZ] también coincida con las letras ASCII en minúsculas)
    • (?:/s*(?:[,&+-]|and)/s*(?:(?:e(?:pisodes?)|c(?:hapters?)|c(?:hs?)|e(?:ps?)|c(?:s?)|e(?:s?)|)/s*)?(?4))* - cero o más secuencias de
      • /s*(?:[,&+-]|and)/s* - a /s*(?:[,&+-]|and)/s* & , + , - and adjunta con espacios en blanco opcionales de 0+
      • (?:e(?:pisodes?)|c(?:hapters?)|c(?:hs?)|e(?:ps?)|c(?:s?)|e(?:s?)|) - cualquiera de los términos con terminaciones adicionales opcionales s
      • (?4) - Patrón del grupo 4 recursivo / repetido

Cuando coincida la expresión regular, el valor del Grupo 1 es c , por lo que será la primera parte del resultado. Entonces,

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

se usa dentro de preg_replace_callback para eliminar espacios en blanco entre - (si hay alguno) y términos (si hay) seguidos con más de 0 caracteres de espacios en blanco, y si el Grupo 1 coincide, la coincidencia se divide con

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

expresiones regulares (coincide con & ,,, + y and entre espacios en blanco opcionales 0+ seguidos con espacios en blanco 0+ y luego una cadena opcional, términos seguidos con espacios en blanco 0+) y la matriz se pasa a la función buildNumChain que construye la cadena resultante.

Estoy sacando títulos de publicaciones relacionadas con novelas. El objetivo es, mediante el uso de expresiones regulares, determinar de qué capítulo (s) se trata la publicación. Cada sitio utiliza diferentes maneras de identificar los capítulos. Aquí están los casos más comunes:

$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

Los números de los capítulos siempre se enumeran en el título, solo en diferentes variaciones como se muestra arriba.

Lo que tengo hasta ahora

Hasta ahora, tengo una expresión regular para determinar casos individuales de capítulos, como:

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

Utilizando este código (intente 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'']; }

¿Cómo puedo hacer este trabajo con los otros ejemplos mencionados anteriormente? Dada la complejidad de esta pregunta, obtendré 200 puntos cuando sea elegible.


Creo que es muy complejo construir algo como esto sin lanzar algunos falsos positivos porque algunos de los patrones podrían estar contenidos en el título y, en esos casos, el código los detectará.

De todos modos, expondré una solución que podría ser interesante para ti, experimenta con ella cuando tengas tiempo. No lo he probado en profundidad, por lo que, si encuentra algún problema con esta implementación, avíseme e intentaré encontrarle una solución.

Mirando sus patrones, todos ellos podrían ser separados en dos grandes grupos:

  • de un número a otro número (G1)
  • uno o varios números separados por comas, signos más o signos (G2)

Entonces, si podemos separar estos dos grupos, podemos tratarlos de manera diferente. De los siguientes títulos, intentaré obtener los números de los capítulos de esta manera:

+-------------------------------------------+-------+------------------------+ | 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 | +-------------------------------------------+-------+------------------------+

Para extraer solo el número de capítulos y diferenciarlos, una solución podría ser construir una expresión regular que capture dos grupos para los rangos de capítulos (G1) y un solo grupo para los números separados por caracteres (G2). Después de extraer los números de los capítulos, podemos procesar el resultado para mostrar los capítulos con el formato correcto.

Aquí está el código:

He visto que todavía está agregando más casos en los comentarios que no están contenidos en la pregunta. Si desea agregar un nuevo caso, simplemente cree un nuevo patrón coincidente y agréguelo a la expresión regular final. Simplemente siga la regla de dos grupos coincidentes para los rangos y un solo grupo coincidente para los números separados por caracteres. Además, tenga en cuenta que los patrones más detallados deben ubicarse antes que los menos. Por ejemplo, ccc N - ccc N debe ubicarse antes de cc N - cc N y esta última antes de 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; } }

Puedes consultar el código trabajando en Ideone .


Intenta con esto. Parece trabajar con ejemplos dados y algunos más:

<?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"; }

Salida: Ver: https://ideone.com/Ebzr9R


Ramifiqué su ejemplo, agregando un poco para tomar, por ejemplo, "capítulo" y unir tanto "c" como "capítulo", luego extraí todas las expresiones coincidentes de las cadenas, extraí los números individuales, alisé los rangos encontrados y devolví cadena formateada como la que tenías en tus comentarios para cada una:

Así que aquí está el enlace: ideone

La función en sí (modificó la suya un poco):

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)); } } } }

Y el bloque llamante:

$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"); }

Lo que resulta en la salida:

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


Use una expresión regular que capture la información del capítulo.

''~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~''

Luego limpie el grupo 1 con esta búsqueda ''~[^-./d+,&/r/n]+~'' reemplazar con nada '''' .

Luego limpie la limpieza con este hallazgo ''~[+&]~'' reemplazar con coma '',''

Updae
El código php a continuación incluye una función para consolidar la secuencia de capítulos individuales
a los rangos de capítulos.

Regex principal, versión legible

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

Ejemplo de código 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; }

Salida

$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