Domanda

Supponiamo che io abbia le seguenti due stringhe contenenti espressioni regolari. Come li unisco? Più specificamente, voglio avere le due espressioni come alternative.

$a = '# /[a-z] #i';
$b = '/ Moo /x';
$c = preg_magic_coalesce('|', $a, $b);
// Desired result should be equivalent to:
// '/ \/[a-zA-Z] |Moo/'

Ovviamente, fare questo come operazioni su stringhe non è pratico perché comporterebbe l'analisi delle espressioni, la costruzione di alberi di sintassi, la coalescenza degli alberi e quindi l'output di un'altra espressione regolare equivalente all'albero. Sono completamente felice senza quest'ultimo passaggio. Sfortunatamente, PHP non ha una classe RegExp (o lo fa?).

Esiste un modo per raggiungere questo obiettivo? Per inciso, un'altra lingua offre un modo? Non è uno scenario abbastanza normale? Non indovinare. : - (

In alternativa , c'è un modo per controllare in modo efficiente se una delle due espressioni corrisponde e quale corrisponde prima (e se corrispondono nella stessa posizione, che la partita è più lunga)? Questo è quello che sto facendo al momento. Sfortunatamente, lo faccio su stringhe lunghe, molto spesso, per più di due schemi. Il risultato è lento (e sì, questo è sicuramente il collo di bottiglia).

EDIT:

