init
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Esun\Translation\Model\Html;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMNode;
|
||||
use DOMText;
|
||||
use DOMXPath;
|
||||
|
||||
class HtmlTextExtractor
|
||||
{
|
||||
private const ROOT_ID = 'esun-translation-root';
|
||||
private const DIRECTIVE_TOKEN_PREFIX = '__ESUN_HTML_DIRECTIVE_';
|
||||
|
||||
public function extract(string $html): HtmlFragment
|
||||
{
|
||||
[$protectedHtml, $directivePlaceholders] = $this->protectMagentoDirectives($html);
|
||||
$document = $this->createDocument($protectedHtml);
|
||||
$xpath = new DOMXPath($document);
|
||||
$query = sprintf(
|
||||
'//*[@id="%s"]//text()[normalize-space(.) != "" and not(ancestor::script) and not(ancestor::style) and not(ancestor::code) and not(ancestor::pre)]',
|
||||
self::ROOT_ID
|
||||
);
|
||||
$nodes = $xpath->query($query);
|
||||
$textNodes = [];
|
||||
$texts = [];
|
||||
$index = 0;
|
||||
|
||||
if ($nodes !== false) {
|
||||
foreach ($nodes as $node) {
|
||||
if (!$node instanceof DOMText) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$text = $this->restoreMagentoDirectives($node->nodeValue ?? '', $directivePlaceholders);
|
||||
if (trim($text) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = 'html_' . $index++;
|
||||
$textNodes[$id] = $node;
|
||||
$texts[$id] = $text;
|
||||
}
|
||||
}
|
||||
|
||||
$root = $document->getElementById(self::ROOT_ID);
|
||||
if (!$root) {
|
||||
throw new \RuntimeException('Unable to parse HTML fragment.');
|
||||
}
|
||||
|
||||
return new HtmlFragment($document, $root, $textNodes, $texts, $directivePlaceholders);
|
||||
}
|
||||
|
||||
private function createDocument(string $html): DOMDocument
|
||||
{
|
||||
$document = new DOMDocument('1.0', 'UTF-8');
|
||||
$wrappedHtml = sprintf(
|
||||
'<?xml encoding="UTF-8"><div id="%s">%s</div>',
|
||||
self::ROOT_ID,
|
||||
$html
|
||||
);
|
||||
|
||||
$previous = libxml_use_internal_errors(true);
|
||||
$loaded = $document->loadHTML($wrappedHtml, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED);
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($previous);
|
||||
|
||||
if (!$loaded) {
|
||||
throw new \RuntimeException('Unable to parse HTML fragment.');
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: array<string, string>}
|
||||
*/
|
||||
private function protectMagentoDirectives(string $html): array
|
||||
{
|
||||
$placeholders = [];
|
||||
$index = 0;
|
||||
$protected = preg_replace_callback(
|
||||
'/\{\{.*?\}\}/s',
|
||||
function (array $matches) use (&$placeholders, &$index): string {
|
||||
$token = self::DIRECTIVE_TOKEN_PREFIX . $index++ . '__';
|
||||
$placeholders[$token] = $this->normalizeMagentoDirectiveQuotes($matches[0]);
|
||||
|
||||
return $token;
|
||||
},
|
||||
$html
|
||||
);
|
||||
|
||||
return [$protected ?? $html, $placeholders];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $placeholders
|
||||
*/
|
||||
private function restoreMagentoDirectives(string $html, array $placeholders): string
|
||||
{
|
||||
$restored = $placeholders === [] ? $html : strtr($html, $placeholders);
|
||||
|
||||
return $this->normalizeMagentoDirectiveQuotes($restored);
|
||||
}
|
||||
|
||||
private function normalizeMagentoDirectiveQuotes(string $html): string
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/\{\{.*?\}\}/s',
|
||||
static function (array $matches): string {
|
||||
return str_ireplace(
|
||||
['"', '"', '"', ''', ''', '''],
|
||||
['"', '"', '"', "'", "'", "'"],
|
||||
$matches[0]
|
||||
);
|
||||
},
|
||||
$html
|
||||
) ?? $html;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user