FormatService::getDirectory()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Decline\TransformatBundle\Services;
4
5
use Decline\TransformatBundle\Exception\DuplicateKeyException;
6
use Decline\TransformatBundle\Exception\InvalidSchemaException;
7
use Decline\TransformatBundle\Exception\NoTransUnitsFoundException;
8
use Exception;
9
use SimpleXMLElement;
10
use Symfony\Component\Console\Style\SymfonyStyle;
11
use Symfony\Component\HttpFoundation\File\Exception\FileException;
12
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
13
use Symfony\Component\HttpFoundation\File\File;
14
15
/**
16
 * Class FormatService
17
 * @package Decline\TransformatBundle\Services
18
 */
19
class FormatService
20
{
21
22
    /**
23
     * Bundle configuration
24
     *
25
     * @var array
26
     */
27
    private $config;
28
29
    /**
30
     * @var \Twig_Environment
31
     */
32
    private $twig;
33
34
    /**
35
     * @var ValidatorService
36
     */
37
    private $validator;
38
39
    /**
40
     * FormatService constructor.
41
     *
42
     * @param                   $config
43
     * @param \Twig_Environment $twig
44
     * @param ValidatorService  $validator
45
     */
46 3
    public function __construct($config, \Twig_Environment $twig, ValidatorService $validator)
47
    {
48 3
        $this->config = $config;
49 3
        $this->twig = $twig;
50 3
        $this->validator = $validator;
51 3
    }
52
53
    /**
54
     * Formats the configured set of translation files
55
     *
56
     * @param SymfonyStyle $io
57
     * @param string|null  $fileName Name of single file to format
58
     *
59
     * @return array List of Errors
60
     */
61 6
    public function format(SymfonyStyle $io = null, $fileName = null)
62
    {
63 6
        $files = $this->getPreparedFileset($fileName);
64
65
        // check if directory contains files
66 6
        if (empty($files)) {
67
            return [
68 2
                sprintf(
69 2
                    'No supported files could be found in the configured directory %s!',
70 2
                    $this->getDirectory()
71
                ),
72
            ];
73
        }
74
75 4
        $errors = [];
76 4
        foreach ($files as $file) {
77
            try {
78 4
                $this->formatSingleFile($file);
79 2
                $msg = sprintf('<info>Success:</info> %s', $file->getFilename());
80 2
            } catch (Exception $e) {
81 2
                $errors[] = $file->getFilename() . ': ' . $e->getMessage();
82 2
                $msg = sprintf('<fg=red>Failure:</fg=red> %s', $file->getFilename());
83
            }
84
85
            // write to formatted output
86 4
            if ($io) {
87 4
                $io->text($msg);
88
            }
89
        }
90
91 4
        return $errors;
92
    }
93
94
    /**
95
     * Create a set of files on which the formatting should be performed
96
     *
97
     * @param string|null $fileName
98
     *
99
     * @return File[]
100
     */
101 6
    private function getPreparedFileset($fileName = null)
102
    {
103 6
        $filesToCheck = $fileName ? [$fileName] : scandir($this->getDirectory());
104 6
        $preparedFileset = [];
105 6
        foreach ($filesToCheck as $fileToCheck) {
106 6
            if (in_array($fileToCheck, ['.', '..'], true)) {
107 1
                continue;
108
            }
109
110
            try {
111 6
                $file = new File($this->getDirectory() . '/' . $fileToCheck);
112 1
            } catch (FileNotFoundException $e) {
113 1
                continue;
114
            }
115
116
            // ignore files with unsupported extension
117 5
            if ($file->getExtension() !== $this->config['xliff']['extension']) {
118 2
                continue;
119
            }
120
121 4
            $preparedFileset[] = $file;
122
        }
123
124 6
        return $preparedFileset;
125
    }
126
127
    /**
128
     * Formats a single translation file and updates its content
129
     *
130
     * @param File $file
131
     *
132
     * @throws DuplicateKeyException
133
     * @throws \Symfony\Component\HttpFoundation\File\Exception\FileException
134
     * @throws \Twig_Error_Loader
135
     * @throws \Twig_Error_Runtime
136
     * @throws \Twig_Error_Syntax
137
     * @throws \Decline\TransformatBundle\Exception\NoTransUnitsFoundException
138
     * @throws \Symfony\Component\Filesystem\Exception\FileNotFoundException
139
     * @throws \Decline\TransformatBundle\Exception\InvalidSchemaException
140
     */
141 4
    private function formatSingleFile(File $file)
142
    {
143
144 4
        if (!$file->isReadable()) {
145
            throw new FileException('File %s is not readable!', $file->getFilename());
146
        }
147
148 4
        if (!$file->isWritable()) {
149
            throw new FileException('File %s is not writable!', $file->getFilename());
150
        }
151
152
        // render template
153
        $twigContext = [
154 4
            'namespace'      => $this->getXliffNamespace(),
155 4
            'sourceLanguage' => $this->getXliffSourceLanguage(),
156 4
            'transUnits'     => $this->getTransUnits($file),
157
        ];
158 2
        $formattedOutput = $this->twig->render('@DeclineTransformat/format.xml.twig', $twigContext);
159
160
        // update file with formatted content
161 2
        file_put_contents($file->getPathname(), $formattedOutput);
162 2
    }
163
164
    /**
165
     * Validates the given file and returns the sorted trans-units
166
     *
167
     * @param File $file
168
     *
169
     * @return array
170
     * @throws DuplicateKeyException
171
     * @throws InvalidSchemaException
172
     * @throws NoTransUnitsFoundException
173
     */
174 4
    private function getTransUnits(File $file)
175
    {
176
        // get content of xliff file
177 4
        $xliffContent = file_get_contents($file->getPathname());
178
179
        // validate xliff against schema
180 4
        $validationErrors = $this->validator->validate($xliffContent);
181 4
        if (count($validationErrors)) {
182
            throw new InvalidSchemaException($validationErrors[0]->message, $file->getFilename());
183
        }
184
185 4
        $xml = new SimpleXMLElement($xliffContent);
186 4
        $xml->registerXPathNamespace('x', $this->getXliffNamespace());
187 4
        $transUnits = array();
188 4
        foreach ($xml->xpath('//x:file/x:body/x:trans-unit') as $translation) {
189
            /** @var SimpleXMLElement $translation */
190 3
            $source = str_replace('&', '&amp;', (string) $translation->source);
191 3
            $target = str_replace('&', '&amp;', (string) $translation->target);
192
193 3
            if (array_key_exists($source, $transUnits)) {
194 1
                throw new DuplicateKeyException($source, $file->getFilename());
195
            }
196
197 3
            $transUnits[$source] = [
198 3
                'id'     => $source,
199 3
                'source' => $source,
200 3
                'target' => $target,
201
            ];
202
        }
203
204 3
        if (empty($transUnits)) {
205 1
            throw new NoTransUnitsFoundException($file->getFilename());
206
        }
207
208
        // we need to use case-senstivie sorting, or else it will screw up when merging etc.
209 2
        ksort($transUnits);
210
211 2
        return $transUnits;
212
    }
213
214
    /**
215
     * The configured directory of the translation files
216
     *
217
     * @return mixed
218
     */
219 6
    private function getDirectory()
220
    {
221 6
        return $this->config['directory'];
222
    }
223
224
    /**
225
     * The configured source language of the xliff files
226
     *
227
     * @return mixed
228
     */
229 4
    private function getXliffSourceLanguage()
230
    {
231 4
        return $this->config['xliff']['sourceLanguage'];
232
    }
233
234
    /**
235
     * The configured namespace of the xliff files
236
     *
237
     * @return mixed
238
     */
239 4
    private function getXliffNamespace()
240
    {
241 4
        return $this->config['xliff']['namespace'];
242
    }
243
}
244