Page MenuHomeWickedGov Phorge

PluralRules.php
No OneTemporary

Size
7 KB
Referenced Files
None
Subscribers
None

PluralRules.php

<?php
/**
* @license GPL-2.0-or-later
* @file
*/
namespace Wikimedia\Leximorph\Provider;
use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
use CLDRPluralRuleParser\Evaluator;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Wikimedia\Leximorph\Provider;
use Wikimedia\Leximorph\Util\XmlLoader;
/**
* PluralRules
*
* Provides functionality to load, cache, and compile pluralization rules from XML files.
*
* @since 1.45
* @author Doğu Abaris (abaris@null.net)
* @license https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
*/
class PluralRules {
/**
* The paths to the plural rules XML files.
*/
public const PLURAL_FILES = [
// Load CLDR plural rules
__DIR__ . '/../data/plurals.xml',
// Override or extend with MW-specific rules
__DIR__ . '/../data/plurals-mediawiki.xml',
];
/**
* Associative array of cached plural data.
*
* The key is the language code, and the value is an array of plural rules.
* Each plural rule is an associative array with the keys:
* - 'type': the plural rule type (e.g., 'one', 'few', etc.)
* - 'rule': the plural rule as a string.
*
* @var array<string, list<array{type: string, rule: string}>>|null
*/
private static ?array $pluralData = null;
/**
* Initializes the PluralRules.
*
* @param string $langCode The language code for which plural rules will be evaluated.
* @param Evaluator $evaluator The evaluator used for compiling and evaluating plural rules.
* @param Provider $provider The provider used to access fallback and related resources.
* @param LoggerInterface $logger The logger instance used for reporting errors.
* @param XmlLoader $xmlLoader The xml loader to load data
*
* @since 1.45
*/
public function __construct(
private readonly string $langCode,
private readonly Evaluator $evaluator,
private readonly Provider $provider,
private readonly LoggerInterface $logger,
private readonly XmlLoader $xmlLoader,
) {
}
/**
* Finds the index number of the plural rule appropriate for the given number.
*
* @param float $number
*
* @since 1.45
* @return int The index number of the plural rule.
*/
public function getPluralRuleIndexNumber( float $number ): int {
$compiledRules = $this->getCompiledPluralRules();
$evaluatedNumber = ( $number == (int)$number ) ? (int)$number : (string)$number;
return $this->evaluator->evaluateCompiled( $evaluatedNumber, $compiledRules );
}
/**
* Returns the compiled plural rules for the current language.
*
* It uses the cached raw rules from the XML files and compiles them.
*
* @since 1.45
* @return array<int, string> The compiled plural rules.
*/
public function getCompiledPluralRules(): array {
$rules = $this->compileRulesFor( $this->langCode );
if ( count( $rules ) === 0 ) {
foreach ( $this->provider->getLanguageFallbacksProvider()->getFallbacks() as $fallbackCode ) {
$rules = $this->compileRulesFor( $fallbackCode );
if ( count( $rules ) > 0 ) {
break;
}
}
}
return $rules;
}
/**
* Compiles the plural rules for the specified language code.
*
* It uses the cached data and returns a compiled version via the CLDR Evaluator.
* Returns an empty array if the rules are unavailable or if a compilation error occurs.
*
* @param string $code The language code.
*
* @since 1.45
* @return array<int, string> The compiled plural rules.
*/
public function compileRulesFor( string $code ): array {
if ( self::$pluralData === null ) {
self::$pluralData = self::loadPluralFiles();
}
$data = self::$pluralData[$code] ?? null;
$rules = $data ? array_column( $data, 'rule' ) : null;
return $this->compileRulesFromArray( $rules );
}
/**
* Loads the plural XML files.
*
* @since 1.45
* @return array<string, list<array{type: string, rule: string}>>
*/
private function loadPluralFiles(): array {
$pluralData = [];
foreach ( self::PLURAL_FILES as $fileName ) {
$pluralData = array_merge( $pluralData, $this->loadPluralFile( $fileName ) );
}
return $pluralData;
}
/**
* Loads a plural XML file and extracts the plural data.
*
* @param string $fileName The path to the XML file.
*
* @since 1.45
* @return array<string, list<array{type: string, rule: string}>>
* @throws RuntimeException if the file cannot be read.
*/
private function loadPluralFile( string $fileName ): array {
$data = [];
$doc = $this->xmlLoader->load( $fileName, 'PluralRules' );
if ( $doc === null ) {
return $data;
}
$rulesets = $doc->getElementsByTagName( "pluralRules" );
foreach ( $rulesets as $ruleset ) {
$codes = $ruleset->getAttribute( 'locales' );
$rules = [];
$ruleElements = $ruleset->getElementsByTagName( "pluralRule" );
foreach ( $ruleElements as $elt ) {
$ruleType = $elt->getAttribute( 'count' );
if ( $ruleType === 'other' ) {
// Skip "other" rules, which have an empty condition.
continue;
}
$rules[] = [
'type' => $ruleType,
'rule' => (string)$elt->nodeValue,
];
}
foreach ( explode( ' ', $codes ) as $code ) {
$data[$code] = $rules;
}
}
return $data;
}
/**
* Helper method that compiles an array of rules.
*
* If the rules array is empty, returns an empty array.
* Otherwise, attempts to compile using the CLDR Evaluator.
* On error, logs the message and returns an empty array.
*
* @param array<int, string>|null $rules The raw plural rules.
*
* @since 1.45
* @return array<int, string> The compiled plural rules.
*/
private function compileRulesFromArray( ?array $rules ): array {
if ( $rules === null ) {
return [];
}
try {
/** @var array<int|string, string> $compiled */
$compiled = $this->evaluator->compile( $rules );
return array_values( $compiled );
} catch ( CLDRPluralRuleError $e ) {
$this->logger->debug( 'Unable to compile rules', [ 'exception' => $e ] );
return [];
}
}
/**
* Returns the raw plural rules for the current language from the XML files.
*
* The data is loaded from the cache if available; otherwise, the XML files are read.
*
* @since 1.45
* @return array<int, string>|null The plural rules, or null if they are not available.
*/
public function getPluralRules(): ?array {
self::$pluralData ??= self::loadPluralFiles();
$data = self::$pluralData[$this->langCode] ?? null;
return $data ? array_column( $data, 'rule' ) : null;
}
/**
* Finds the plural rule type corresponding to the given number.
* For example, if the language is set to Arabic, getPluralRuleType(5) should return 'few'.
*
* @param float $number
*
* @since 1.45
* @return string The name of the plural rule type (e.g., one, two, few, many).
*/
public function getPluralRuleType( float $number ): string {
$index = $this->getPluralRuleIndexNumber( $number );
$types = $this->getPluralRuleTypes();
return $types[$index] ?? 'other';
}
/**
* Returns the plural rule types for the current language from the XML files.
*
* The data is loaded from the cache if available; otherwise, the XML files are read.
*
* @since 1.45
* @return array<int, string> The plural rule types.
*/
public function getPluralRuleTypes(): array {
if ( self::$pluralData === null ) {
self::$pluralData = self::loadPluralFiles();
}
$data = self::$pluralData[$this->langCode] ?? [];
if ( count( $data ) === 0 ) {
foreach ( $this->provider->getLanguageFallbacksProvider()->getFallbacks() as $fallbackCode ) {
$data = self::$pluralData[$fallbackCode] ?? [];
if ( count( $data ) > 0 ) {
break;
}
}
}
return array_column( $data, 'type' );
}
}

File Metadata

Mime Type
text/x-php
Expires
Tue, Aug 18, 16:09 (3 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
32/d7/f34514579aa434fd93db0ece7b0d
Default Alt Text
PluralRules.php (7 KB)

Event Timeline