[BUGFIX] Properly handle translation file detection
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Localization / Parser / LocallangXmlParser.php
1 <?php
2 namespace TYPO3\CMS\Core\Localization\Parser;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Localization\Exception\InvalidXmlFileException;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Parser for XML locallang file.
22 *
23 * @author Dominique Feyer <dfeyer@reelpeek.net>
24 */
25 class LocallangXmlParser extends AbstractXmlParser {
26
27 /**
28 * Associative array of "filename => parsed data" pairs.
29 *
30 * @var array
31 */
32 protected $parsedTargetFiles;
33
34 /**
35 * Returns parsed representation of XML file.
36 *
37 * @param string $sourcePath Source file path
38 * @param string $languageKey Language key
39 * @param string $charset Charset
40 * @return array
41 */
42 public function getParsedData($sourcePath, $languageKey, $charset = '') {
43 $this->sourcePath = $sourcePath;
44 $this->languageKey = $languageKey;
45 $this->charset = $this->getCharset($languageKey, $charset);
46 // Parse source
47 $parsedSource = $this->parseXmlFile();
48 // Parse target
49 $targetPath = $this->sourcePath;
50 if ($this->languageKey !== 'default') {
51 $localizedTargetPath = GeneralUtility::getFileAbsFileName(GeneralUtility::llXmlAutoFileName($this->sourcePath, $this->languageKey));
52 if (!@is_file($localizedTargetPath)) {
53 // Global localization is not available, try split localization file
54 $localizedTargetPath = GeneralUtility::getFileAbsFileName(GeneralUtility::llXmlAutoFileName($this->sourcePath, $this->languageKey, TRUE));
55 }
56 if (!@is_file($localizedTargetPath)) {
57 $localizedTargetPath = $this->sourcePath;
58 }
59 $targetPath = $localizedTargetPath;
60 }
61 try {
62 $parsedTarget = $this->getParsedTargetData($targetPath);
63 } catch (InvalidXmlFileException $e) {
64 $parsedTarget = $this->getParsedTargetData($this->sourcePath);
65 }
66 $LOCAL_LANG = array();
67 $LOCAL_LANG[$languageKey] = $parsedSource;
68 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($LOCAL_LANG[$languageKey], $parsedTarget);
69 return $LOCAL_LANG;
70 }
71
72 /**
73 * Returns array representation of XLIFF data, starting from a root node.
74 *
75 * @param \SimpleXMLElement $root XML root element
76 * @param string $element Target or Source
77 * @return array
78 */
79 protected function doParsingFromRootForElement(\SimpleXMLElement $root, $element) {
80 $bodyOfFileTag = $root->data->languageKey;
81 // Check if the source llxml file contains localized records
82 $localizedBodyOfFileTag = $root->data->xpath('languageKey[@index=\'' . $this->languageKey . '\']');
83 if ($element === 'source' || $this->languageKey === 'default') {
84 $parsedData = $this->getParsedDataForElement($bodyOfFileTag, $element);
85 } else {
86 $parsedData = array();
87 }
88 if ($element === 'target' && isset($localizedBodyOfFileTag[0]) && $localizedBodyOfFileTag[0] instanceof \SimpleXMLElement) {
89 $parsedDataTarget = $this->getParsedDataForElement($localizedBodyOfFileTag[0], $element);
90 $mergedData = $parsedDataTarget + $parsedData;
91 if ($this->languageKey === 'default') {
92 $parsedData = array_intersect_key($mergedData, $parsedData, $parsedDataTarget);
93 } else {
94 $parsedData = array_intersect_key($mergedData, $parsedDataTarget);
95 }
96 }
97 return $parsedData;
98 }
99
100 /**
101 * Parse the given language key tag
102 *
103 * @param \SimpleXMLElement $bodyOfFileTag
104 * @param string $element
105 * @return array
106 */
107 protected function getParsedDataForElement(\SimpleXMLElement $bodyOfFileTag, $element) {
108 $parsedData = array();
109 $children = $bodyOfFileTag->children();
110 if ($children->count() == 0) {
111 // Check for externally-referenced resource:
112 // <languageKey index="fr">EXT:yourext/path/to/localized/locallang.xml</languageKey>
113 $reference = sprintf('%s', $bodyOfFileTag);
114 if (substr($reference, -4) === '.xml') {
115 return $this->getParsedTargetData(GeneralUtility::getFileAbsFileName($reference));
116 }
117 }
118 /** @var \SimpleXMLElement $translationElement */
119 foreach ($children as $translationElement) {
120 if ($translationElement->getName() === 'label') {
121 // If restype would be set, it could be metadata from Gettext to XLIFF conversion (and we don't need this data)
122 $parsedData[(string)$translationElement['index']][0] = array(
123 $element => (string)$translationElement
124 );
125 }
126 }
127 return $parsedData;
128 }
129
130 /**
131 * Returns array representation of XLIFF data, starting from a root node.
132 *
133 * @param \SimpleXMLElement $root A root node
134 * @return array An array representing parsed XLIFF
135 */
136 protected function doParsingFromRoot(\SimpleXMLElement $root) {
137 return $this->doParsingFromRootForElement($root, 'source');
138 }
139
140 /**
141 * Returns array representation of XLIFF data, starting from a root node.
142 *
143 * @param \SimpleXMLElement $root A root node
144 * @return array An array representing parsed XLIFF
145 */
146 protected function doParsingTargetFromRoot(\SimpleXMLElement $root) {
147 return $this->doParsingFromRootForElement($root, 'target');
148 }
149
150 /**
151 * Returns parsed representation of XML file.
152 *
153 * Parses XML if it wasn't done before. Caches parsed data.
154 *
155 * @param string $path An absolute path to XML file
156 * @return array Parsed XML file
157 */
158 public function getParsedTargetData($path) {
159 if (!isset($this->parsedTargetFiles[$path])) {
160 $this->parsedTargetFiles[$path] = $this->parseXmlTargetFile($path);
161 }
162 return $this->parsedTargetFiles[$path];
163 }
164
165 /**
166 * Reads and parses XML file and returns internal representation of data.
167 *
168 * @param string $targetPath Path of the target file
169 * @return array
170 * @throws InvalidXmlFileException
171 */
172 protected function parseXmlTargetFile($targetPath) {
173 $rootXmlNode = FALSE;
174 if (file_exists($targetPath)) {
175 $rootXmlNode = simplexml_load_file($targetPath, 'SimpleXmlElement', \LIBXML_NOWARNING);
176 }
177 if (!isset($rootXmlNode) || $rootXmlNode === FALSE) {
178 throw new InvalidXmlFileException('The path provided does not point to existing and accessible well-formed XML file (' . $targetPath . ').', 1278155987);
179 }
180 return $this->doParsingTargetFromRoot($rootXmlNode);
181 }
182
183 }