編集
コードを書き直しました!現在、次のようにリストされている変更が含まれています。さらに、エラーを探すために大規模なテストを行いました (数が多すぎるため、ここには掲載しません)。これまでのところ、私は何も見つけていません。
関数は 2 つの部分に分割されましpreg_split
た。正規表現を受け取り、そのままの式 (区切り記号なし) と修飾子の配列を含む配列を返す別の関数があります。これは便利かもしれません (実際には既に使用されています。これが、この変更を行った理由です)。
コードが後方参照を正しく処理するようになりました。結局のところ、これは私の目的のために必要でした。追加するのは難しくありませんでした.後方参照をキャプチャするために使用される正規表現は奇妙に見えます. . ところで、奇数の一致をチェックする私の方法よりも良い方法を知っている人はいますか? 負の後読みは、正規表現ではなく固定長の文字列しか受け付けないため、ここでは機能しません。ただし、前のバックスラッシュが実際にエスケープされているかどうかをテストするには、ここで正規表現が必要です。
さらに、PHP が匿名使用をキャッシュするのにどれほど優れているかはわかりませんcreate_function
。パフォーマンスに関しては、これは最善の解決策ではないかもしれませんが、十分なようです。
健全性チェックのバグを修正しました。
私のテストでは不要であることが示されたので、廃止された修飾子のキャンセルを削除しました。
ところで、このコードは、私が PHP で取り組んでいるさまざまな言語の構文ハイライターのコア コンポーネントの 1 つです。他の場所にリストされている代替手段に満足できないためです。
ありがとう!
porneL ,まぶたのない, 素晴らしい作品! 大変感謝します。実は諦めていました。
私はあなたのソリューションに基づいて構築したので、ここで共有したいと思います。これは私の場合は関係ないので、後方参照の再番号付けは実装しませんでした (… と思います)。たぶん、これは後で必要になるでしょう。
いくつか質問があります...</h2>
@eyelidlessness : _古いモディファイヤをキャンセルする必要性を感じるのはなぜですか? 私が見る限り、修飾子はとにかくローカルでのみ適用されるため、これは必要ありません。あ、そうそう、もう一つ。区切り文字のエスケープは複雑すぎるようです。これが必要だと思う理由を説明してください。私のバージョンも同様に機能するはずですが、非常に間違っている可能性があります。
また、ニーズに合わせて関数のシグネチャを変更しました。また、私のバージョンはより一般的に役立つと思います。繰り返しますが、私は間違っているかもしれません。
ところで、SO での実名の重要性に気付くはずです。;-) コードの真の功績を認めることはできません。:-/
コード
とにかく、これまでの結果を共有したいと思います。他の誰もそのようなものを必要としていないとは信じられないからです. コードは非常にうまく機能しているようです。ただし、広範なテストはまだ行われていません。 コメントしてください!
そして、これ以上苦労することなく…</p>
/**
* 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, $_);
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: この投稿コミュニティ wiki を編集可能にしました。これが何を意味するか分かります…!