Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
HtmlPreserver
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
2 / 2
2
100.00% covered (success)
100.00%
1 / 1
 replaceTags
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 restoreTags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/**
6 * HTML tag preservation utility for translation services.
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public License,
9 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
10 * obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * @package   phpMyFAQ
13 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
14 * @copyright 2026 phpMyFAQ Team
15 * @license   http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
16 * @link      https://www.phpmyfaq.de
17 * @since     2026-01-17
18 */
19
20namespace phpMyFAQ\Translation;
21
22/**
23 * Class HtmlPreserver
24 *
25 * Replaces HTML tags with placeholders before translation and restores them after,
26 * preventing translation services from breaking HTML structure.
27 */
28class HtmlPreserver
29{
30    private const string PLACEHOLDER_PREFIX = '##HTML_TAG_';
31    private const string PLACEHOLDER_SUFFIX = '##';
32
33    /**
34     * Replace HTML tags with placeholders.
35     *
36     * @param string $html HTML content with tags
37     * @return array{string, array<string, string>} [text with placeholders, tag map]
38     */
39    public function replaceTags(string $html): array
40    {
41        $tagMap = [];
42        $counter = 0;
43
44        // Replace all HTML tags (opening, closing, self-closing) with placeholders
45        $textWithPlaceholders = preg_replace_callback(
46            '/<[^>]+>/',
47            static function ($matches) use (&$tagMap, &$counter) {
48                $placeholder = self::PLACEHOLDER_PREFIX . $counter . self::PLACEHOLDER_SUFFIX;
49                $tagMap[$placeholder] = $matches[0];
50                $counter++;
51                return $placeholder;
52            },
53            $html,
54        );
55
56        return [$textWithPlaceholders ?? $html, $tagMap];
57    }
58
59    /**
60     * Restore HTML tags from placeholders.
61     *
62     * @param string $text Text containing placeholders
63     * @param array<string, string> $tagMap Map of placeholders to original tags
64     * @return string Text with HTML tags restored
65     */
66    public function restoreTags(string $text, array $tagMap): string
67    {
68        return str_replace(array_keys($tagMap), array_values($tagMap), $text);
69    }
70}