Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
AndroidXmlFFS.php
Go to the documentation of this file.
1<?php
11
17class AndroidXmlFFS extends SimpleFFS {
19 private $flattener;
20
21 public function __construct( FileBasedMessageGroup $group ) {
22 parent::__construct( $group );
23 $this->flattener = $this->getFlattener();
24 }
25
26 public function supportsFuzzy() {
27 return 'yes';
28 }
29
30 public function getFileExtensions() {
31 return [ '.xml' ];
32 }
33
38 public function readFromVariable( $data ) {
39 $reader = new SimpleXMLElement( $data );
40
41 $messages = [];
42 $mangler = $this->group->getMangler();
43
44 $regexBacktrackLimit = ini_get( 'pcre.backtrack_limit' );
45 ini_set( 'pcre.backtrack_limit', 10 );
46
48 foreach ( $reader as $element ) {
49 $key = (string)$element['name'];
50
51 if ( $element->getName() === 'string' ) {
52 $value = $this->readElementContents( $element );
53 } elseif ( $element->getName() === 'plurals' ) {
54 $forms = [];
55 foreach ( $element as $item ) {
56 $forms[(string)$item['quantity']] = $this->readElementContents( $item );
57 }
58 $value = $this->flattener->flattenCLDRPlurals( $forms );
59 } else {
60 wfDebug( __METHOD__ . ': Unknown XML element name.' );
61 continue;
62 }
63
64 if ( isset( $element['fuzzy'] ) && (string)$element['fuzzy'] === 'true' ) {
65 $value = TRANSLATE_FUZZY . $value;
66 }
67
68 $messages[$key] = $value;
69 }
70
71 ini_set( 'pcre.backtrack_limit', $regexBacktrackLimit );
72
73 return [
74 'AUTHORS' => $this->scrapeAuthors( $data ),
75 'MESSAGES' => $mangler->mangleArray( $messages ),
76 ];
77 }
78
79 protected function scrapeAuthors( $string ) {
80 if ( !preg_match( '~<!-- Authors:\n((?:\* .*\n)*)-->~', $string, $match ) ) {
81 return [];
82 }
83
84 $authors = $matches = [];
85 preg_match_all( '~\* (.*)~', $match[ 1 ], $matches );
86 foreach ( $matches[1] as $author ) {
87 // PHP7: \u{2011}
88 $authors[] = str_replace( "\xE2\x80\x91\xE2\x80\x91", '--', $author );
89 }
90 return $authors;
91 }
92
93 protected function readElementContents( $element ): string {
94 $elementStr = (string)$element;
95
96 // Convert string of format \uNNNN (eg: \u1234) to symbols
97 $converted = preg_replace_callback(
98 '/(?<!\\\\)(?:\\\\{2})*+\\K\\\\u([0-9A-Fa-f]{4,6})+/',
99 static function ( array $matches ) {
100 return IntlChar::chr( hexdec( $matches[1] ) );
101 },
102 $elementStr
103 );
104
105 return stripcslashes( $converted );
106 }
107
108 protected function formatElementContents( $contents ) {
109 // Kudos to the brilliant person who invented this braindead file format
110 $escaped = addcslashes( $contents, '"\'\\' );
111 if ( substr( $escaped, 0, 1 ) === '@' ) {
112 // '@' at beginning of string refers to another string by name.
113 // Add backslash to escape it too.
114 $escaped = '\\' . $escaped;
115 }
116 // All html entities seen would be inserted by translators themselves.
117 // Treat them as plain text.
118 $escaped = str_replace( '&', '&amp;', $escaped );
119
120 // Newlines must be escaped
121 $escaped = str_replace( "\n", '\n', $escaped );
122 return $escaped;
123 }
124
125 protected function doAuthors( MessageCollection $collection ) {
126 $authors = $collection->getAuthors();
127 $authors = $this->filterAuthors( $authors, $collection->code );
128
129 if ( !$authors ) {
130 return '';
131 }
132
133 $output = "\n<!-- Authors:\n";
134
135 foreach ( $authors as $author ) {
136 // Since -- is not allowed in XML comments, we rewrite them to
137 // U+2011 (non-breaking hyphen). PHP7: \u{2011}
138 $author = str_replace( '--', "\xE2\x80\x91\xE2\x80\x91", $author );
139 $output .= "* $author\n";
140 }
141
142 $output .= "-->\n";
143
144 return $output;
145 }
146
147 protected function writeReal( MessageCollection $collection ) {
148 global $wgTranslateDocumentationLanguageCode;
149
150 $collection->filter( 'hastranslation', false );
151 if ( count( $collection ) === 0 ) {
152 return '';
153 }
154
155 $template = '<?xml version="1.0" encoding="utf-8"?>';
156 $template .= $this->doAuthors( $collection );
157 $template .= '<resources></resources>';
158
159 $writer = new SimpleXMLElement( $template );
160
161 if ( $collection->getLanguage() === $wgTranslateDocumentationLanguageCode ) {
162 $writer->addAttribute(
163 'tools:ignore',
164 'all',
165 'http://schemas.android.com/tools'
166 );
167 }
168
169 $mangler = $this->group->getMangler();
171 foreach ( $collection as $key => $m ) {
172 $key = $mangler->unmangle( $key );
173
174 $value = $m->translation();
175 $value = str_replace( TRANSLATE_FUZZY, '', $value );
176
177 $plurals = $this->flattener->unflattenCLDRPlurals( '', $value );
178
179 if ( $plurals === false ) {
180 $element = $writer->addChild( 'string', $this->formatElementContents( $value ) );
181 } else {
182 $element = $writer->addChild( 'plurals' );
183 foreach ( $plurals as $quantity => $content ) {
184 $item = $element->addChild( 'item', $this->formatElementContents( $content ) );
185 $item->addAttribute( 'quantity', $quantity );
186 }
187 }
188
189 $element->addAttribute( 'name', $key );
190 // This is non-standard
191 if ( $m->hasTag( 'fuzzy' ) ) {
192 $element->addAttribute( 'fuzzy', 'true' );
193 }
194 }
195
196 // Make the output pretty with DOMDocument
197 $dom = new DOMDocument( '1.0' );
198 $dom->formatOutput = true;
199 $dom->loadXML( $writer->asXML() );
200
201 return $dom->saveXML();
202 }
203
204 protected function getFlattener() {
205 $flattener = new ArrayFlattener( '', true );
206 return $flattener;
207 }
208
209 public function isContentEqual( $a, $b ) {
210 return $this->flattener->compareContent( $a, $b );
211 }
212}
Support for XML translation format used by Android.
readFromVariable( $data)
supportsFuzzy()
Query the capabilities of this FFS.
writeReal(MessageCollection $collection)
isContentEqual( $a, $b)
Checks whether two strings are equal.
getFileExtensions()
Return the commonly used file extensions for these formats.
This class implements default behavior for file based message groups.
Flattens message arrays for further processing.
Core message collection class.
getAuthors()
Lists all translators that have contributed to the latest revisions of each translation.
filter( $type, $condition=true, $value=null)
Filters messages based on some condition.