00001 <?php
00002
00018 class LanguageConverter {
00019 var $mMainLanguageCode;
00020 var $mVariants, $mVariantFallbacks, $mVariantNames;
00021 var $mTablesLoaded = false;
00022 var $mTables;
00023
00024 var $mManualLevel;
00025 var $mCacheKey;
00026 var $mLangObj;
00027 var $mFlags;
00028 var $mDescCodeSep = ':', $mDescVarSep = ';';
00029 var $mUcfirst = false;
00030 var $mConvRuleTitle = false;
00031 var $mURLVariant;
00032 var $mUserVariant;
00033 var $mHeaderVariant;
00034 var $mMaxDepth = 10;
00035 var $mVarSeparatorPattern;
00036
00037 const CACHE_VERSION_KEY = 'VERSION 6';
00038
00049 public function __construct( $langobj, $maincode,
00050 $variants = array(),
00051 $variantfallbacks = array(),
00052 $flags = array(),
00053 $manualLevel = array() ) {
00054 $this->mLangObj = $langobj;
00055 $this->mMainLanguageCode = $maincode;
00056
00057 global $wgDisabledVariants;
00058 $this->mVariants = array();
00059 foreach ( $variants as $variant ) {
00060 if ( !in_array( $variant, $wgDisabledVariants ) ) {
00061 $this->mVariants[] = $variant;
00062 }
00063 }
00064 $this->mVariantFallbacks = $variantfallbacks;
00065 global $wgLanguageNames;
00066 $this->mVariantNames = $wgLanguageNames;
00067 $this->mCacheKey = wfMemcKey( 'conversiontables', $maincode );
00068 $f = array(
00069
00070
00071
00072
00073 'A' => 'A',
00074 'T' => 'T',
00075 'R' => 'R',
00076 'D' => 'D',
00077 '-' => '-',
00078 'H' => 'H',
00079
00080 'N' => 'N'
00081 );
00082 $this->mFlags = array_merge( $f, $flags );
00083 foreach ( $this->mVariants as $v ) {
00084 if ( array_key_exists( $v, $manualLevel ) ) {
00085 $this->mManualLevel[$v] = $manualLevel[$v];
00086 } else {
00087 $this->mManualLevel[$v] = 'bidirectional';
00088 }
00089 $this->mFlags[$v] = $v;
00090 }
00091 }
00092
00096 function getVariants() {
00097 return $this->mVariants;
00098 }
00099
00112 function getVariantFallbacks( $v ) {
00113 if ( isset( $this->mVariantFallbacks[$v] ) ) {
00114 return $this->mVariantFallbacks[$v];
00115 }
00116 return $this->mMainLanguageCode;
00117 }
00118
00123 function getConvRuleTitle() {
00124 return $this->mConvRuleTitle;
00125 }
00126
00134 function getPreferredVariant( $fromUser = true, $fromHeader = false ) {
00135 global $wgDefaultLanguageVariant;
00136
00137 $req = $this->getURLVariant();
00138
00139 if ( $fromUser && !$req ) {
00140 $req = $this->getUserVariant();
00141 }
00142
00143 if ( $fromHeader && !$req ) {
00144 $req = $this->getHeaderVariant();
00145 }
00146
00147 if ( $wgDefaultLanguageVariant && !$req ) {
00148 $req = $this->validateVariant( $wgDefaultLanguageVariant );
00149 }
00150
00151
00152
00153
00154
00155 if ( $req ) {
00156 return $req;
00157 }
00158 return $this->mMainLanguageCode;
00159 }
00160
00166 function validateVariant( $v = null ) {
00167 if ( $v !== null && in_array( $v, $this->mVariants ) ) {
00168 return $v;
00169 }
00170 return null;
00171 }
00172
00178 function getURLVariant() {
00179 global $wgRequest;
00180 $ret = null;
00181
00182 if ( $this->mURLVariant ) {
00183 return $this->mURLVariant;
00184 }
00185
00186
00187 $ret = $wgRequest->getText( 'variant' );
00188
00189 if ( !$ret ) {
00190 $ret = $wgRequest->getVal( 'uselang' );
00191 }
00192
00193 return $this->mURLVariant = $this->validateVariant( $ret );
00194 }
00195
00201 function getUserVariant() {
00202 global $wgUser;
00203 $ret = null;
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213 if ( $wgUser->isLoggedIn() ) {
00214 $ret = $wgUser->getOption( 'variant' );
00215 }
00216 else {
00217
00218
00219 $ret = $wgUser->getOption( 'language' );
00220 }
00221
00222 return $this->mUserVariant = $this->validateVariant( $ret );
00223 }
00224
00225
00231 function getHeaderVariant() {
00232 global $wgRequest;
00233 $ret = null;
00234
00235 if ( $this->mHeaderVariant ) {
00236 return $this->mHeaderVariant;
00237 }
00238
00239
00240
00241
00242 $acceptLanguage = $wgRequest->getHeader( 'Accept-Language' );
00243 if ( !$acceptLanguage ) {
00244 return null;
00245 }
00246
00247
00248 $result = StringUtils::explode( ',', strtolower( $acceptLanguage ) );
00249 $languages = array();
00250
00251 foreach ( $result as $elem ) {
00252
00253 if ( ( $posi = strpos( $elem, ';' ) ) !== false ) {
00254
00255 $languages[] = substr( $elem, 0, $posi );
00256 } else {
00257 $languages[] = $elem;
00258 }
00259 }
00260
00261 $fallback_languages = array();
00262 foreach ( $languages as $language ) {
00263
00264 $language = trim( $language );
00265 $this->mHeaderVariant = $this->validateVariant( $language );
00266 if ( $this->mHeaderVariant ) {
00267 break;
00268 }
00269
00270
00271
00272
00273 $fallbacks = $this->getVariantFallbacks( $language );
00274 if ( is_string( $fallbacks ) ) {
00275 $fallback_languages[] = $fallbacks;
00276 } elseif ( is_array( $fallbacks ) ) {
00277 $fallback_languages =
00278 array_merge( $fallback_languages,
00279 $fallbacks );
00280 }
00281 }
00282
00283 if ( !$this->mHeaderVariant ) {
00284
00285 $fallback_languages = array_unique( $fallback_languages );
00286 foreach ( $fallback_languages as $language ) {
00287 $this->mHeaderVariant = $this->validateVariant( $language );
00288 if ( $this->mHeaderVariant ) {
00289 break;
00290 }
00291 }
00292 }
00293
00294 return $this->mHeaderVariant;
00295 }
00296
00306 function captionConvert( $matches ) {
00307 $toVariant = $this->getPreferredVariant();
00308 $title = $matches[1];
00309 $text = $matches[2];
00310
00311 if ( !strpos( $text, '://' ) ) {
00312 $text = $this->translate( $text, $toVariant );
00313 }
00314 return " $title=\"$text\"";
00315 }
00316
00325 function autoConvert( $text, $toVariant = false ) {
00326 $fname = 'LanguageConverter::autoConvert';
00327
00328 wfProfileIn( $fname );
00329
00330 if ( !$this->mTablesLoaded ) {
00331 $this->loadTables();
00332 }
00333
00334 if ( !$toVariant ) {
00335 $toVariant = $this->getPreferredVariant();
00336 if ( !$toVariant ) {
00337 return $text;
00338 }
00339 }
00340
00341
00342
00343
00344
00345
00346 global $wgParser;
00347 if ( isset( $wgParser ) && $wgParser->UniqPrefix() != '' ) {
00348 $marker = '|' . $wgParser->UniqPrefix() . '[\-a-zA-Z0-9]+';
00349 } else {
00350 $marker = '';
00351 }
00352
00353
00354 $htmlfix = '|<[^>]+$|^[^<>]*>';
00355
00356
00357 $codefix = '<code>.+?<\/code>|';
00358
00359 $scriptfix = '<script.*?>.*?<\/script>|';
00360
00361 $prefix = '<pre.*?>.*?<\/pre>|';
00362
00363 $reg = '/' . $codefix . $scriptfix . $prefix .
00364 '<[^>]+>|&[a-zA-Z#][a-z0-9]+;' . $marker . $htmlfix . '/s';
00365
00366 $matches = preg_split( $reg, $text, - 1, PREG_SPLIT_OFFSET_CAPTURE );
00367
00368 $m = array_shift( $matches );
00369
00370 $ret = $this->translate( $m[0], $toVariant );
00371 $mstart = $m[1] + strlen( $m[0] );
00372
00373
00374
00375 $captionpattern = '/\s(title|alt)\s*=\s*"([\s\S]*?)"/';
00376
00377 $trtext = '';
00378 $trtextmark = "\0";
00379 $notrtext = array();
00380 foreach ( $matches as $m ) {
00381 $mark = substr( $text, $mstart, $m[1] - $mstart );
00382 $mark = preg_replace_callback( $captionpattern,
00383 array( &$this, 'captionConvert' ),
00384 $mark );
00385
00386
00387 $notrtext[] = $mark;
00388 $trtext .= $m[0] . $trtextmark;
00389 $mstart = $m[1] + strlen( $m[0] );
00390 }
00391 $notrtext[] = '';
00392 $trtext = $this->translate( $trtext, $toVariant );
00393 $trtext = StringUtils::explode( $trtextmark, $trtext );
00394 foreach ( $trtext as $t ) {
00395 $ret .= array_shift( $notrtext );
00396 $ret .= $t;
00397 }
00398 wfProfileOut( $fname );
00399 return $ret;
00400 }
00401
00412 function translate( $text, $variant ) {
00413 wfProfileIn( __METHOD__ );
00414
00415
00416 if ( trim( $text ) ) {
00417 if ( !$this->mTablesLoaded ) {
00418 $this->loadTables();
00419 }
00420 $text = $this->mTables[$variant]->replace( $text );
00421 }
00422 wfProfileOut( __METHOD__ );
00423 return $text;
00424 }
00425
00433 function autoConvertToAllVariants( $text ) {
00434 $fname = 'LanguageConverter::autoConvertToAllVariants';
00435 wfProfileIn( $fname );
00436 if ( !$this->mTablesLoaded ) {
00437 $this->loadTables();
00438 }
00439
00440 $ret = array();
00441 foreach ( $this->mVariants as $variant ) {
00442 $ret[$variant] = $this->translate( $text, $variant );
00443 }
00444
00445 wfProfileOut( $fname );
00446 return $ret;
00447 }
00448
00456 function convertLinkToAllVariants( $text ) {
00457 if ( !$this->mTablesLoaded ) {
00458 $this->loadTables();
00459 }
00460
00461 $ret = array();
00462 $tarray = StringUtils::explode( '-{', $text );
00463 $first = true;
00464
00465 foreach ( $tarray as $txt ) {
00466 if ( $first ) {
00467 $first = false;
00468 foreach ( $this->mVariants as $variant ) {
00469 $ret[$variant] = $this->translate( $txt, $variant );
00470 }
00471 continue;
00472 }
00473
00474 $marked = explode( '}-', $txt, 2 );
00475
00476 foreach ( $this->mVariants as $variant ) {
00477 $ret[$variant] .= '-{' . $marked[0] . '}-';
00478 if ( array_key_exists( 1, $marked ) ) {
00479 $ret[$variant] .= $this->translate( $marked[1], $variant );
00480 }
00481 }
00482
00483 }
00484
00485 return $ret;
00486 }
00487
00492 function applyManualConv( $convRule ) {
00493
00494
00495
00496
00497 $newConvRuleTitle = $convRule->getTitle();
00498 if( $newConvRuleTitle ) {
00499 $this->mConvRuleTitle = $newConvRuleTitle;
00500 }
00501
00502
00503 $convTable = $convRule->getConvTable();
00504 $action = $convRule->getRulesAction();
00505 foreach ( $convTable as $variant => $pair ) {
00506 if ( !$this->validateVariant( $variant ) ) {
00507 continue;
00508 }
00509
00510 if ( $action == 'add' ) {
00511 foreach ( $pair as $from => $to ) {
00512
00513
00514 if ( $from || $to ) {
00515
00516 $this->mTables[$variant]->setPair( $from, $to );
00517 }
00518 }
00519 } elseif ( $action == 'remove' ) {
00520 $this->mTables[$variant]->removeArray( $pair );
00521 }
00522 }
00523 }
00524
00539 public function convert( $text ) {
00540 global $wgDisableLangConversion;
00541 if ( $wgDisableLangConversion ) return $text;
00542
00543 $variant = $this->getPreferredVariant();
00544
00545 return $this->recursiveConvertTopLevel( $text, $variant );
00546 }
00547
00551 public function convertTitle( $title ) {
00552 $variant = $this->getPreferredVariant();
00553 $index = $title->getNamespace();
00554 if ( $index === NS_MAIN || $index === NS_SPECIAL ) {
00555 $text = '';
00556 } else {
00557
00558 $nsConvKey = 'conversion-ns' . $index;
00559 $nsLocalText = wfMsgForContentNoTrans( $nsConvKey );
00560 if ( !wfEmptyMsg( $nsConvKey, $nsLocalText ) ) {
00561 $text = $nsLocalText;
00562 } else {
00563
00564
00565 $langObj = $this->mLangObj->factory( $variant );
00566 $text = $langObj->getFormattedNsText( $index );
00567 }
00568 $text .= ':';
00569 }
00570 $text .= $title->getText();
00571 $text = $this->autoConvert( $text, $variant );
00572 return $text;
00573 }
00574
00575 protected function recursiveConvertTopLevel( $text, $variant, $depth = 0 ) {
00576 $startPos = 0;
00577 $out = '';
00578 $length = strlen( $text );
00579 while ( $startPos < $length ) {
00580 $m = false;
00581 $pos = strpos( $text, '-{', $startPos );
00582
00583 if ( $pos === false ) {
00584
00585 $out .= $this->autoConvert( substr( $text, $startPos ), $variant );
00586 $startPos = $length;
00587 return $out;
00588 }
00589
00590
00591
00592 $out .= $this->autoConvert( substr( $text, $startPos, $pos - $startPos ), $variant );
00593
00594
00595 $startPos = $pos;
00596
00597
00598 $out .= $this->recursiveConvertRule( $text, $variant, $startPos, $depth + 1 );
00599 }
00600
00601 return $out;
00602 }
00603
00604 protected function recursiveConvertRule( $text, $variant, &$startPos, $depth = 0 ) {
00605
00606 if ( $text[$startPos] !== '-' || $text[$startPos + 1] !== '{' ) {
00607 throw new MWException( __METHOD__.': invalid input string' );
00608 }
00609
00610 $startPos += 2;
00611 $inner = '';
00612 $warningDone = false;
00613 $length = strlen( $text );
00614
00615 while ( $startPos < $length ) {
00616 $m = false;
00617 preg_match( '/-\{|\}-/', $text, $m, PREG_OFFSET_CAPTURE, $startPos );
00618 if ( !$m ) {
00619
00620 break;
00621 }
00622
00623 $token = $m[0][0];
00624 $pos = $m[0][1];
00625
00626
00627
00628 $inner .= substr( $text, $startPos, $pos - $startPos );
00629
00630
00631 $startPos = $pos;
00632
00633 switch ( $token ) {
00634 case '-{':
00635
00636 if ( $depth >= $this->mMaxDepth ) {
00637 $inner .= '-{';
00638 if ( !$warningDone ) {
00639 $inner .= '<span class="error">' .
00640 wfMsgForContent( 'language-converter-depth-warning',
00641 $this->mMaxDepth ) .
00642 '</span>';
00643 $warningDone = true;
00644 }
00645 $startPos += 2;
00646 continue;
00647 }
00648
00649 $inner .= $this->recursiveConvertRule( $text, $variant, $startPos, $depth + 1 );
00650 break;
00651 case '}-':
00652
00653 $startPos += 2;
00654 $rule = new ConverterRule( $inner, $this );
00655 $rule->parse( $variant );
00656 $this->applyManualConv( $rule );
00657 return $rule->getDisplay();
00658 default:
00659 throw new MWException( __METHOD__.': invalid regex match' );
00660 }
00661 }
00662
00663
00664 if ( $startPos < $length ) {
00665 $inner .= substr( $text, $startPos );
00666 }
00667 $startPos = $length;
00668 return '-{' . $this->autoConvert( $inner, $variant );
00669 }
00670
00684 function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
00685 # If the article has already existed, there is no need to
00686 # check it again, otherwise it may cause a fault.
00687 if ( is_object( $nt ) && $nt->exists() ) {
00688 return;
00689 }
00690
00691 global $wgDisableLangConversion, $wgDisableTitleConversion, $wgRequest,
00692 $wgUser;
00693 $isredir = $wgRequest->getText( 'redirect', 'yes' );
00694 $action = $wgRequest->getText( 'action' );
00695 $linkconvert = $wgRequest->getText( 'linkconvert', 'yes' );
00696 $disableLinkConversion = $wgDisableLangConversion
00697 || $wgDisableTitleConversion;
00698 $linkBatch = new LinkBatch();
00699
00700 $ns = NS_MAIN;
00701
00702 if ( $disableLinkConversion ||
00703 ( !$ignoreOtherCond &&
00704 ( $isredir == 'no'
00705 || $action == 'edit'
00706 || $action == 'submit'
00707 || $linkconvert == 'no'
00708 || $wgUser->getOption( 'noconvertlink' ) == 1 ) ) ) {
00709 return;
00710 }
00711
00712 if ( is_object( $nt ) ) {
00713 $ns = $nt->getNamespace();
00714 }
00715
00716 $variants = $this->autoConvertToAllVariants( $link );
00717 if ( $variants == false ) {
00718 return;
00719 }
00720
00721 $titles = array();
00722
00723 foreach ( $variants as $v ) {
00724 if ( $v != $link ) {
00725 $varnt = Title::newFromText( $v, $ns );
00726 if ( !is_null( $varnt ) ) {
00727 $linkBatch->addObj( $varnt );
00728 $titles[] = $varnt;
00729 }
00730 }
00731 }
00732
00733
00734 $linkBatch->execute();
00735
00736 foreach ( $titles as $varnt ) {
00737 if ( $varnt->getArticleID() > 0 ) {
00738 $nt = $varnt;
00739 $link = $varnt->getText();
00740 break;
00741 }
00742 }
00743 }
00744
00750 function getExtraHashOptions() {
00751 $variant = $this->getPreferredVariant();
00752 return '!' . $variant ;
00753 }
00754
00761 function loadDefaultTables() {
00762 $name = get_class( $this );
00763 wfDie( "Must implement loadDefaultTables() method in class $name" );
00764 }
00765
00770 function loadTables( $fromcache = true ) {
00771 global $wgMemc;
00772 if ( $this->mTablesLoaded ) {
00773 return;
00774 }
00775 wfProfileIn( __METHOD__ );
00776 $this->mTablesLoaded = true;
00777 $this->mTables = false;
00778 if ( $fromcache ) {
00779 wfProfileIn( __METHOD__ . '-cache' );
00780 $this->mTables = $wgMemc->get( $this->mCacheKey );
00781 wfProfileOut( __METHOD__ . '-cache' );
00782 }
00783 if ( !$this->mTables
00784 || !array_key_exists( self::CACHE_VERSION_KEY, $this->mTables ) ) {
00785 wfProfileIn( __METHOD__ . '-recache' );
00786
00787
00788
00789 $this->loadDefaultTables();
00790 foreach ( $this->mVariants as $var ) {
00791 $cached = $this->parseCachedTable( $var );
00792 $this->mTables[$var]->mergeArray( $cached );
00793 }
00794
00795 $this->postLoadTables();
00796 $this->mTables[self::CACHE_VERSION_KEY] = true;
00797
00798 $wgMemc->set( $this->mCacheKey, $this->mTables, 43200 );
00799 wfProfileOut( __METHOD__ . '-recache' );
00800 }
00801 wfProfileOut( __METHOD__ );
00802 }
00803
00808 function postLoadTables() { }
00809
00815 function reloadTables() {
00816 if ( $this->mTables ) {
00817 unset( $this->mTables );
00818 }
00819 $this->mTablesLoaded = false;
00820 $this->loadTables( false );
00821 }
00822
00823
00838 function parseCachedTable( $code, $subpage = '', $recursive = true ) {
00839 global $wgMessageCache;
00840 static $parsed = array();
00841
00842 if ( !is_object( $wgMessageCache ) ) {
00843 return array();
00844 }
00845
00846 $key = 'Conversiontable/' . $code;
00847 if ( $subpage ) {
00848 $key .= '/' . $subpage;
00849 }
00850 if ( array_key_exists( $key, $parsed ) ) {
00851 return array();
00852 }
00853
00854 if ( strpos( $code, '/' ) === false ) {
00855 $txt = $wgMessageCache->get( 'Conversiontable', true, $code );
00856 } else {
00857 $title = Title::makeTitleSafe( NS_MEDIAWIKI,
00858 "Conversiontable/$code" );
00859 if ( $title && $title->exists() ) {
00860 $article = new Article( $title );
00861 $txt = $article->getContents();
00862 } else {
00863 $txt = '';
00864 }
00865 }
00866
00867
00868
00869 $linkhead = $this->mLangObj->getNsText( NS_MEDIAWIKI ) .
00870 ':Conversiontable';
00871 $subs = StringUtils::explode( '[[', $txt );
00872 $sublinks = array();
00873 foreach ( $subs as $sub ) {
00874 $link = explode( ']]', $sub, 2 );
00875 if ( count( $link ) != 2 ) {
00876 continue;
00877 }
00878 $b = explode( '|', $link[0], 2 );
00879 $b = explode( '/', trim( $b[0] ), 3 );
00880 if ( count( $b ) == 3 ) {
00881 $sublink = $b[2];
00882 } else {
00883 $sublink = '';
00884 }
00885
00886 if ( $b[0] == $linkhead && $b[1] == $code ) {
00887 $sublinks[] = $sublink;
00888 }
00889 }
00890
00891
00892
00893 $blocks = StringUtils::explode( '-{', $txt );
00894 $ret = array();
00895 $first = true;
00896 foreach ( $blocks as $block ) {
00897 if ( $first ) {
00898
00899 $first = false;
00900 continue;
00901 }
00902 $mappings = explode( '}-', $block, 2 );
00903 $stripped = str_replace( array( "'", '"', '*', '#' ), '',
00904 $mappings[0] );
00905 $table = StringUtils::explode( ';', $stripped );
00906 foreach ( $table as $t ) {
00907 $m = explode( '=>', $t, 3 );
00908 if ( count( $m ) != 2 )
00909 continue;
00910
00911 $tt = explode( '//', $m[1], 2 );
00912 $ret[trim( $m[0] )] = trim( $tt[0] );
00913 }
00914 }
00915 $parsed[$key] = true;
00916
00917
00918
00919 if ( $recursive ) {
00920 foreach ( $sublinks as $link ) {
00921 $s = $this->parseCachedTable( $code, $link, $recursive );
00922 $ret = array_merge( $ret, $s );
00923 }
00924 }
00925
00926 if ( $this->mUcfirst ) {
00927 foreach ( $ret as $k => $v ) {
00928 $ret[Language::ucfirst( $k )] = Language::ucfirst( $v );
00929 }
00930 }
00931 return $ret;
00932 }
00933
00942 function markNoConversion( $text, $noParse = false ) {
00943 # don't mark if already marked
00944 if ( strpos( $text, '-{' ) || strpos( $text, '}-' ) ) {
00945 return $text;
00946 }
00947
00948 $ret = "-{R|$text}-";
00949 return $ret;
00950 }
00951
00956 function convertCategoryKey( $key ) {
00957 return $key;
00958 }
00959
00965 function OnArticleSaveComplete( $article, $user, $text, $summary, $isminor,
00966 $iswatch, $section, $flags, $revision ) {
00967 $titleobj = $article->getTitle();
00968 if ( $titleobj->getNamespace() == NS_MEDIAWIKI ) {
00969 $title = $titleobj->getDBkey();
00970 $t = explode( '/', $title, 3 );
00971 $c = count( $t );
00972 if ( $c > 1 && $t[0] == 'Conversiontable' ) {
00973 if ( $this->validateVariant( $t[1] ) ) {
00974 $this->reloadTables();
00975 }
00976 }
00977 }
00978 return true;
00979 }
00980
00986 function armourMath( $text ) {
00987
00988
00989 $text = strtr( $text, array( '-{' => '-{', '}-' => '}-' ) );
00990 $ret = "-{R|$text}-";
00991 return $ret;
00992 }
00993
00997 function getVarSeparatorPattern() {
00998 if ( is_null( $this->mVarSeparatorPattern ) ) {
00999
01000
01001
01002
01003
01004
01005
01006
01007
01008
01009
01010 $pat = '/;\s*(?=';
01011 foreach ( $this->mVariants as $variant ) {
01012
01013 $pat .= $variant . '\s*:|';
01014
01015 $pat .= '[^;]*?=>\s*' . $variant . '\s*:|';
01016 }
01017 $pat .= '\s*$)/';
01018 $this->mVarSeparatorPattern = $pat;
01019 }
01020 return $this->mVarSeparatorPattern;
01021 }
01022 }
01023
01029 class ConverterRule {
01030 var $mText;
01031 var $mConverter;
01032 var $mManualCodeError = '<strong class="error">code error!</strong>';
01033 var $mRuleDisplay = '';
01034 var $mRuleTitle = false;
01035 var $mRules = '';
01036 var $mRulesAction = 'none';
01037 var $mFlags = array();
01038 var $mVariantFlags = array();
01039 var $mConvTable = array();
01040 var $mBidtable = array();
01041 var $mUnidtable = array();
01042
01049 public function __construct( $text, $converter ) {
01050 $this->mText = $text;
01051 $this->mConverter = $converter;
01052 }
01053
01060 public function getTextInBidtable( $variants ) {
01061 $variants = (array)$variants;
01062 if ( !$variants ) {
01063 return false;
01064 }
01065 foreach ( $variants as $variant ) {
01066 if ( isset( $this->mBidtable[$variant] ) ) {
01067 return $this->mBidtable[$variant];
01068 }
01069 }
01070 return false;
01071 }
01072
01077 function parseFlags() {
01078 $text = $this->mText;
01079 $flags = array();
01080 $variantFlags = array();
01081
01082 $sepPos = strpos( $text, '|' );
01083 if ( $sepPos !== false ) {
01084 $validFlags = $this->mConverter->mFlags;
01085 $f = StringUtils::explode( ';', substr( $text, 0, $sepPos ) );
01086 foreach ( $f as $ff ) {
01087 $ff = trim( $ff );
01088 if ( isset( $validFlags[$ff] ) ) {
01089 $flags[$validFlags[$ff]] = true;
01090 }
01091 }
01092 $text = strval( substr( $text, $sepPos + 1 ) );
01093 }
01094
01095 if ( !$flags ) {
01096 $flags['S'] = true;
01097 } elseif ( isset( $flags['R'] ) ) {
01098 $flags = array( 'R' => true );
01099 } elseif ( isset( $flags['N'] ) ) {
01100 $flags = array( 'N' => true );
01101 } elseif ( isset( $flags['-'] ) ) {
01102 $flags = array( '-' => true );
01103 } elseif ( count( $flags ) == 1 && isset( $flags['T'] ) ) {
01104 $flags['H'] = true;
01105 } elseif ( isset( $flags['H'] ) ) {
01106
01107 $temp = array( '+' => true, 'H' => true );
01108 if ( isset( $flags['T'] ) ) {
01109 $temp['T'] = true;
01110 }
01111 if ( isset( $flags['D'] ) ) {
01112 $temp['D'] = true;
01113 }
01114 $flags = $temp;
01115 } else {
01116 if ( isset( $flags['A'] ) ) {
01117 $flags['+'] = true;
01118 $flags['S'] = true;
01119 }
01120 if ( isset( $flags['D'] ) ) {
01121 unset( $flags['S'] );
01122 }
01123
01124
01125 $variantFlags = array_intersect( array_keys( $flags ), $this->mConverter->mVariants );
01126 if ( $variantFlags ) {
01127 $variantFlags = array_flip( $variantFlags );
01128 $flags = array();
01129 }
01130 }
01131 $this->mVariantFlags = $variantFlags;
01132 $this->mRules = $text;
01133 $this->mFlags = $flags;
01134 }
01135
01140 function parseRules() {
01141 $rules = $this->mRules;
01142 $flags = $this->mFlags;
01143 $bidtable = array();
01144 $unidtable = array();
01145 $variants = $this->mConverter->mVariants;
01146 $varsep_pattern = $this->mConverter->getVarSeparatorPattern();
01147
01148 $choice = preg_split( $varsep_pattern, $rules );
01149
01150 foreach ( $choice as $c ) {
01151 $v = explode( ':', $c, 2 );
01152 if ( count( $v ) != 2 ) {
01153
01154 continue;
01155 }
01156 $to = trim( $v[1] );
01157 $v = trim( $v[0] );
01158 $u = explode( '=>', $v, 2 );
01159
01160 if ( count( $u ) == 1 && $to && in_array( $v, $variants ) ) {
01161 $bidtable[$v] = $to;
01162 } elseif ( count( $u ) == 2 ) {
01163 $from = trim( $u[0] );
01164 $v = trim( $u[1] );
01165 if ( array_key_exists( $v, $unidtable )
01166 && !is_array( $unidtable[$v] )
01167 && $to
01168 && in_array( $v, $variants ) ) {
01169 $unidtable[$v] = array( $from => $to );
01170 } elseif ( $to && in_array( $v, $variants ) ) {
01171 $unidtable[$v][$from] = $to;
01172 }
01173 }
01174
01175 if ( !isset( $this->mConverter->mVariantNames[$v] ) ) {
01176 $bidtable = array();
01177 $unidtable = array();
01178 break;
01179 }
01180 }
01181 $this->mBidtable = $bidtable;
01182 $this->mUnidtable = $unidtable;
01183 }
01184
01188 function getRulesDesc() {
01189 $codesep = $this->mConverter->mDescCodeSep;
01190 $varsep = $this->mConverter->mDescVarSep;
01191 $text = '';
01192 foreach ( $this->mBidtable as $k => $v ) {
01193 $text .= $this->mConverter->mVariantNames[$k] . "$codesep$v$varsep";
01194 }
01195 foreach ( $this->mUnidtable as $k => $a ) {
01196 foreach ( $a as $from => $to ) {
01197 $text .= $from . '⇒' . $this->mConverter->mVariantNames[$k] .
01198 "$codesep$to$varsep";
01199 }
01200 }
01201 return $text;
01202 }
01203
01208 function getRuleConvertedStr( $variant ) {
01209 $bidtable = $this->mBidtable;
01210 $unidtable = $this->mUnidtable;
01211
01212 if ( count( $bidtable ) + count( $unidtable ) == 0 ) {
01213 return $this->mRules;
01214 } else {
01215
01216 $disp = $this->getTextInBidtable( $variant );
01217
01218 if ( !$disp ) {
01219 $disp = $this->getTextInBidtable(
01220 $this->mConverter->getVariantFallbacks( $variant ) );
01221 }
01222
01223 if ( !$disp && array_key_exists( $variant, $unidtable ) ) {
01224 $disp = array_values( $unidtable[$variant] );
01225 $disp = $disp[0];
01226 }
01227
01228 if ( !$disp
01229 && $this->mConverter->mManualLevel[$variant] == 'disable' ) {
01230 if ( count( $bidtable ) > 0 ) {
01231 $disp = array_values( $bidtable );
01232 $disp = $disp[0];
01233 } else {
01234 $disp = array_values( $unidtable );
01235 $disp = array_values( $disp[0] );
01236 $disp = $disp[0];
01237 }
01238 }
01239 return $disp;
01240 }
01241 }
01242
01247 function generateConvTable() {
01248
01249 if ( !$this->mBidtable && !$this->mUnidtable ) {
01250 $this->mConvTable = array();
01251 return;
01252 }
01253
01254 $bidtable = $this->mBidtable;
01255 $unidtable = $this->mUnidtable;
01256 $manLevel = $this->mConverter->mManualLevel;
01257
01258 $vmarked = array();
01259 foreach ( $this->mConverter->mVariants as $v ) {
01260
01261
01262
01263 if ( !isset( $bidtable[$v] ) ) {
01264 $variantFallbacks =
01265 $this->mConverter->getVariantFallbacks( $v );
01266 $vf = $this->getTextInBidtable( $variantFallbacks );
01267 if ( $vf ) {
01268 $bidtable[$v] = $vf;
01269 }
01270 }
01271
01272 if ( isset( $bidtable[$v] ) ) {
01273 foreach ( $vmarked as $vo ) {
01274
01275
01276
01277
01278
01279 if ( $manLevel[$v] == 'bidirectional' ) {
01280 $this->mConvTable[$v][$bidtable[$vo]] = $bidtable[$v];
01281 }
01282 if ( $manLevel[$vo] == 'bidirectional' ) {
01283 $this->mConvTable[$vo][$bidtable[$v]] = $bidtable[$vo];
01284 }
01285 }
01286 $vmarked[] = $v;
01287 }
01288
01289 if ( ( $manLevel[$v] == 'bidirectional' || $manLevel[$v] == 'unidirectional' )
01290 && isset( $unidtable[$v] ) )
01291 {
01292 if ( isset( $this->mConvTable[$v] ) ) {
01293 $this->mConvTable[$v] = array_merge( $this->mConvTable[$v], $unidtable[$v] );
01294 } else {
01295 $this->mConvTable[$v] = $unidtable[$v];
01296 }
01297 }
01298 }
01299 }
01300
01305 function parse( $variant = NULL ) {
01306 if ( !$variant ) {
01307 $variant = $this->mConverter->getPreferredVariant();
01308 }
01309
01310 $variants = $this->mConverter->mVariants;
01311 $this->parseFlags();
01312 $flags = $this->mFlags;
01313
01314
01315
01316 if ( $this->mVariantFlags ) {
01317
01318 if ( isset( $this->mVariantFlags[$variant] ) ) {
01319
01320 $this->mRules = $this->mConverter->autoConvert( $this->mRules,
01321 $variant );
01322 } else {
01323
01324 $variantFallbacks =
01325 $this->mConverter->getVariantFallbacks( $variant );
01326 foreach ( $variantFallbacks as $variantFallback ) {
01327
01328 if ( isset( $this->mVariantFlags[$variantFallback] ) ) {
01329
01330 $this->mRules =
01331 $this->mConverter->autoConvert( $this->mRules,
01332 $variantFallback );
01333 break;
01334 }
01335 }
01336 }
01337 $this->mFlags = $flags = array( 'R' => true );
01338 }
01339
01340 if ( !isset( $flags['R'] ) && !isset( $flags['N'] ) ) {
01341
01342 $this->mRules = str_replace( '=>', '=>', $this->mRules );
01343 $this->parseRules();
01344 }
01345 $rules = $this->mRules;
01346
01347 if ( !$this->mBidtable && !$this->mUnidtable ) {
01348 if ( isset( $flags['+'] ) || isset( $flags['-'] ) ) {
01349
01350 foreach ( $this->mConverter->mVariants as $v ) {
01351 $this->mBidtable[$v] = $rules;
01352 }
01353 } elseif ( !isset( $flags['N'] ) && !isset( $flags['T'] ) ) {
01354 $this->mFlags = $flags = array( 'R' => true );
01355 }
01356 }
01357
01358 $this->mRuleDisplay = false;
01359 foreach ( $flags as $flag => $unused ) {
01360 switch ( $flag ) {
01361 case 'R':
01362
01363 $this->mRuleDisplay = $rules;
01364 break;
01365 case 'N':
01366
01367 $ruleVar = trim( $rules );
01368 if ( isset( $this->mConverter->mVariantNames[$ruleVar] ) ) {
01369 $this->mRuleDisplay = $this->mConverter->mVariantNames[$ruleVar];
01370 } else {
01371 $this->mRuleDisplay = '';
01372 }
01373 break;
01374 case 'D':
01375
01376 $this->mRuleDisplay = $this->getRulesDesc();
01377 break;
01378 case 'H':
01379
01380 $this->mRuleDisplay = '';
01381 break;
01382 case '-':
01383 $this->mRulesAction = 'remove';
01384 $this->mRuleDisplay = '';
01385 break;
01386 case '+':
01387 $this->mRulesAction = 'add';
01388 $this->mRuleDisplay = '';
01389 break;
01390 case 'S':
01391 $this->mRuleDisplay = $this->getRuleConvertedStr( $variant );
01392 break;
01393 case 'T':
01394 $this->mRuleTitle = $this->getRuleConvertedStr( $variant );
01395 $this->mRuleDisplay = '';
01396 break;
01397 default:
01398
01399 }
01400 }
01401 if ( $this->mRuleDisplay === false ) {
01402 $this->mRuleDisplay = $this->mManualCodeError;
01403 }
01404
01405 $this->generateConvTable();
01406 }
01407
01411 function hasRules() {
01412
01413 }
01414
01419 function getDisplay() {
01420 return $this->mRuleDisplay;
01421 }
01422
01427 function getTitle() {
01428 return $this->mRuleTitle;
01429 }
01430
01435 function getRulesAction() {
01436 return $this->mRulesAction;
01437 }
01438
01444 function getConvTable() {
01445 return $this->mConvTable;
01446 }
01447
01452 function getRules() {
01453 return $this->mRules;
01454 }
01455
01460 function getFlags() {
01461 return $this->mFlags;
01462 }
01463 }