Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
64 / 64
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
Xml
100.00% covered (success)
100.00%
64 / 64
100.00% covered (success)
100.00%
3 / 3
29
100.00% covered (success)
100.00%
1 / 1
 createFile
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
1 / 1
18
 createFragment
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
10
 escape
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\I18n\Format;
16
17/**
18 * I18n XML format class
19 *
20 * @category   Pop
21 * @package    Pop_I18n
22 * @author     Nick Sagona, III <dev@noladev.com>
23 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
24 * @license    https://www.popphp.org/license     New BSD License
25 * @version    4.1.0
26 */
27class Xml
28{
29
30    /**
31     * Create an XML language file from an array of data.
32     * The format of the parameters should be as follows:
33     *
34     * $lang = [
35     *     'src'    => 'en',
36     *     'output' => 'de',
37     *     'name'   => 'German',
38     *     'native' => 'Deutsch'
39     * ];
40     *
41     * $locales = [
42     *     [
43     *         'region' => 'DE',
44     *         'name'   => 'Germany',
45     *         'native' => 'Deutschland',
46     *         'text' => [
47     *             [
48     *                 'source' => 'This field is required.',
49     *                 'output' => 'Dieses Feld ist erforderlich.'
50     *             ], ...
51     *         ]
52     *     ], ...
53     * ];
54     *
55     * @param  array  $lang
56     * @param  array  $locales
57     * @param  string $file
58     * @throws Exception
59     * @return void
60     */
61    public static function createFile(array $lang, array $locales, string $file): void
62    {
63        // Validate the $lang parameter
64        if (!isset($lang['src']) || !isset($lang['output'])) {
65            throw new Exception("Error: The language parameter must have at least the 'src' and 'output' keys defined.");
66        }
67
68        // Validate the $locales parameter
69        foreach ($locales as $locale) {
70            if (!isset($locale['region'])) {
71                throw new Exception("Error: The locales parameter must have at least the 'region' key defined in each locale.");
72            }
73            if (!isset($locale['text'])) {
74                throw new Exception("Error: The locales parameter must have at least the 'text' key defined in each locale.");
75            }
76            if (!is_array($locale['text'])) {
77                throw new Exception("Error: The parameter key 'text' in each locale must be an array.");
78            }
79        }
80
81        $xmlHeader = file_get_contents(__DIR__ . '/../Data/__.xml');
82        $xmlHeader = substr($xmlHeader, 0, (strpos($xmlHeader, 'native="">') + 10));
83        $xmlHeader = str_replace(
84            ['src=""', 'output=""'],
85            ['src="' . self::escape($lang['src']) . '"', 'output="' . self::escape($lang['output']) . '"'],
86            $xmlHeader
87        );
88
89        if (isset($lang['name'])) {
90            $xmlHeader = str_replace('name=""', 'name="' . self::escape($lang['name']) . '"', $xmlHeader);
91        }
92
93        if (isset($lang['native'])) {
94            $xmlHeader = str_replace('native=""', 'native="' . self::escape($lang['native']) . '"', $xmlHeader);
95        }
96
97        // Format the Locales
98        $xmlLocales = null;
99
100        foreach ($locales as $locale) {
101            $name = (isset($locale['name'])) ? self::escape($locale['name']) : null;
102            $native = (isset($locale['native'])) ? self::escape($locale['native']) : null;
103            $xmlLocales .= '    <locale region="' . self::escape($locale['region']) . '" name="' . $name . '" native="' . $native . '">' . PHP_EOL;
104            foreach ($locale['text'] as $text) {
105                if (!isset($text['source']) || !isset($text['output'])) {
106                    throw new Exception("Error: The 'source' and 'output' keys must be defined in each 'text' array.");
107                }
108                $xmlLocales .= '        <text>' . PHP_EOL;
109                $xmlLocales .= '            <source>' . self::escape($text['source']) . '</source>' . PHP_EOL;
110                if (is_array($text['output'])) {
111                    $xmlLocales .= '            <output>' . PHP_EOL;
112                    foreach ($text['output'] as $alt => $output) {
113                        $altAttrib   = (!is_numeric($alt)) ? ' alt="' . self::escape($alt) . '"' : null;
114                        $xmlLocales .= '                <output' . $altAttrib . '>' . self::escape($output) . '</output>' . PHP_EOL;
115                    }
116                    $xmlLocales .= '            </output>' . PHP_EOL;
117                } else {
118                    $xmlLocales .= '            <output>' . self::escape($text['output']) . '</output>' . PHP_EOL;
119                }
120                $xmlLocales .= '        </text>' . PHP_EOL;
121            }
122            $xmlLocales .= '    </locale>' . PHP_EOL;
123        }
124
125        // Save XML file
126        file_put_contents($file, $xmlHeader . PHP_EOL . $xmlLocales . '</language>');
127    }
128
129    /**
130     * Create an language file fragment from a source file and an output file,
131     * each entry separated by a new line
132     *
133     * @param  string  $source
134     * @param  string  $output
135     * @param  ?string $dir
136     * @throws Exception
137     * @return void
138     */
139    public static function createFragment(string $source, string $output, ?string $dir = null): void
140    {
141        if (!file_exists($source)) {
142            throw new Exception('Error: The source file does not exist.');
143        }
144        if (!file_exists($output)) {
145            throw new Exception('Error: The output file does not exist.');
146        }
147
148        $sourceLines = explode(PHP_EOL, file_get_contents($source));
149        $outputLines = explode(PHP_EOL, file_get_contents($output));
150
151        $targetDir = ($dir !== null) ? $dir : dirname($output);
152
153        if (!file_exists($targetDir)) {
154            throw new Exception('Error: The target directory does not exist.');
155        }
156
157        if (str_contains($output, '/')) {
158            $lang = substr($output, (strrpos($output, '/') + 1));
159            $lang = substr($lang, 0, strpos($lang, '.'));
160        } else if (str_contains($output, "\\")) {
161            $lang = substr($output, (strrpos($output, "\\") + 1));
162            $lang = substr($lang, 0, strpos($lang, '.'));
163        } else {
164            $lang = substr($output, 0, strpos($output, '.'));
165        }
166
167        $xml  = null;
168
169        foreach ($outputLines as $key => $value) {
170            if (!empty($value) && !empty($sourceLines[$key])) {
171                $xml .= '        <text>' . PHP_EOL . '            <source>' . self::escape($sourceLines[$key]) . '</source>' . PHP_EOL .
172                    '            <output>' . self::escape($value) . '</output>' . PHP_EOL .
173                    '        </text>' . PHP_EOL;
174            }
175        }
176
177        file_put_contents($targetDir . DIRECTORY_SEPARATOR . $lang . '.xml', $xml);
178    }
179
180    /**
181     * Escape a value for safe use as XML element content or attribute value
182     *
183     * @param  string $value
184     * @return string
185     */
186    protected static function escape(string $value): string
187    {
188        return htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, 'UTF-8');
189    }
190
191}