ConverterRule::getRulesDesc()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 6
nop 0
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * This program is free software; you can redistribute it and/or modify
4
 * it under the terms of the GNU General Public License as published by
5
 * the Free Software Foundation; either version 2 of the License, or
6
 * (at your option) any later version.
7
 *
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
 * GNU General Public License for more details.
12
 *
13
 * You should have received a copy of the GNU General Public License along
14
 * with this program; if not, write to the Free Software Foundation, Inc.,
15
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
 * http://www.gnu.org/copyleft/gpl.html
17
 *
18
 * @file
19
 * @ingroup Language
20
 */
21
22
/**
23
 * Parser for rules of language conversion , parse rules in -{ }- tag.
24
 * @ingroup Language
25
 * @author fdcn <[email protected]>, PhiLiP <[email protected]>
26
 */
27
class ConverterRule {
28
	public $mText; // original text in -{text}-
29
	public $mConverter; // LanguageConverter object
30
	public $mRuleDisplay = '';
31
	public $mRuleTitle = false;
32
	public $mRules = '';// string : the text of the rules
33
	public $mRulesAction = 'none';
34
	public $mFlags = [];
35
	public $mVariantFlags = [];
36
	public $mConvTable = [];
37
	public $mBidtable = [];// array of the translation in each variant
38
	public $mUnidtable = [];// array of the translation in each variant
39
40
	/**
41
	 * Constructor
42
	 *
43
	 * @param string $text The text between -{ and }-
44
	 * @param LanguageConverter $converter
45
	 */
46
	public function __construct( $text, $converter ) {
47
		$this->mText = $text;
48
		$this->mConverter = $converter;
49
	}
50
51
	/**
52
	 * Check if variants array in convert array.
53
	 *
54
	 * @param array|string $variants Variant language code
55
	 * @return string Translated text
56
	 */
57
	public function getTextInBidtable( $variants ) {
58
		$variants = (array)$variants;
59
		if ( !$variants ) {
60
			return false;
61
		}
62
		foreach ( $variants as $variant ) {
63
			if ( isset( $this->mBidtable[$variant] ) ) {
64
				return $this->mBidtable[$variant];
65
			}
66
		}
67
		return false;
68
	}
69
70
	/**
71
	 * Parse flags with syntax -{FLAG| ... }-
72
	 * @private
73
	 */
74
	function parseFlags() {
75
		$text = $this->mText;
76
		$flags = [];
77
		$variantFlags = [];
78
79
		$sepPos = strpos( $text, '|' );
80
		if ( $sepPos !== false ) {
81
			$validFlags = $this->mConverter->mFlags;
82
			$f = StringUtils::explode( ';', substr( $text, 0, $sepPos ) );
83
			foreach ( $f as $ff ) {
84
				$ff = trim( $ff );
85
				if ( isset( $validFlags[$ff] ) ) {
86
					$flags[$validFlags[$ff]] = true;
87
				}
88
			}
89
			$text = strval( substr( $text, $sepPos + 1 ) );
90
		}
91
92
		if ( !$flags ) {
93
			$flags['S'] = true;
94
		} elseif ( isset( $flags['R'] ) ) {
95
			$flags = [ 'R' => true ];// remove other flags
96
		} elseif ( isset( $flags['N'] ) ) {
97
			$flags = [ 'N' => true ];// remove other flags
98
		} elseif ( isset( $flags['-'] ) ) {
99
			$flags = [ '-' => true ];// remove other flags
100
		} elseif ( count( $flags ) == 1 && isset( $flags['T'] ) ) {
101
			$flags['H'] = true;
102
		} elseif ( isset( $flags['H'] ) ) {
103
			// replace A flag, and remove other flags except T
104
			$temp = [ '+' => true, 'H' => true ];
105
			if ( isset( $flags['T'] ) ) {
106
				$temp['T'] = true;
107
			}
108
			if ( isset( $flags['D'] ) ) {
109
				$temp['D'] = true;
110
			}
111
			$flags = $temp;
112
		} else {
113
			if ( isset( $flags['A'] ) ) {
114
				$flags['+'] = true;
115
				$flags['S'] = true;
116
			}
117
			if ( isset( $flags['D'] ) ) {
118
				unset( $flags['S'] );
119
			}
120
			// try to find flags like "zh-hans", "zh-hant"
121
			// allow syntaxes like "-{zh-hans;zh-hant|XXXX}-"
122
			$variantFlags = array_intersect( array_keys( $flags ), $this->mConverter->mVariants );
123
			if ( $variantFlags ) {
124
				$variantFlags = array_flip( $variantFlags );
125
				$flags = [];
126
			}
127
		}
128
		$this->mVariantFlags = $variantFlags;
129
		$this->mRules = $text;
130
		$this->mFlags = $flags;
131
	}
132
133
	/**
134
	 * Generate conversion table.
135
	 * @private
136
	 */
137
	function parseRules() {
138
		$rules = $this->mRules;
139
		$bidtable = [];
140
		$unidtable = [];
141
		$variants = $this->mConverter->mVariants;
142
		$varsep_pattern = $this->mConverter->getVarSeparatorPattern();
143
144
		// Split according to $varsep_pattern, but ignore semicolons from HTML entities
145
		$rules = preg_replace( '/(&[#a-zA-Z0-9]+);/', "$1\x01", $rules );
146
		$choice = preg_split( $varsep_pattern, $rules );
147
		$choice = str_replace( "\x01", ';', $choice );
148
149
		foreach ( $choice as $c ) {
150
			$v = explode( ':', $c, 2 );
151
			if ( count( $v ) != 2 ) {
152
				// syntax error, skip
153
				continue;
154
			}
155
			$to = trim( $v[1] );
156
			$v = trim( $v[0] );
157
			$u = explode( '=>', $v, 2 );
158
			// if $to is empty (which is also used as $from in bidtable),
159
			// strtr() could return a wrong result.
160
			if ( count( $u ) == 1 && $to !== '' && in_array( $v, $variants ) ) {
161
				$bidtable[$v] = $to;
162
			} elseif ( count( $u ) == 2 ) {
163
				$from = trim( $u[0] );
164
				$v = trim( $u[1] );
165
				// if $from is empty, strtr() could return a wrong result.
166
				if ( array_key_exists( $v, $unidtable )
167
					&& !is_array( $unidtable[$v] )
168
					&& $from !== ''
169
					&& in_array( $v, $variants ) ) {
170
					$unidtable[$v] = [ $from => $to ];
171
				} elseif ( $from !== '' && in_array( $v, $variants ) ) {
172
					$unidtable[$v][$from] = $to;
173
				}
174
			}
175
			// syntax error, pass
176
			if ( !isset( $this->mConverter->mVariantNames[$v] ) ) {
177
				$bidtable = [];
178
				$unidtable = [];
179
				break;
180
			}
181
		}
182
		$this->mBidtable = $bidtable;
183
		$this->mUnidtable = $unidtable;
184
	}
185
186
	/**
187
	 * @private
188
	 *
189
	 * @return string
190
	 */
191
	function getRulesDesc() {
192
		$codesep = $this->mConverter->mDescCodeSep;
193
		$varsep = $this->mConverter->mDescVarSep;
194
		$text = '';
195
		foreach ( $this->mBidtable as $k => $v ) {
196
			$text .= $this->mConverter->mVariantNames[$k] . "$codesep$v$varsep";
197
		}
198
		foreach ( $this->mUnidtable as $k => $a ) {
199
			foreach ( $a as $from => $to ) {
200
				$text .= $from . '⇒' . $this->mConverter->mVariantNames[$k] .
201
					"$codesep$to$varsep";
202
			}
203
		}
204
		return $text;
205
	}
206
207
	/**
208
	 * Parse rules conversion.
209
	 * @private
210
	 *
211
	 * @param string $variant
212
	 *
213
	 * @return string
214
	 */
215
	function getRuleConvertedStr( $variant ) {
216
		$bidtable = $this->mBidtable;
217
		$unidtable = $this->mUnidtable;
218
219
		if ( count( $bidtable ) + count( $unidtable ) == 0 ) {
220
			return $this->mRules;
221
		} else {
222
			// display current variant in bidirectional array
223
			$disp = $this->getTextInBidtable( $variant );
224
			// or display current variant in fallbacks
225
			if ( $disp === false ) {
226
				$disp = $this->getTextInBidtable(
227
					$this->mConverter->getVariantFallbacks( $variant ) );
228
			}
229
			// or display current variant in unidirectional array
230
			if ( $disp === false && array_key_exists( $variant, $unidtable ) ) {
231
				$disp = array_values( $unidtable[$variant] )[0];
232
			}
233
			// or display frist text under disable manual convert
234
			if ( $disp === false && $this->mConverter->mManualLevel[$variant] == 'disable' ) {
235
				if ( count( $bidtable ) > 0 ) {
236
					$disp = array_values( $bidtable )[0];
237
				} else {
238
					$disp = array_values( array_values( $unidtable )[0] )[0];
239
				}
240
			}
241
			return $disp;
242
		}
243
	}
244
245
	/**
246
	 * Similar to getRuleConvertedStr(), but this prefers to use original
247
	 * page title if $variant === $this->mConverter->mMainLanguageCode
248
	 * and may return false in this case (so this title conversion rule
249
	 * will be ignored and the original title is shown).
250
	 *
251
	 * @since 1.22
252
	 * @param string $variant The variant code to display page title in
253
	 * @return string|bool The converted title or false if just page name
254
	 */
255
	function getRuleConvertedTitle( $variant ) {
256
		if ( $variant === $this->mConverter->mMainLanguageCode ) {
257
			// If a string targeting exactly this variant is set,
258
			// use it. Otherwise, just return false, so the real
259
			// page name can be shown (and because variant === main,
260
			// there'll be no further automatic conversion).
261
			$disp = $this->getTextInBidtable( $variant );
262
			if ( $disp ) {
263
				return $disp;
264
			}
265
			if ( array_key_exists( $variant, $this->mUnidtable ) ) {
266
				$disp = array_values( $this->mUnidtable[$variant] )[0];
267
			}
268
			// Assigned above or still false.
269
			return $disp;
270
		} else {
271
			return $this->getRuleConvertedStr( $variant );
272
		}
273
	}
274
275
	/**
276
	 * Generate conversion table for all text.
277
	 * @private
278
	 */
279
	function generateConvTable() {
280
		// Special case optimisation
281
		if ( !$this->mBidtable && !$this->mUnidtable ) {
282
			$this->mConvTable = [];
283
			return;
284
		}
285
286
		$bidtable = $this->mBidtable;
287
		$unidtable = $this->mUnidtable;
288
		$manLevel = $this->mConverter->mManualLevel;
289
290
		$vmarked = [];
291
		foreach ( $this->mConverter->mVariants as $v ) {
292
			/* for bidirectional array
293
				fill in the missing variants, if any,
294
				with fallbacks */
295
			if ( !isset( $bidtable[$v] ) ) {
296
				$variantFallbacks =
297
					$this->mConverter->getVariantFallbacks( $v );
298
				$vf = $this->getTextInBidtable( $variantFallbacks );
299
				if ( $vf ) {
300
					$bidtable[$v] = $vf;
301
				}
302
			}
303
304
			if ( isset( $bidtable[$v] ) ) {
305
				foreach ( $vmarked as $vo ) {
306
					// use syntax: -{A|zh:WordZh;zh-tw:WordTw}-
307
					// or -{H|zh:WordZh;zh-tw:WordTw}-
308
					// or -{-|zh:WordZh;zh-tw:WordTw}-
309
					// to introduce a custom mapping between
310
					// words WordZh and WordTw in the whole text
311 View Code Duplication
					if ( $manLevel[$v] == 'bidirectional' ) {
312
						$this->mConvTable[$v][$bidtable[$vo]] = $bidtable[$v];
313
					}
314 View Code Duplication
					if ( $manLevel[$vo] == 'bidirectional' ) {
315
						$this->mConvTable[$vo][$bidtable[$v]] = $bidtable[$vo];
316
					}
317
				}
318
				$vmarked[] = $v;
319
			}
320
			/* for unidirectional array fill to convert tables */
321
			if ( ( $manLevel[$v] == 'bidirectional' || $manLevel[$v] == 'unidirectional' )
322
				&& isset( $unidtable[$v] )
323
			) {
324
				if ( isset( $this->mConvTable[$v] ) ) {
325
					$this->mConvTable[$v] = $unidtable[$v] + $this->mConvTable[$v];
326
				} else {
327
					$this->mConvTable[$v] = $unidtable[$v];
328
				}
329
			}
330
		}
331
	}
332
333
	/**
334
	 * Parse rules and flags.
335
	 * @param string $variant Variant language code
336
	 */
337
	public function parse( $variant = null ) {
338
		if ( !$variant ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $variant of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
339
			$variant = $this->mConverter->getPreferredVariant();
340
		}
341
342
		$this->parseFlags();
343
		$flags = $this->mFlags;
344
345
		// convert to specified variant
346
		// syntax: -{zh-hans;zh-hant[;...]|<text to convert>}-
347
		if ( $this->mVariantFlags ) {
348
			// check if current variant in flags
349
			if ( isset( $this->mVariantFlags[$variant] ) ) {
350
				// then convert <text to convert> to current language
351
				$this->mRules = $this->mConverter->autoConvert( $this->mRules,
352
					$variant );
353
			} else {
354
				// if current variant no in flags,
355
				// then we check its fallback variants.
356
				$variantFallbacks =
357
					$this->mConverter->getVariantFallbacks( $variant );
358
				if ( is_array( $variantFallbacks ) ) {
359
					foreach ( $variantFallbacks as $variantFallback ) {
360
						// if current variant's fallback exist in flags
361
						if ( isset( $this->mVariantFlags[$variantFallback] ) ) {
362
							// then convert <text to convert> to fallback language
363
							$this->mRules =
364
								$this->mConverter->autoConvert( $this->mRules,
365
									$variantFallback );
366
							break;
367
						}
368
					}
369
				}
370
			}
371
			$this->mFlags = $flags = [ 'R' => true ];
372
		}
373
374
		if ( !isset( $flags['R'] ) && !isset( $flags['N'] ) ) {
375
			// decode => HTML entities modified by Sanitizer::removeHTMLtags
376
			$this->mRules = str_replace( '=&gt;', '=>', $this->mRules );
377
			$this->parseRules();
378
		}
379
		$rules = $this->mRules;
380
381
		if ( !$this->mBidtable && !$this->mUnidtable ) {
382
			if ( isset( $flags['+'] ) || isset( $flags['-'] ) ) {
383
				// fill all variants if text in -{A/H/-|text}- is non-empty but without rules
384
				if ( $rules !== '' ) {
385
					foreach ( $this->mConverter->mVariants as $v ) {
386
						$this->mBidtable[$v] = $rules;
387
					}
388
				}
389
			} elseif ( !isset( $flags['N'] ) && !isset( $flags['T'] ) ) {
390
				$this->mFlags = $flags = [ 'R' => true ];
391
			}
392
		}
393
394
		$this->mRuleDisplay = false;
0 ignored issues
show
Documentation Bug introduced by
The property $mRuleDisplay was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
395
		foreach ( $flags as $flag => $unused ) {
396
			switch ( $flag ) {
397
				case 'R':
398
					// if we don't do content convert, still strip the -{}- tags
399
					$this->mRuleDisplay = $rules;
400
					break;
401
				case 'N':
402
					// process N flag: output current variant name
403
					$ruleVar = trim( $rules );
404
					if ( isset( $this->mConverter->mVariantNames[$ruleVar] ) ) {
405
						$this->mRuleDisplay = $this->mConverter->mVariantNames[$ruleVar];
406
					} else {
407
						$this->mRuleDisplay = '';
408
					}
409
					break;
410
				case 'D':
411
					// process D flag: output rules description
412
					$this->mRuleDisplay = $this->getRulesDesc();
413
					break;
414
				case 'H':
415
					// process H,- flag or T only: output nothing
416
					$this->mRuleDisplay = '';
417
					break;
418
				case '-':
419
					$this->mRulesAction = 'remove';
420
					$this->mRuleDisplay = '';
421
					break;
422
				case '+':
423
					$this->mRulesAction = 'add';
424
					$this->mRuleDisplay = '';
425
					break;
426
				case 'S':
427
					$this->mRuleDisplay = $this->getRuleConvertedStr( $variant );
428
					break;
429
				case 'T':
430
					$this->mRuleTitle = $this->getRuleConvertedTitle( $variant );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getRuleConvertedTitle($variant) can also be of type string. However, the property $mRuleTitle is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
431
					$this->mRuleDisplay = '';
432
					break;
433
				default:
434
					// ignore unknown flags (but see error case below)
435
			}
436
		}
437
		if ( $this->mRuleDisplay === false ) {
438
			$this->mRuleDisplay = '<span class="error">'
439
				. wfMessage( 'converter-manual-rule-error' )->inContentLanguage()->escaped()
440
				. '</span>';
441
		}
442
443
		$this->generateConvTable();
444
	}
445
446
	/**
447
	 * Checks if there are conversion rules.
448
	 * @return bool
449
	 */
450
	public function hasRules() {
451
		return $this->mRules !== '';
452
	}
453
454
	/**
455
	 * Get display text on markup -{...}-
456
	 * @return string
457
	 */
458
	public function getDisplay() {
459
		return $this->mRuleDisplay;
460
	}
461
462
	/**
463
	 * Get converted title.
464
	 * @return string
465
	 */
466
	public function getTitle() {
467
		return $this->mRuleTitle;
468
	}
469
470
	/**
471
	 * Return how deal with conversion rules.
472
	 * @return string
473
	 */
474
	public function getRulesAction() {
475
		return $this->mRulesAction;
476
	}
477
478
	/**
479
	 * Get conversion table. (bidirectional and unidirectional
480
	 * conversion table)
481
	 * @return array
482
	 */
483
	public function getConvTable() {
484
		return $this->mConvTable;
485
	}
486
487
	/**
488
	 * Get conversion rules string.
489
	 * @return string
490
	 */
491
	public function getRules() {
492
		return $this->mRules;
493
	}
494
495
	/**
496
	 * Get conversion flags.
497
	 * @return array
498
	 */
499
	public function getFlags() {
500
		return $this->mFlags;
501
	}
502
}
503