refactor: split TextReplacer into HtmlTokens and StoredForms (M1)
- HtmlTokens (static, with the S45 docblock): SPLIT, RAW_OPEN, tokenize(), structure(), closes(), unterminated() moved unchanged; new RAW_ELEMENTS list. - StoredForms: of() (was TextReplacer::forms), mentions(), unicodeEscaped() – the DB prefilter forms; TextReplacer::mentions() delegates, the ability's candidate query uses StoredForms::of(). - TextReplacer keeps problems(), apply() and the splice/safe() verification; safe() still runs the tokenizer and HtmlContext as two independent scans, in the same order. HtmlTokensTest asserts RAW_ELEMENTS === HtmlContext::RAW and that RAW_OPEN opens exactly those elements. TextReplacerTest and HtmlContextTest unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Compose;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Serializer;
|
||||
|
||||
/**
|
||||
* The forms a search text can take in stored block markup – the database prefilter of
|
||||
* lit/divi-replace-text and the check for global modules it does not edit. Only a prefilter:
|
||||
* the exact, safe match happens in the tree (TextReplacer). Pure PHP.
|
||||
*/
|
||||
final class StoredForms
|
||||
{
|
||||
/**
|
||||
* The search text as it can be stored in block markup: raw, as serialize_block_attributes()
|
||||
* escapes it (<, >, &, " and --; the JS builder keeps non-ASCII raw) and with \uXXXX for
|
||||
* non-ASCII (WordPress' PHP serializer). Case-insensitive adds the lower and upper case
|
||||
* forms, since the hex digits of \u escapes do not fold.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function of(string $search, bool $caseSensitive): array
|
||||
{
|
||||
$variants = $caseSensitive ? [$search] : [$search, mb_strtolower($search), mb_strtoupper($search)];
|
||||
$forms = [];
|
||||
foreach ($variants as $variant) {
|
||||
$escaped = substr(Serializer::attributes(['s' => $variant]), 6, -2);
|
||||
array_push($forms, $variant, $escaped, self::unicodeEscaped($escaped));
|
||||
}
|
||||
|
||||
return array_values(array_unique($forms));
|
||||
}
|
||||
|
||||
/** Whether block markup contains $search in one of its stored forms. */
|
||||
public static function mentions(string $markup, string $search, bool $caseSensitive): bool
|
||||
{
|
||||
foreach (self::of($search, $caseSensitive) as $form) {
|
||||
$found = $caseSensitive ? str_contains($markup, $form) : mb_stripos($markup, $form) !== false;
|
||||
if ($found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Non-ASCII characters as JSON \uXXXX escapes (UTF-16, lower-case hex like json_encode()). */
|
||||
private static function unicodeEscaped(string $value): string
|
||||
{
|
||||
return (string) preg_replace_callback('/[^\x00-\x7F]/u', static function (array $match): string {
|
||||
$units = unpack('n*', (string) mb_convert_encoding($match[0], 'UTF-16BE', 'UTF-8'));
|
||||
|
||||
return implode('', array_map(static fn (int $unit): string => sprintf('\\u%04x', $unit), (array) $units));
|
||||
}, $value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user