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