Avrei dovuto essere più specifico - scusa. $ a e $ b sono variabili , il loro contenuto è al di fuori del mio controllo! Altrimenti, li vorrei solo fondere manualmente. Pertanto, non posso fare ipotesi sui delimitatori o sui modificatori regex utilizzati. Si noti, ad esempio, che la mia prima espressione utilizza il modificatore i (ignora l'involucro) mentre la seconda utilizza x (sintassi estesa). Pertanto, non posso semplicemente concatenare i due perché la seconda espressione non ignora il case e la prima non usa la sintassi estesa (e qualsiasi spazio bianco in essa è significativo!

È stato utile?

Soluzione

Vedo che porneL in realtà ha descritto un sacco di questo, ma questo gestisce la maggior parte del problema. Annulla i modificatori impostati nelle sottoespressioni precedenti (che mancavano l'altra risposta) e imposta i modificatori come specificato in ciascuna sottoespressione. Gestisce anche i delimitatori non slash (non sono riuscito a trovare una specifica di quali caratteri sono consentiti qui, quindi ho usato . , potresti voler restringere ulteriormente).

Un punto debole è che non gestisce i riferimenti a ritroso all'interno delle espressioni. La mia più grande preoccupazione è che i limiti dei riferimenti secondari stessi. Lo lascerò come esercizio al lettore / interrogante.

// Pass as many expressions as you'd like
function preg_magic_coalesce() {
    $active_modifiers = array();

    $expression = '/(?:';
    $sub_expressions = array();
    foreach(func_get_args() as $arg) {
        // Determine modifiers from sub-expression
        if(preg_match('/^(.)(.*)\1([eimsuxADJSUX]+)$/', $arg, $matches)) {
            $modifiers = preg_split('//', $matches[3]);
            if($modifiers[0] == '') {
                array_shift($modifiers);
            }
            if($modifiers[(count($modifiers) - 1)] == '') {
                array_pop($modifiers);
            }

            $cancel_modifiers = $active_modifiers;
            foreach($cancel_modifiers as $key => $modifier) {
                if(in_array($modifier, $modifiers)) {
                    unset($cancel_modifiers[$key]);
                }
            }
            $active_modifiers = $modifiers;
        } elseif(preg_match('/(.)(.*)\1$/', $arg)) {
            $cancel_modifiers = $active_modifiers;
            $active_modifiers = array();
        }

        // If expression has modifiers, include them in sub-expression
        $sub_modifier = '(?';
        $sub_modifier .= implode('', $active_modifiers);

        // Cancel modifiers from preceding sub-expression
        if(count($cancel_modifiers) > 0) {
            $sub_modifier .= '-' . implode('-', $cancel_modifiers);
        }

        $sub_modifier .= ')';

        $sub_expression = preg_replace('/^(.)(.*)\1[eimsuxADJSUX]*$/', $sub_modifier . '$2', $arg);

        // Properly escape slashes
        $sub_expression = preg_replace('/(?<!\\\)\//', '\\\/', $sub_expression);

        $sub_expressions[] = $sub_expression;
    }

    // Join expressions
    $expression .= implode('|', $sub_expressions);

    $expression .= ')/';
    return $expression;
}

Modifica: l'ho riscritto (perché sono un DOC) e ho finito con:

function preg_magic_coalesce($expressions = array(), $global_modifier = '') {
    if(!preg_match('/^((?:-?[eimsuxADJSUX])+)$/', $global_modifier)) {
        $global_modifier = '';
    }

    $expression = '/(?:';
    $sub_expressions = array();
    foreach($expressions as $sub_expression) {
        $active_modifiers = array();
        // Determine modifiers from sub-expression
        if(preg_match('/^(.)(.*)\1((?:-?[eimsuxADJSUX])+)$/', $sub_expression, $matches)) {
            $active_modifiers = preg_split('/(-?[eimsuxADJSUX])/',
                $matches[3], -1, PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
        }

        // If expression has modifiers, include them in sub-expression
        if(count($active_modifiers) > 0) {
            $replacement = '(?';
            $replacement .= implode('', $active_modifiers);
            $replacement .= ':$2)';
        } else {
            $replacement = '$2';
        }

        $sub_expression = preg_replace('/^(.)(.*)\1(?:(?:-?[eimsuxADJSUX])*)$/',
            $replacement, $sub_expression);

        // Properly escape slashes if another delimiter was used
        $sub_expression = preg_replace('/(?<!\\\)\//', '\\\/', $sub_expression);

        $sub_expressions[] = $sub_expression;
    }

    // Join expressions
    $expression .= implode('|', $sub_expressions);

    $expression .= ')/' . $global_modifier;
    return $expression;
}

Ora utilizza (? modificatori: sottoespressione) anziché (? modificatori) sottoespressione | (? annulla-modificatori) sottoespressione ma io ' ho notato che entrambi hanno alcuni strani effetti collaterali modificatori. Ad esempio, in entrambi i casi se un'espressione secondaria ha un modificatore / u , non riuscirà a corrispondere (ma se passi 'u' come secondo argomento del nuova funzione, che corrisponderà perfettamente).

Altri suggerimenti

  1. Rimuovi i delimitatori e i flag da ciascuno. Questa regex dovrebbe farlo:

    /^(.)(.*)\1([imsxeADSUXJu]*)$/
    
  2. Unisci espressioni. Per iniettare i flag avrai bisogno di parentesi non catturanti:

    "(?$flags1:$regexp1)|(?$flags2:$regexp2)"
    
  3. Se sono presenti riferimenti a ritroso, conta la cattura della parentesi e aggiorna i riferimenti a ritroso di conseguenza (ad es. /(.)x\1/ e / (.) y \ correttamente collegati 1 / è /(.)x\1|(.)y\2/ ).

Modifica

Ho riscritto il codice! Ora contiene le modifiche elencate di seguito. Inoltre, ho fatto test approfonditi (che non ho pubblicato qui perché sono troppi) per cercare errori. Finora, non ne ho trovato nessuno.

  • La funzione è ora divisa in due parti: esiste una funzione separata preg_split che accetta un'espressione regolare e restituisce un array contenente l'espressione nuda (senza delimitatori) e un array di modificatori. Questo potrebbe tornare utile (lo è già, in effetti; ecco perché ho apportato questa modifica).

  • Il codice ora gestisce correttamente i riferimenti a ritroso. Questo era necessario per il mio scopo dopo tutto. Non è stato difficile aggiungere, l'espressione regolare usata per catturare i riferimenti secondari sembra semplicemente strana (e in realtà può essere estremamente inefficiente, mi sembra NP-difficile per me, ma questo è solo un intuizione e si applica solo in casi con bordi strani). A proposito, qualcuno conosce un modo migliore per controllare un numero irregolare di partite rispetto al mio modo? I lookbehind negativi non funzioneranno qui perché accettano solo stringhe a lunghezza fissa anziché espressioni regolari. Tuttavia, ho bisogno della regex qui per verificare se la precedente barra rovesciata è effettivamente sfuggita a se stessa.

    Inoltre, non so quanto sia bravo PHP a memorizzare nella cache l'uso anonimo di create_function . Per quanto riguarda le prestazioni, questa potrebbe non essere la soluzione migliore ma sembra abbastanza buona.

  • Ho corretto un bug nel controllo di integrità.

  • Ho rimosso la cancellazione di modificatori obsoleti poiché i miei test dimostrano che non è necessario.

A proposito, questo codice è uno dei componenti principali di un evidenziatore di sintassi per varie lingue su cui sto lavorando in PHP poiché non sono soddisfatto delle alternative elencate altrove

Grazie!

porneL , mancanza di palpebre , lavoro straordinario! Molte molte grazie. Mi ero davvero arreso.

Ho sviluppato la tua soluzione e vorrei condividerla qui. Non ho implementato i riferimenti secondari di riclassificazione poiché questo non è pertinente nel mio caso (penso & # 8230;). Forse questo sarà necessario in seguito, però.

Alcune domande & # 8230;

Una cosa, @eyelidlessness : Perché senti la necessità di cancellare i vecchi modificatori? A mio avviso, ciò non è necessario poiché i modificatori vengono comunque applicati solo localmente. Ah sì, un'altra cosa. La tua fuga dal delimitatore sembra eccessivamente complicata. Ti interessa spiegare perché pensi che sia necessario? Credo che anche la mia versione dovrebbe funzionare, ma potrei sbagliarmi.

Inoltre, ho modificato la firma della tua funzione in base alle mie esigenze. Penso anche che la mia versione sia generalmente più utile. Ancora una volta, potrei sbagliarmi.

A proposito, ora dovresti capire l'importanza dei nomi reali su SO. ;-) Non ti posso dare credito reale nel codice. : - /

Il codice

Ad ogni modo, vorrei condividere il mio risultato finora perché non posso credere che nessun altro abbia mai bisogno di qualcosa del genere. Il codice sembra funzionare molto bene. Tuttavia, non sono ancora stati effettuati test approfonditi. Per favore, commenta!

E senza ulteriori indugi & # 8230;

/**
 * Merges several regular expressions into one, using the indicated 'glue'.
 *
 * This function takes care of individual modifiers so it's safe to use
 * <em>different</em> modifiers on the individual expressions. The order of
 * sub-matches is preserved as well. Numbered back-references are adapted to
 * the new overall sub-match count. This means that it's safe to use numbered
 * back-refences in the individual expressions!
 * If {@link $names} is given, the individual expressions are captured in
 * named sub-matches using the contents of that array as names.
 * Matching pair-delimiters (e.g. <code>"{…}"</code>) are currently
 * <strong>not</strong> supported.
 *
 * The function assumes that all regular expressions are well-formed.
 * Behaviour is undefined if they aren't.
 *
 * This function was created after a {@link https://stackoverflow.com/questions/244959/
 * StackOverflow discussion}. Much of it was written or thought of by
 * “porneL” and “eyelidlessness”. Many thanks to both of them.
 *
 * @param string $glue  A string to insert between the individual expressions.
 *      This should usually be either the empty string, indicating
 *      concatenation, or the pipe (<code>|</code>), indicating alternation.
 *      Notice that this string might have to be escaped since it is treated
 *      like a normal character in a regular expression (i.e. <code>/</code>)
 *      will end the expression and result in an invalid output.
 * @param array $expressions    The expressions to merge. The expressions may
 *      have arbitrary different delimiters and modifiers.
 * @param array $names  Optional. This is either an empty array or an array of
 *      strings of the same length as {@link $expressions}. In that case,
 *      the strings of this array are used to create named sub-matches for the
 *      expressions.
 * @return string An string representing a regular expression equivalent to the
 *      merged expressions. Returns <code>FALSE</code> if an error occurred.
 */
function preg_merge($glue, array $expressions, array $names = array()) {
    // … then, a miracle occurs.

    // Sanity check …

    $use_names = ($names !== null and count($names) !== 0);

    if (
        $use_names and count($names) !== count($expressions) or
        !is_string($glue)
    )
        return false;

    $result = array();
    // For keeping track of the names for sub-matches.
    $names_count = 0;
    // For keeping track of *all* captures to re-adjust backreferences.
    $capture_count = 0;

    foreach ($expressions as $expression) {
        if ($use_names)
            $name = str_replace(' ', '_', $names[$names_count++]);

        // Get delimiters and modifiers:

        $stripped = preg_strip($expression);

        if ($stripped === false)
            return false;

        list($sub_expr, $modifiers) = $stripped;

        // Re-adjust backreferences:

        // We assume that the expression is correct and therefore don't check
        // for matching parentheses.

        $number_of_captures = preg_match_all('/\([^?]|\(\?[^:]/', $sub_expr, 

Modifica

Ho riscritto il codice! Ora contiene le modifiche elencate di seguito. Inoltre, ho fatto test approfonditi (che non ho pubblicato qui perché sono troppi) per cercare errori. Finora, non ne ho trovato nessuno.

  • La funzione è ora divisa in due parti: esiste una funzione separata preg_split che accetta un'espressione regolare e restituisce un array contenente l'espressione nuda (senza delimitatori) e un array di modificatori. Questo potrebbe tornare utile (lo è già, in effetti; ecco perché ho apportato questa modifica).

  • Il codice ora gestisce correttamente i riferimenti a ritroso. Questo era necessario per il mio scopo dopo tutto. Non è stato difficile aggiungere, l'espressione regolare usata per catturare i riferimenti secondari sembra semplicemente strana (e in realtà può essere estremamente inefficiente, mi sembra NP-difficile per me, ma questo è solo un intuizione e si applica solo in casi con bordi strani). A proposito, qualcuno conosce un modo migliore per controllare un numero irregolare di partite rispetto al mio modo? I lookbehind negativi non funzioneranno qui perché accettano solo stringhe a lunghezza fissa anziché espressioni regolari. Tuttavia, ho bisogno della regex qui per verificare se la precedente barra rovesciata è effettivamente sfuggita a se stessa.

    Inoltre, non so quanto sia bravo PHP a memorizzare nella cache l'uso anonimo di create_function . Per quanto riguarda le prestazioni, questa potrebbe non essere la soluzione migliore ma sembra abbastanza buona.

  • Ho corretto un bug nel controllo di integrità.

  • Ho rimosso la cancellazione di modificatori obsoleti poiché i miei test dimostrano che non è necessario.

A proposito, questo codice è uno dei componenti principali di un evidenziatore di sintassi per varie lingue su cui sto lavorando in PHP poiché non sono soddisfatto delle alternative elencate altrove

Grazie!

porneL , mancanza di palpebre , lavoro straordinario! Molte molte grazie. Mi ero davvero arreso.

Ho sviluppato la tua soluzione e vorrei condividerla qui. Non ho implementato i riferimenti secondari di riclassificazione poiché questo non è pertinente nel mio caso (penso & # 8230;). Forse questo sarà necessario in seguito, però.

Alcune domande & # 8230;

Una cosa, @eyelidlessness : Perché senti la necessità di cancellare i vecchi modificatori? A mio avviso, ciò non è necessario poiché i modificatori vengono comunque applicati solo localmente. Ah sì, un'altra cosa. La tua fuga dal delimitatore sembra eccessivamente complicata. Ti interessa spiegare perché pensi che sia necessario? Credo che anche la mia versione dovrebbe funzionare, ma potrei sbagliarmi.

Inoltre, ho modificato la firma della tua funzione in base alle mie esigenze. Penso anche che la mia versione sia generalmente più utile. Ancora una volta, potrei sbagliarmi.

A proposito, ora dovresti capire l'importanza dei nomi reali su SO. ;-) Non ti posso dare credito reale nel codice. : - /

Il codice

Ad ogni modo, vorrei condividere il mio risultato finora perché non posso credere che nessun altro abbia mai bisogno di qualcosa del genere. Il codice sembra funzionare molto bene. Tuttavia, non sono ancora stati effettuati test approfonditi. Per favore, commenta!

E senza ulteriori indugi & # 8230;

<*>

PS: ho reso modificabile questa wiki della community di post. Sai cosa significa & # 8230 ;!

); if ($number_of_captures === false) return false; if ($number_of_captures > 0) { // NB: This looks NP-hard. Consider replacing. $backref_expr = '/ ( # Only match when not escaped: [^\\\\] # guarantee an even number of backslashes (\\\\*?)\\2 # (twice n, preceded by something else). ) \\\\ (\d) # Backslash followed by a digit. /x'; $sub_expr = preg_replace_callback( $backref_expr, create_function( '$m', 'return $m[1] . "\\\\" . ((int)$m[3] + ' . $capture_count . ');' ), $sub_expr ); $capture_count += $number_of_captures; } // Last, construct the new sub-match: $modifiers = implode('', $modifiers); $sub_modifiers = "(?$modifiers)"; if ($sub_modifiers === '(?)') $sub_modifiers = ''; $sub_name = $use_names ? "?<$name>" : '?:'; $new_expr = "($sub_name$sub_modifiers$sub_expr)"; $result[] = $new_expr; } return '/' . implode($glue, $result) . '/'; } /** * Strips a regular expression string off its delimiters and modifiers. * Additionally, normalize the delimiters (i.e. reformat the pattern so that * it could have used '/' as delimiter). * * @param string $expression The regular expression string to strip. * @return array An array whose first entry is the expression itself, the * second an array of delimiters. If the argument is not a valid regular * expression, returns <code>FALSE</code>. * */ function preg_strip($expression) { if (preg_match('/^(.)(.*)\\1([imsxeADSUXJu]*)$/s', $expression, $matches) !== 1) return false; $delim = $matches[1]; $sub_expr = $matches[2]; if ($delim !== '/') { // Replace occurrences by the escaped delimiter by its unescaped // version and escape new delimiter. $sub_expr = str_replace("\\$delim", $delim, $sub_expr); $sub_expr = str_replace('/', '\\/', $sub_expr); } $modifiers = $matches[3] === '' ? array() : str_split(trim($matches[3])); return array($sub_expr, $modifiers); }

PS: ho reso modificabile questa wiki della community di post. Sai cosa significa & # 8230 ;!

Sono abbastanza sicuro che non è possibile mettere insieme regexps in questo modo in qualsiasi lingua: potrebbero avere modificatori incompatibili.

Probabilmente li metterei semplicemente in un array e li attraverserei, o li combinerei a mano.

Modifica: se li esegui uno alla volta, come descritto nella modifica, potresti essere in grado di eseguire il secondo su una sottostringa (dall'inizio fino alla prima corrispondenza). Ciò potrebbe aiutare le cose.

function preg_magic_coalasce($split, $re1, $re2) {
  $re1 = rtrim($re1, "\/#is");
  $re2 = ltrim($re2, "\/#");
  return $re1.$split.$re2;
}

Potresti farlo in questo modo alternativo:

$a = '# /[a-z] #i';
$b = '/ Moo /x';

$a_matched = preg_match($a, $text, $a_matches);
$b_matched = preg_match($b, $text, $b_matches);

if ($a_matched && $b_matched) {
    $a_pos = strpos($text, $a_matches[1]);
    $b_pos = strpos($text, $b_matches[1]);

    if ($a_pos == $b_pos) {
        if (strlen($a_matches[1]) == strlen($b_matches[1])) {
            // $a and $b matched the exact same string
        } else if (strlen($a_matches[1]) > strlen($b_matches[1])) {
            // $a and $b started matching at the same spot but $a is longer
        } else {
            // $a and $b started matching at the same spot but $b is longer
        }
    } else if ($a_pos < $b_pos) {
        // $a matched first
    } else {
        // $b matched first
    }
} else if ($a_matched) {
    // $a matched, $b didn't
} else if ($b_matched) {
    // $b matched, $a didn't
} else {
    // neither one matched
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top