Completed
Push — master ( 47c6f0...fa1990 )
by Tobias
04:27
created

XliffLoader::utf8ToCharset()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3.1406

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 3
cts 4
cp 0.75
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 2
crap 3.1406
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
Bug introduced by
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)));
0 ignored issues
show
Bug introduced by
Since getXmlErrors() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getXmlErrors() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
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)
0 ignored issues
show
Bug introduced by
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...
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
Bug introduced by
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