Completed
Push — master ( 75ac31...11edf4 )
by Tobias
08:15 queued 06:58
created

src/Loader/XliffLoader.php (2 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the PHP Translation package.
5
 *
6
 * (c) PHP Translation team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Translation\SymfonyStorage\Loader;
13
14
use Nyholm\NSA;
15
use Symfony\Component\Translation\Loader\XliffFileLoader;
16
use Symfony\Component\Translation\MessageCatalogue;
17
use Symfony\Component\Translation\Exception\InvalidResourceException;
18
19
/**
20
 * This class is an ugly hack to allow loading Xliff from string content.
21
 *
22
 * @author Tobias Nyholm <[email protected]>
23
 */
24
class XliffLoader extends XliffFileLoader
25
{
26
    /**
27
     * @param string           $content   xml content
28
     * @param MessageCatalogue $catalogue
29
     * @param string           $domain
30
     */
31 6
    public function extractFromContent($content, MessageCatalogue $catalogue, $domain)
32
    {
33
        try {
34 6
            $dom = $this->loadFileContent($content);
35 6
        } catch (\InvalidArgumentException $e) {
36 2
            throw new InvalidResourceException(sprintf('Unable to load data: %s', $e->getMessage()), $e->getCode(), $e);
37
        }
38
39 4
        if (!method_exists($this, 'getVersionNumber')) {
40
            // Symfony 2.7
41
            throw new \RuntimeException('Cannot use XliffLoader::extractFromContent with Symfony 2.7');
42
        }
43
44 4
        $xliffVersion = NSA::invokeMethod($this, 'getVersionNumber', $dom);
45 4
        NSA::invokeMethod($this, 'validateSchema', $xliffVersion, $dom, NSA::invokeMethod($this, 'getSchema', $xliffVersion));
46
47 4
        if ('1.2' === $xliffVersion) {
48 2
            NSA::invokeMethod($this, 'extractXliff1', $dom, $catalogue, $domain);
49 2
        }
50
51 4
        if ('2.0' === $xliffVersion) {
52 2
            $this->extractXliff2($dom, $catalogue, $domain);
53 2
        }
54 4
    }
55
56
    /**
57
     * @param \DOMDocument     $dom
58
     * @param MessageCatalogue $catalogue
59
     * @param string           $domain
60
     */
61 3
    private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, $domain)
0 ignored issues
show
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
62
    {
63 2
        $xml = simplexml_import_dom($dom);
64 2
        $encoding = strtoupper($dom->encoding);
65
66 2
        $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0');
67
68 2
        foreach ($xml->xpath('//xliff:unit') as $unit) {
69 2
            $segment = $unit->segment;
70 3
            $source = $segment->source;
71
72
            // If the xlf file has another encoding specified, try to convert it because
73
            // simple_xml will always return utf-8 encoded values
74 2
            $target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding);
75
76 2
            $catalogue->set((string) $source, $target, $domain);
77
78 2
            $metadata = [];
79 2
            if (isset($segment->target) && $segment->target->attributes()) {
80
                $metadata['target-attributes'] = [];
81
                foreach ($segment->target->attributes() as $key => $value) {
82
                    $metadata['target-attributes'][$key] = (string) $value;
83
                }
84
            }
85
86 2
            if (isset($unit->notes)) {
87 1
                $metadata['notes'] = [];
88 1
                foreach ($unit->notes->note as $noteNode) {
89 1
                    $note = [];
90 1
                    foreach ($noteNode->attributes() as $key => $value) {
91 1
                        $note[$key] = (string) $value;
92 1
                    }
93 1
                    $note['content'] = (string) $noteNode;
94 1
                    $metadata['notes'][] = $note;
95 1
                }
96 1
            }
97
98 2
            $catalogue->setMetadata((string) $source, $metadata, $domain);
99 2
        }
100 2
    }
101
102
    /**
103
     * Loads an XML file.
104
     *
105
     * Taken and modified from Symfony\Component\Config\Util\XmlUtils
106
     *
107
     * @author Fabien Potencier <[email protected]>
108
     * @author Martin Hasoň <[email protected]>
109
     *
110
     * @param string $content An XML file path
111
     *
112
     * @return \DOMDocument
113
     *
114
     * @throws \InvalidArgumentException When loading of XML file returns error
115
     */
116 6
    private function loadFileContent($content)
117
    {
118 6
        if ('' === trim($content)) {
119 1
            throw new \InvalidArgumentException('Content does not contain valid XML, it is empty.');
120
        }
121
122 5
        $internalErrors = libxml_use_internal_errors(true);
123 5
        $disableEntities = libxml_disable_entity_loader(true);
124 5
        libxml_clear_errors();
125
126 5
        $dom = new \DOMDocument();
127 5
        $dom->validateOnParse = true;
128 5
        if (!$dom->loadXML($content, LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
129 1
            libxml_disable_entity_loader($disableEntities);
130
131 1
            throw new \InvalidArgumentException(implode("\n", static::getXmlErrors($internalErrors)));
132
        }
133
134 4
        $dom->normalizeDocument();
135
136 4
        libxml_use_internal_errors($internalErrors);
137 4
        libxml_disable_entity_loader($disableEntities);
138
139 4
        foreach ($dom->childNodes as $child) {
140 4
            if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
141
                throw new \InvalidArgumentException('Document types are not allowed.');
142
            }
143 4
        }
144
145 4
        libxml_clear_errors();
146 4
        libxml_use_internal_errors($internalErrors);
147
148 4
        return $dom;
149
    }
150
151 1
    private function getXmlErrors($internalErrors)
152
    {
153 1
        $errors = [];
154 1
        foreach (libxml_get_errors() as $error) {
155 1
            $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
156 1
                LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
157 1
                $error->code,
158 1
                trim($error->message),
159 1
                $error->file ?: 'n/a',
160 1
                $error->line,
161 1
                $error->column
162 1
            );
163 1
        }
164
165 1
        libxml_clear_errors();
166 1
        libxml_use_internal_errors($internalErrors);
167
168 1
        return $errors;
169
    }
170
171
    /**
172
     * Convert a UTF8 string to the specified encoding.
173
     *
174
     * @param string $content  String to decode
175
     * @param string $encoding Target encoding
176
     *
177
     * @return string
178
     */
179 2
    private function utf8ToCharset($content, $encoding = null)
0 ignored issues
show
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
180
    {
181 2
        if ('UTF-8' !== $encoding && !empty($encoding)) {
182
            return mb_convert_encoding($content, $encoding, 'UTF-8');
183
        }
184
185 2
        return $content;
186
    }
187
}
188