MediaWiki  master
Parser.php
Go to the documentation of this file.
1 <?php
67 use Psr\Log\LoggerInterface;
68 use Wikimedia\Bcp47Code\Bcp47CodeValue;
69 use Wikimedia\IPUtils;
70 use Wikimedia\Parsoid\Core\SectionMetadata;
71 use Wikimedia\Parsoid\Core\TOCData;
72 use Wikimedia\ScopedCallback;
73 
114 #[AllowDynamicProperties]
115 class Parser {
116 
117  # Flags for Parser::setFunctionHook
118  public const SFH_NO_HASH = 1;
119  public const SFH_OBJECT_ARGS = 2;
120 
121  # Constants needed for external link processing
129  public const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]';
134  // phpcs:ignore Generic.Files.LineLength
135  private const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
137  // phpcs:ignore Generic.Files.LineLength
138  private const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
139  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
140 
142  private const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
143 
148  public const PTD_FOR_INCLUSION = Preprocessor::DOM_FOR_INCLUSION;
149 
150  # Allowed values for $this->mOutputType
152  public const OT_HTML = 1;
154  public const OT_WIKI = 2;
156  public const OT_PREPROCESS = 3;
161  public const OT_PLAIN = 4;
162 
180  public const MARKER_SUFFIX = "-QINU`\"'\x7f";
181  public const MARKER_PREFIX = "\x7f'\"`UNIQ-";
182 
197  public const TOC_PLACEHOLDER = '<meta property="mw:PageProp/toc" />';
198 
206  private const TOC_PLACEHOLDER_REGEX = '/<meta\\b[^>]*\\bproperty\\s*=\\s*"mw:PageProp\\/toc"[^>]*\\/>/';
207 
208  # Persistent:
210  private array $mTagHooks = [];
212  private array $mFunctionHooks = [];
214  private array $mFunctionSynonyms = [ 0 => [], 1 => [] ];
216  private array $mStripList = [];
218  private array $mVarCache = [];
220  private array $mImageParams = [];
222  private array $mImageParamsMagicArray = [];
224  public $mMarkerIndex = 0;
225 
226  # Initialised by initializeVariables()
227  private MagicWordArray $mVariables;
228  private MagicWordArray $mSubstWords;
229 
230  # Initialised in constructor
231  private string $mExtLinkBracketedRegex;
232  private UrlUtils $urlUtils;
233  private Preprocessor $mPreprocessor;
234 
235  # Cleared with clearState():
236  private ParserOutput $mOutput;
237  private int $mAutonumber = 0;
238  private StripState $mStripState;
239  private LinkHolderArray $mLinkHolders;
240  private int $mLinkID = 0;
241  private array $mIncludeSizes;
246  private array $mTplRedirCache;
248  public array $mHeadings;
250  private array $mDoubleUnderscores;
256  private bool $mShowToc;
257  private bool $mForceTocPosition;
258  private array $mTplDomCache;
259  private ?UserIdentity $mUser;
260 
261  # Temporary
262  # These are variables reset at least once per parse regardless of $clearState
263 
268  public $mOptions;
269 
279  public $mTitle;
281  private int $mOutputType;
286  public $ot;
288  private ?int $mRevisionId = null;
290  private ?string $mRevisionTimestamp = null;
292  private ?string $mRevisionUser = null;
294  private ?int $mRevisionSize = null;
296  private $mInputSize = false;
297 
298  private ?RevisionRecord $mRevisionRecordObject = null;
299 
305  private array $mLangLinkLanguages;
306 
312  private ?MapCacheLRU $currentRevisionCache = null;
313 
318  private $mInParse = false;
319 
320  private SectionProfiler $mProfiler;
321  private ?LinkRenderer $mLinkRenderer = null;
322 
323  private MagicWordFactory $magicWordFactory;
324  private Language $contLang;
325  private LanguageConverterFactory $languageConverterFactory;
326  private ParserFactory $factory;
327  private SpecialPageFactory $specialPageFactory;
328  private TitleFormatter $titleFormatter;
334  private ServiceOptions $svcOptions;
335  private LinkRendererFactory $linkRendererFactory;
336  private NamespaceInfo $nsInfo;
337  private LoggerInterface $logger;
338  private BadFileLookup $badFileLookup;
339  private HookContainer $hookContainer;
340  private HookRunner $hookRunner;
341  private TidyDriverBase $tidy;
342  private WANObjectCache $wanCache;
343  private UserOptionsLookup $userOptionsLookup;
344  private UserFactory $userFactory;
345  private HttpRequestFactory $httpRequestFactory;
346  private TrackingCategories $trackingCategories;
347  private SignatureValidatorFactory $signatureValidatorFactory;
348  private UserNameUtils $userNameUtils;
349 
353  public const CONSTRUCTOR_OPTIONS = [
354  // See documentation for the corresponding config options
355  // Many of these are only used in (eg) CoreMagicVariables
356  MainConfigNames::AllowDisplayTitle,
357  MainConfigNames::AllowSlowParserFunctions,
358  MainConfigNames::ArticlePath,
359  MainConfigNames::EnableScaryTranscluding,
360  MainConfigNames::ExtraInterlanguageLinkPrefixes,
361  MainConfigNames::FragmentMode,
362  MainConfigNames::Localtimezone,
363  MainConfigNames::MaxSigChars,
364  MainConfigNames::MaxTocLevel,
365  MainConfigNames::MiserMode,
366  MainConfigNames::RawHtml,
367  MainConfigNames::ScriptPath,
368  MainConfigNames::Server,
369  MainConfigNames::ServerName,
370  MainConfigNames::ShowHostnames,
371  MainConfigNames::SignatureValidation,
372  MainConfigNames::Sitename,
373  MainConfigNames::StylePath,
374  MainConfigNames::TranscludeCacheExpiry,
375  MainConfigNames::PreprocessorCacheThreshold,
376  MainConfigNames::ParserEnableLegacyMediaDOM,
377  MainConfigNames::EnableParserLimitReporting,
378  ];
379 
406  public function __construct(
407  ServiceOptions $svcOptions,
408  MagicWordFactory $magicWordFactory,
409  Language $contLang,
410  ParserFactory $factory,
411  UrlUtils $urlUtils,
412  SpecialPageFactory $spFactory,
413  LinkRendererFactory $linkRendererFactory,
414  NamespaceInfo $nsInfo,
415  LoggerInterface $logger,
416  BadFileLookup $badFileLookup,
417  LanguageConverterFactory $languageConverterFactory,
418  HookContainer $hookContainer,
419  TidyDriverBase $tidy,
420  WANObjectCache $wanCache,
421  UserOptionsLookup $userOptionsLookup,
422  UserFactory $userFactory,
423  TitleFormatter $titleFormatter,
424  HttpRequestFactory $httpRequestFactory,
425  TrackingCategories $trackingCategories,
426  SignatureValidatorFactory $signatureValidatorFactory,
427  UserNameUtils $userNameUtils
428  ) {
429  if ( ParserFactory::$inParserFactory === 0 ) {
430  // Direct construction of Parser was deprecated in 1.34 and
431  // removed in 1.36; use a ParserFactory instead.
432  throw new BadMethodCallException( 'Direct construction of Parser not allowed' );
433  }
434  $svcOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
435  $this->svcOptions = $svcOptions;
436 
437  $this->urlUtils = $urlUtils;
438  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->urlUtils->validProtocols() . ')' .
439  self::EXT_LINK_ADDR .
440  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*)\]/Su';
441 
442  $this->magicWordFactory = $magicWordFactory;
443 
444  $this->contLang = $contLang;
445 
446  $this->factory = $factory;
447  $this->specialPageFactory = $spFactory;
448  $this->linkRendererFactory = $linkRendererFactory;
449  $this->nsInfo = $nsInfo;
450  $this->logger = $logger;
451  $this->badFileLookup = $badFileLookup;
452 
453  $this->languageConverterFactory = $languageConverterFactory;
454 
455  $this->hookContainer = $hookContainer;
456  $this->hookRunner = new HookRunner( $hookContainer );
457 
458  $this->tidy = $tidy;
459 
460  $this->wanCache = $wanCache;
461  $this->mPreprocessor = new Preprocessor_Hash(
462  $this,
463  $this->wanCache,
464  [
465  'cacheThreshold' => $svcOptions->get( MainConfigNames::PreprocessorCacheThreshold ),
466  'disableLangConversion' => $languageConverterFactory->isConversionDisabled(),
467  ]
468  );
469 
470  $this->userOptionsLookup = $userOptionsLookup;
471  $this->userFactory = $userFactory;
472  $this->titleFormatter = $titleFormatter;
473  $this->httpRequestFactory = $httpRequestFactory;
474  $this->trackingCategories = $trackingCategories;
475  $this->signatureValidatorFactory = $signatureValidatorFactory;
476  $this->userNameUtils = $userNameUtils;
477 
478  // These steps used to be done in "::firstCallInit()"
479  // (if you're chasing a reference from some old code)
481  $this,
483  );
485  $this,
487  );
488  $this->initializeVariables();
489 
490  $this->hookRunner->onParserFirstCallInit( $this );
491  }
492 
496  public function __destruct() {
497  // @phan-suppress-next-line PhanRedundantCondition Typed property not set in constructor, may be uninitialized
498  if ( isset( $this->mLinkHolders ) ) {
499  // @phan-suppress-next-line PhanTypeObjectUnsetDeclaredProperty
500  unset( $this->mLinkHolders );
501  }
502  // @phan-suppress-next-line PhanTypeSuspiciousNonTraversableForeach
503  foreach ( $this as $name => $value ) {
504  unset( $this->$name );
505  }
506  }
507 
511  public function __clone() {
512  $this->mInParse = false;
513 
514  // T58226: When you create a reference "to" an object field, that
515  // makes the object field itself be a reference too (until the other
516  // reference goes out of scope). When cloning, any field that's a
517  // reference is copied as a reference in the new object. Both of these
518  // are defined PHP5 behaviors, as inconvenient as it is for us when old
519  // hooks from PHP4 days are passing fields by reference.
520  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
521  // Make a non-reference copy of the field, then rebind the field to
522  // reference the new copy.
523  $tmp = $this->$k;
524  $this->$k =& $tmp;
525  unset( $tmp );
526  }
527 
528  $this->mPreprocessor = clone $this->mPreprocessor;
529  $this->mPreprocessor->resetParser( $this );
530 
531  $this->hookRunner->onParserCloned( $this );
532  }
533 
541  public function firstCallInit() {
542  /*
543  * This method should be hard-deprecated once remaining calls are
544  * removed; it no longer does anything.
545  */
546  }
547 
553  public function clearState() {
554  $this->resetOutput();
555  $this->mAutonumber = 0;
556  $this->mLinkHolders = new LinkHolderArray(
557  $this,
558  $this->getContentLanguageConverter(),
559  $this->getHookContainer()
560  );
561  $this->mLinkID = 0;
562  $this->mRevisionTimestamp = null;
563  $this->mRevisionId = null;
564  $this->mRevisionUser = null;
565  $this->mRevisionSize = null;
566  $this->mRevisionRecordObject = null;
567  $this->mVarCache = [];
568  $this->mUser = null;
569  $this->mLangLinkLanguages = [];
570  $this->currentRevisionCache = null;
571 
572  $this->mStripState = new StripState( $this );
573 
574  # Clear these on every parse, T6549
575  $this->mTplRedirCache = [];
576  $this->mTplDomCache = [];
577 
578  $this->mShowToc = true;
579  $this->mForceTocPosition = false;
580  $this->mIncludeSizes = [
581  'post-expand' => 0,
582  'arg' => 0,
583  ];
584  $this->mPPNodeCount = 0;
585  $this->mHighestExpansionDepth = 0;
586  $this->mHeadings = [];
587  $this->mDoubleUnderscores = [];
588  $this->mExpensiveFunctionCount = 0;
589 
590  $this->mProfiler = new SectionProfiler();
591 
592  $this->hookRunner->onParserClearState( $this );
593  }
594 
599  public function resetOutput() {
600  $this->mOutput = new ParserOutput;
601  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
602  }
603 
622  public function parse(
623  $text, PageReference $page, ParserOptions $options,
624  $linestart = true, $clearState = true, $revid = null
625  ) {
626  if ( $clearState ) {
627  // We use U+007F DELETE to construct strip markers, so we have to make
628  // sure that this character does not occur in the input text.
629  $text = strtr( $text, "\x7f", "?" );
630  $magicScopeVariable = $this->lock();
631  }
632  // Strip U+0000 NULL (T159174)
633  $text = str_replace( "\000", '', $text );
634 
635  $this->startParse( $page, $options, self::OT_HTML, $clearState );
636 
637  $this->currentRevisionCache = null;
638  $this->mInputSize = strlen( $text );
639  $this->mOutput->resetParseStartTime();
640 
641  $oldRevisionId = $this->mRevisionId;
642  $oldRevisionRecordObject = $this->mRevisionRecordObject;
643  $oldRevisionTimestamp = $this->mRevisionTimestamp;
644  $oldRevisionUser = $this->mRevisionUser;
645  $oldRevisionSize = $this->mRevisionSize;
646  if ( $revid !== null ) {
647  $this->mRevisionId = $revid;
648  $this->mRevisionRecordObject = null;
649  $this->mRevisionTimestamp = null;
650  $this->mRevisionUser = null;
651  $this->mRevisionSize = null;
652  }
653 
654  $text = $this->internalParse( $text );
655  $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
656 
657  $text = $this->internalParseHalfParsed( $text, true, $linestart );
658 
666  if ( !$options->getDisableTitleConversion()
667  && !isset( $this->mDoubleUnderscores['nocontentconvert'] )
668  && !isset( $this->mDoubleUnderscores['notitleconvert'] )
669  && $this->mOutput->getDisplayTitle() === false
670  ) {
671  $titleText = $this->getTargetLanguageConverter()->getConvRuleTitle();
672  if ( $titleText !== false ) {
673  $titleText = Sanitizer::removeSomeTags( $titleText );
674  } else {
675  [ $nsText, $nsSeparator, $mainText ] = $this->getTargetLanguageConverter()->convertSplitTitle( $page );
676  // In the future, those three pieces could be stored separately rather than joined into $titleText,
677  // and OutputPage would format them and join them together, to resolve T314399.
678  $titleText = self::formatPageTitle( $nsText, $nsSeparator, $mainText );
679  }
680  $this->mOutput->setTitleText( $titleText );
681  }
682 
683  # Compute runtime adaptive expiry if set
684  $this->mOutput->finalizeAdaptiveCacheExpiry();
685 
686  # Warn if too many heavyweight parser functions were used
687  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
688  $this->limitationWarn( 'expensive-parserfunction',
689  $this->mExpensiveFunctionCount,
690  $this->mOptions->getExpensiveParserFunctionLimit()
691  );
692  }
693 
694  # Information on limits, for the benefit of users who try to skirt them
695  if ( $this->svcOptions->get( MainConfigNames::EnableParserLimitReporting ) ) {
696  $this->makeLimitReport();
697  }
698 
699  # Wrap non-interface parser output in a <div> so it can be targeted
700  # with CSS (T37247)
701  $class = $this->mOptions->getWrapOutputClass();
702  if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
703  $this->mOutput->addWrapperDivClass( $class );
704  }
705 
706  $this->mOutput->setText( $text );
707 
708  $this->mRevisionId = $oldRevisionId;
709  $this->mRevisionRecordObject = $oldRevisionRecordObject;
710  $this->mRevisionTimestamp = $oldRevisionTimestamp;
711  $this->mRevisionUser = $oldRevisionUser;
712  $this->mRevisionSize = $oldRevisionSize;
713  $this->mInputSize = false;
714  $this->currentRevisionCache = null;
715 
716  return $this->mOutput;
717  }
718 
722  protected function makeLimitReport() {
723  $maxIncludeSize = $this->mOptions->getMaxIncludeSize();
724 
725  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
726  if ( $cpuTime !== null ) {
727  $this->mOutput->setLimitReportData( 'limitreport-cputime',
728  sprintf( "%.3f", $cpuTime )
729  );
730  }
731 
732  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
733  $this->mOutput->setLimitReportData( 'limitreport-walltime',
734  sprintf( "%.3f", $wallTime )
735  );
736 
737  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
738  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
739  );
740  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
741  [ $this->mIncludeSizes['post-expand'], $maxIncludeSize ]
742  );
743  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
744  [ $this->mIncludeSizes['arg'], $maxIncludeSize ]
745  );
746  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
747  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
748  );
749  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
750  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
751  );
752 
753  foreach ( $this->mStripState->getLimitReport() as [ $key, $value ] ) {
754  $this->mOutput->setLimitReportData( $key, $value );
755  }
756 
757  $this->hookRunner->onParserLimitReportPrepare( $this, $this->mOutput );
758 
759  // Add on template profiling data in human/machine readable way
760  $dataByFunc = $this->mProfiler->getFunctionStats();
761  uasort( $dataByFunc, static function ( $a, $b ) {
762  return $b['real'] <=> $a['real']; // descending order
763  } );
764  $profileReport = [];
765  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
766  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
767  $item['%real'], $item['real'], $item['calls'],
768  htmlspecialchars( $item['name'] ) );
769  }
770 
771  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
772 
773  // Add other cache related metadata
774  if ( $this->svcOptions->get( MainConfigNames::ShowHostnames ) ) {
775  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
776  }
777  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
778  $this->mOutput->getCacheTime() );
779  $this->mOutput->setLimitReportData( 'cachereport-ttl',
780  $this->mOutput->getCacheExpiry() );
781  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
782  $this->mOutput->hasReducedExpiry() );
783  }
784 
810  public function recursiveTagParse( $text, $frame = false ) {
811  $text = $this->internalParse( $text, false, $frame );
812  return $text;
813  }
814 
834  public function recursiveTagParseFully( $text, $frame = false ) {
835  $text = $this->recursiveTagParse( $text, $frame );
836  $text = $this->internalParseHalfParsed( $text, false );
837  return $text;
838  }
839 
859  public function parseExtensionTagAsTopLevelDoc( $text ) {
860  $text = $this->recursiveTagParse( $text );
861  $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
862  $text = $this->internalParseHalfParsed( $text, true );
863  return $text;
864  }
865 
878  public function preprocess(
879  $text,
880  ?PageReference $page,
881  ParserOptions $options,
882  $revid = null,
883  $frame = false
884  ) {
885  $magicScopeVariable = $this->lock();
886  $this->startParse( $page, $options, self::OT_PREPROCESS, true );
887  if ( $revid !== null ) {
888  $this->mRevisionId = $revid;
889  }
890  $this->hookRunner->onParserBeforePreprocess( $this, $text, $this->mStripState );
891  $text = $this->replaceVariables( $text, $frame );
892  $text = $this->mStripState->unstripBoth( $text );
893  return $text;
894  }
895 
905  public function recursivePreprocess( $text, $frame = false ) {
906  $text = $this->replaceVariables( $text, $frame );
907  $text = $this->mStripState->unstripBoth( $text );
908  return $text;
909  }
910 
925  public function getPreloadText( $text, PageReference $page, ParserOptions $options, $params = [] ) {
926  $msg = new RawMessage( $text );
927  $text = $msg->params( $params )->plain();
928 
929  # Parser (re)initialisation
930  $magicScopeVariable = $this->lock();
931  $this->startParse( $page, $options, self::OT_PLAIN, true );
932 
934  $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
935  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
936  $text = $this->mStripState->unstripBoth( $text );
937  return $text;
938  }
939 
947  public function setUser( ?UserIdentity $user ) {
948  $this->mUser = $user;
949  }
950 
958  public function setTitle( Title $t = null ) {
959  $this->setPage( $t );
960  }
961 
967  public function getTitle(): Title {
968  if ( !$this->mTitle ) {
969  $this->mTitle = Title::makeTitle( NS_SPECIAL, 'Badtitle/Parser' );
970  }
971  return $this->mTitle;
972  }
973 
980  public function setPage( ?PageReference $t = null ) {
981  if ( !$t ) {
982  $t = Title::makeTitle( NS_SPECIAL, 'Badtitle/Parser' );
983  } else {
984  // For now (early 1.37 alpha), always convert to Title, so we don't have to do it over
985  // and over again in other methods. Eventually, we will no longer need to have a Title
986  // instance internally.
987  $t = Title::castFromPageReference( $t );
988  }
989 
990  if ( $t->hasFragment() ) {
991  # Strip the fragment to avoid various odd effects
992  $this->mTitle = $t->createFragmentTarget( '' );
993  } else {
994  $this->mTitle = $t;
995  }
996  }
997 
1003  public function getPage(): ?PageReference {
1004  return $this->mTitle;
1005  }
1006 
1012  public function getOutputType(): int {
1013  return $this->mOutputType;
1014  }
1015 
1021  public function setOutputType( $ot ): void {
1022  $this->mOutputType = $ot;
1023  # Shortcut alias
1024  $this->ot = [
1025  'html' => $ot == self::OT_HTML,
1026  'wiki' => $ot == self::OT_WIKI,
1027  'pre' => $ot == self::OT_PREPROCESS,
1028  'plain' => $ot == self::OT_PLAIN,
1029  ];
1030  }
1031 
1039  public function OutputType( $x = null ) {
1040  wfDeprecated( __METHOD__, '1.35' );
1041  return wfSetVar( $this->mOutputType, $x );
1042  }
1043 
1048  public function getOutput() {
1049  return $this->mOutput;
1050  }
1051 
1056  public function getOptions() {
1057  return $this->mOptions;
1058  }
1059 
1065  public function setOptions( ParserOptions $options ): void {
1066  $this->mOptions = $options;
1067  }
1068 
1076  public function Options( $x = null ) {
1077  wfDeprecated( __METHOD__, '1.35' );
1078  return wfSetVar( $this->mOptions, $x );
1079  }
1080 
1085  public function nextLinkID() {
1086  return $this->mLinkID++;
1087  }
1088 
1093  public function setLinkID( $id ) {
1094  $this->mLinkID = $id;
1095  }
1096 
1103  public function getFunctionLang() {
1104  wfDeprecated( __METHOD__, '1.40' );
1105  return $this->getTargetLanguage();
1106  }
1107 
1116  public function getTargetLanguage() {
1117  $target = $this->mOptions->getTargetLanguage();
1118 
1119  if ( $target !== null ) {
1120  return $target;
1121  } elseif ( $this->mOptions->getInterfaceMessage() ) {
1122  return $this->mOptions->getUserLangObj();
1123  }
1124 
1125  return $this->getTitle()->getPageLanguage();
1126  }
1127 
1135  public function getUserIdentity(): UserIdentity {
1136  return $this->mUser ?? $this->getOptions()->getUserIdentity();
1137  }
1138 
1145  public function getPreprocessor() {
1146  return $this->mPreprocessor;
1147  }
1148 
1155  public function getLinkRenderer() {
1156  // XXX We make the LinkRenderer with current options and then cache it forever
1157  if ( !$this->mLinkRenderer ) {
1158  $this->mLinkRenderer = $this->linkRendererFactory->create();
1159  }
1160 
1161  return $this->mLinkRenderer;
1162  }
1163 
1170  public function getMagicWordFactory() {
1171  return $this->magicWordFactory;
1172  }
1173 
1180  public function getContentLanguage() {
1181  return $this->contLang;
1182  }
1183 
1190  public function getBadFileLookup() {
1191  return $this->badFileLookup;
1192  }
1193 
1213  public static function extractTagsAndParams( array $elements, $text, &$matches ) {
1214  static $n = 1;
1215  $stripped = '';
1216  $matches = [];
1217 
1218  $taglist = implode( '|', $elements );
1219  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i";
1220 
1221  while ( $text != '' ) {
1222  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1223  $stripped .= $p[0];
1224  if ( count( $p ) < 5 ) {
1225  break;
1226  }
1227  if ( count( $p ) > 5 ) {
1228  # comment
1229  $element = $p[4];
1230  $attributes = '';
1231  $close = '';
1232  $inside = $p[5];
1233  } else {
1234  # tag
1235  [ , $element, $attributes, $close, $inside ] = $p;
1236  }
1237 
1238  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
1239  $stripped .= $marker;
1240 
1241  if ( $close === '/>' ) {
1242  # Empty element tag, <tag />
1243  $content = null;
1244  $text = $inside;
1245  $tail = null;
1246  } else {
1247  if ( $element === '!--' ) {
1248  $end = '/(-->)/';
1249  } else {
1250  $end = "/(<\\/$element\\s*>)/i";
1251  }
1252  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1253  $content = $q[0];
1254  if ( count( $q ) < 3 ) {
1255  # No end tag -- let it run out to the end of the text.
1256  $tail = '';
1257  $text = '';
1258  } else {
1259  [ , $tail, $text ] = $q;
1260  }
1261  }
1262 
1263  $matches[$marker] = [ $element,
1264  $content,
1265  Sanitizer::decodeTagAttributes( $attributes ),
1266  "<$element$attributes$close$content$tail" ];
1267  }
1268  return $stripped;
1269  }
1270 
1276  public function getStripList() {
1277  return $this->mStripList;
1278  }
1279 
1284  public function getStripState() {
1285  return $this->mStripState;
1286  }
1287 
1297  public function insertStripItem( $text ) {
1298  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1299  $this->mMarkerIndex++;
1300  $this->mStripState->addGeneral( $marker, $text );
1301  return $marker;
1302  }
1303 
1310  private function handleTables( $text ) {
1311  $lines = StringUtils::explode( "\n", $text );
1312  $out = '';
1313  $td_history = []; # Is currently a td tag open?
1314  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1315  $tr_history = []; # Is currently a tr tag open?
1316  $tr_attributes = []; # history of tr attributes
1317  $has_opened_tr = []; # Did this table open a <tr> element?
1318  $indent_level = 0; # indent level of the table
1319 
1320  foreach ( $lines as $outLine ) {
1321  $line = trim( $outLine );
1322 
1323  if ( $line === '' ) { # empty line, go to next line
1324  $out .= $outLine . "\n";
1325  continue;
1326  }
1327 
1328  $first_character = $line[0];
1329  $first_two = substr( $line, 0, 2 );
1330  $matches = [];
1331 
1332  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1333  # First check if we are starting a new table
1334  $indent_level = strlen( $matches[1] );
1335 
1336  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1337  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1338 
1339  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1340  $td_history[] = false;
1341  $last_tag_history[] = '';
1342  $tr_history[] = false;
1343  $tr_attributes[] = '';
1344  $has_opened_tr[] = false;
1345  } elseif ( count( $td_history ) == 0 ) {
1346  # Don't do any of the following
1347  $out .= $outLine . "\n";
1348  continue;
1349  } elseif ( $first_two === '|}' ) {
1350  # We are ending a table
1351  $line = '</table>' . substr( $line, 2 );
1352  $last_tag = array_pop( $last_tag_history );
1353 
1354  if ( !array_pop( $has_opened_tr ) ) {
1355  $line = "<tr><td></td></tr>{$line}";
1356  }
1357 
1358  if ( array_pop( $tr_history ) ) {
1359  $line = "</tr>{$line}";
1360  }
1361 
1362  if ( array_pop( $td_history ) ) {
1363  $line = "</{$last_tag}>{$line}";
1364  }
1365  array_pop( $tr_attributes );
1366  if ( $indent_level > 0 ) {
1367  $outLine = rtrim( $line ) . str_repeat( '</dd></dl>', $indent_level );
1368  } else {
1369  $outLine = $line;
1370  }
1371  } elseif ( $first_two === '|-' ) {
1372  # Now we have a table row
1373  $line = preg_replace( '#^\|-+#', '', $line );
1374 
1375  # Whats after the tag is now only attributes
1376  $attributes = $this->mStripState->unstripBoth( $line );
1377  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1378  array_pop( $tr_attributes );
1379  $tr_attributes[] = $attributes;
1380 
1381  $line = '';
1382  $last_tag = array_pop( $last_tag_history );
1383  array_pop( $has_opened_tr );
1384  $has_opened_tr[] = true;
1385 
1386  if ( array_pop( $tr_history ) ) {
1387  $line = '</tr>';
1388  }
1389 
1390  if ( array_pop( $td_history ) ) {
1391  $line = "</{$last_tag}>{$line}";
1392  }
1393 
1394  $outLine = $line;
1395  $tr_history[] = false;
1396  $td_history[] = false;
1397  $last_tag_history[] = '';
1398  } elseif ( $first_character === '|'
1399  || $first_character === '!'
1400  || $first_two === '|+'
1401  ) {
1402  # This might be cell elements, td, th or captions
1403  if ( $first_two === '|+' ) {
1404  $first_character = '+';
1405  $line = substr( $line, 2 );
1406  } else {
1407  $line = substr( $line, 1 );
1408  }
1409 
1410  // Implies both are valid for table headings.
1411  if ( $first_character === '!' ) {
1412  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1413  }
1414 
1415  # Split up multiple cells on the same line.
1416  # FIXME : This can result in improper nesting of tags processed
1417  # by earlier parser steps.
1418  $cells = explode( '||', $line );
1419 
1420  $outLine = '';
1421 
1422  # Loop through each table cell
1423  foreach ( $cells as $cell ) {
1424  $previous = '';
1425  if ( $first_character !== '+' ) {
1426  $tr_after = array_pop( $tr_attributes );
1427  if ( !array_pop( $tr_history ) ) {
1428  $previous = "<tr{$tr_after}>\n";
1429  }
1430  $tr_history[] = true;
1431  $tr_attributes[] = '';
1432  array_pop( $has_opened_tr );
1433  $has_opened_tr[] = true;
1434  }
1435 
1436  $last_tag = array_pop( $last_tag_history );
1437 
1438  if ( array_pop( $td_history ) ) {
1439  $previous = "</{$last_tag}>\n{$previous}";
1440  }
1441 
1442  if ( $first_character === '|' ) {
1443  $last_tag = 'td';
1444  } elseif ( $first_character === '!' ) {
1445  $last_tag = 'th';
1446  } elseif ( $first_character === '+' ) {
1447  $last_tag = 'caption';
1448  } else {
1449  $last_tag = '';
1450  }
1451 
1452  $last_tag_history[] = $last_tag;
1453 
1454  # A cell could contain both parameters and data
1455  $cell_data = explode( '|', $cell, 2 );
1456 
1457  # T2553: Note that a '|' inside an invalid link should not
1458  # be mistaken as delimiting cell parameters
1459  # Bug T153140: Neither should language converter markup.
1460  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1461  $cell = "{$previous}<{$last_tag}>" . trim( $cell );
1462  } elseif ( count( $cell_data ) == 1 ) {
1463  // Whitespace in cells is trimmed
1464  $cell = "{$previous}<{$last_tag}>" . trim( $cell_data[0] );
1465  } else {
1466  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1467  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1468  // Whitespace in cells is trimmed
1469  $cell = "{$previous}<{$last_tag}{$attributes}>" . trim( $cell_data[1] );
1470  }
1471 
1472  $outLine .= $cell;
1473  $td_history[] = true;
1474  }
1475  }
1476  $out .= $outLine . "\n";
1477  }
1478 
1479  # Closing open td, tr && table
1480  while ( count( $td_history ) > 0 ) {
1481  if ( array_pop( $td_history ) ) {
1482  $out .= "</td>\n";
1483  }
1484  if ( array_pop( $tr_history ) ) {
1485  $out .= "</tr>\n";
1486  }
1487  if ( !array_pop( $has_opened_tr ) ) {
1488  $out .= "<tr><td></td></tr>\n";
1489  }
1490 
1491  $out .= "</table>\n";
1492  }
1493 
1494  # Remove trailing line-ending (b/c)
1495  if ( substr( $out, -1 ) === "\n" ) {
1496  $out = substr( $out, 0, -1 );
1497  }
1498 
1499  # special case: don't return empty table
1500  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1501  $out = '';
1502  }
1503 
1504  return $out;
1505  }
1506 
1520  public function internalParse( $text, $isMain = true, $frame = false ) {
1521  $origText = $text;
1522 
1523  # Hook to suspend the parser in this state
1524  if ( !$this->hookRunner->onParserBeforeInternalParse( $this, $text, $this->mStripState ) ) {
1525  return $text;
1526  }
1527 
1528  # if $frame is provided, then use $frame for replacing any variables
1529  if ( $frame ) {
1530  # use frame depth to infer how include/noinclude tags should be handled
1531  # depth=0 means this is the top-level document; otherwise it's an included document
1532  if ( !$frame->depth ) {
1533  $flag = 0;
1534  } else {
1536  }
1537  $dom = $this->preprocessToDom( $text, $flag );
1538  $text = $frame->expand( $dom );
1539  } else {
1540  # if $frame is not provided, then use old-style replaceVariables
1541  $text = $this->replaceVariables( $text );
1542  }
1543 
1544  $text = Sanitizer::internalRemoveHtmlTags(
1545  $text,
1546  // Callback from the Sanitizer for expanding items found in
1547  // HTML attribute values, so they can be safely tested and escaped.
1548  function ( &$text, $frame = false ) {
1549  $text = $this->replaceVariables( $text, $frame );
1550  $text = $this->mStripState->unstripBoth( $text );
1551  },
1552  false,
1553  [],
1554  []
1555  );
1556  $this->hookRunner->onInternalParseBeforeLinks( $this, $text, $this->mStripState );
1557 
1558  # Tables need to come after variable replacement for things to work
1559  # properly; putting them before other transformations should keep
1560  # exciting things like link expansions from showing up in surprising
1561  # places.
1562  $text = $this->handleTables( $text );
1563 
1564  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1565 
1566  $text = $this->handleDoubleUnderscore( $text );
1567 
1568  $text = $this->handleHeadings( $text );
1569  $text = $this->handleInternalLinks( $text );
1570  $text = $this->handleAllQuotes( $text );
1571  $text = $this->handleExternalLinks( $text );
1572 
1573  # handleInternalLinks may sometimes leave behind
1574  # absolute URLs, which have to be masked to hide them from handleExternalLinks
1575  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1576 
1577  $text = $this->handleMagicLinks( $text );
1578  $text = $this->finalizeHeadings( $text, $origText, $isMain );
1579 
1580  return $text;
1581  }
1582 
1590  return $this->languageConverterFactory->getLanguageConverter(
1591  $this->getTargetLanguage()
1592  );
1593  }
1594 
1600  private function getContentLanguageConverter(): ILanguageConverter {
1601  return $this->languageConverterFactory->getLanguageConverter(
1602  $this->getContentLanguage()
1603  );
1604  }
1605 
1613  protected function getHookContainer() {
1614  return $this->hookContainer;
1615  }
1616 
1625  protected function getHookRunner() {
1626  return $this->hookRunner;
1627  }
1628 
1638  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1639  $text = $this->mStripState->unstripGeneral( $text );
1640 
1641  $text = BlockLevelPass::doBlockLevels( $text, $linestart );
1642 
1643  $this->replaceLinkHoldersPrivate( $text );
1644 
1652  $converter = null;
1653  if ( !( $this->mOptions->getDisableContentConversion()
1654  || isset( $this->mDoubleUnderscores['nocontentconvert'] )
1655  || $this->mOptions->getInterfaceMessage() )
1656  ) {
1657  # The position of the convert() call should not be changed. it
1658  # assumes that the links are all replaced and the only thing left
1659  # is the <nowiki> mark.
1660  $converter = $this->getTargetLanguageConverter();
1661  $text = $converter->convert( $text );
1662  // TOC will be converted below.
1663  }
1664  // Convert the TOC. This is done *after* the main text
1665  // so that all the editor-defined conversion rules (by convention
1666  // defined at the start of the article) are applied to the TOC
1667  self::localizeTOC(
1668  $this->mOutput->getTOCData(),
1669  $this->getTargetLanguage(),
1670  $converter // null if conversion is to be suppressed.
1671  );
1672  if ( $converter ) {
1673  $this->mOutput->setLanguage( new Bcp47CodeValue(
1674  LanguageCode::bcp47( $converter->getPreferredVariant() )
1675  ) );
1676  } else {
1677  $this->mOutput->setLanguage( $this->getTargetLanguage() );
1678  }
1679 
1680  $text = $this->mStripState->unstripNoWiki( $text );
1681 
1682  $text = $this->mStripState->unstripGeneral( $text );
1683 
1684  $text = $this->tidy->tidy( $text, [ Sanitizer::class, 'armorFrenchSpaces' ] );
1685 
1686  if ( $isMain ) {
1687  $this->hookRunner->onParserAfterTidy( $this, $text );
1688  }
1689 
1690  return $text;
1691  }
1692 
1703  private function handleMagicLinks( $text ) {
1704  $prots = $this->urlUtils->validAbsoluteProtocols();
1705  $urlChar = self::EXT_LINK_URL_CLASS;
1706  $addr = self::EXT_LINK_ADDR;
1707  $space = self::SPACE_NOT_NL; # non-newline space
1708  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1709  $spaces = "$space++"; # possessive match of 1 or more spaces
1710  $text = preg_replace_callback(
1711  '!(?: # Start cases
1712  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1713  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1714  (\b # m[3]: Free external links
1715  (?i:$prots)
1716  ($addr$urlChar*) # m[4]: Post-protocol path
1717  ) |
1718  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1719  ([0-9]+)\b |
1720  \bISBN $spaces ( # m[6]: ISBN, capture number
1721  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1722  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1723  [0-9Xx] # check digit
1724  )\b
1725  )!xu",
1726  [ $this, 'magicLinkCallback' ],
1727  $text
1728  );
1729  return $text;
1730  }
1731 
1736  private function magicLinkCallback( array $m ) {
1737  if ( isset( $m[1] ) && $m[1] !== '' ) {
1738  # Skip anchor
1739  return $m[0];
1740  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1741  # Skip HTML element
1742  return $m[0];
1743  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1744  # Free external link
1745  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1746  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1747  # RFC or PMID
1748  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1749  if ( !$this->mOptions->getMagicRFCLinks() ) {
1750  return $m[0];
1751  }
1752  $keyword = 'RFC';
1753  $urlmsg = 'rfcurl';
1754  $cssClass = 'mw-magiclink-rfc';
1755  $trackingCat = 'magiclink-tracking-rfc';
1756  $id = $m[5];
1757  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1758  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1759  return $m[0];
1760  }
1761  $keyword = 'PMID';
1762  $urlmsg = 'pubmedurl';
1763  $cssClass = 'mw-magiclink-pmid';
1764  $trackingCat = 'magiclink-tracking-pmid';
1765  $id = $m[5];
1766  } else {
1767  // Should never happen
1768  throw new UnexpectedValueException( __METHOD__ . ': unrecognised match type "' .
1769  substr( $m[0], 0, 20 ) . '"' );
1770  }
1771  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1772  $this->addTrackingCategory( $trackingCat );
1773  return Linker::makeExternalLink(
1774  $url,
1775  "{$keyword} {$id}",
1776  true,
1777  $cssClass,
1778  [],
1779  $this->getTitle()
1780  );
1781  } elseif ( isset( $m[6] ) && $m[6] !== ''
1782  && $this->mOptions->getMagicISBNLinks()
1783  ) {
1784  # ISBN
1785  $isbn = $m[6];
1786  $space = self::SPACE_NOT_NL; # non-newline space
1787  $isbn = preg_replace( "/$space/", ' ', $isbn );
1788  $num = strtr( $isbn, [
1789  '-' => '',
1790  ' ' => '',
1791  'x' => 'X',
1792  ] );
1793  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1794  return $this->getLinkRenderer()->makeKnownLink(
1795  SpecialPage::getTitleFor( 'Booksources', $num ),
1796  "ISBN $isbn",
1797  [
1798  'class' => 'internal mw-magiclink-isbn',
1799  'title' => false // suppress title attribute
1800  ]
1801  );
1802  } else {
1803  return $m[0];
1804  }
1805  }
1806 
1816  private function makeFreeExternalLink( $url, $numPostProto ) {
1817  $trail = '';
1818 
1819  # The characters '<' and '>' (which were escaped by
1820  # internalRemoveHtmlTags()) should not be included in
1821  # URLs, per RFC 2396.
1822  # Make &nbsp; terminate a URL as well (bug T84937)
1823  $m2 = [];
1824  if ( preg_match(
1825  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1826  $url,
1827  $m2,
1828  PREG_OFFSET_CAPTURE
1829  ) ) {
1830  $trail = substr( $url, $m2[0][1] ) . $trail;
1831  $url = substr( $url, 0, $m2[0][1] );
1832  }
1833 
1834  # Move trailing punctuation to $trail
1835  $sep = ',;\.:!?';
1836  # If there is no left bracket, then consider right brackets fair game too
1837  if ( strpos( $url, '(' ) === false ) {
1838  $sep .= ')';
1839  }
1840 
1841  $urlRev = strrev( $url );
1842  $numSepChars = strspn( $urlRev, $sep );
1843  # Don't break a trailing HTML entity by moving the ; into $trail
1844  # This is in hot code, so use substr_compare to avoid having to
1845  # create a new string object for the comparison
1846  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1847  # more optimization: instead of running preg_match with a $
1848  # anchor, which can be slow, do the match on the reversed
1849  # string starting at the desired offset.
1850  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1851  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1852  $numSepChars--;
1853  }
1854  }
1855  if ( $numSepChars ) {
1856  $trail = substr( $url, -$numSepChars ) . $trail;
1857  $url = substr( $url, 0, -$numSepChars );
1858  }
1859 
1860  # Verify that we still have a real URL after trail removal, and
1861  # not just lone protocol
1862  if ( strlen( $trail ) >= $numPostProto ) {
1863  return $url . $trail;
1864  }
1865 
1866  $url = Sanitizer::cleanUrl( $url );
1867 
1868  # Is this an external image?
1869  $text = $this->maybeMakeExternalImage( $url );
1870  if ( $text === false ) {
1871  # Not an image, make a link
1872  $text = Linker::makeExternalLink(
1873  $url,
1874  $this->getTargetLanguageConverter()->markNoConversion( $url ),
1875  true,
1876  'free',
1877  $this->getExternalLinkAttribs( $url ),
1878  $this->getTitle()
1879  );
1880  # Register it in the output object...
1881  $this->mOutput->addExternalLink( $url );
1882  }
1883  return $text . $trail;
1884  }
1885 
1892  private function handleHeadings( $text ) {
1893  for ( $i = 6; $i >= 1; --$i ) {
1894  $h = str_repeat( '=', $i );
1895  // Trim non-newline whitespace from headings
1896  // Using \s* will break for: "==\n===\n" and parse as <h2>=</h2>
1897  $text = preg_replace( "/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m", "<h$i>\\1</h$i>", $text );
1898  }
1899  return $text;
1900  }
1901 
1909  private function handleAllQuotes( $text ) {
1910  $outtext = '';
1911  $lines = StringUtils::explode( "\n", $text );
1912  foreach ( $lines as $line ) {
1913  $outtext .= $this->doQuotes( $line ) . "\n";
1914  }
1915  $outtext = substr( $outtext, 0, -1 );
1916  return $outtext;
1917  }
1918 
1927  public function doQuotes( $text ) {
1928  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1929  $countarr = count( $arr );
1930  if ( $countarr == 1 ) {
1931  return $text;
1932  }
1933 
1934  // First, do some preliminary work. This may shift some apostrophes from
1935  // being mark-up to being text. It also counts the number of occurrences
1936  // of bold and italics mark-ups.
1937  $numbold = 0;
1938  $numitalics = 0;
1939  for ( $i = 1; $i < $countarr; $i += 2 ) {
1940  $thislen = strlen( $arr[$i] );
1941  // If there are ever four apostrophes, assume the first is supposed to
1942  // be text, and the remaining three constitute mark-up for bold text.
1943  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1944  if ( $thislen == 4 ) {
1945  $arr[$i - 1] .= "'";
1946  $arr[$i] = "'''";
1947  $thislen = 3;
1948  } elseif ( $thislen > 5 ) {
1949  // If there are more than 5 apostrophes in a row, assume they're all
1950  // text except for the last 5.
1951  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1952  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1953  $arr[$i] = "'''''";
1954  $thislen = 5;
1955  }
1956  // Count the number of occurrences of bold and italics mark-ups.
1957  if ( $thislen == 2 ) {
1958  $numitalics++;
1959  } elseif ( $thislen == 3 ) {
1960  $numbold++;
1961  } elseif ( $thislen == 5 ) {
1962  $numitalics++;
1963  $numbold++;
1964  }
1965  }
1966 
1967  // If there is an odd number of both bold and italics, it is likely
1968  // that one of the bold ones was meant to be an apostrophe followed
1969  // by italics. Which one we cannot know for certain, but it is more
1970  // likely to be one that has a single-letter word before it.
1971  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1972  $firstsingleletterword = -1;
1973  $firstmultiletterword = -1;
1974  $firstspace = -1;
1975  for ( $i = 1; $i < $countarr; $i += 2 ) {
1976  if ( strlen( $arr[$i] ) == 3 ) {
1977  $x1 = substr( $arr[$i - 1], -1 );
1978  $x2 = substr( $arr[$i - 1], -2, 1 );
1979  if ( $x1 === ' ' ) {
1980  if ( $firstspace == -1 ) {
1981  $firstspace = $i;
1982  }
1983  } elseif ( $x2 === ' ' ) {
1984  $firstsingleletterword = $i;
1985  // if $firstsingleletterword is set, we don't
1986  // look at the other options, so we can bail early.
1987  break;
1988  } elseif ( $firstmultiletterword == -1 ) {
1989  $firstmultiletterword = $i;
1990  }
1991  }
1992  }
1993 
1994  // If there is a single-letter word, use it!
1995  if ( $firstsingleletterword > -1 ) {
1996  $arr[$firstsingleletterword] = "''";
1997  $arr[$firstsingleletterword - 1] .= "'";
1998  } elseif ( $firstmultiletterword > -1 ) {
1999  // If not, but there's a multi-letter word, use that one.
2000  $arr[$firstmultiletterword] = "''";
2001  $arr[$firstmultiletterword - 1] .= "'";
2002  } elseif ( $firstspace > -1 ) {
2003  // ... otherwise use the first one that has neither.
2004  // (notice that it is possible for all three to be -1 if, for example,
2005  // there is only one pentuple-apostrophe in the line)
2006  $arr[$firstspace] = "''";
2007  $arr[$firstspace - 1] .= "'";
2008  }
2009  }
2010 
2011  // Now let's actually convert our apostrophic mush to HTML!
2012  $output = '';
2013  $buffer = '';
2014  $state = '';
2015  $i = 0;
2016  foreach ( $arr as $r ) {
2017  if ( ( $i % 2 ) == 0 ) {
2018  if ( $state === 'both' ) {
2019  $buffer .= $r;
2020  } else {
2021  $output .= $r;
2022  }
2023  } else {
2024  $thislen = strlen( $r );
2025  if ( $thislen == 2 ) {
2026  // two quotes - open or close italics
2027  if ( $state === 'i' ) {
2028  $output .= '</i>';
2029  $state = '';
2030  } elseif ( $state === 'bi' ) {
2031  $output .= '</i>';
2032  $state = 'b';
2033  } elseif ( $state === 'ib' ) {
2034  $output .= '</b></i><b>';
2035  $state = 'b';
2036  } elseif ( $state === 'both' ) {
2037  $output .= '<b><i>' . $buffer . '</i>';
2038  $state = 'b';
2039  } else { // $state can be 'b' or ''
2040  $output .= '<i>';
2041  $state .= 'i';
2042  }
2043  } elseif ( $thislen == 3 ) {
2044  // three quotes - open or close bold
2045  if ( $state === 'b' ) {
2046  $output .= '</b>';
2047  $state = '';
2048  } elseif ( $state === 'bi' ) {
2049  $output .= '</i></b><i>';
2050  $state = 'i';
2051  } elseif ( $state === 'ib' ) {
2052  $output .= '</b>';
2053  $state = 'i';
2054  } elseif ( $state === 'both' ) {
2055  $output .= '<i><b>' . $buffer . '</b>';
2056  $state = 'i';
2057  } else { // $state can be 'i' or ''
2058  $output .= '<b>';
2059  $state .= 'b';
2060  }
2061  } elseif ( $thislen == 5 ) {
2062  // five quotes - open or close both separately
2063  if ( $state === 'b' ) {
2064  $output .= '</b><i>';
2065  $state = 'i';
2066  } elseif ( $state === 'i' ) {
2067  $output .= '</i><b>';
2068  $state = 'b';
2069  } elseif ( $state === 'bi' ) {
2070  $output .= '</i></b>';
2071  $state = '';
2072  } elseif ( $state === 'ib' ) {
2073  $output .= '</b></i>';
2074  $state = '';
2075  } elseif ( $state === 'both' ) {
2076  $output .= '<i><b>' . $buffer . '</b></i>';
2077  $state = '';
2078  } else { // ($state == '')
2079  $buffer = '';
2080  $state = 'both';
2081  }
2082  }
2083  }
2084  $i++;
2085  }
2086  // Now close all remaining tags. Notice that the order is important.
2087  if ( $state === 'b' || $state === 'ib' ) {
2088  $output .= '</b>';
2089  }
2090  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
2091  $output .= '</i>';
2092  }
2093  if ( $state === 'bi' ) {
2094  $output .= '</b>';
2095  }
2096  // There might be lonely ''''', so make sure we have a buffer
2097  if ( $state === 'both' && $buffer ) {
2098  $output .= '<b><i>' . $buffer . '</i></b>';
2099  }
2100  return $output;
2101  }
2102 
2112  private function handleExternalLinks( $text ) {
2113  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
2114  // @phan-suppress-next-line PhanTypeComparisonFromArray See phan issue #3161
2115  if ( $bits === false ) {
2116  throw new RuntimeException( "PCRE failure" );
2117  }
2118  $s = array_shift( $bits );
2119 
2120  $i = 0;
2121  while ( $i < count( $bits ) ) {
2122  $url = $bits[$i++];
2123  $i++; // protocol
2124  $text = $bits[$i++];
2125  $trail = $bits[$i++];
2126 
2127  # The characters '<' and '>' (which were escaped by
2128  # internalRemoveHtmlTags()) should not be included in
2129  # URLs, per RFC 2396.
2130  $m2 = [];
2131  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
2132  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
2133  $url = substr( $url, 0, $m2[0][1] );
2134  }
2135 
2136  # If the link text is an image URL, replace it with an <img> tag
2137  # This happened by accident in the original parser, but some people used it extensively
2138  $img = $this->maybeMakeExternalImage( $text );
2139  if ( $img !== false ) {
2140  $text = $img;
2141  }
2142 
2143  $dtrail = '';
2144 
2145  # Set linktype for CSS
2146  $linktype = 'text';
2147 
2148  # No link text, e.g. [http://domain.tld/some.link]
2149  if ( $text == '' ) {
2150  # Autonumber
2151  $langObj = $this->getTargetLanguage();
2152  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
2153  $linktype = 'autonumber';
2154  } else {
2155  # Have link text, e.g. [http://domain.tld/some.link text]s
2156  # Check for trail
2157  [ $dtrail, $trail ] = Linker::splitTrail( $trail );
2158  }
2159 
2160  // Excluding protocol-relative URLs may avoid many false positives.
2161  if ( preg_match( '/^(?:' . $this->urlUtils->validAbsoluteProtocols() . ')/', $text ) ) {
2162  $text = $this->getTargetLanguageConverter()->markNoConversion( $text );
2163  }
2164 
2165  $url = Sanitizer::cleanUrl( $url );
2166 
2167  # Use the encoded URL
2168  # This means that users can paste URLs directly into the text
2169  # Funny characters like ö aren't valid in URLs anyway
2170  # This was changed in August 2004
2171  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
2172  $this->getExternalLinkAttribs( $url ), $this->getTitle() ) . $dtrail . $trail;
2173 
2174  # Register link in the output object.
2175  $this->mOutput->addExternalLink( $url );
2176  }
2177 
2178  // @phan-suppress-next-line PhanTypeMismatchReturnNullable False positive from array_shift
2179  return $s;
2180  }
2181 
2192  public static function getExternalLinkRel( $url = false, LinkTarget $title = null ) {
2193  $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
2194  $noFollowLinks = $mainConfig->get( MainConfigNames::NoFollowLinks );
2195  $noFollowNsExceptions = $mainConfig->get( MainConfigNames::NoFollowNsExceptions );
2196  $noFollowDomainExceptions = $mainConfig->get( MainConfigNames::NoFollowDomainExceptions );
2197  $ns = $title ? $title->getNamespace() : false;
2198  if ( $noFollowLinks && !in_array( $ns, $noFollowNsExceptions )
2199  && !wfMatchesDomainList( $url, $noFollowDomainExceptions )
2200  ) {
2201  return 'nofollow';
2202  }
2203  return null;
2204  }
2205 
2217  public function getExternalLinkAttribs( $url ) {
2218  $attribs = [];
2219  $rel = self::getExternalLinkRel( $url, $this->getTitle() );
2220 
2221  $target = $this->mOptions->getExternalLinkTarget();
2222  if ( $target ) {
2223  $attribs['target'] = $target;
2224  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
2225  // T133507. New windows can navigate parent cross-origin.
2226  // Including noreferrer due to lacking browser
2227  // support of noopener. Eventually noreferrer should be removed.
2228  if ( $rel !== '' ) {
2229  $rel .= ' ';
2230  }
2231  $rel .= 'noreferrer noopener';
2232  }
2233  }
2234  $attribs['rel'] = $rel;
2235  return $attribs;
2236  }
2237 
2248  public static function normalizeLinkUrl( $url ) {
2249  # Test for RFC 3986 IPv6 syntax
2250  $scheme = '[a-z][a-z0-9+.-]*:';
2251  $userinfo = '(?:[a-z0-9\-._~!$&\'()*+,;=:]|%[0-9a-f]{2})*';
2252  $ipv6Host = '\\[((?:[0-9a-f:]|%3[0-A]|%[46][1-6])+)\\]';
2253  if ( preg_match( "<^(?:{$scheme})?//(?:{$userinfo}@)?{$ipv6Host}(?:[:/?#].*|)$>i", $url, $m ) &&
2254  IPUtils::isValid( rawurldecode( $m[1] ) )
2255  ) {
2256  $isIPv6 = rawurldecode( $m[1] );
2257  } else {
2258  $isIPv6 = false;
2259  }
2260 
2261  # Make sure unsafe characters are encoded
2262  $url = preg_replace_callback(
2263  '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
2264  static function ( $m ) {
2265  return rawurlencode( $m[0] );
2266  },
2267  $url
2268  );
2269 
2270  $ret = '';
2271  $end = strlen( $url );
2272 
2273  # Fragment part - 'fragment'
2274  $start = strpos( $url, '#' );
2275  if ( $start !== false && $start < $end ) {
2276  $ret = self::normalizeUrlComponent(
2277  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
2278  $end = $start;
2279  }
2280 
2281  # Query part - 'query' minus &=+;
2282  $start = strpos( $url, '?' );
2283  if ( $start !== false && $start < $end ) {
2284  $ret = self::normalizeUrlComponent(
2285  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
2286  $end = $start;
2287  }
2288 
2289  # Scheme and path part - 'pchar'
2290  # (we assume no userinfo or encoded colons in the host)
2291  $ret = self::normalizeUrlComponent(
2292  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
2293 
2294  # Fix IPv6 syntax
2295  if ( $isIPv6 !== false ) {
2296  $ipv6Host = "%5B({$isIPv6})%5D";
2297  $ret = preg_replace(
2298  "<^((?:{$scheme})?//(?:{$userinfo}@)?){$ipv6Host}(?=[:/?#]|$)>i",
2299  "$1[$2]",
2300  $ret
2301  );
2302  }
2303 
2304  return $ret;
2305  }
2306 
2307  private static function normalizeUrlComponent( $component, $unsafe ) {
2308  $callback = static function ( $matches ) use ( $unsafe ) {
2309  $char = urldecode( $matches[0] );
2310  $ord = ord( $char );
2311  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2312  # Unescape it
2313  return $char;
2314  } else {
2315  # Leave it escaped, but use uppercase for a-f
2316  return strtoupper( $matches[0] );
2317  }
2318  };
2319  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2320  }
2321 
2330  private function maybeMakeExternalImage( $url ) {
2331  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2332  $imagesexception = (bool)$imagesfrom;
2333  $text = false;
2334  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2335  if ( $imagesexception && is_array( $imagesfrom ) ) {
2336  $imagematch = false;
2337  foreach ( $imagesfrom as $match ) {
2338  if ( strpos( $url, $match ) === 0 ) {
2339  $imagematch = true;
2340  break;
2341  }
2342  }
2343  } elseif ( $imagesexception ) {
2344  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2345  } else {
2346  $imagematch = false;
2347  }
2348 
2349  if ( $this->mOptions->getAllowExternalImages()
2350  || ( $imagesexception && $imagematch )
2351  ) {
2352  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2353  # Image found
2354  $text = Linker::makeExternalImage( $url );
2355  }
2356  }
2357  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2358  && preg_match( self::EXT_IMAGE_REGEX, $url )
2359  ) {
2360  $whitelist = explode(
2361  "\n",
2362  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2363  );
2364 
2365  foreach ( $whitelist as $entry ) {
2366  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2367  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2368  continue;
2369  }
2370  // @phan-suppress-next-line SecurityCheck-ReDoS preg_quote is not wanted here
2371  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2372  # Image matches a whitelist entry
2373  $text = Linker::makeExternalImage( $url );
2374  break;
2375  }
2376  }
2377  }
2378  return $text;
2379  }
2380 
2388  private function handleInternalLinks( $text ) {
2389  $this->mLinkHolders->merge( $this->handleInternalLinks2( $text ) );
2390  return $text;
2391  }
2392 
2398  private function handleInternalLinks2( &$s ) {
2399  static $tc = false, $e1, $e1_img;
2400  # the % is needed to support urlencoded titles as well
2401  if ( !$tc ) {
2402  $tc = Title::legalChars() . '#%';
2403  # Match a link having the form [[namespace:link|alternate]]trail
2404  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2405  # Match cases where there is no "]]", which might still be images
2406  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2407  }
2408 
2409  $holders = new LinkHolderArray(
2410  $this,
2411  $this->getContentLanguageConverter(),
2412  $this->getHookContainer() );
2413 
2414  # split the entire text string on occurrences of [[
2415  $a = StringUtils::explode( '[[', ' ' . $s );
2416  # get the first element (all text up to first [[), and remove the space we added
2417  $s = $a->current();
2418  $a->next();
2419  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2420  $s = substr( $s, 1 );
2421 
2422  $nottalk = !$this->getTitle()->isTalkPage();
2423 
2424  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2425  $e2 = null;
2426  if ( $useLinkPrefixExtension ) {
2427  # Match the end of a line for a word that's not followed by whitespace,
2428  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2429  $charset = $this->contLang->linkPrefixCharset();
2430  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2431  $m = [];
2432  if ( preg_match( $e2, $s, $m ) ) {
2433  $first_prefix = $m[2];
2434  } else {
2435  $first_prefix = false;
2436  }
2437  $prefix = false;
2438  } else {
2439  $first_prefix = false;
2440  $prefix = '';
2441  }
2442 
2443  # Some namespaces don't allow subpages
2444  $useSubpages = $this->nsInfo->hasSubpages(
2445  $this->getTitle()->getNamespace()
2446  );
2447 
2448  # Loop for each link
2449  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2450  # Check for excessive memory usage
2451  if ( $holders->isBig() ) {
2452  # Too big
2453  # Do the existence check, replace the link holders and clear the array
2454  $holders->replace( $s );
2455  $holders->clear();
2456  }
2457 
2458  if ( $useLinkPrefixExtension ) {
2459  // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal $e2 is set under this condition
2460  if ( preg_match( $e2, $s, $m ) ) {
2461  [ , $s, $prefix ] = $m;
2462  } else {
2463  $prefix = '';
2464  }
2465  # first link
2466  if ( $first_prefix ) {
2467  $prefix = $first_prefix;
2468  $first_prefix = false;
2469  }
2470  }
2471 
2472  $might_be_img = false;
2473 
2474  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2475  $text = $m[2];
2476  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2477  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2478  # the real problem is with the $e1 regex
2479  # See T1500.
2480  # Still some problems for cases where the ] is meant to be outside punctuation,
2481  # and no image is in sight. See T4095.
2482  if ( $text !== ''
2483  && substr( $m[3], 0, 1 ) === ']'
2484  && strpos( $text, '[' ) !== false
2485  ) {
2486  $text .= ']'; # so that handleExternalLinks($text) works later
2487  $m[3] = substr( $m[3], 1 );
2488  }
2489  # fix up urlencoded title texts
2490  if ( strpos( $m[1], '%' ) !== false ) {
2491  # Should anchors '#' also be rejected?
2492  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2493  }
2494  $trail = $m[3];
2495  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2496  # Invalid, but might be an image with a link in its caption
2497  $might_be_img = true;
2498  $text = $m[2];
2499  if ( strpos( $m[1], '%' ) !== false ) {
2500  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2501  }
2502  $trail = "";
2503  } else { # Invalid form; output directly
2504  $s .= $prefix . '[[' . $line;
2505  continue;
2506  }
2507 
2508  // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset preg_match success when reached here
2509  $origLink = ltrim( $m[1], ' ' );
2510 
2511  # Don't allow internal links to pages containing
2512  # PROTO: where PROTO is a valid URL protocol; these
2513  # should be external links.
2514  if ( preg_match( '/^(?i:' . $this->urlUtils->validProtocols() . ')/', $origLink ) ) {
2515  $s .= $prefix . '[[' . $line;
2516  continue;
2517  }
2518 
2519  # Make subpage if necessary
2520  if ( $useSubpages ) {
2521  $link = Linker::normalizeSubpageLink(
2522  $this->getTitle(), $origLink, $text
2523  );
2524  } else {
2525  $link = $origLink;
2526  }
2527 
2528  // \x7f isn't a default legal title char, so most likely strip
2529  // markers will force us into the "invalid form" path above. But,
2530  // just in case, let's assert that xmlish tags aren't valid in
2531  // the title position.
2532  $unstrip = $this->mStripState->killMarkers( $link );
2533  $noMarkers = ( $unstrip === $link );
2534 
2535  $nt = $noMarkers ? Title::newFromText( $link ) : null;
2536  if ( $nt === null ) {
2537  $s .= $prefix . '[[' . $line;
2538  continue;
2539  }
2540 
2541  $ns = $nt->getNamespace();
2542  $iw = $nt->getInterwiki();
2543 
2544  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2545 
2546  if ( $might_be_img ) { # if this is actually an invalid link
2547  if ( $ns === NS_FILE && $noforce ) { # but might be an image
2548  $found = false;
2549  while ( true ) {
2550  # look at the next 'line' to see if we can close it there
2551  $a->next();
2552  $next_line = $a->current();
2553  if ( $next_line === false || $next_line === null ) {
2554  break;
2555  }
2556  $m = explode( ']]', $next_line, 3 );
2557  if ( count( $m ) == 3 ) {
2558  # the first ]] closes the inner link, the second the image
2559  $found = true;
2560  $text .= "[[{$m[0]}]]{$m[1]}";
2561  $trail = $m[2];
2562  break;
2563  } elseif ( count( $m ) == 2 ) {
2564  # if there's exactly one ]] that's fine, we'll keep looking
2565  $text .= "[[{$m[0]}]]{$m[1]}";
2566  } else {
2567  # if $next_line is invalid too, we need look no further
2568  $text .= '[[' . $next_line;
2569  break;
2570  }
2571  }
2572  if ( !$found ) {
2573  # we couldn't find the end of this imageLink, so output it raw
2574  # but don't ignore what might be perfectly normal links in the text we've examined
2575  $holders->merge( $this->handleInternalLinks2( $text ) );
2576  $s .= "{$prefix}[[$link|$text";
2577  # note: no $trail, because without an end, there *is* no trail
2578  continue;
2579  }
2580  } else { # it's not an image, so output it raw
2581  $s .= "{$prefix}[[$link|$text";
2582  # note: no $trail, because without an end, there *is* no trail
2583  continue;
2584  }
2585  }
2586 
2587  $wasblank = ( $text == '' );
2588  if ( $wasblank ) {
2589  $text = $link;
2590  if ( !$noforce ) {
2591  # Strip off leading ':'
2592  $text = substr( $text, 1 );
2593  }
2594  } else {
2595  # T6598 madness. Handle the quotes only if they come from the alternate part
2596  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2597  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2598  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2599  $text = $this->doQuotes( $text );
2600  }
2601 
2602  # Link not escaped by : , create the various objects
2603  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2604  # Interwikis
2605  if (
2606  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2607  MediaWikiServices::getInstance()->getLanguageNameUtils()
2608  ->getLanguageName(
2609  $iw,
2610  LanguageNameUtils::AUTONYMS,
2611  LanguageNameUtils::DEFINED
2612  )
2613  || in_array( $iw, $this->svcOptions->get( MainConfigNames::ExtraInterlanguageLinkPrefixes ) )
2614  )
2615  ) {
2616  # T26502: filter duplicates
2617  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2618  $this->mLangLinkLanguages[$iw] = true;
2619  $this->mOutput->addLanguageLink( $nt->getFullText() );
2620  }
2621 
2625  $s = rtrim( $s . $prefix ) . $trail; # T175416
2626  continue;
2627  }
2628 
2629  if ( $ns === NS_FILE ) {
2630  if ( $wasblank ) {
2631  # if no parameters were passed, $text
2632  # becomes something like "File:Foo.png",
2633  # which we don't want to pass on to the
2634  # image generator
2635  $text = '';
2636  } else {
2637  # recursively parse links inside the image caption
2638  # actually, this will parse them in any other parameters, too,
2639  # but it might be hard to fix that, and it doesn't matter ATM
2640  $text = $this->handleExternalLinks( $text );
2641  $holders->merge( $this->handleInternalLinks2( $text ) );
2642  }
2643  # cloak any absolute URLs inside the image markup, so handleExternalLinks() won't touch them
2644  $s .= $prefix . $this->armorLinks(
2645  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2646  continue;
2647  } elseif ( $ns === NS_CATEGORY ) {
2651  $s = rtrim( $s . $prefix ) . $trail; # T2087, T87753
2652 
2653  if ( $wasblank ) {
2654  $sortkey = $this->mOutput->getPageProperty( 'defaultsort' ) ?? '';
2655  } else {
2656  $sortkey = $text;
2657  }
2658  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2659  $sortkey = str_replace( "\n", '', $sortkey );
2660  $sortkey = $this->getTargetLanguageConverter()->convertCategoryKey( $sortkey );
2661  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2662 
2663  continue;
2664  }
2665  }
2666 
2667  # Self-link checking. For some languages, variants of the title are checked in
2668  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2669  # for linking to a different variant.
2670  if ( $ns !== NS_SPECIAL && $nt->equals( $this->getTitle() ) ) {
2671  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail, '',
2672  Sanitizer::escapeIdForLink( $nt->getFragment() ) );
2673  continue;
2674  }
2675 
2676  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2677  # @todo FIXME: Should do batch file existence checks, see comment below
2678  if ( $ns === NS_MEDIA ) {
2679  # Give extensions a chance to select the file revision for us
2680  $options = [];
2681  $descQuery = false;
2682  $this->hookRunner->onBeforeParserFetchFileAndTitle(
2683  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
2684  $this, $nt, $options, $descQuery
2685  );
2686  # Fetch and register the file (file title may be different via hooks)
2687  [ $file, $nt ] = $this->fetchFileAndTitle( $nt, $options );
2688  # Cloak with NOPARSE to avoid replacement in handleExternalLinks
2689  $s .= $prefix . $this->armorLinks(
2690  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2691  continue;
2692  }
2693 
2694  # Some titles, such as valid special pages or files in foreign repos, should
2695  # be shown as bluelinks even though they're not included in the page table
2696  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2697  # batch file existence checks for NS_FILE and NS_MEDIA
2698  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2699  $this->mOutput->addLink( $nt );
2700  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2701  } else {
2702  # Links will be added to the output link list after checking
2703  $s .= $holders->makeHolder( $nt, $text, $trail, $prefix );
2704  }
2705  }
2706  return $holders;
2707  }
2708 
2722  private function makeKnownLinkHolder( LinkTarget $nt, $text = '', $trail = '', $prefix = '' ) {
2723  [ $inside, $trail ] = Linker::splitTrail( $trail );
2724 
2725  if ( $text == '' ) {
2726  $text = htmlspecialchars( $this->titleFormatter->getPrefixedText( $nt ) );
2727  }
2728 
2729  $link = $this->getLinkRenderer()->makeKnownLink(
2730  $nt, new HtmlArmor( "$prefix$text$inside" )
2731  );
2732 
2733  return $this->armorLinks( $link ) . $trail;
2734  }
2735 
2746  private function armorLinks( $text ) {
2747  return preg_replace( '/\b((?i)' . $this->urlUtils->validProtocols() . ')/',
2748  self::MARKER_PREFIX . "NOPARSE$1", $text );
2749  }
2750 
2760  public function doBlockLevels( $text, $linestart ) {
2761  wfDeprecated( __METHOD__, '1.35' );
2762  return BlockLevelPass::doBlockLevels( $text, $linestart );
2763  }
2764 
2773  private function expandMagicVariable( $index, $frame = false ) {
2778  if ( isset( $this->mVarCache[$index] ) ) {
2779  return $this->mVarCache[$index];
2780  }
2781 
2782  $ts = new MWTimestamp( $this->mOptions->getTimestamp() /* TS_MW */ );
2783  if ( $this->hookContainer->isRegistered( 'ParserGetVariableValueTs' ) ) {
2784  $s = $ts->getTimestamp( TS_UNIX );
2785  $this->hookRunner->onParserGetVariableValueTs( $this, $s );
2786  $ts = new MWTimestamp( $s );
2787  }
2788 
2789  $value = CoreMagicVariables::expand(
2790  $this, $index, $ts, $this->svcOptions, $this->logger
2791  );
2792 
2793  if ( $value === null ) {
2794  // Not a defined core magic word
2795  // Don't give this hook unrestricted access to mVarCache
2796  $fakeCache = [];
2797  $this->hookRunner->onParserGetVariableValueSwitch(
2798  // @phan-suppress-next-line PhanTypeMismatchArgument $value is passed as null but returned as string
2799  $this, $fakeCache, $index, $value, $frame
2800  );
2801  // Cache the value returned by the hook by falling through here.
2802  // Assert the the hook returned a non-null value for this MV
2803  '@phan-var string $value';
2804  }
2805 
2806  $this->mVarCache[$index] = $value;
2807 
2808  return $value;
2809  }
2810 
2815  private function initializeVariables() {
2816  $variableIDs = $this->magicWordFactory->getVariableIDs();
2817  $substIDs = $this->magicWordFactory->getSubstIDs();
2818 
2819  $this->mVariables = $this->magicWordFactory->newArray( $variableIDs );
2820  $this->mSubstWords = $this->magicWordFactory->newArray( $substIDs );
2821  }
2822 
2841  public function preprocessToDom( $text, $flags = 0 ) {
2842  return $this->getPreprocessor()->preprocessToObj( $text, $flags );
2843  }
2844 
2866  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2867  # Is there any text? Also, Prevent too big inclusions!
2868  $textSize = strlen( $text );
2869  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2870  return $text;
2871  }
2872 
2873  if ( $frame === false ) {
2874  $frame = $this->getPreprocessor()->newFrame();
2875  } elseif ( !( $frame instanceof PPFrame ) ) {
2876  $this->logger->debug(
2877  __METHOD__ . " called using plain parameters instead of " .
2878  "a PPFrame instance. Creating custom frame."
2879  );
2880  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2881  }
2882 
2883  $dom = $this->preprocessToDom( $text );
2884  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2885  $text = $frame->expand( $dom, $flags );
2886 
2887  return $text;
2888  }
2889 
2917  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2918  # does no harm if $current and $max are present but are unnecessary for the message
2919  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2920  # only during preview, and that would split the parser cache unnecessarily.
2921  $this->mOutput->addWarningMsg(
2922  "$limitationType-warning",
2923  Message::numParam( $current ),
2924  Message::numParam( $max )
2925  );
2926  $this->addTrackingCategory( "$limitationType-category" );
2927  }
2928 
2942  public function braceSubstitution( array $piece, PPFrame $frame ) {
2943  // Flags
2944 
2945  // $text has been filled
2946  $found = false;
2947  $text = '';
2948  // wiki markup in $text should be escaped
2949  $nowiki = false;
2950  // $text is HTML, armour it against wikitext transformation
2951  $isHTML = false;
2952  // Force interwiki transclusion to be done in raw mode not rendered
2953  $forceRawInterwiki = false;
2954  // $text is a DOM node needing expansion in a child frame
2955  $isChildObj = false;
2956  // $text is a DOM node needing expansion in the current frame
2957  $isLocalObj = false;
2958 
2959  # Title object, where $text came from
2960  $title = false;
2961 
2962  # $part1 is the bit before the first |, and must contain only title characters.
2963  # Various prefixes will be stripped from it later.
2964  $titleWithSpaces = $frame->expand( $piece['title'] );
2965  $part1 = trim( $titleWithSpaces );
2966  $titleText = false;
2967 
2968  # Original title text preserved for various purposes
2969  $originalTitle = $part1;
2970 
2971  # $args is a list of argument nodes, starting from index 0, not including $part1
2972  # @todo FIXME: If piece['parts'] is null then the call to getLength()
2973  # below won't work b/c this $args isn't an object
2974  $args = ( $piece['parts'] == null ) ? [] : $piece['parts'];
2975 
2976  $profileSection = null; // profile templates
2977 
2978  $sawDeprecatedTemplateEquals = false; // T91154
2979 
2980  # SUBST
2981  // @phan-suppress-next-line PhanImpossibleCondition
2982  if ( !$found ) {
2983  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
2984 
2985  # Possibilities for substMatch: "subst", "safesubst" or FALSE
2986  # Decide whether to expand template or keep wikitext as-is.
2987  if ( $this->ot['wiki'] ) {
2988  if ( $substMatch === false ) {
2989  $literal = true; # literal when in PST with no prefix
2990  } else {
2991  $literal = false; # expand when in PST with subst: or safesubst:
2992  }
2993  } else {
2994  if ( $substMatch == 'subst' ) {
2995  $literal = true; # literal when not in PST with plain subst:
2996  } else {
2997  $literal = false; # expand when not in PST with safesubst: or no prefix
2998  }
2999  }
3000  if ( $literal ) {
3001  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3002  $isLocalObj = true;
3003  $found = true;
3004  }
3005  }
3006 
3007  # Variables
3008  if ( !$found && $args->getLength() == 0 ) {
3009  $id = $this->mVariables->matchStartToEnd( $part1 );
3010  if ( $id !== false ) {
3011  if ( strpos( $part1, ':' ) !== false ) {
3013  'Registering a magic variable with a name including a colon',
3014  '1.39', false, false
3015  );
3016  }
3017  $text = $this->expandMagicVariable( $id, $frame );
3018  $found = true;
3019  }
3020  }
3021 
3022  # MSG, MSGNW and RAW
3023  if ( !$found ) {
3024  # Check for MSGNW:
3025  $mwMsgnw = $this->magicWordFactory->get( 'msgnw' );
3026  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3027  $nowiki = true;
3028  } else {
3029  # Remove obsolete MSG:
3030  $mwMsg = $this->magicWordFactory->get( 'msg' );
3031  $mwMsg->matchStartAndRemove( $part1 );
3032  }
3033 
3034  # Check for RAW:
3035  $mwRaw = $this->magicWordFactory->get( 'raw' );
3036  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3037  $forceRawInterwiki = true;
3038  }
3039  }
3040 
3041  # Parser functions
3042  if ( !$found ) {
3043  $colonPos = strpos( $part1, ':' );
3044  if ( $colonPos !== false ) {
3045  $func = substr( $part1, 0, $colonPos );
3046  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3047  $argsLength = $args->getLength();
3048  for ( $i = 0; $i < $argsLength; $i++ ) {
3049  $funcArgs[] = $args->item( $i );
3050  }
3051 
3052  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3053 
3054  // Extract any forwarded flags
3055  if ( isset( $result['title'] ) ) {
3056  $title = $result['title'];
3057  }
3058  if ( isset( $result['found'] ) ) {
3059  $found = $result['found'];
3060  }
3061  if ( array_key_exists( 'text', $result ) ) {
3062  // a string or null
3063  $text = $result['text'];
3064  }
3065  if ( isset( $result['nowiki'] ) ) {
3066  $nowiki = $result['nowiki'];
3067  }
3068  if ( isset( $result['isHTML'] ) ) {
3069  $isHTML = $result['isHTML'];
3070  }
3071  if ( isset( $result['forceRawInterwiki'] ) ) {
3072  $forceRawInterwiki = $result['forceRawInterwiki'];
3073  }
3074  if ( isset( $result['isChildObj'] ) ) {
3075  $isChildObj = $result['isChildObj'];
3076  }
3077  if ( isset( $result['isLocalObj'] ) ) {
3078  $isLocalObj = $result['isLocalObj'];
3079  }
3080  }
3081  }
3082 
3083  # Finish mangling title and then check for loops.
3084  # Set $title to a Title object and $titleText to the PDBK
3085  if ( !$found ) {
3086  $ns = NS_TEMPLATE;
3087  # Split the title into page and subpage
3088  $subpage = '';
3089  $relative = Linker::normalizeSubpageLink(
3090  $this->getTitle(), $part1, $subpage
3091  );
3092  if ( $part1 !== $relative ) {
3093  $part1 = $relative;
3094  $ns = $this->getTitle()->getNamespace();
3095  }
3096  $title = Title::newFromText( $part1, $ns );
3097  if ( $title ) {
3098  $titleText = $title->getPrefixedText();
3099  # Check for language variants if the template is not found
3100  if ( $this->getTargetLanguageConverter()->hasVariants() && $title->getArticleID() == 0 ) {
3101  $this->getTargetLanguageConverter()->findVariantLink( $part1, $title, true );
3102  }
3103  # Do recursion depth check
3104  $limit = $this->mOptions->getMaxTemplateDepth();
3105  if ( $frame->depth >= $limit ) {
3106  $found = true;
3107  $text = '<span class="error">'
3108  . wfMessage( 'parser-template-recursion-depth-warning' )
3109  ->numParams( $limit )->inContentLanguage()->text()
3110  . '</span>';
3111  }
3112  }
3113  }
3114 
3115  # Load from database
3116  if ( !$found && $title ) {
3117  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3118  if ( !$title->isExternal() ) {
3119  if ( $title->isSpecialPage()
3120  && $this->mOptions->getAllowSpecialInclusion()
3121  && $this->ot['html']
3122  ) {
3123  $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() );
3124  // Pass the template arguments as URL parameters.
3125  // "uselang" will have no effect since the Language object
3126  // is forced to the one defined in ParserOptions.
3127  $pageArgs = [];
3128  $argsLength = $args->getLength();
3129  for ( $i = 0; $i < $argsLength; $i++ ) {
3130  $bits = $args->item( $i )->splitArg();
3131  if ( strval( $bits['index'] ) === '' ) {
3132  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3133  $value = trim( $frame->expand( $bits['value'] ) );
3134  $pageArgs[$name] = $value;
3135  }
3136  }
3137 
3138  // Create a new context to execute the special page
3139  $context = new RequestContext;
3140  $context->setTitle( $title );
3141  $context->setRequest( new FauxRequest( $pageArgs ) );
3142  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3143  $context->setUser( $this->userFactory->newFromUserIdentity( $this->getUserIdentity() ) );
3144  } else {
3145  // If this page is cached, then we better not be per user.
3146  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3147  }
3148  $context->setLanguage( $this->mOptions->getUserLangObj() );
3149  $ret = $this->specialPageFactory->capturePath( $title, $context, $this->getLinkRenderer() );
3150  if ( $ret ) {
3151  $text = $context->getOutput()->getHTML();
3152  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3153  $found = true;
3154  $isHTML = true;
3155  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3156  $this->mOutput->updateRuntimeAdaptiveExpiry(
3157  $specialPage->maxIncludeCacheTime()
3158  );
3159  }
3160  }
3161  } elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
3162  $found = false; # access denied
3163  $this->logger->debug(
3164  __METHOD__ .
3165  ": template inclusion denied for " . $title->getPrefixedDBkey()
3166  );
3167  } else {
3168  [ $text, $title ] = $this->getTemplateDom( $title );
3169  if ( $text !== false ) {
3170  $found = true;
3171  $isChildObj = true;
3172  if (
3173  $title->getNamespace() === NS_TEMPLATE &&
3174  $title->getDBkey() === '=' &&
3175  $originalTitle === '='
3176  ) {
3177  // Note that we won't get here if `=` is evaluated
3178  // (in the future) as a parser function, nor if
3179  // the Template namespace is given explicitly,
3180  // ie `{{Template:=}}`. Only `{{=}}` triggers.
3181  $sawDeprecatedTemplateEquals = true; // T91154
3182  }
3183  }
3184  }
3185 
3186  # If the title is valid but undisplayable, make a link to it
3187  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3188  $text = "[[:$titleText]]";
3189  $found = true;
3190  }
3191  } elseif ( $title->isTrans() ) {
3192  # Interwiki transclusion
3193  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3194  $text = $this->interwikiTransclude( $title, 'render' );
3195  $isHTML = true;
3196  } else {
3197  $text = $this->interwikiTransclude( $title, 'raw' );
3198  # Preprocess it like a template
3199  $text = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
3200  $isChildObj = true;
3201  }
3202  $found = true;
3203  }
3204 
3205  # Do infinite loop check
3206  # This has to be done after redirect resolution to avoid infinite loops via redirects
3207  if ( !$frame->loopCheck( $title ) ) {
3208  $found = true;
3209  $text = '<span class="error">'
3210  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3211  . '</span>';
3212  $this->addTrackingCategory( 'template-loop-category' );
3213  $this->mOutput->addWarningMsg(
3214  'template-loop-warning',
3215  Message::plaintextParam( $titleText )
3216  );
3217  $this->logger->debug( __METHOD__ . ": template loop broken at '$titleText'" );
3218  }
3219  }
3220 
3221  # If we haven't found text to substitute by now, we're done
3222  # Recover the source wikitext and return it
3223  if ( !$found ) {
3224  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3225  if ( $profileSection ) {
3226  $this->mProfiler->scopedProfileOut( $profileSection );
3227  }
3228  return [ 'object' => $text ];
3229  }
3230 
3231  # Expand DOM-style return values in a child frame
3232  if ( $isChildObj ) {
3233  # Clean up argument array
3234  $newFrame = $frame->newChild( $args, $title );
3235 
3236  if ( $nowiki ) {
3237  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3238  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3239  # Expansion is eligible for the empty-frame cache
3240  $text = $newFrame->cachedExpand( $titleText, $text );
3241  } else {
3242  # Uncached expansion
3243  $text = $newFrame->expand( $text );
3244  }
3245  }
3246  if ( $isLocalObj && $nowiki ) {
3247  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3248  $isLocalObj = false;
3249  }
3250 
3251  if ( $profileSection ) {
3252  $this->mProfiler->scopedProfileOut( $profileSection );
3253  }
3254  if (
3255  $sawDeprecatedTemplateEquals &&
3256  $this->mStripState->unstripBoth( $text ) !== '='
3257  ) {
3258  // T91154: {{=}} is deprecated when it doesn't expand to `=`;
3259  // use {{Template:=}} if you must.
3260  $this->addTrackingCategory( 'template-equals-category' );
3261  $this->mOutput->addWarningMsg( 'template-equals-warning' );
3262  }
3263 
3264  # Replace raw HTML by a placeholder
3265  if ( $isHTML ) {
3266  $text = $this->insertStripItem( $text );
3267  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3268  # Escape nowiki-style return values
3269  $text = wfEscapeWikiText( $text );
3270  } elseif ( is_string( $text )
3271  && !$piece['lineStart']
3272  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3273  ) {
3274  # T2529: if the template begins with a table or block-level
3275  # element, it should be treated as beginning a new line.
3276  # This behavior is somewhat controversial.
3277  $text = "\n" . $text;
3278  }
3279 
3280  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3281  # Error, oversize inclusion
3282  if ( $titleText !== false ) {
3283  # Make a working, properly escaped link if possible (T25588)
3284  $text = "[[:$titleText]]";
3285  } else {
3286  # This will probably not be a working link, but at least it may
3287  # provide some hint of where the problem is
3288  $originalTitle = preg_replace( '/^:/', '', $originalTitle );
3289  $text = "[[:$originalTitle]]";
3290  }
3291  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3292  . 'post-expand include size too large -->' );
3293  $this->limitationWarn( 'post-expand-template-inclusion' );
3294  }
3295 
3296  if ( $isLocalObj ) {
3297  $ret = [ 'object' => $text ];
3298  } else {
3299  $ret = [ 'text' => $text ];
3300  }
3301 
3302  return $ret;
3303  }
3304 
3323  public function callParserFunction( PPFrame $frame, $function, array $args = [] ) {
3324  # Case sensitive functions
3325  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3326  $function = $this->mFunctionSynonyms[1][$function];
3327  } else {
3328  # Case insensitive functions
3329  $function = $this->contLang->lc( $function );
3330  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3331  $function = $this->mFunctionSynonyms[0][$function];
3332  } else {
3333  return [ 'found' => false ];
3334  }
3335  }
3336 
3337  [ $callback, $flags ] = $this->mFunctionHooks[$function];
3338 
3339  $allArgs = [ $this ];
3340  if ( $flags & self::SFH_OBJECT_ARGS ) {
3341  # Convert arguments to PPNodes and collect for appending to $allArgs
3342  $funcArgs = [];
3343  foreach ( $args as $k => $v ) {
3344  if ( $v instanceof PPNode || $k === 0 ) {
3345  $funcArgs[] = $v;
3346  } else {
3347  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3348  }
3349  }
3350 
3351  # Add a frame parameter, and pass the arguments as an array
3352  $allArgs[] = $frame;
3353  $allArgs[] = $funcArgs;
3354  } else {
3355  # Convert arguments to plain text and append to $allArgs
3356  foreach ( $args as $k => $v ) {
3357  if ( $v instanceof PPNode ) {
3358  $allArgs[] = trim( $frame->expand( $v ) );
3359  } elseif ( is_int( $k ) && $k >= 0 ) {
3360  $allArgs[] = trim( $v );
3361  } else {
3362  $allArgs[] = trim( "$k=$v" );
3363  }
3364  }
3365  }
3366 
3367  $result = $callback( ...$allArgs );
3368 
3369  # The interface for function hooks allows them to return a wikitext
3370  # string or an array containing the string and any flags. This mungs
3371  # things around to match what this method should return.
3372  if ( !is_array( $result ) ) {
3373  $result = [
3374  'found' => true,
3375  'text' => $result,
3376  ];
3377  } else {
3378  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3379  $result['text'] = $result[0];
3380  }
3381  unset( $result[0] );
3382  $result += [
3383  'found' => true,
3384  ];
3385  }
3386 
3387  $noparse = true;
3388  $preprocessFlags = 0;
3389  if ( isset( $result['noparse'] ) ) {
3390  $noparse = $result['noparse'];
3391  }
3392  if ( isset( $result['preprocessFlags'] ) ) {
3393  $preprocessFlags = $result['preprocessFlags'];
3394  }
3395 
3396  if ( !$noparse ) {
3397  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3398  $result['isChildObj'] = true;
3399  }
3400 
3401  return $result;
3402  }
3403 
3413  public function getTemplateDom( LinkTarget $title ) {
3414  $cacheTitle = $title;
3415  $titleKey = CacheKeyHelper::getKeyForPage( $title );
3416 
3417  if ( isset( $this->mTplRedirCache[$titleKey] ) ) {
3418  [ $ns, $dbk ] = $this->mTplRedirCache[$titleKey];
3419  $title = Title::makeTitle( $ns, $dbk );
3420  $titleKey = CacheKeyHelper::getKeyForPage( $title );
3421  }
3422  if ( isset( $this->mTplDomCache[$titleKey] ) ) {
3423  return [ $this->mTplDomCache[$titleKey], $title ];
3424  }
3425 
3426  # Cache miss, go to the database
3427  [ $text, $title ] = $this->fetchTemplateAndTitle( $title );
3428 
3429  if ( $text === false ) {
3430  $this->mTplDomCache[$titleKey] = false;
3431  return [ false, $title ];
3432  }
3433 
3434  $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
3435  $this->mTplDomCache[$titleKey] = $dom;
3436 
3437  if ( !$title->isSamePageAs( $cacheTitle ) ) {
3438  $this->mTplRedirCache[ CacheKeyHelper::getKeyForPage( $cacheTitle ) ] =
3439  [ $title->getNamespace(), $title->getDBkey() ];
3440  }
3441 
3442  return [ $dom, $title ];
3443  }
3444 
3459  $cacheKey = CacheKeyHelper::getKeyForPage( $link );
3460  if ( !$this->currentRevisionCache ) {
3461  $this->currentRevisionCache = new MapCacheLRU( 100 );
3462  }
3463  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3464  $title = Title::newFromLinkTarget( $link ); // hook signature compat
3465  $revisionRecord =
3466  // Defaults to Parser::statelessFetchRevisionRecord()
3467  call_user_func(
3468  $this->mOptions->getCurrentRevisionRecordCallback(),
3469  $title,
3470  $this
3471  );
3472  if ( $revisionRecord === false ) {
3473  // Parser::statelessFetchRevisionRecord() can return false;
3474  // normalize it to null.
3475  $revisionRecord = null;
3476  }
3477  $this->currentRevisionCache->set( $cacheKey, $revisionRecord );
3478  }
3479  return $this->currentRevisionCache->get( $cacheKey );
3480  }
3481 
3488  public function isCurrentRevisionOfTitleCached( LinkTarget $link ) {
3489  $key = CacheKeyHelper::getKeyForPage( $link );
3490  return (
3491  $this->currentRevisionCache &&
3492  $this->currentRevisionCache->has( $key )
3493  );
3494  }
3495 
3504  public static function statelessFetchRevisionRecord( LinkTarget $link, $parser = null ) {
3505  if ( $link instanceof PageIdentity ) {
3506  // probably a Title, just use it.
3507  $page = $link;
3508  } else {
3509  // XXX: use RevisionStore::getPageForLink()!
3510  // ...but get the info for the current revision at the same time?
3511  // Should RevisionStore::getKnownCurrentRevision accept a LinkTarget?
3512  $page = Title::newFromLinkTarget( $link );
3513  }
3514 
3515  $revRecord = MediaWikiServices::getInstance()
3516  ->getRevisionLookup()
3517  ->getKnownCurrentRevision( $page );
3518  return $revRecord;
3519  }
3520 
3527  public function fetchTemplateAndTitle( LinkTarget $link ) {
3528  // Use Title for compatibility with callbacks and return type
3529  $title = Title::newFromLinkTarget( $link );
3530 
3531  // Defaults to Parser::statelessFetchTemplate()
3532  $templateCb = $this->mOptions->getTemplateCallback();
3533  $stuff = $templateCb( $title, $this );
3534  $revRecord = $stuff['revision-record'] ?? null;
3535 
3536  $text = $stuff['text'];
3537  if ( is_string( $stuff['text'] ) ) {
3538  // We use U+007F DELETE to distinguish strip markers from regular text
3539  $text = strtr( $text, "\x7f", "?" );
3540  }
3541  $finalTitle = $stuff['finalTitle'] ?? $title;
3542  foreach ( ( $stuff['deps'] ?? [] ) as $dep ) {
3543  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3544  if ( $dep['title']->equals( $this->getTitle() ) && $revRecord instanceof RevisionRecord ) {
3545  // Self-transclusion; final result may change based on the new page version
3546  try {
3547  $sha1 = $revRecord->getSha1();
3548  } catch ( RevisionAccessException $e ) {
3549  $sha1 = null;
3550  }
3551  $this->setOutputFlag( ParserOutputFlags::VARY_REVISION_SHA1, 'Self transclusion' );
3552  $this->getOutput()->setRevisionUsedSha1Base36( $sha1 );
3553  }
3554  }
3555 
3556  return [ $text, $finalTitle ];
3557  }
3558 
3569  public static function statelessFetchTemplate( $page, $parser = false ) {
3570  $title = Title::castFromLinkTarget( $page ); // for compatibility with return type
3571  $text = $skip = false;
3572  $finalTitle = $title;
3573  $deps = [];
3574  $revRecord = null;
3575  $contextTitle = $parser ? $parser->getTitle() : null;
3576 
3577  $services = MediaWikiServices::getInstance();
3578  # Loop to fetch the article, with up to 2 redirects
3579  $revLookup = $services->getRevisionLookup();
3580  $hookRunner = new HookRunner( $services->getHookContainer() );
3581  for ( $i = 0; $i < 3 && is_object( $title ); $i++ ) {
3582  # Give extensions a chance to select the revision instead
3583  $revRecord = null; # Assume no hook
3584  $id = false; # Assume current
3585  $origTitle = $title;
3586  $titleChanged = false;
3587  $hookRunner->onBeforeParserFetchTemplateRevisionRecord(
3588  # The $title is a not a PageIdentity, as it may
3589  # contain fragments or even represent an attempt to transclude
3590  # a broken or otherwise-missing Title, which the hook may
3591  # fix up. Similarly, the $contextTitle may represent a special
3592  # page or other page which "exists" as a parsing context but
3593  # is not in the DB.
3594  $contextTitle, $title,
3595  $skip, $revRecord
3596  );
3597 
3598  if ( $skip ) {
3599  $text = false;
3600  $deps[] = [
3601  'title' => $title,
3602  'page_id' => $title->getArticleID(),
3603  'rev_id' => null
3604  ];
3605  break;
3606  }
3607  # Get the revision
3608  if ( !$revRecord ) {
3609  if ( $id ) {
3610  # Handle $id returned by deprecated legacy hook
3611  $revRecord = $revLookup->getRevisionById( $id );
3612  } elseif ( $parser ) {
3613  $revRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
3614  } else {
3615  $revRecord = $revLookup->getRevisionByTitle( $title );
3616  }
3617  }
3618  if ( $revRecord ) {
3619  # Update title, as $revRecord may have been changed by hook
3620  $title = Title::newFromLinkTarget(
3621  $revRecord->getPageAsLinkTarget()
3622  );
3623  $deps[] = [
3624  'title' => $title,
3625  'page_id' => $revRecord->getPageId(),
3626  'rev_id' => $revRecord->getId(),
3627  ];
3628  } else {
3629  $deps[] = [
3630  'title' => $title,
3631  'page_id' => $title->getArticleID(),
3632  'rev_id' => null,
3633  ];
3634  }
3635  if ( !$title->equals( $origTitle ) ) {
3636  # If we fetched a rev from a different title, register
3637  # the original title too...
3638  $deps[] = [
3639  'title' => $origTitle,
3640  'page_id' => $origTitle->getArticleID(),
3641  'rev_id' => null,
3642  ];
3643  $titleChanged = true;
3644  }
3645  # If there is no current revision, there is no page
3646  if ( $revRecord === null || $revRecord->getId() === null ) {
3647  $linkCache = $services->getLinkCache();
3648  $linkCache->addBadLinkObj( $title );
3649  }
3650  if ( $revRecord ) {
3651  if ( $titleChanged && !$revRecord->hasSlot( SlotRecord::MAIN ) ) {
3652  // We've added this (missing) title to the dependencies;
3653  // give the hook another chance to redirect it to an
3654  // actual page.
3655  $text = false;
3656  $finalTitle = $title;
3657  continue;
3658  }
3659  if ( $revRecord->hasSlot( SlotRecord::MAIN ) ) { // T276476
3660  $content = $revRecord->getContent( SlotRecord::MAIN );
3661  $text = $content ? $content->getWikitextForTransclusion() : null;
3662  } else {
3663  $text = false;
3664  }
3665 
3666  if ( $text === false || $text === null ) {
3667  $text = false;
3668  break;
3669  }
3670  } elseif ( $title->getNamespace() === NS_MEDIAWIKI ) {
3671  $message = wfMessage( $services->getContentLanguage()->
3672  lcfirst( $title->getText() ) )->inContentLanguage();
3673  if ( !$message->exists() ) {
3674  $text = false;
3675  break;
3676  }
3677  $text = $message->plain();
3678  break;
3679  } else {
3680  break;
3681  }
3682  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable Only reached when content is set
3683  if ( !$content ) {
3684  break;
3685  }
3686  # Redirect?
3687  $finalTitle = $title;
3688  $title = $content->getRedirectTarget();
3689  }
3690 
3691  $retValues = [
3692  // previously, when this also returned a Revision object, we set
3693  // 'revision-record' to false instead of null if it was unavailable,
3694  // so that callers to use isset and then rely on the revision-record
3695  // key instead of the revision key, even if there was no corresponding
3696  // object - we continue to set to false here for backwards compatability
3697  'revision-record' => $revRecord ?: false,
3698  'text' => $text,
3699  'finalTitle' => $finalTitle,
3700  'deps' => $deps
3701  ];
3702  return $retValues;
3703  }
3704 
3713  public function fetchFileAndTitle( LinkTarget $link, array $options = [] ) {
3714  $file = $this->fetchFileNoRegister( $link, $options );
3715 
3716  $time = $file ? $file->getTimestamp() : false;
3717  $sha1 = $file ? $file->getSha1() : false;
3718  # Register the file as a dependency...
3719  $this->mOutput->addImage( $link->getDBkey(), $time, $sha1 );
3720  if ( $file && !$link->isSameLinkAs( $file->getTitle() ) ) {
3721  # Update fetched file title after resolving redirects, etc.
3722  $link = $file->getTitle();
3723  $this->mOutput->addImage( $link->getDBkey(), $time, $sha1 );
3724  }
3725 
3726  $title = Title::newFromLinkTarget( $link ); // for return type compat
3727  return [ $file, $title ];
3728  }
3729 
3740  protected function fetchFileNoRegister( LinkTarget $link, array $options = [] ) {
3741  if ( isset( $options['broken'] ) ) {
3742  $file = false; // broken thumbnail forced by hook
3743  } else {
3744  $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
3745  if ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3746  $file = $repoGroup->findFileFromKey( $options['sha1'], $options );
3747  } else { // get by (name,timestamp)
3748  $file = $repoGroup->findFile( $link, $options );
3749  }
3750  }
3751  return $file;
3752  }
3753 
3763  public function interwikiTransclude( LinkTarget $link, $action ) {
3764  if ( !$this->svcOptions->get( MainConfigNames::EnableScaryTranscluding ) ) {
3765  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3766  }
3767 
3768  // TODO: extract relevant functionality from Title
3769  $title = Title::newFromLinkTarget( $link );
3770 
3771  $url = $title->getFullURL( [ 'action' => $action ] );
3772  if ( strlen( $url ) > 1024 ) {
3773  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3774  }
3775 
3776  $wikiId = $title->getTransWikiID(); // remote wiki ID or false
3777 
3778  $fname = __METHOD__;
3779 
3780  $cache = $this->wanCache;
3781  $data = $cache->getWithSetCallback(
3782  $cache->makeGlobalKey(
3783  'interwiki-transclude',
3784  ( $wikiId !== false ) ? $wikiId : 'external',
3785  sha1( $url )
3786  ),
3787  $this->svcOptions->get( MainConfigNames::TranscludeCacheExpiry ),
3788  function ( $oldValue, &$ttl ) use ( $url, $fname, $cache ) {
3789  $req = $this->httpRequestFactory->create( $url, [], $fname );
3790 
3791  $status = $req->execute(); // Status object
3792  if ( !$status->isOK() ) {
3793  $ttl = $cache::TTL_UNCACHEABLE;
3794  } elseif ( $req->getResponseHeader( 'X-Database-Lagged' ) !== null ) {
3795  $ttl = min( $cache::TTL_LAGGED, $ttl );
3796  }
3797 
3798  return [
3799  'text' => $status->isOK() ? $req->getContent() : null,
3800  'code' => $req->getStatus()
3801  ];
3802  },
3803  [
3804  'checkKeys' => ( $wikiId !== false )
3805  ? [ $cache->makeGlobalKey( 'interwiki-page', $wikiId, $title->getDBkey() ) ]
3806  : [],
3807  'pcGroup' => 'interwiki-transclude:5',
3808  'pcTTL' => $cache::TTL_PROC_LONG
3809  ]
3810  );
3811 
3812  if ( is_string( $data['text'] ) ) {
3813  $text = $data['text'];
3814  } elseif ( $data['code'] != 200 ) {
3815  // Though we failed to fetch the content, this status is useless.
3816  $text = wfMessage( 'scarytranscludefailed-httpstatus' )
3817  ->params( $url, $data['code'] )->inContentLanguage()->text();
3818  } else {
3819  $text = wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3820  }
3821 
3822  return $text;
3823  }
3824 
3835  public function argSubstitution( array $piece, PPFrame $frame ) {
3836  $error = false;
3837  $parts = $piece['parts'];
3838  $nameWithSpaces = $frame->expand( $piece['title'] );
3839  $argName = trim( $nameWithSpaces );
3840  $object = false;
3841  $text = $frame->getArgument( $argName );
3842  if ( $text === false && $parts->getLength() > 0
3843  && ( $this->ot['html']
3844  || $this->ot['pre']
3845  || ( $this->ot['wiki'] && $frame->isTemplate() )
3846  )
3847  ) {
3848  # No match in frame, use the supplied default
3849  $object = $parts->item( 0 )->getChildren();
3850  }
3851  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3852  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3853  $this->limitationWarn( 'post-expand-template-argument' );
3854  }
3855 
3856  if ( $text === false && $object === false ) {
3857  # No match anywhere
3858  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3859  }
3860  if ( $error !== false ) {
3861  $text .= $error;
3862  }
3863  if ( $object !== false ) {
3864  $ret = [ 'object' => $object ];
3865  } else {
3866  $ret = [ 'text' => $text ];
3867  }
3868 
3869  return $ret;
3870  }
3871 
3876  public function tagNeedsNowikiStrippedInTagPF( string $lowerTagName ): bool {
3877  $parsoidSiteConfig = MediaWikiServices::getInstance()->getParsoidSiteConfig();
3878  return $parsoidSiteConfig->tagNeedsNowikiStrippedInTagPF( $lowerTagName );
3879  }
3880 
3900  public function extensionSubstitution( array $params, PPFrame $frame, bool $processNowiki = false ) {
3901  static $errorStr = '<span class="error">';
3902 
3903  $name = $frame->expand( $params['name'] );
3904  if ( str_starts_with( $name, $errorStr ) ) {
3905  // Probably expansion depth or node count exceeded. Just punt the
3906  // error up.
3907  return $name;
3908  }
3909 
3910  // Parse attributes from XML-like wikitext syntax
3911  $attrText = !isset( $params['attr'] ) ? '' : $frame->expand( $params['attr'] );
3912  if ( str_starts_with( $attrText, $errorStr ) ) {
3913  // See above
3914  return $attrText;
3915  }
3916 
3917  // We can't safely check if the expansion for $content resulted in an
3918  // error, because the content could happen to be the error string
3919  // (T149622).
3920  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3921 
3922  $marker = self::MARKER_PREFIX . "-$name-"
3923  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3924 
3925  $normalizedName = strtolower( $name );
3926  $isNowiki = $normalizedName === 'nowiki';
3927  $markerType = $isNowiki ? 'nowiki' : 'general';
3928  if ( $this->ot['html'] || ( $processNowiki && $isNowiki ) ) {
3929  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3930  // Merge in attributes passed via {{#tag:}} parser function
3931  if ( isset( $params['attributes'] ) ) {
3932  $attributes += $params['attributes'];
3933  }
3934 
3935  if ( isset( $this->mTagHooks[$normalizedName] ) ) {
3936  // Note that $content may be null here, for example if the
3937  // tag is self-closed.
3938  $output = call_user_func_array( $this->mTagHooks[$normalizedName],
3939  [ $content, $attributes, $this, $frame ] );
3940  } else {
3941  $output = '<span class="error">Invalid tag extension name: ' .
3942  htmlspecialchars( $normalizedName ) . '</span>';
3943  }
3944 
3945  if ( is_array( $output ) ) {
3946  // Extract flags
3947  $flags = $output;
3948  $output = $flags[0];
3949  if ( isset( $flags['markerType'] ) ) {
3950  $markerType = $flags['markerType'];
3951  }
3952  }
3953  } else {
3954  // We're substituting a {{subst:#tag:}} parser function.
3955  // Convert the attributes it passed into the XML-like string.
3956  if ( isset( $params['attributes'] ) ) {
3957  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3958  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3959  htmlspecialchars( $attrValue, ENT_COMPAT ) . '"';
3960  }
3961  }
3962  if ( $content === null ) {
3963  $output = "<$name$attrText/>";
3964  } else {
3965  $close = $params['close'] === null ? '' : $frame->expand( $params['close'] );
3966  if ( str_starts_with( $close, $errorStr ) ) {
3967  // See above
3968  return $close;
3969  }
3970  $output = "<$name$attrText>$content$close";
3971  }
3972  }
3973 
3974  if ( $markerType === 'none' ) {
3975  return $output;
3976  } elseif ( $markerType === 'nowiki' ) {
3977  $this->mStripState->addNoWiki( $marker, $output );
3978  } elseif ( $markerType === 'general' ) {
3979  $this->mStripState->addGeneral( $marker, $output );
3980  } else {
3981  throw new UnexpectedValueException( __METHOD__ . ': invalid marker type' );
3982  }
3983  return $marker;
3984  }
3985 
3993  private function incrementIncludeSize( $type, $size ) {
3994  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3995  return false;
3996  } else {
3997  $this->mIncludeSizes[$type] += $size;
3998  return true;
3999  }
4000  }
4001 
4007  $this->mExpensiveFunctionCount++;
4008  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
4009  }
4010 
4018  private function handleDoubleUnderscore( $text ) {
4019  # The position of __TOC__ needs to be recorded
4020  $mw = $this->magicWordFactory->get( 'toc' );
4021  if ( $mw->match( $text ) ) {
4022  $this->mShowToc = true;
4023  $this->mForceTocPosition = true;
4024 
4025  # Set a placeholder. At the end we'll fill it in with the TOC.
4026  $text = $mw->replace( self::TOC_PLACEHOLDER, $text, 1 );
4027 
4028  # Only keep the first one.
4029  $text = $mw->replace( '', $text );
4030  # For consistency with all other double-underscores
4031  # (see below)
4032  $this->mOutput->setPageProperty( 'toc', '' );
4033  }
4034 
4035  # Now match and remove the rest of them
4036  $mwa = $this->magicWordFactory->getDoubleUnderscoreArray();
4037  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
4038 
4039  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
4040  $this->mOutput->setNoGallery( true );
4041  }
4042  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
4043  $this->mShowToc = false;
4044  }
4045  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
4046  && $this->getTitle()->getNamespace() === NS_CATEGORY
4047  ) {
4048  $this->addTrackingCategory( 'hidden-category-category' );
4049  }
4050  # (T10068) Allow control over whether robots index a page.
4051  # __INDEX__ always overrides __NOINDEX__, see T16899
4052  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->getTitle()->canUseNoindex() ) {
4053  $this->mOutput->setIndexPolicy( 'noindex' );
4054  $this->addTrackingCategory( 'noindex-category' );
4055  }
4056  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->getTitle()->canUseNoindex() ) {
4057  $this->mOutput->setIndexPolicy( 'index' );
4058  $this->addTrackingCategory( 'index-category' );
4059  }
4060 
4061  # Cache all double underscores in the database
4062  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4063  $this->mOutput->setPageProperty( $key, '' );
4064  }
4065 
4066  return $text;
4067  }
4068 
4075  public function addTrackingCategory( $msg ) {
4076  return $this->trackingCategories->addTrackingCategory(
4077  $this->mOutput, $msg, $this->getPage()
4078  );
4079  }
4080 
4094  public function msg( string $msg, ...$args ): Message {
4095  return wfMessage( $msg, ...$args )
4096  ->inLanguage( $this->getTargetLanguage() )
4097  ->page( $this->getPage() );
4098  }
4099 
4115  private function finalizeHeadings( $text, $origText, $isMain = true ) {
4116  # Inhibit editsection links if requested in the page
4117  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4118  $maybeShowEditLink = false;
4119  } else {
4120  $maybeShowEditLink = true; /* Actual presence will depend on post-cache transforms */
4121  }
4122 
4123  # Get all headlines for numbering them and adding funky stuff like [edit]
4124  # links - this is for later, but we need the number of headlines right now
4125  # NOTE: white space in headings have been trimmed in handleHeadings. They shouldn't
4126  # be trimmed here since whitespace in HTML headings is significant.
4127  $matches = [];
4128  $numMatches = preg_match_all(
4129  '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4130  $text,
4131  $matches
4132  );
4133 
4134  # if there are fewer than 4 headlines in the article, do not show TOC
4135  # unless it's been explicitly enabled.
4136  $enoughToc = $this->mShowToc &&
4137  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4138 
4139  # Allow user to stipulate that a page should have a "new section"
4140  # link added via __NEWSECTIONLINK__
4141  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4142  $this->mOutput->setNewSection( true );
4143  }
4144 
4145  # Allow user to remove the "new section"
4146  # link via __NONEWSECTIONLINK__
4147  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4148  $this->mOutput->setHideNewSection( true );
4149  }
4150 
4151  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4152  # override above conditions and always show TOC above first header
4153  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4154  $this->mShowToc = true;
4155  $enoughToc = true;
4156  }
4157 
4158  # headline counter
4159  $headlineCount = 0;
4160  $haveTocEntries = false;
4161 
4162  # Ugh .. the TOC should have neat indentation levels which can be
4163  # passed to the skin functions. These are determined here
4164  $full = '';
4165  $head = [];
4166  $level = 0;
4167  $tocData = new TOCData();
4168  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4169  $baseTitleText = $this->getTitle()->getPrefixedDBkey();
4170  $oldType = $this->mOutputType;
4171  $this->setOutputType( self::OT_WIKI );
4172  $frame = $this->getPreprocessor()->newFrame();
4173  $root = $this->preprocessToDom( $origText );
4174  $node = $root->getFirstChild();
4175  $cpOffset = 0;
4176  $refers = [];
4177 
4178  $headlines = $numMatches !== false ? $matches[3] : [];
4179 
4180  $maxTocLevel = $this->svcOptions->get( MainConfigNames::MaxTocLevel );
4181  foreach ( $headlines as $headline ) {
4182  $isTemplate = false;
4183  $titleText = false;
4184  $sectionIndex = false;
4185  $markerMatches = [];
4186  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4187  $serial = (int)$markerMatches[1];
4188  [ $titleText, $sectionIndex ] = $this->mHeadings[$serial];
4189  $isTemplate = ( $titleText != $baseTitleText );
4190  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4191  }
4192 
4193  $sectionMetadata = SectionMetadata::fromLegacy( [
4194  "fromtitle" => $titleText ?: null,
4195  "index" => $sectionIndex === false
4196  ? '' : ( ( $isTemplate ? 'T-' : '' ) . $sectionIndex )
4197  ] );
4198  $tocData->addSection( $sectionMetadata );
4199 
4200  $oldLevel = $level;
4201  $level = (int)$matches[1][$headlineCount];
4202  $tocData->processHeading( $oldLevel, $level, $sectionMetadata );
4203 
4204  if ( $tocData->getCurrentTOCLevel() < $maxTocLevel ) {
4205  $haveTocEntries = true;
4206  }
4207 
4208  # The safe header is a version of the header text safe to use for links
4209 
4210  # Remove link placeholders by the link text.
4211  # <!--LINK number-->
4212  # turns into
4213  # link text with suffix
4214  # Do this before unstrip since link text can contain strip markers
4215  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4216 
4217  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4218  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4219 
4220  # Remove any <style> or <script> tags (T198618)
4221  $safeHeadline = preg_replace(
4222  '#<(style|script)(?: [^>]*[^>/])?>.*?</\1>#is',
4223  '',
4224  $safeHeadline
4225  );
4226 
4227  # Strip out HTML (first regex removes any tag not allowed)
4228  # Allowed tags are:
4229  # * <sup> and <sub> (T10393)
4230  # * <i> (T28375)
4231  # * <b> (r105284)
4232  # * <bdi> (T74884)
4233  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4234  # * <s> and <strike> (T35715)
4235  # * <q> (T251672)
4236  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4237  # to allow setting directionality in toc items.
4238  $tocline = preg_replace(
4239  [
4240  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike|q)(?: [^>]*)?>).*?>#',
4241  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4242  ],
4243  [ '', '<$1>' ],
4244  $safeHeadline
4245  );
4246 
4247  # Strip '<span></span>', which is the result from the above if
4248  # <span id="foo"></span> is used to produce an additional anchor
4249  # for a section.
4250  $tocline = str_replace( '<span></span>', '', $tocline );
4251 
4252  $tocline = trim( $tocline );
4253 
4254  # For the anchor, strip out HTML-y stuff period
4255  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4256  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4257 
4258  # Save headline for section edit hint before it's escaped
4259  $headlineHint = $safeHeadline;
4260 
4261  # Decode HTML entities
4262  $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4263 
4264  $safeHeadline = self::normalizeSectionName( $safeHeadline );
4265 
4266  $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4267  $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4268  $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4269  if ( $fallbackHeadline === $safeHeadline ) {
4270  # No reason to have both (in fact, we can't)
4271  $fallbackHeadline = false;
4272  }
4273 
4274  # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4275  $arrayKey = strtolower( $safeHeadline );
4276  if ( $fallbackHeadline === false ) {
4277  $fallbackArrayKey = false;
4278  } else {
4279  $fallbackArrayKey = strtolower( $fallbackHeadline );
4280  }
4281 
4282  # Create the anchor for linking from the TOC to the section
4283  $anchor = $safeHeadline;
4284  $fallbackAnchor = $fallbackHeadline;
4285  if ( isset( $refers[$arrayKey] ) ) {
4286  for ( $i = 2; isset( $refers["{$arrayKey}_$i"] ); ++$i );
4287  $anchor .= "_$i";
4288  $linkAnchor .= "_$i";
4289  $refers["{$arrayKey}_$i"] = true;
4290  } else {
4291  $refers[$arrayKey] = true;
4292  }
4293  if ( $fallbackHeadline !== false && isset( $refers[$fallbackArrayKey] ) ) {
4294  for ( $i = 2; isset( $refers["{$fallbackArrayKey}_$i"] ); ++$i );
4295  $fallbackAnchor .= "_$i";
4296  $refers["{$fallbackArrayKey}_$i"] = true;
4297  } else {
4298  $refers[$fallbackArrayKey] = true;
4299  }
4300 
4301  # Add the section to the section tree
4302  # Find the DOM node for this header
4303  $noOffset = ( $isTemplate || $sectionIndex === false );
4304  while ( $node && !$noOffset ) {
4305  if ( $node->getName() === 'h' ) {
4306  $bits = $node->splitHeading();
4307  if ( $bits['i'] == $sectionIndex ) {
4308  break;
4309  }
4310  }
4311  $cpOffset += mb_strlen(
4312  $this->mStripState->unstripBoth(
4313  $frame->expand( $node, PPFrame::RECOVER_ORIG )
4314  )
4315  );
4316  $node = $node->getNextSibling();
4317  }
4318  $sectionMetadata->line = $tocline;
4319  $sectionMetadata->codepointOffset = ( $noOffset ? null : $cpOffset );
4320  $sectionMetadata->anchor = $anchor;
4321  $sectionMetadata->linkAnchor = $linkAnchor;
4322 
4323  # give headline the correct <h#> tag
4324  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4325  // Output edit section links as markers with styles that can be customized by skins
4326  if ( $isTemplate ) {
4327  # Put a T flag in the section identifier, to indicate to extractSections()
4328  # that sections inside <includeonly> should be counted.
4329  $editsectionPage = $titleText;
4330  $editsectionSection = "T-$sectionIndex";
4331  } else {
4332  $editsectionPage = $this->getTitle()->getPrefixedText();
4333  $editsectionSection = $sectionIndex;
4334  }
4335  $editsectionContent = $headlineHint;
4336  // We use a bit of pesudo-xml for editsection markers. The
4337  // language converter is run later on. Using a UNIQ style marker
4338  // leads to the converter screwing up the tokens when it
4339  // converts stuff. And trying to insert strip tags fails too. At
4340  // this point all real inputted tags have already been escaped,
4341  // so we don't have to worry about a user trying to input one of
4342  // these markers directly. We use a page and section attribute
4343  // to stop the language converter from converting these
4344  // important bits of data, but put the headline hint inside a
4345  // content block because the language converter is supposed to
4346  // be able to convert that piece of data.
4347  // Gets replaced with html in ParserOutput::getText
4348  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage, ENT_COMPAT );
4349  $editlink .= '" section="' . htmlspecialchars( $editsectionSection, ENT_COMPAT ) . '"';
4350  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4351  } else {
4352  $editlink = '';
4353  }
4354  $head[$headlineCount] = Linker::makeHeadline(
4355  $level,
4356  $matches['attrib'][$headlineCount],
4357  $anchor,
4358  $headline,
4359  $editlink,
4360  $fallbackAnchor
4361  );
4362 
4363  $headlineCount++;
4364  }
4365 
4366  $this->setOutputType( $oldType );
4367 
4368  # Never ever show TOC if no headers (or suppressed)
4369  $suppressToc = $this->mOptions->getSuppressTOC();
4370  if ( !$haveTocEntries ) {
4371  $enoughToc = false;
4372  }
4373  $addTOCPlaceholder = false;
4374 
4375  if ( $isMain && !$suppressToc ) {
4376  // We generally output the section information via the API
4377  // even if there isn't "enough" of a ToC to merit showing
4378  // it -- but the "suppress TOC" parser option is set when
4379  // any sections that might be found aren't "really there"
4380  // (ie, JavaScript content that might have spurious === or
4381  // <h2>: T307691) so we will *not* set section information
4382  // in that case.
4383  $this->mOutput->setTOCData( $tocData );
4384 
4385  // T294950: Record a suggestion that the TOC should be shown.
4386  // We shouldn't be looking at ::getTOCHTML() for this because
4387  // that was replaced (T293513); and $tocData will contain sections
4388  // even if there aren't $enoughToc to show (T332243).
4389  // Skins are free to ignore this suggestion and implement their
4390  // own criteria for showing/suppressing TOC (T318186).
4391  if ( $enoughToc ) {
4392  $this->mOutput->setOutputFlag( ParserOutputFlags::SHOW_TOC );
4393  if ( !$this->mForceTocPosition ) {
4394  $addTOCPlaceholder = true;
4395  }
4396  }
4397 
4398  // If __NOTOC__ is used on the page (and not overridden by
4399  // __TOC__ or __FORCETOC__) set the NO_TOC flag to tell
4400  // the skin that although the section information is
4401  // valid, it should perhaps not be presented as a Table Of
4402  // Contents.
4403  if ( !$this->mShowToc ) {
4404  $this->mOutput->setOutputFlag( ParserOutputFlags::NO_TOC );
4405  }
4406  }
4407 
4408  # split up and insert constructed headlines
4409  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4410  $i = 0;
4411 
4412  // build an array of document sections
4413  $sections = [];
4414  foreach ( $blocks as $block ) {
4415  // $head is zero-based, sections aren't.
4416  if ( empty( $head[$i - 1] ) ) {
4417  $sections[$i] = $block;
4418  } else {
4419  $sections[$i] = $head[$i - 1] . $block;
4420  }
4421 
4422  $i++;
4423  }
4424 
4425  if ( $addTOCPlaceholder ) {
4426  // append the TOC at the beginning
4427  // Top anchor now in skin
4428  // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset At least one element when enoughToc is true
4429  $sections[0] .= self::TOC_PLACEHOLDER . "\n";
4430  }
4431 
4432  $full .= implode( '', $sections );
4433 
4434  return $full;
4435  }
4436 
4446  private static function localizeTOC(
4447  ?TOCData $tocData, Language $lang, ?ILanguageConverter $converter
4448  ) {
4449  if ( $tocData === null ) {
4450  return; // Nothing to do
4451  }
4452  foreach ( $tocData->getSections() as $s ) {
4453  // Localize heading
4454  if ( $converter ) {
4455  // T331316: don't use 'convert' or 'convertTo' as these reset
4456  // the language converter state.
4457  $s->line = $converter->convertTo(
4458  $s->line, $converter->getPreferredVariant(), false
4459  );
4460  }
4461  // Localize numbering
4462  $dot = '.';
4463  $pieces = explode( $dot, $s->number );
4464  $numbering = '';
4465  foreach ( $pieces as $i => $p ) {
4466  if ( $i > 0 ) {
4467  $numbering .= $dot;
4468  }
4469  $numbering .= $lang->formatNum( $p );
4470  }
4471  $s->number = $numbering;
4472  }
4473  }
4474 
4487  public function preSaveTransform(
4488  $text,
4489  PageReference $page,
4490  UserIdentity $user,
4491  ParserOptions $options,
4492  $clearState = true
4493  ) {
4494  if ( $clearState ) {
4495  $magicScopeVariable = $this->lock();
4496  }
4497  $this->startParse( $page, $options, self::OT_WIKI, $clearState );
4498  $this->setUser( $user );
4499 
4500  // Strip U+0000 NULL (T159174)
4501  $text = str_replace( "\000", '', $text );
4502 
4503  // We still normalize line endings (including trimming trailing whitespace) for
4504  // backwards-compatibility with other code that just calls PST, but this should already
4505  // be handled in TextContent subclasses
4506  $text = TextContent::normalizeLineEndings( $text );
4507 
4508  if ( $options->getPreSaveTransform() ) {
4509  $text = $this->pstPass2( $text, $user );
4510  }
4511  $text = $this->mStripState->unstripBoth( $text );
4512 
4513  // Trim trailing whitespace again, because the previous steps can introduce it.
4514  $text = rtrim( $text );
4515 
4516  $this->hookRunner->onParserPreSaveTransformComplete( $this, $text );
4517 
4518  $this->setUser( null ); # Reset
4519 
4520  return $text;
4521  }
4522 
4531  private function pstPass2( $text, UserIdentity $user ) {
4532  # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
4533  # $this->contLang here in order to give everyone the same signature and use the default one
4534  # rather than the one selected in each user's preferences. (see also T14815)
4535  $ts = $this->mOptions->getTimestamp();
4536  $timestamp = MWTimestamp::getLocalInstance( $ts );
4537  $ts = $timestamp->format( 'YmdHis' );
4538  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4539 
4540  $d = $this->contLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4541 
4542  # Variable replacement
4543  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4544  $text = $this->replaceVariables( $text );
4545 
4546  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4547  # which may corrupt this parser instance via its wfMessage()->text() call-
4548 
4549  # Signatures
4550  if ( strpos( $text, '~~~' ) !== false ) {
4551  $sigText = $this->getUserSig( $user );
4552  $text = strtr( $text, [
4553  '~~~~~' => $d,
4554  '~~~~' => "$sigText $d",
4555  '~~~' => $sigText
4556  ] );
4557  # The main two signature forms used above are time-sensitive
4558  $this->setOutputFlag( ParserOutputFlags::USER_SIGNATURE, 'User signature detected' );
4559  }
4560 
4561  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4562  $tc = '[' . Title::legalChars() . ']';
4563  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4564 
4565  // [[ns:page (context)|]]
4566  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4567  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4568  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4569  // [[ns:page (context), context|]] (using single, double-width or Arabic comma)
4570  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,|، )$tc+|)\\|]]/";
4571  // [[|page]] (reverse pipe trick: add context from page title)
4572  $p2 = "/\[\[\\|($tc+)]]/";
4573 
4574  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4575  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4576  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4577  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4578 
4579  $t = $this->getTitle()->getText();
4580  $m = [];
4581  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4582  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4583  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4584  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4585  } else {
4586  # if there's no context, don't bother duplicating the title
4587  $text = preg_replace( $p2, '[[\\1]]', $text );
4588  }
4589 
4590  return $text;
4591  }
4592 
4608  public function getUserSig( UserIdentity $user, $nickname = false, $fancySig = null ) {
4609  $username = $user->getName();
4610 
4611  # If not given, retrieve from the user object.
4612  if ( $nickname === false ) {
4613  $nickname = $this->userOptionsLookup->getOption( $user, 'nickname' );
4614  }
4615 
4616  if ( $fancySig === null ) {
4617  $fancySig = $this->userOptionsLookup->getBoolOption( $user, 'fancysig' );
4618  }
4619 
4620  if ( $nickname === null || $nickname === '' ) {
4621  // Empty value results in the default signature (even when fancysig is enabled)
4622  $nickname = $username;
4623  } elseif ( mb_strlen( $nickname ) > $this->svcOptions->get( MainConfigNames::MaxSigChars ) ) {
4624  $nickname = $username;
4625  $this->logger->debug( __METHOD__ . ": $username has overlong signature." );
4626  } elseif ( $fancySig !== false ) {
4627  # Sig. might contain markup; validate this
4628  $isValid = $this->validateSig( $nickname ) !== false;
4629 
4630  # New validator
4631  $sigValidation = $this->svcOptions->get( MainConfigNames::SignatureValidation );
4632  if ( $isValid && $sigValidation === 'disallow' ) {
4633  $parserOpts = new ParserOptions(
4634  $this->mOptions->getUserIdentity(),
4635  $this->contLang
4636  );
4637  $validator = $this->signatureValidatorFactory
4638  ->newSignatureValidator( $user, null, $parserOpts );
4639  $isValid = !$validator->validateSignature( $nickname );
4640  }
4641 
4642  if ( $isValid ) {
4643  # Validated; clean up (if needed) and return it
4644  return $this->cleanSig( $nickname, true );
4645  } else {
4646  # Failed to validate; fall back to the default
4647  $nickname = $username;
4648  $this->logger->debug( __METHOD__ . ": $username has invalid signature." );
4649  }
4650  }
4651 
4652  # Make sure nickname doesnt get a sig in a sig
4653  $nickname = self::cleanSigInSig( $nickname );
4654 
4655  # If we're still here, make it a link to the user page
4656  $userText = wfEscapeWikiText( $username );
4657  $nickText = wfEscapeWikiText( $nickname );
4658  if ( $this->userNameUtils->isTemp( $username ) ) {
4659  $msgName = 'signature-temp';
4660  } elseif ( $user->isRegistered() ) {
4661  $msgName = 'signature';
4662  } else {
4663  $msgName = 'signature-anon';
4664  }
4665 
4666  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4667  ->page( $this->getPage() )->text();
4668  }
4669 
4677  public function validateSig( $text ) {
4678  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4679  }
4680 
4692  public function cleanSig( $text, $parsing = false ) {
4693  if ( !$parsing ) {
4694  global $wgTitle;
4695  $magicScopeVariable = $this->lock();
4696  $this->startParse(
4697  $wgTitle,
4700  true
4701  );
4702  }
4703 
4704  # Option to disable this feature
4705  if ( !$this->mOptions->getCleanSignatures() ) {
4706  return $text;
4707  }
4708 
4709  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4710  # => Move this logic to braceSubstitution()
4711  $substWord = $this->magicWordFactory->get( 'subst' );
4712  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4713  $substText = '{{' . $substWord->getSynonym( 0 );
4714 
4715  $text = preg_replace( $substRegex, $substText, $text );
4716  $text = self::cleanSigInSig( $text );
4717  $dom = $this->preprocessToDom( $text );
4718  $frame = $this->getPreprocessor()->newFrame();
4719  $text = $frame->expand( $dom );
4720 
4721  if ( !$parsing ) {
4722  $text = $this->mStripState->unstripBoth( $text );
4723  }
4724 
4725  return $text;
4726  }
4727 
4735  public static function cleanSigInSig( $text ) {
4736  $text = preg_replace( '/~{3,5}/', '', $text );
4737  return $text;
4738  }
4739 
4756  public static function replaceTableOfContentsMarker( $text, $toc ) {
4757  return preg_replace_callback(
4758  self::TOC_PLACEHOLDER_REGEX,
4759  static function ( array $matches ) use( $toc ) {
4760  return $toc; // Ensure $1 \1 etc are safe to use in $toc
4761  },
4762  $text
4763  );
4764  }
4765 
4777  public function startExternalParse( ?PageReference $page, ParserOptions $options,
4778  $outputType, $clearState = true, $revId = null
4779  ) {
4780  $this->startParse( $page, $options, $outputType, $clearState );
4781  if ( $revId !== null ) {
4782  $this->mRevisionId = $revId;
4783  }
4784  }
4785 
4792  private function startParse( ?PageReference $page, ParserOptions $options,
4793  $outputType, $clearState = true
4794  ) {
4795  $this->setPage( $page );
4796  $this->mOptions = $options;
4797  $this->setOutputType( $outputType );
4798  if ( $clearState ) {
4799  $this->clearState();
4800  }
4801  }
4802 
4812  public function transformMsg( $text, ParserOptions $options, ?PageReference $page = null ) {
4813  static $executing = false;
4814 
4815  # Guard against infinite recursion
4816  if ( $executing ) {
4817  return $text;
4818  }
4819  $executing = true;
4820 
4821  if ( !$page ) {
4822  global $wgTitle;
4823  $page = $wgTitle;
4824  }
4825 
4826  $text = $this->preprocess( $text, $page, $options );
4827 
4828  $executing = false;
4829  return $text;
4830  }
4831 
4851  public function setHook( $tag, callable $callback ) {
4852  $tag = strtolower( $tag );
4853  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4854  throw new InvalidArgumentException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4855  }
4856  $oldVal = $this->mTagHooks[$tag] ?? null;
4857  $this->mTagHooks[$tag] = $callback;
4858  if ( !in_array( $tag, $this->mStripList ) ) {
4859  $this->mStripList[] = $tag;
4860  }
4861 
4862  return $oldVal;
4863  }
4864 
4869  public function clearTagHooks() {
4870  $this->mTagHooks = [];
4871  $this->mStripList = [];
4872  }
4873 
4917  public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
4918  $oldVal = $this->mFunctionHooks[$id][0] ?? null;
4919  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4920 
4921  # Add to function cache
4922  $mw = $this->magicWordFactory->get( $id );
4923  if ( !$mw ) {
4924  throw new InvalidArgumentException( __METHOD__ . '() expecting a magic word identifier.' );
4925  }
4926 
4927  $synonyms = $mw->getSynonyms();
4928  $sensitive = intval( $mw->isCaseSensitive() );
4929 
4930  foreach ( $synonyms as $syn ) {
4931  # Case
4932  if ( !$sensitive ) {
4933  $syn = $this->contLang->lc( $syn );
4934  }
4935  # Add leading hash
4936  if ( !( $flags & self::SFH_NO_HASH ) ) {
4937  $syn = '#' . $syn;
4938  }
4939  # Remove trailing colon
4940  if ( substr( $syn, -1, 1 ) === ':' ) {
4941  $syn = substr( $syn, 0, -1 );
4942  }
4943  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4944  }
4945  return $oldVal;
4946  }
4947 
4954  public function getFunctionHooks() {
4955  return array_keys( $this->mFunctionHooks );
4956  }
4957 
4965  public function replaceLinkHolders( &$text ) {
4966  $this->replaceLinkHoldersPrivate( $text );
4967  }
4968 
4975  private function replaceLinkHoldersPrivate( &$text ) {
4976  $this->mLinkHolders->replace( $text );
4977  }
4978 
4986  private function replaceLinkHoldersText( $text ) {
4987  return $this->mLinkHolders->replaceText( $text );
4988  }
4989 
5004  public function renderImageGallery( $text, array $params ) {
5005  $mode = false;
5006  if ( isset( $params['mode'] ) ) {
5007  $mode = $params['mode'];
5008  }
5009 
5010  try {
5011  $ig = ImageGalleryBase::factory( $mode );
5012  } catch ( ImageGalleryClassNotFoundException $e ) {
5013  // If invalid type set, fallback to default.
5014  $ig = ImageGalleryBase::factory( false );
5015  }
5016 
5017  $ig->setContextTitle( $this->getTitle() );
5018  $ig->setShowBytes( false );
5019  $ig->setShowDimensions( false );
5020  $ig->setShowFilename( false );
5021  $ig->setParser( $this );
5022  $ig->setHideBadImages();
5023  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
5024 
5025  if ( isset( $params['showfilename'] ) ) {
5026  $ig->setShowFilename( true );
5027  } else {
5028  $ig->setShowFilename( false );
5029  }
5030  if ( isset( $params['caption'] ) ) {
5031  // NOTE: We aren't passing a frame here or below. Frame info
5032  // is currently opaque to Parsoid, which acts on OT_PREPROCESS.
5033  // See T107332#4030581
5034  $caption = $this->recursiveTagParse( $params['caption'] );
5035  $ig->setCaptionHtml( $caption );
5036  }
5037  if ( isset( $params['perrow'] ) ) {
5038  $ig->setPerRow( $params['perrow'] );
5039  }
5040  if ( isset( $params['widths'] ) ) {
5041  $ig->setWidths( $params['widths'] );
5042  }
5043  if ( isset( $params['heights'] ) ) {
5044  $ig->setHeights( $params['heights'] );
5045  }
5046  $ig->setAdditionalOptions( $params );
5047 
5048  $enableLegacyMediaDOM = $this->svcOptions->get( MainConfigNames::ParserEnableLegacyMediaDOM );
5049 
5050  $lines = StringUtils::explode( "\n", $text );
5051  foreach ( $lines as $line ) {
5052  # match lines like these:
5053  # Image:someimage.jpg|This is some image
5054  $matches = [];
5055  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5056  # Skip empty lines
5057  if ( count( $matches ) == 0 ) {
5058  continue;
5059  }
5060 
5061  if ( strpos( $matches[0], '%' ) !== false ) {
5062  $matches[1] = rawurldecode( $matches[1] );
5063  }
5064  $title = Title::newFromText( $matches[1], NS_FILE );
5065  if ( $title === null ) {
5066  # Bogus title. Ignore these so we don't bomb out later.
5067  continue;
5068  }
5069 
5070  # We need to get what handler the file uses, to figure out parameters.
5071  # Note, a hook can override the file name, and chose an entirely different
5072  # file (which potentially could be of a different type and have different handler).
5073  $options = [];
5074  $descQuery = false;
5075  $this->hookRunner->onBeforeParserFetchFileAndTitle(
5076  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
5077  $this, $title, $options, $descQuery
5078  );
5079  # Don't register it now, as TraditionalImageGallery does that later.
5080  $file = $this->fetchFileNoRegister( $title, $options );
5081  $handler = $file ? $file->getHandler() : false;
5082 
5083  $paramMap = [
5084  'img_alt' => 'gallery-internal-alt',
5085  'img_link' => 'gallery-internal-link',
5086  ];
5087  if ( $handler ) {
5088  $paramMap += $handler->getParamMap();
5089  // We don't want people to specify per-image widths.
5090  // Additionally the width parameter would need special casing anyhow.
5091  unset( $paramMap['img_width'] );
5092  }
5093 
5094  $mwArray = $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5095 
5096  $label = '';
5097  $alt = null;
5098  $handlerOptions = [];
5099  $imageOptions = [];
5100  $hasAlt = false;
5101 
5102  if ( isset( $matches[3] ) ) {
5103  // look for an |alt= definition while trying not to break existing
5104  // captions with multiple pipes (|) in it, until a more sensible grammar
5105  // is defined for images in galleries
5106 
5107  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5108  // splitting on '|' is a bit odd, and different from makeImage.
5109  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5110  // Protect LanguageConverter markup
5111  $parameterMatches = StringUtils::delimiterExplode(
5112  '-{', '}-',
5113  '|',
5114  $matches[3],
5115  true /* nested */
5116  );
5117 
5118  foreach ( $parameterMatches as $parameterMatch ) {
5119  [ $magicName, $match ] = $mwArray->matchVariableStartToEnd( $parameterMatch );
5120  if ( !$magicName ) {
5121  // Last pipe wins.
5122  $label = $parameterMatch;
5123  continue;
5124  }
5125 
5126  $paramName = $paramMap[$magicName];
5127  switch ( $paramName ) {
5128  case 'gallery-internal-alt':
5129  $hasAlt = true;
5130  $alt = $this->stripAltText( $match, false );
5131  break;
5132  case 'gallery-internal-link':
5133  $linkValue = $this->stripAltText( $match, false );
5134  if ( preg_match( '/^-{R\|(.*)}-$/', $linkValue ) ) {
5135  // Result of LanguageConverter::markNoConversion
5136  // invoked on an external link.
5137  $linkValue = substr( $linkValue, 4, -2 );
5138  }
5139  [ $type, $target ] = $this->parseLinkParameter( $linkValue );
5140  if ( $type ) {
5141  if ( $type === 'no-link' ) {
5142  $target = true;
5143  }
5144  $imageOptions[$type] = $target;
5145  }
5146  break;
5147  default:
5148  // Must be a handler specific parameter.
5149  if ( $handler->validateParam( $paramName, $match ) ) {
5150  $handlerOptions[$paramName] = $match;
5151  } else {
5152  // Guess not, consider it as caption.
5153  $this->logger->debug(
5154  "$parameterMatch failed parameter validation" );
5155  $label = $parameterMatch;
5156  }
5157  }
5158  }
5159  }
5160 
5161  // Match makeImage when !$hasVisibleCaption
5162  if ( !$hasAlt ) {
5163  if ( $label !== '' ) {
5164  $alt = $this->stripAltText( $label, false );
5165  } else {
5166  if ( $enableLegacyMediaDOM ) {
5167  $alt = $title->getText();
5168  }
5169  }
5170  }
5171  $imageOptions['title'] = $this->stripAltText( $label, false );
5172 
5173  // Match makeImage which sets this unconditionally
5174  $handlerOptions['targetlang'] = $this->getTargetLanguage()->getCode();
5175 
5176  $ig->add(
5177  $title, $label, $alt, '', $handlerOptions,
5178  ImageGalleryBase::LOADING_DEFAULT, $imageOptions
5179  );
5180  }
5181  $html = $ig->toHTML();
5182  $this->hookRunner->onAfterParserFetchFileAndTitle( $this, $ig, $html );
5183  return $html;
5184  }
5185 
5190  private function getImageParams( $handler ) {
5191  if ( $handler ) {
5192  $handlerClass = get_class( $handler );
5193  } else {
5194  $handlerClass = '';
5195  }
5196  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5197  # Initialise static lists
5198  static $internalParamNames = [
5199  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5200  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5201  'bottom', 'text-bottom' ],
5202  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5203  'upright', 'border', 'link', 'alt', 'class' ],
5204  ];
5205  static $internalParamMap;
5206  if ( !$internalParamMap ) {
5207  $internalParamMap = [];
5208  foreach ( $internalParamNames as $type => $names ) {
5209  foreach ( $names as $name ) {
5210  // For grep: img_left, img_right, img_center, img_none,
5211  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5212  // img_bottom, img_text_bottom,
5213  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5214  // img_border, img_link, img_alt, img_class
5215  $magicName = str_replace( '-', '_', "img_$name" );
5216  $internalParamMap[$magicName] = [ $type, $name ];
5217  }
5218  }
5219  }
5220 
5221  # Add handler params
5222  $paramMap = $internalParamMap;
5223  if ( $handler ) {
5224  $handlerParamMap = $handler->getParamMap();
5225  foreach ( $handlerParamMap as $magic => $paramName ) {
5226  $paramMap[$magic] = [ 'handler', $paramName ];
5227  }
5228  } else {
5229  // Parse the size for non-existent files. See T273013
5230  $paramMap[ 'img_width' ] = [ 'handler', 'width' ];
5231  }
5232  $this->mImageParams[$handlerClass] = $paramMap;
5233  $this->mImageParamsMagicArray[$handlerClass] =
5234  $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5235  }
5236  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5237  }
5238 
5248  public function makeImage( LinkTarget $link, $options, $holders = false ) {
5249  # Check if the options text is of the form "options|alt text"
5250  # Options are:
5251  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5252  # * left no resizing, just left align. label is used for alt= only
5253  # * right same, but right aligned
5254  # * none same, but not aligned
5255  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5256  # * center center the image
5257  # * framed Keep original image size, no magnify-button.
5258  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5259  # * upright reduce width for upright images, rounded to full __0 px
5260  # * border draw a 1px border around the image
5261  # * alt Text for HTML alt attribute (defaults to empty)
5262  # * class Set a class for img node
5263  # * link Set the target of the image link. Can be external, interwiki, or local
5264  # vertical-align values (no % or length right now):
5265  # * baseline
5266  # * sub
5267  # * super
5268  # * top
5269  # * text-top
5270  # * middle
5271  # * bottom
5272  # * text-bottom
5273 
5274  # Protect LanguageConverter markup when splitting into parts
5276  '-{', '}-', '|', $options, true /* allow nesting */
5277  );
5278 
5279  # Give extensions a chance to select the file revision for us
5280  $options = [];
5281  $descQuery = false;
5282  $title = Title::castFromLinkTarget( $link ); // hook signature compat
5283  $this->hookRunner->onBeforeParserFetchFileAndTitle(
5284  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
5285  $this, $title, $options, $descQuery
5286  );
5287  # Fetch and register the file (file title may be different via hooks)
5288  [ $file, $link ] = $this->fetchFileAndTitle( $link, $options );
5289 
5290  # Get parameter map
5291  $handler = $file ? $file->getHandler() : false;
5292 
5293  [ $paramMap, $mwArray ] = $this->getImageParams( $handler );
5294 
5295  if ( !$file ) {
5296  $this->addTrackingCategory( 'broken-file-category' );
5297  }
5298 
5299  # Process the input parameters
5300  $caption = '';
5301  $params = [ 'frame' => [], 'handler' => [],
5302  'horizAlign' => [], 'vertAlign' => [] ];
5303  $seenformat = false;
5304  foreach ( $parts as $part ) {
5305  $part = trim( $part );
5306  [ $magicName, $value ] = $mwArray->matchVariableStartToEnd( $part );
5307  $validated = false;
5308  if ( isset( $paramMap[$magicName] ) ) {
5309  [ $type, $paramName ] = $paramMap[$magicName];
5310 
5311  # Special case; width and height come in one variable together
5312  if ( $type === 'handler' && $paramName === 'width' ) {
5313  $parsedWidthParam = self::parseWidthParam( $value );
5314  // Parsoid applies data-(width|height) attributes to broken
5315  // media spans, for client use. See T273013
5316  $validateFunc = static function ( $name, $value ) use ( $handler ) {
5317  return $handler
5318  ? $handler->validateParam( $name, $value )
5319  : $value > 0;
5320  };
5321  if ( isset( $parsedWidthParam['width'] ) ) {
5322  $width = $parsedWidthParam['width'];
5323  if ( $validateFunc( 'width', $width ) ) {
5324  $params[$type]['width'] = $width;
5325  $validated = true;
5326  }
5327  }
5328  if ( isset( $parsedWidthParam['height'] ) ) {
5329  $height = $parsedWidthParam['height'];
5330  if ( $validateFunc( 'height', $height ) ) {
5331  $params[$type]['height'] = $height;
5332  $validated = true;
5333  }
5334  }
5335  # else no validation -- T15436
5336  } else {
5337  if ( $type === 'handler' ) {
5338  # Validate handler parameter
5339  $validated = $handler->validateParam( $paramName, $value );
5340  } else {
5341  # Validate internal parameters
5342  switch ( $paramName ) {
5343  case 'alt':
5344  case 'class':
5345  $validated = true;
5346  $value = $this->stripAltText( $value, $holders );
5347  break;
5348  case 'link':
5349  [ $paramName, $value ] =
5350  $this->parseLinkParameter(
5351  $this->stripAltText( $value, $holders )
5352  );
5353  if ( $paramName ) {
5354  $validated = true;
5355  if ( $paramName === 'no-link' ) {
5356  $value = true;
5357  }
5358  }
5359  break;
5360  case 'manualthumb':
5361  # @todo FIXME: Possibly check validity here for
5362  # manualthumb? downstream behavior seems odd with
5363  # missing manual thumbs.
5364  $value = $this->stripAltText( $value, $holders );
5365  // fall through
5366  case 'frameless':
5367  case 'framed':
5368  case 'thumbnail':
5369  // use first appearing option, discard others.
5370  $validated = !$seenformat;
5371  $seenformat = true;
5372  break;
5373  default:
5374  # Most other things appear to be empty or numeric...
5375  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5376  }
5377  }
5378 
5379  if ( $validated ) {
5380  $params[$type][$paramName] = $value;
5381  }
5382  }
5383  }
5384  if ( !$validated ) {
5385  $caption = $part;
5386  }
5387  }
5388 
5389  # Process alignment parameters
5390  if ( $params['horizAlign'] !== [] ) {
5391  $params['frame']['align'] = array_key_first( $params['horizAlign'] );
5392  }
5393  if ( $params['vertAlign'] !== [] ) {
5394  $params['frame']['valign'] = array_key_first( $params['vertAlign'] );
5395  }
5396 
5397  $params['frame']['caption'] = $caption;
5398 
5399  $enableLegacyMediaDOM = $this->svcOptions->get( MainConfigNames::ParserEnableLegacyMediaDOM );
5400 
5401  # Will the image be presented in a frame, with the caption below?
5402  // @phan-suppress-next-line PhanImpossibleCondition
5403  $hasVisibleCaption = isset( $params['frame']['framed'] )
5404  // @phan-suppress-next-line PhanImpossibleCondition
5405  || isset( $params['frame']['thumbnail'] )
5406  // @phan-suppress-next-line PhanImpossibleCondition
5407  || isset( $params['frame']['manualthumb'] );
5408 
5409  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5410  # came to also set the caption, ordinary text after the image -- which
5411  # makes no sense, because that just repeats the text multiple times in
5412  # screen readers. It *also* came to set the title attribute.
5413  # Now that we have an alt attribute, we should not set the alt text to
5414  # equal the caption: that's worse than useless, it just repeats the
5415  # text. This is the framed/thumbnail case. If there's no caption, we
5416  # use the unnamed parameter for alt text as well, just for the time be-
5417  # ing, if the unnamed param is set and the alt param is not.
5418  # For the future, we need to figure out if we want to tweak this more,
5419  # e.g., introducing a title= parameter for the title; ignoring the un-
5420  # named parameter entirely for images without a caption; adding an ex-
5421  # plicit caption= parameter and preserving the old magic unnamed para-
5422  # meter for BC; ...
5423  if ( $hasVisibleCaption ) {
5424  if (
5425  // @phan-suppress-next-line PhanImpossibleCondition
5426  $caption === '' && !isset( $params['frame']['alt'] ) &&
5427  $enableLegacyMediaDOM
5428  ) {
5429  # No caption or alt text, add the filename as the alt text so
5430  # that screen readers at least get some description of the image
5431  $params['frame']['alt'] = $link->getText();
5432  }
5433  # Do not set $params['frame']['title'] because tooltips are unnecessary
5434  # for framed images, the caption is visible
5435  } else {
5436  // @phan-suppress-next-line PhanImpossibleCondition
5437  if ( !isset( $params['frame']['alt'] ) ) {
5438  # No alt text, use the "caption" for the alt text
5439  if ( $caption !== '' ) {
5440  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5441  } elseif ( $enableLegacyMediaDOM ) {
5442  # No caption, fall back to using the filename for the
5443  # alt text
5444  $params['frame']['alt'] = $link->getText();
5445  }
5446  }
5447  # Use the "caption" for the tooltip text
5448  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5449  }
5450  $params['handler']['targetlang'] = $this->getTargetLanguage()->getCode();
5451 
5452  // hook signature compat again, $link may have changed
5453  $title = Title::castFromLinkTarget( $link );
5454  $this->hookRunner->onParserMakeImageParams( $title, $file, $params, $this );
5455 
5456  # Linker does the rest
5457  $time = $options['time'] ?? false;
5458  // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
5459  $ret = Linker::makeImageLink( $this, $link, $file, $params['frame'], $params['handler'],
5460  $time, $descQuery, $this->mOptions->getThumbSize() );
5461 
5462  # Give the handler a chance to modify the parser object
5463  if ( $handler ) {
5464  $handler->parserTransformHook( $this, $file );
5465  }
5466  if ( $file ) {
5467  $this->modifyImageHtml( $file, $params, $ret );
5468  }
5469 
5470  return $ret;
5471  }
5472 
5491  private function parseLinkParameter( $value ) {
5492  $chars = self::EXT_LINK_URL_CLASS;
5493  $addr = self::EXT_LINK_ADDR;
5494  $prots = $this->urlUtils->validProtocols();
5495  $type = null;
5496  $target = false;
5497  if ( $value === '' ) {
5498  $type = 'no-link';
5499  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5500  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value ) ) {
5501  $this->mOutput->addExternalLink( $value );
5502  $type = 'link-url';
5503  $target = $value;
5504  }
5505  } else {
5506  // Percent-decode link arguments for consistency with wikilink
5507  // handling (T216003#7836261).
5508  //
5509  // There's slight concern here though. The |link= option supports
5510  // two formats, link=Test%22test vs link=[[Test%22test]], both of
5511  // which are about to be decoded.
5512  //
5513  // In the former case, the decoding here is straightforward and
5514  // desirable.
5515  //
5516  // In the latter case, there's a potential for double decoding,
5517  // because the wikilink syntax has a higher precedence and has
5518  // already been parsed as a link before we get here. $value
5519  // has had stripAltText() called on it, which in turn calls
5520  // replaceLinkHoldersText() on the link. So, the text we're
5521  // getting at this point has already been percent decoded.
5522  //
5523  // The problematic case is if %25 is in the title, since that
5524  // decodes to %, which could combine with trailing characters.
5525  // However, % is not a valid link title character, so it would
5526  // not parse as a link and the string we received here would
5527  // still contain the encoded %25.
5528  //
5529  // Hence, double decoded is not an issue. See the test,
5530  // "Should not double decode the link option"
5531  if ( strpos( $value, '%' ) !== false ) {
5532  $value = rawurldecode( $value );
5533  }
5534  $linkTitle = Title::newFromText( $value );
5535  if ( $linkTitle ) {
5536  $this->mOutput->addLink( $linkTitle );
5537  $type = 'link-title';
5538  $target = $linkTitle;
5539  }
5540  }
5541  return [ $type, $target ];
5542  }
5543 
5551  public function modifyImageHtml( File $file, array $params, string &$html ) {
5552  $this->hookRunner->onParserModifyImageHTML( $this, $file, $params, $html );
5553  }
5554 
5560  private function stripAltText( $caption, $holders ) {
5561  # Strip bad stuff out of the title (tooltip). We can't just use
5562  # replaceLinkHoldersText() here, because if this function is called
5563  # from handleInternalLinks2(), mLinkHolders won't be up-to-date.
5564  if ( $holders ) {
5565  $tooltip = $holders->replaceText( $caption );
5566  } else {
5567  $tooltip = $this->replaceLinkHoldersText( $caption );
5568  }
5569 
5570  # make sure there are no placeholders in thumbnail attributes
5571  # that are later expanded to html- so expand them now and
5572  # remove the tags
5573  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5574  # Compatibility hack! In HTML certain entity references not terminated
5575  # by a semicolon are decoded (but not if we're in an attribute; that's
5576  # how link URLs get away without properly escaping & in queries).
5577  # But wikitext has always required semicolon-termination of entities,
5578  # so encode & where needed to avoid decode of semicolon-less entities.
5579  # See T209236 and
5580  # https://www.w3.org/TR/html5/syntax.html#named-character-references
5581  # T210437 discusses moving this workaround to Sanitizer::stripAllTags.
5582  $tooltip = preg_replace( "/
5583  & # 1. entity prefix
5584  (?= # 2. followed by:
5585  (?: # a. one of the legacy semicolon-less named entities
5586  A(?:Elig|MP|acute|circ|grave|ring|tilde|uml)|
5587  C(?:OPY|cedil)|E(?:TH|acute|circ|grave|uml)|
5588  GT|I(?:acute|circ|grave|uml)|LT|Ntilde|
5589  O(?:acute|circ|grave|slash|tilde|uml)|QUOT|REG|THORN|
5590  U(?:acute|circ|grave|uml)|Yacute|
5591  a(?:acute|c(?:irc|ute)|elig|grave|mp|ring|tilde|uml)|brvbar|
5592  c(?:cedil|edil|urren)|cent(?!erdot;)|copy(?!sr;)|deg|
5593  divide(?!ontimes;)|e(?:acute|circ|grave|th|uml)|
5594  frac(?:1(?:2|4)|34)|
5595  gt(?!c(?:c|ir)|dot|lPar|quest|r(?:a(?:pprox|rr)|dot|eq(?:less|qless)|less|sim);)|
5596  i(?:acute|circ|excl|grave|quest|uml)|laquo|
5597  lt(?!c(?:c|ir)|dot|hree|imes|larr|quest|r(?:Par|i(?:e|f|));)|
5598  m(?:acr|i(?:cro|ddot))|n(?:bsp|tilde)|
5599  not(?!in(?:E|dot|v(?:a|b|c)|)|ni(?:v(?:a|b|c)|);)|
5600  o(?:acute|circ|grave|rd(?:f|m)|slash|tilde|uml)|
5601  p(?:lusmn|ound)|para(?!llel;)|quot|r(?:aquo|eg)|
5602  s(?:ect|hy|up(?:1|2|3)|zlig)|thorn|times(?!b(?:ar|)|d;)|
5603  u(?:acute|circ|grave|ml|uml)|y(?:acute|en|uml)
5604  )
5605  (?:[^;]|$)) # b. and not followed by a semicolon
5606  # S = study, for efficiency
5607  /Sx", '&amp;', $tooltip );
5608  $tooltip = Sanitizer::stripAllTags( $tooltip );
5609 
5610  return $tooltip;
5611  }
5612 
5622  public function attributeStripCallback( &$text, $frame = false ) {
5623  wfDeprecated( __METHOD__, '1.35' );
5624  $text = $this->replaceVariables( $text, $frame );
5625  $text = $this->mStripState->unstripBoth( $text );
5626  return $text;
5627  }
5628 
5635  public function getTags(): array {
5636  return array_keys( $this->mTagHooks );
5637  }
5638 
5643  public function getFunctionSynonyms() {
5644  return $this->mFunctionSynonyms;
5645  }
5646 
5651  public function getUrlProtocols() {
5652  return $this->urlUtils->validProtocols();
5653  }
5654 
5684  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5685  global $wgTitle; # not generally used but removes an ugly failure mode
5686 
5687  $magicScopeVariable = $this->lock();
5688  $this->startParse(
5689  $wgTitle,
5692  true
5693  );
5694  $outText = '';
5695  $frame = $this->getPreprocessor()->newFrame();
5696 
5697  # Process section extraction flags
5698  $flags = 0;
5699  $sectionParts = explode( '-', $sectionId );
5700  // The section ID may either be a magic string such as 'new' (which should be treated as 0),
5701  // or a numbered section ID in the format of "T-<section index>".
5702  // Explicitly coerce the section index into a number accordingly. (T323373)
5703  $sectionIndex = (int)array_pop( $sectionParts );
5704  foreach ( $sectionParts as $part ) {
5705  if ( $part === 'T' ) {
5707  }
5708  }
5709 
5710  # Check for empty input
5711  if ( strval( $text ) === '' ) {
5712  # Only sections 0 and T-0 exist in an empty document
5713  if ( $sectionIndex === 0 ) {
5714  if ( $mode === 'get' ) {
5715  return '';
5716  }
5717 
5718  return $newText;
5719  } else {
5720  if ( $mode === 'get' ) {
5721  return $newText;
5722  }
5723 
5724  return $text;
5725  }
5726  }
5727 
5728  # Preprocess the text
5729  $root = $this->preprocessToDom( $text, $flags );
5730 
5731  # <h> nodes indicate section breaks
5732  # They can only occur at the top level, so we can find them by iterating the root's children
5733  $node = $root->getFirstChild();
5734 
5735  # Find the target section
5736  if ( $sectionIndex === 0 ) {
5737  # Section zero doesn't nest, level=big
5738  $targetLevel = 1000;
5739  } else {
5740  while ( $node ) {
5741  if ( $node->getName() === 'h' ) {
5742  $bits = $node->splitHeading();
5743  if ( $bits['i'] == $sectionIndex ) {
5744  $targetLevel = $bits['level'];
5745  break;
5746  }
5747  }
5748  if ( $mode === 'replace' ) {
5749  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5750  }
5751  $node = $node->getNextSibling();
5752  }
5753  }
5754 
5755  if ( !$node ) {
5756  # Not found
5757  if ( $mode === 'get' ) {
5758  return $newText;
5759  } else {
5760  return $text;
5761  }
5762  }
5763 
5764  # Find the end of the section, including nested sections
5765  do {
5766  if ( $node->getName() === 'h' ) {
5767  $bits = $node->splitHeading();
5768  $curLevel = $bits['level'];
5769  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable False positive
5770  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5771  break;
5772  }
5773  }
5774  if ( $mode === 'get' ) {
5775  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5776  }
5777  $node = $node->getNextSibling();
5778  } while ( $node );
5779 
5780  # Write out the remainder (in replace mode only)
5781  if ( $mode === 'replace' ) {
5782  # Output the replacement text
5783  # Add two newlines on -- trailing whitespace in $newText is conventionally
5784  # stripped by the editor, so we need both newlines to restore the paragraph gap
5785  # Only add trailing whitespace if there is newText
5786  if ( $newText != "" ) {
5787  $outText .= $newText . "\n\n";
5788  }
5789 
5790  while ( $node ) {
5791  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5792  $node = $node->getNextSibling();
5793  }
5794  }
5795 
5796  # Re-insert stripped tags
5797  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5798 
5799  return $outText;
5800  }
5801 
5817  public function getSection( $text, $sectionId, $defaultText = '' ) {
5818  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5819  }
5820 
5834  public function replaceSection( $oldText, $sectionId, $newText ) {
5835  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5836  }
5837 
5867  public function getFlatSectionInfo( $text ) {
5868  $magicScopeVariable = $this->lock();
5869  $this->startParse(
5870  null,
5873  true
5874  );
5875  $frame = $this->getPreprocessor()->newFrame();
5876  $root = $this->preprocessToDom( $text, 0 );
5877  $node = $root->getFirstChild();
5878  $offset = 0;
5879  $currentSection = [
5880  'index' => 0,
5881  'level' => 0,
5882  'offset' => 0,
5883  'heading' => '',
5884  'text' => ''
5885  ];
5886  $sections = [];
5887 
5888  while ( $node ) {
5889  $nodeText = $frame->expand( $node, PPFrame::RECOVER_ORIG );
5890  if ( $node->getName() === 'h' ) {
5891  $bits = $node->splitHeading();
5892  $sections[] = $currentSection;
5893  $currentSection = [
5894  'index' => $bits['i'],
5895  'level' => $bits['level'],
5896  'offset' => $offset,
5897  'heading' => $nodeText,
5898  'text' => $nodeText
5899  ];
5900  } else {
5901  $currentSection['text'] .= $nodeText;
5902  }
5903  $offset += strlen( $nodeText );
5904  $node = $node->getNextSibling();
5905  }
5906  $sections[] = $currentSection;
5907  return $sections;
5908  }
5909 
5921  public function getRevisionId() {
5922  return $this->mRevisionId;
5923  }
5924 
5931  public function getRevisionRecordObject() {
5932  if ( $this->mRevisionRecordObject ) {
5933  return $this->mRevisionRecordObject;
5934  }
5935 
5936  // NOTE: try to get the RevisionRecord object even if mRevisionId is null.
5937  // This is useful when parsing a revision that has not yet been saved.
5938  // However, if we get back a saved revision even though we are in
5939  // preview mode, we'll have to ignore it, see below.
5940  // NOTE: This callback may be used to inject an OLD revision that was
5941  // already loaded, so "current" is a bit of a misnomer. We can't just
5942  // skip it if mRevisionId is set.
5943  $rev = call_user_func(
5944  $this->mOptions->getCurrentRevisionRecordCallback(),
5945  $this->getTitle(),
5946  $this
5947  );
5948 
5949  if ( !$rev ) {
5950  // The revision record callback returns `false` (not null) to
5951  // indicate that the revision is missing. (See for example
5952  // Parser::statelessFetchRevisionRecord(), the default callback.)
5953  // This API expects `null` instead. (T251952)
5954  return null;
5955  }
5956 
5957  if ( $this->mRevisionId === null && $rev->getId() ) {
5958  // We are in preview mode (mRevisionId is null), and the current revision callback
5959  // returned an existing revision. Ignore it and return null, it's probably the page's
5960  // current revision, which is not what we want here. Note that we do want to call the
5961  // callback to allow the unsaved revision to be injected here, e.g. for
5962  // self-transclusion previews.
5963  return null;
5964  }
5965 
5966  // If the parse is for a new revision, then the callback should have
5967  // already been set to force the object and should match mRevisionId.
5968  // If not, try to fetch by mRevisionId instead.
5969  if ( $this->mRevisionId && $rev->getId() != $this->mRevisionId ) {
5970  $rev = MediaWikiServices::getInstance()
5971  ->getRevisionLookup()
5972  ->getRevisionById( $this->mRevisionId );
5973  }
5974 
5975  $this->mRevisionRecordObject = $rev;
5976 
5977  return $this->mRevisionRecordObject;
5978  }
5979 
5986  public function getRevisionTimestamp() {
5987  if ( $this->mRevisionTimestamp !== null ) {
5988  return $this->mRevisionTimestamp;
5989  }
5990 
5991  # Use specified revision timestamp, falling back to the current timestamp
5992  $revObject = $this->getRevisionRecordObject();
5993  $timestamp = $revObject && $revObject->getTimestamp()
5994  ? $revObject->getTimestamp()
5995  : $this->mOptions->getTimestamp();
5996  $this->mOutput->setRevisionTimestampUsed( $timestamp ); // unadjusted time zone
5997 
5998  # The cryptic '' timezone parameter tells to use the site-default
5999  # timezone offset instead of the user settings.
6000  # Since this value will be saved into the parser cache, served
6001  # to other users, and potentially even used inside links and such,
6002  # it needs to be consistent for all visitors.
6003  $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp, '' );
6004 
6005  return $this->mRevisionTimestamp;
6006  }
6007 
6014  public function getRevisionUser(): ?string {
6015  if ( $this->mRevisionUser === null ) {
6016  $revObject = $this->getRevisionRecordObject();
6017 
6018  # if this template is subst: the revision id will be blank,
6019  # so just use the current user's name
6020  if ( $revObject && $revObject->getUser() ) {
6021  $this->mRevisionUser = $revObject->getUser()->getName();
6022  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
6023  $this->mRevisionUser = $this->getUserIdentity()->getName();
6024  } else {
6025  # Note that we fall through here with
6026  # $this->mRevisionUser still null
6027  }
6028  }
6029  return $this->mRevisionUser;
6030  }
6031 
6038  public function getRevisionSize() {
6039  if ( $this->mRevisionSize === null ) {
6040  $revObject = $this->getRevisionRecordObject();
6041 
6042  # if this variable is subst: the revision id will be blank,
6043  # so just use the parser input size, because the own substitution
6044  # will change the size.
6045  if ( $revObject ) {
6046  $this->mRevisionSize = $revObject->getSize();
6047  } else {
6048  $this->mRevisionSize = $this->mInputSize;
6049  }
6050  }
6051  return $this->mRevisionSize;
6052  }
6053 
6062  public function setDefaultSort( $sort ) {
6063  wfDeprecated( __METHOD__, '1.38' );
6064  $this->mOutput->setPageProperty( 'defaultsort', $sort );
6065  }
6066 
6080  public function getDefaultSort() {
6081  wfDeprecated( __METHOD__, '1.38' );
6082  return $this->mOutput->getPageProperty( 'defaultsort' ) ?? '';
6083  }
6084 
6094  public function getCustomDefaultSort() {
6095  wfDeprecated( __METHOD__, '1.38' );
6096  return $this->mOutput->getPageProperty( 'defaultsort' ) ?? false;
6097  }
6098 
6099  private static function getSectionNameFromStrippedText( $text ) {
6100  $text = Sanitizer::normalizeSectionNameWhitespace( $text );
6101  $text = Sanitizer::decodeCharReferences( $text );
6102  $text = self::normalizeSectionName( $text );
6103  return $text;
6104  }
6105 
6106  private static function makeAnchor( $sectionName ) {
6107  return '#' . Sanitizer::escapeIdForLink( $sectionName );
6108  }
6109 
6110  private function makeLegacyAnchor( $sectionName ) {
6111  $fragmentMode = $this->svcOptions->get( MainConfigNames::FragmentMode );
6112  if ( isset( $fragmentMode[1] ) && $fragmentMode[1] === 'legacy' ) {
6113  // ForAttribute() and ForLink() are the same for legacy encoding
6114  $id = Sanitizer::escapeIdForAttribute( $sectionName, Sanitizer::ID_FALLBACK );
6115  } else {
6116  $id = Sanitizer::escapeIdForLink( $sectionName );
6117  }
6118 
6119  return "#$id";
6120  }
6121 
6131  public function guessSectionNameFromWikiText( $text ) {
6132  # Strip out wikitext links(they break the anchor)
6133  $text = $this->stripSectionName( $text );
6134  $sectionName = self::getSectionNameFromStrippedText( $text );
6135  return self::makeAnchor( $sectionName );
6136  }
6137 
6148  public function guessLegacySectionNameFromWikiText( $text ) {
6149  # Strip out wikitext links(they break the anchor)
6150  $text = $this->stripSectionName( $text );
6151  $sectionName = self::getSectionNameFromStrippedText( $text );
6152  return $this->makeLegacyAnchor( $sectionName );
6153  }
6154 
6161  public static function guessSectionNameFromStrippedText( $text ) {
6162  $sectionName = self::getSectionNameFromStrippedText( $text );
6163  return self::makeAnchor( $sectionName );
6164  }
6165 
6172  private static function normalizeSectionName( $text ) {
6173  # T90902: ensure the same normalization is applied for IDs as to links
6175  $titleParser = MediaWikiServices::getInstance()->getTitleParser();
6176  '@phan-var MediaWikiTitleCodec $titleParser';
6177  try {
6178 
6179  $parts = $titleParser->splitTitleString( "#$text" );
6180  } catch ( MalformedTitleException $ex ) {
6181  return $text;
6182  }
6183  return $parts['fragment'];
6184  }
6185 
6201  public function stripSectionName( $text ) {
6202  # Strip internal link markup
6203  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
6204  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
6205 
6206  # Strip external link markup
6207  # @todo FIXME: Not tolerant to blank link text
6208  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
6209  # on how many empty links there are on the page - need to figure that out.
6210  $text = preg_replace(
6211  '/\[(?i:' . $this->urlUtils->validProtocols() . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
6212 
6213  # Parse wikitext quotes (italics & bold)
6214  $text = $this->doQuotes( $text );
6215 
6216  # Strip HTML tags
6217  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
6218  return $text;
6219  }
6220 
6239  public function markerSkipCallback( $s, callable $callback ) {
6240  $i = 0;
6241  $out = '';
6242  while ( $i < strlen( $s ) ) {
6243  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
6244  if ( $markerStart === false ) {
6245  $out .= call_user_func( $callback, substr( $s, $i ) );
6246  break;
6247  } else {
6248  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
6249  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
6250  if ( $markerEnd === false ) {
6251  $out .= substr( $s, $markerStart );
6252  break;
6253  } else {
6254  $markerEnd += strlen( self::MARKER_SUFFIX );
6255  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
6256  $i = $markerEnd;
6257  }
6258  }
6259  }
6260  return $out;
6261  }
6262 
6270  public function killMarkers( $text ) {
6271  return $this->mStripState->killMarkers( $text );
6272  }
6273 
6284  public static function parseWidthParam( $value, $parseHeight = true ) {
6285  $parsedWidthParam = [];
6286  if ( $value === '' ) {
6287  return $parsedWidthParam;
6288  }
6289  $m = [];
6290  # (T15500) In both cases (width/height and width only),
6291  # permit trailing "px" for backward compatibility.
6292  if ( $parseHeight && preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6293  $width = intval( $m[1] );
6294  $height = intval( $m[2] );
6295  $parsedWidthParam['width'] = $width;
6296  $parsedWidthParam['height'] = $height;
6297  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6298  $width = intval( $value );
6299  $parsedWidthParam['width'] = $width;
6300  }
6301  return $parsedWidthParam;
6302  }
6303 
6313  protected function lock() {
6314  if ( $this->mInParse ) {
6315  throw new MWException( "Parser state cleared while parsing. "
6316  . "Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6317  }
6318 
6319  // Save the backtrace when locking, so that if some code tries locking again,
6320  // we can print the lock owner's backtrace for easier debugging
6321  $e = new Exception;
6322  $this->mInParse = $e->getTraceAsString();
6323 
6324  $recursiveCheck = new ScopedCallback( function () {
6325  $this->mInParse = false;
6326  } );
6327 
6328  return $recursiveCheck;
6329  }
6330 
6338  public function isLocked() {
6339  return (bool)$this->mInParse;
6340  }
6341 
6352  public static function stripOuterParagraph( $html ) {
6353  $m = [];
6354  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) && strpos( $m[1], '</p>' ) === false ) {
6355  $html = $m[1];
6356  }
6357 
6358  return $html;
6359  }
6360 
6371  public static function formatPageTitle( $nsText, $nsSeparator, $mainText ): string {
6372  $html = '';
6373  if ( $nsText !== '' ) {
6374  $html .= '<span class="mw-page-title-namespace">' . HtmlArmor::getHtml( $nsText ) . '</span>';
6375  $html .= '<span class="mw-page-title-separator">' . HtmlArmor::getHtml( $nsSeparator ) . '</span>';
6376  }
6377  $html .= '<span class="mw-page-title-main">' . HtmlArmor::getHtml( $mainText ) . '</span>';
6378  return $html;
6379  }
6380 
6392  public function getFreshParser() {
6393  if ( $this->mInParse ) {
6394  return $this->factory->create();
6395  } else {
6396  return $this;
6397  }
6398  }
6399 
6407  public function enableOOUI() {
6408  wfDeprecated( __METHOD__, '1.35' );
6409  OutputPage::setupOOUI();
6410  $this->mOutput->setEnableOOUI( true );
6411  }
6412 
6419  private function setOutputFlag( string $flag, string $reason ): void {
6420  $this->mOutput->setOutputFlag( $flag );
6421  $name = $this->getTitle()->getPrefixedText();
6422  $this->logger->debug( __METHOD__ . ": set $flag flag on '$name'; $reason" );
6423  }
6424 }
getUser()
const OT_WIKI
Definition: Defines.php:158
const SFH_NO_HASH
Definition: Defines.php:169
const SFH_OBJECT_ARGS
Definition: Defines.php:170
const NS_FILE
Definition: Defines.php:70
const NS_MEDIAWIKI
Definition: Defines.php:72
const NS_TEMPLATE
Definition: Defines.php:74
const NS_SPECIAL
Definition: Defines.php:53
const OT_PLAIN
Definition: Defines.php:160
const OT_PREPROCESS
Definition: Defines.php:159
const OT_HTML
Definition: Defines.php:157
const NS_MEDIA
Definition: Defines.php:52
const NS_CATEGORY
Definition: Defines.php:78
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfHostname()
Get host name of the current machine, for use in error reporting.
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
wfMatchesDomainList( $url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
$matches
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgTitle
Definition: Setup.php:535
if(!defined('MW_SETUP_CALLBACK'))
Definition: WebStart.php:88
static doBlockLevels( $text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
static expand(Parser $parser, string $id, ConvertibleTimestamp $ts, ServiceOptions $svcOptions, LoggerInterface $logger)
Expand the magic variable given by $index.
static register(Parser $parser, ServiceOptions $options)
static register(Parser $parser, ServiceOptions $options)
const REGISTER_OPTIONS
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:70
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
static getHtml( $input)
Provide a string or HtmlArmor object and get safe HTML back.
Definition: HtmlArmor.php:55
static factory( $mode=false, IContextSource $context=null)
Get a new image gallery.
Class for exceptions thrown by ImageGalleryBase::factory().
static bcp47( $code)
Get the normalised IANA language tag See unit test for examples.
Base class for language-specific code.
Definition: Language.php:61
formatNum( $number)
Normally we output all numbers in plain en_US style, that is 293,291.235 for two hundred ninety-three...
Definition: Language.php:2999
MediaWiki exception.
Definition: MWException.php:33
Store key-value entries in a size-limited in-memory LRU cache.
Definition: MapCacheLRU.php:34
Helper class for mapping value objects representing basic entities to cache keys.
This class performs some operations related to tracking categories, such as adding a tracking categor...
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:568
Factory creating MWHttpRequest objects.
Variant of the Message class.
Definition: RawMessage.php:40
An interface for creating language converters.
isConversionDisabled()
Whether to disable language variant conversion.
A service that provides utilities to do with language names and codes.
Factory to create LinkRender objects.
Class that generates HTML for internal links.
Some internal bits split of from Skin.php.
Definition: Linker.php:65
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
This is one of the Core classes and should be read at least once by any new developers.
Definition: OutputPage.php:93
Class for handling an array of magic words.
A factory that stores information about MagicWords, and creates them on demand with caching.
HTML sanitizer for MediaWiki.
Definition: Sanitizer.php:46
static removeSomeTags(string $text, array $options=[])
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments; the result will alw...
Definition: Sanitizer.php:397
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:42
Exception representing a failure to look up a revision.
Page revision base class.
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
Factory for handling the special page list and generating SpecialPage objects.
Parent class for all special pages.
Definition: SpecialPage.php:66
Base class for HTML cleanup utilities.
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
A codec for MediaWiki page titles.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition: Title.php:76
Creates User objects.
Definition: UserFactory.php:41
UserNameUtils service.
Provides access to user options.
internal since 1.36
Definition: User.php:98
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:48
A service to expand, parse, and otherwise manipulate URLs.
Definition: UrlUtils.php:17
validProtocols()
Returns a regular expression of recognized URL protocols.
Definition: UrlUtils.php:355
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:144
static plaintextParam( $plaintext)
Definition: Message.php:1275
static numParam( $num)
Definition: Message.php:1154
static int $inParserFactory
Track calls to Parser constructor to aid in deprecation of direct Parser invocation.
Set options of the Parser.
getPreSaveTransform()
Transform wiki markup when saving the page?
static newFromUser( $user)
Get a ParserOptions object from a given user.
getDisableTitleConversion()
Whether title conversion should be disabled.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition: Parser.php:115
addTrackingCategory( $msg)
Definition: Parser.php:4075
$ot
Shortcut alias, see Parser::setOutputType()
Definition: Parser.php:286
static extractTagsAndParams(array $elements, $text, &$matches)
Replaces all occurrences of HTML-style comments and the given tags in the text with a random marker a...
Definition: Parser.php:1213
getTargetLanguage()
Get the target language for the content being parsed.
Definition: Parser.php:1116
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
Definition: Parser.php:5986
setDefaultSort( $sort)
Mutator for the 'defaultsort' page property.
Definition: Parser.php:6062
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6352
static normalizeLinkUrl( $url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:2248
__clone()
Allow extensions to clean up when the parser is cloned.
Definition: Parser.php:511
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4735
getMagicWordFactory()
Get the MagicWordFactory that this Parser is using.
Definition: Parser.php:1170
$mHighestExpansionDepth
Definition: Parser.php:245
parse( $text, PageReference $page, ParserOptions $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
Definition: Parser.php:622
getCustomDefaultSort()
Accessor for the 'defaultsort' page property.
Definition: Parser.php:6094
ParserOptions null $mOptions
Definition: Parser.php:268
getOptions()
Definition: Parser.php:1056
cleanSig( $text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4692
getStripState()
Definition: Parser.php:1284
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:6014
array $mHeadings
Definition: Parser.php:248
isCurrentRevisionOfTitleCached(LinkTarget $link)
Definition: Parser.php:3488
replaceVariables( $text, $frame=false, $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
Definition: Parser.php:2866
getFunctionSynonyms()
Definition: Parser.php:5643
extensionSubstitution(array $params, PPFrame $frame, bool $processNowiki=false)
Return the text to be used for a given extension tag.
Definition: Parser.php:3900
interwikiTransclude(LinkTarget $link, $action)
Transclude an interwiki link.
Definition: Parser.php:3763
const OT_HTML
Output type: like Parser::parse()
Definition: Parser.php:152
getPreloadText( $text, PageReference $page, ParserOptions $options, $params=[])
Process the wikitext for the "?preload=" feature.
Definition: Parser.php:925
setTitle(Title $t=null)
Set the context title.
Definition: Parser.php:958
static guessSectionNameFromStrippedText( $text)
Like guessSectionNameFromWikiText(), but takes already-stripped text as input.
Definition: Parser.php:6161
const OT_WIKI
Output type: like Parser::preSaveTransform()
Definition: Parser.php:154
getFlatSectionInfo( $text)
Get an array of preprocessor section information.
Definition: Parser.php:5867
limitationWarn( $limitationType, $current='', $max='')
Warn the user when a parser limitation is reached Will warn at most once the user per limitation type...
Definition: Parser.php:2917
modifyImageHtml(File $file, array $params, string &$html)
Give hooks a chance to modify image thumbnail HTML.
Definition: Parser.php:5551
fetchTemplateAndTitle(LinkTarget $link)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3527
Title null $mTitle
Title context, used for self-link rendering and similar things.
Definition: Parser.php:279
setUser(?UserIdentity $user)
Set the current user.
Definition: Parser.php:947
getRevisionSize()
Get the size of the revision.
Definition: Parser.php:6038
stripSectionName( $text)
Strips a text string of wikitext for use in a section anchor.
Definition: Parser.php:6201
tagNeedsNowikiStrippedInTagPF(string $lowerTagName)
Definition: Parser.php:3876
getOutputType()
Accessor for the output type.
Definition: Parser.php:1012
fetchCurrentRevisionRecordOfTitle(LinkTarget $link)
Fetch the current revision of a given title as a RevisionRecord.
Definition: Parser.php:3458
fetchFileAndTitle(LinkTarget $link, array $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3713
recursivePreprocess( $text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:905
fetchFileNoRegister(LinkTarget $link, array $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3740
getUrlProtocols()
Definition: Parser.php:5651
setFunctionHook( $id, callable $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4917
getHookRunner()
Get a HookRunner for calling core hooks.
Definition: Parser.php:1625
getTitle()
Definition: Parser.php:967
braceSubstitution(array $piece, PPFrame $frame)
Return the text of a template, after recursively replacing any variables or templates within the temp...
Definition: Parser.php:2942
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:1155
preprocess( $text, ?PageReference $page, ParserOptions $options, $revid=null, $frame=false)
Expand templates and variables in the text, producing valid, static wikitext.
Definition: Parser.php:878
getExternalLinkAttribs( $url)
Get an associative array of additional HTML attributes appropriate for a particular external link.
Definition: Parser.php:2217
setOutputType( $ot)
Mutator for the output type.
Definition: Parser.php:1021
getFunctionLang()
Get a language object for use in parser functions such as {{FORMATNUM:}}.
Definition: Parser.php:1103
callParserFunction(PPFrame $frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3323
makeLimitReport()
Set the limit report data in the current ParserOutput.
Definition: Parser.php:722
parseExtensionTagAsTopLevelDoc( $text)
Needed by Parsoid/PHP to ensure all the hooks for extensions are run in the right order.
Definition: Parser.php:859
transformMsg( $text, ParserOptions $options, ?PageReference $page=null)
Wrapper for preprocess()
Definition: Parser.php:4812
startExternalParse(?PageReference $page, ParserOptions $options, $outputType, $clearState=true, $revId=null)
Set up some variables which are usually set up in parse() so that an external function can call some ...
Definition: Parser.php:4777
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1276
setHook( $tag, callable $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4851
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition: Parser.php:1190
getTags()
Accessor.
Definition: Parser.php:5635
static statelessFetchRevisionRecord(LinkTarget $link, $parser=null)
Wrapper around RevisionLookup::getKnownCurrentRevision.
Definition: Parser.php:3504
recursiveTagParse( $text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:810
getTargetLanguageConverter()
Shorthand for getting a Language Converter for Target language.
Definition: Parser.php:1589
static replaceTableOfContentsMarker( $text, $toc)
Replace table of contents marker in parsed HTML.
Definition: Parser.php:4756
const OT_PLAIN
Output type: like Parser::extractSections() - portions of the original are returned unchanged.
Definition: Parser.php:161
getSection( $text, $sectionId, $defaultText='')
This function returns the text of a section, specified by a number ($section).
Definition: Parser.php:5817
internalParse( $text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
Definition: Parser.php:1520
replaceSection( $oldText, $sectionId, $newText)
This function returns $oldtext after the content of the section specified by $section has been replac...
Definition: Parser.php:5834
setPage(?PageReference $t=null)
Set the page used as context for parsing, e.g.
Definition: Parser.php:980
getUserIdentity()
Get a user either from the user set on Parser if it's set, or from the ParserOptions object otherwise...
Definition: Parser.php:1135
doBlockLevels( $text, $linestart)
Make lists from lines starting with ':', '*', '#', etc.
Definition: Parser.php:2760
$mPPNodeCount
Definition: Parser.php:243
getDefaultSort()
Accessor for the 'defaultsort' page property.
Definition: Parser.php:6080
const MARKER_PREFIX
Definition: Parser.php:181
isLocked()
Will entry points such as parse() throw an exception due to the parser already being active?
Definition: Parser.php:6338
getHookContainer()
Get a HookContainer capable of returning metadata about hooks or running extension hooks.
Definition: Parser.php:1613
getFunctionHooks()
Get all registered function hook identifiers.
Definition: Parser.php:4954
$mMarkerIndex
Definition: Parser.php:224
replaceLinkHolders(&$text)
Replace "<!--LINK-->" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4965
getPage()
Returns the page used as context for parsing, e.g.
Definition: Parser.php:1003
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:2192
$mExpensiveFunctionCount
Number of expensive parser function calls.
Definition: Parser.php:255
getContentLanguage()
Get the content language that this Parser is using.
Definition: Parser.php:1180
getOutput()
Definition: Parser.php:1048
Options( $x=null)
Accessor/mutator for the ParserOptions object.
Definition: Parser.php:1076
recursiveTagParseFully( $text, $frame=false)
Fully parse wikitext to fully parsed HTML.
Definition: Parser.php:834
guessSectionNameFromWikiText( $text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
Definition: Parser.php:6131
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4869
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5921
setOptions(ParserOptions $options)
Mutator for the ParserOptions object.
Definition: Parser.php:1065
static parseWidthParam( $value, $parseHeight=true)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6284
validateSig( $text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4677
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:1145
guessLegacySectionNameFromWikiText( $text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead, if possible.
Definition: Parser.php:6148
const EXT_LINK_URL_CLASS
Everything except bracket, space, or control characters.
Definition: Parser.php:129
OutputType( $x=null)
Accessor/mutator for the output type.
Definition: Parser.php:1039
const SFH_OBJECT_ARGS
Definition: Parser.php:119
__destruct()
Reduce memory usage to reduce the impact of circular references.
Definition: Parser.php:496
resetOutput()
Reset the ParserOutput.
Definition: Parser.php:599
setLinkID( $id)
Definition: Parser.php:1093
doQuotes( $text)
Helper function for handleAllQuotes()
Definition: Parser.php:1927
getTemplateDom(LinkTarget $title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3413
static formatPageTitle( $nsText, $nsSeparator, $mainText)
Add HTML tags marking the parts of a page title, to be displayed in the first heading of the page.
Definition: Parser.php:6371
insertStripItem( $text)
Add an item to the strip state Returns the unique tag which must be inserted into the stripped text T...
Definition: Parser.php:1297
const CONSTRUCTOR_OPTIONS
Definition: Parser.php:353
incrementExpensiveFunctionCount()
Definition: Parser.php:4006
makeImage(LinkTarget $link, $options, $holders=false)
Parse image options text and use it to make an image.
Definition: Parser.php:5248
const OT_PREPROCESS
Output type: like Parser::preprocess()
Definition: Parser.php:156
getUserSig(UserIdentity $user, $nickname=false, $fancySig=null)
Fetch the user's signature text, if any, and normalize to validated, ready-to-insert wikitext.
Definition: Parser.php:4608
__construct(ServiceOptions $svcOptions, MagicWordFactory $magicWordFactory, Language $contLang, ParserFactory $factory, UrlUtils $urlUtils, SpecialPageFactory $spFactory, LinkRendererFactory $linkRendererFactory, NamespaceInfo $nsInfo, LoggerInterface $logger, BadFileLookup $badFileLookup, LanguageConverterFactory $languageConverterFactory, HookContainer $hookContainer, TidyDriverBase $tidy, WANObjectCache $wanCache, UserOptionsLookup $userOptionsLookup, UserFactory $userFactory, TitleFormatter $titleFormatter, HttpRequestFactory $httpRequestFactory, TrackingCategories $trackingCategories, SignatureValidatorFactory $signatureValidatorFactory, UserNameUtils $userNameUtils)
Constructing parsers directly is not allowed! Use a ParserFactory.
Definition: Parser.php:406
firstCallInit()
Used to do various kinds of initialisation on the first call of the parser.
Definition: Parser.php:541
preprocessToDom( $text, $flags=0)
Get the document object model for the given wikitext.
Definition: Parser.php:2841
markerSkipCallback( $s, callable $callback)
Call a callback function on all regions of the given text that are not inside strip markers,...
Definition: Parser.php:6239
clearState()
Clear Parser state.
Definition: Parser.php:553
killMarkers( $text)
Remove any strip markers found in the given text.
Definition: Parser.php:6270
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6407
nextLinkID()
Definition: Parser.php:1085
msg(string $msg,... $args)
Helper function to correctly set the target language and title of a message based on the parser conte...
Definition: Parser.php:4094
getRevisionRecordObject()
Get the revision record object for $this->mRevisionId.
Definition: Parser.php:5931
attributeStripCallback(&$text, $frame=false)
Callback from the Sanitizer for expanding items found in HTML attribute values, so they can be safely...
Definition: Parser.php:5622
argSubstitution(array $piece, PPFrame $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3835
const SFH_NO_HASH
Definition: Parser.php:118
renderImageGallery( $text, array $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:5004
lock()
Lock the current instance of the parser.
Definition: Parser.php:6313
static statelessFetchTemplate( $page, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3569
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:6392
preSaveTransform( $text, PageReference $page, UserIdentity $user, ParserOptions $options, $clearState=true)
Transform wiki markup when saving a page by doing "\\r\\n" -> "\\n" conversion, substituting signatur...
Definition: Parser.php:4487
Differences from DOM schema:
const DOM_FOR_INCLUSION
Transclusion mode flag for Preprocessor::preprocessToObj()
Group all the pieces relevant to the context of a request into one instance.
setTitle(Title $title=null)
static getMain()
Get the RequestContext object associated with the main request.
Arbitrary section name based PHP profiling.
static delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags='')
Perform an operation equivalent to preg_replace() with flags.
static replaceMarkup( $search, $replace, $text)
More or less "markup-safe" str_replace() Ignores any instances of the separator inside <....
static explode( $separator, $subject)
Workalike for explode() with limited memory usage.
static delimiterExplode( $startDelim, $endDelim, $separator, $subject, $nested=false)
Explode a string, but ignore any instances of the separator inside the given start and end delimiters...
Definition: StringUtils.php:59
static normalizeLineEndings( $text)
Do a "\\r\\n" -> "\\n" and "\\r" -> "\\n" transformation as well as trim trailing whitespace.
Multi-datacenter aware caching interface.
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:747
return[0=> 'ـ', 1=> ' ', 2=> '`', 3=> '´', 4=> '˜', 5=> '^', 6=> '¯', 7=> '‾', 8=> '˘', 9=> '˙', 10=> '¨', 11=> '˚', 12=> '˝', 13=> '᾽', 14=> '῝', 15=> '¸', 16=> '˛', 17=> '_', 18=> '‗', 19=> '῀', 20=> '﮲', 21=> '﮳', 22=> '﮴', 23=> '﮵', 24=> '﮶', 25=> '﮷', 26=> '﮸', 27=> '﮹', 28=> '﮺', 29=> '﮻', 30=> '﮼', 31=> '﮽', 32=> '﮾', 33=> '﮿', 34=> '﯀', 35=> '﯁', 36=> '゛', 37=> '゜', 38=> '-', 39=> '֊', 40=> '᐀', 41=> '᭠', 42=> '᠆', 43=> '᠇', 44=> '‐', 45=> '‒', 46=> '–', 47=> '—', 48=> '―', 49=> '⁓', 50=> '⸗', 51=> '゠', 52=> '・', 53=> ',', 54=> '՝', 55=> '،', 56=> '؍', 57=> '٫', 58=> '٬', 59=> '߸', 60=> '᠂', 61=> '᠈', 62=> '꓾', 63=> '꘍', 64=> '꛵', 65=> '︑', 66=> ';', 67=> '؛', 68=> '⁏', 69=> '꛶', 70=> ':', 71=> '։', 72=> '؞', 73=> '܃', 74=> '܄', 75=> '܅', 76=> '܆', 77=> '܇', 78=> '܈', 79=> '࠰', 80=> '࠱', 81=> '࠲', 82=> '࠳', 83=> '࠴', 84=> '࠵', 85=> '࠶', 86=> '࠷', 87=> '࠸', 88=> '࠹', 89=> '࠺', 90=> '࠻', 91=> '࠼', 92=> '࠽', 93=> '࠾', 94=> '፡', 95=> '፣', 96=> '፤', 97=> '፥', 98=> '፦', 99=> '᠄', 100=> '᠅', 101=> '༔', 102=> '៖', 103=> '᭝', 104=> '꧇', 105=> '᛫', 106=> '᛬', 107=> '᛭', 108=> '꛴', 109=> '!', 110=> '¡', 111=> '՜', 112=> '߹', 113=> '᥄', 114=> '?', 115=> '¿', 116=> '⸮', 117=> '՞', 118=> '؟', 119=> '܉', 120=> '፧', 121=> '᥅', 122=> '⳺', 123=> '⳻', 124=> '꘏', 125=> '꛷', 126=> '‽', 127=> '⸘', 128=> '.', 129=> '᠁', 130=> '۔', 131=> '܁', 132=> '܂', 133=> '።', 134=> '᠃', 135=> '᠉', 136=> '᙮', 137=> '᭜', 138=> '⳹', 139=> '⳾', 140=> '⸰', 141=> '꓿', 142=> '꘎', 143=> '꛳', 144=> '︒', 145=> '·', 146=> '⸱', 147=> '।', 148=> '॥', 149=> '꣎', 150=> '꣏', 151=> '᰻', 152=> '᰼', 153=> '꡶', 154=> '꡷', 155=> '᜵', 156=> '᜶', 157=> '꤯', 158=> '၊', 159=> '။', 160=> '។', 161=> '៕', 162=> '᪨', 163=> '᪩', 164=> '᪪', 165=> '᪫', 166=> '᭞', 167=> '᭟', 168=> '꧈', 169=> '꧉', 170=> '꩝', 171=> '꩞', 172=> '꩟', 173=> '꯫', 174=> '𐩖', 175=> '𐩗', 176=> '𑁇', 177=> '𑁈', 178=> '𑃀', 179=> '𑃁', 180=> '᱾', 181=> '᱿', 182=> '܀', 183=> '߷', 184=> '჻', 185=> '፠', 186=> '፨', 187=> '᨞', 188=> '᨟', 189=> '᭚', 190=> '᭛', 191=> '꧁', 192=> '꧂', 193=> '꧃', 194=> '꧄', 195=> '꧅', 196=> '꧆', 197=> '꧊', 198=> '꧋', 199=> '꧌', 200=> '꧍', 201=> '꛲', 202=> '꥟', 203=> '𐡗', 204=> '𐬺', 205=> '𐬻', 206=> '𐬼', 207=> '𐬽', 208=> '𐬾', 209=> '𐬿', 210=> '𑂾', 211=> '𑂿', 212=> '⁕', 213=> '⁖', 214=> '⁘', 215=> '⁙', 216=> '⁚', 217=> '⁛', 218=> '⁜', 219=> '⁝', 220=> '⁞', 221=> '⸪', 222=> '⸫', 223=> '⸬', 224=> '⸭', 225=> '⳼', 226=> '⳿', 227=> '⸙', 228=> '𐤿', 229=> '𐄀', 230=> '𐄁', 231=> '𐄂', 232=> '𐎟', 233=> '𐏐', 234=> '𐤟', 235=> '𒑰', 236=> '𒑱', 237=> '𒑲', 238=> '𒑳', 239=> '\'', 240=> '‘', 241=> '’', 242=> '‚', 243=> '‛', 244=> '‹', 245=> '›', 246=> '"', 247 => '“', 248 => '”', 249 => '„', 250 => '‟', 251 => '«', 252 => '»', 253 => '(', 254 => ')', 255 => '[', 256 => ']', 257 => '{', 258 => '}', 259 => '༺', 260 => '༻', 261 => '༼', 262 => '༽', 263 => '᚛', 264 => '᚜', 265 => '⁅', 266 => '⁆', 267 => '⧼', 268 => '⧽', 269 => '⦃', 270 => '⦄', 271 => '⦅', 272 => '⦆', 273 => '⦇', 274 => '⦈', 275 => '⦉', 276 => '⦊', 277 => '⦋', 278 => '⦌', 279 => '⦍', 280 => '⦎', 281 => '⦏', 282 => '⦐', 283 => '⦑', 284 => '⦒', 285 => '⦓', 286 => '⦔', 287 => '⦕', 288 => '⦖', 289 => '⦗', 290 => '⦘', 291 => '⟬', 292 => '⟭', 293 => '⟮', 294 => '⟯', 295 => '⸂', 296 => '⸃', 297 => '⸄', 298 => '⸅', 299 => '⸉', 300 => '⸊', 301 => '⸌', 302 => '⸍', 303 => '⸜', 304 => '⸝', 305 => '⸠', 306 => '⸡', 307 => '⸢', 308 => '⸣', 309 => '⸤', 310 => '⸥', 311 => '⸦', 312 => '⸧', 313 => '⸨', 314 => '⸩', 315 => '〈', 316 => '〉', 317 => '「', 318 => '」', 319 => '﹝', 320 => '﹞', 321 => '︗', 322 => '︘', 323 => '﴾', 324 => '﴿', 325 => '§', 326 => '¶', 327 => '⁋', 328 => '©', 329 => '®', 330 => '@', 331 => '*', 332 => '⁎', 333 => '⁑', 334 => '٭', 335 => '꙳', 336 => '/', 337 => '⁄', 338 => '\\', 339 => '&', 340 => '⅋', 341 => '⁊', 342 => '#', 343 => '%', 344 => '٪', 345 => '‰', 346 => '؉', 347 => '‱', 348 => '؊', 349 => '⁒', 350 => '†', 351 => '‡', 352 => '•', 353 => '‣', 354 => '‧', 355 => '⁃', 356 => '⁌', 357 => '⁍', 358 => '′', 359 => '‵', 360 => '‸', 361 => '※', 362 => '‿', 363 => '⁔', 364 => '⁀', 365 => '⁐', 366 => '⁁', 367 => '⁂', 368 => '⸀', 369 => '⸁', 370 => '⸆', 371 => '⸇', 372 => '⸈', 373 => '⸋', 374 => '⸎', 375 => '⸏', 376 => '⸐', 377 => '⸑', 378 => '⸒', 379 => '⸓', 380 => '⸔', 381 => '⸕', 382 => '⸖', 383 => '⸚', 384 => '⸛', 385 => '⸞', 386 => '⸟', 387 => '꙾', 388 => '՚', 389 => '՛', 390 => '՟', 391 => '־', 392 => '׀', 393 => '׃', 394 => '׆', 395 => '׳', 396 => '״', 397 => '܊', 398 => '܋', 399 => '܌', 400 => '܍', 401 => '࡞', 402 => '᠀', 403 => '॰', 404 => '꣸', 405 => '꣹', 406 => '꣺', 407 => '෴', 408 => '๚', 409 => '๛', 410 => '꫞', 411 => '꫟', 412 => '༄', 413 => '༅', 414 => '༆', 415 => '༇', 416 => '༈', 417 => '༉', 418 => '༊', 419 => '࿐', 420 => '࿑', 421 => '་', 422 => '།', 423 => '༎', 424 => '༏', 425 => '༐', 426 => '༑', 427 => '༒', 428 => '྅', 429 => '࿒', 430 => '࿓', 431 => '࿔', 432 => '࿙', 433 => '࿚', 434 => '᰽', 435 => '᰾', 436 => '᰿', 437 => '᥀', 438 => '၌', 439 => '၍', 440 => '၎', 441 => '၏', 442 => '႞', 443 => '႟', 444 => '꩷', 445 => '꩸', 446 => '꩹', 447 => 'ៗ', 448 => '៘', 449 => '៙', 450 => '៚', 451 => '᪠', 452 => '᪡', 453 => '᪢', 454 => '᪣', 455 => '᪤', 456 => '᪥', 457 => '᪦', 458 => '᪬', 459 => '᪭', 460 => '᙭', 461 => '⵰', 462 => '꡴', 463 => '꡵', 464 => '᯼', 465 => '᯽', 466 => '᯾', 467 => '᯿', 468 => '꤮', 469 => '꧞', 470 => '꧟', 471 => '꩜', 472 => '𑁉', 473 => '𑁊', 474 => '𑁋', 475 => '𑁌', 476 => '𑁍', 477 => '𐩐', 478 => '𐩑', 479 => '𐩒', 480 => '𐩓', 481 => '𐩔', 482 => '𐩕', 483 => '𐩘', 484 => '𐬹', 485 => '𑂻', 486 => '𑂼', 487 => 'ʹ', 488 => '͵', 489 => 'ʺ', 490 => '˂', 491 => '˃', 492 => '˄', 493 => '˅', 494 => 'ˆ', 495 => 'ˇ', 496 => 'ˈ', 497 => 'ˉ', 498 => 'ˊ', 499 => 'ˋ', 500 => 'ˌ', 501 => 'ˍ', 502 => 'ˎ', 503 => 'ˏ', 504 => '˒', 505 => '˓', 506 => '˔', 507 => '˕', 508 => '˖', 509 => '˗', 510 => '˞', 511 => '˟', 512 => '˥', 513 => '˦', 514 => '˧', 515 => '˨', 516 => '˩', 517 => '˪', 518 => '˫', 519 => 'ˬ', 520 => '˭', 521 => '˯', 522 => '˰', 523 => '˱', 524 => '˲', 525 => '˳', 526 => '˴', 527 => '˵', 528 => '˶', 529 => '˷', 530 => '˸', 531 => '˹', 532 => '˺', 533 => '˻', 534 => '˼', 535 => '˽', 536 => '˾', 537 => '˿', 538 => '᎐', 539 => '᎑', 540 => '᎒', 541 => '᎓', 542 => '᎔', 543 => '᎕', 544 => '᎖', 545 => '᎗', 546 => '᎘', 547 => '᎙', 548 => '꜀', 549 => '꜁', 550 => '꜂', 551 => '꜃', 552 => '꜄', 553 => '꜅', 554 => '꜆', 555 => '꜇', 556 => '꜈', 557 => '꜉', 558 => '꜊', 559 => '꜋', 560 => '꜌', 561 => '꜍', 562 => '꜎', 563 => '꜏', 564 => '꜐', 565 => '꜑', 566 => '꜒', 567 => '꜓', 568 => '꜔', 569 => '꜕', 570 => '꜖', 571 => 'ꜗ', 572 => 'ꜘ', 573 => 'ꜙ', 574 => 'ꜚ', 575 => 'ꜛ', 576 => 'ꜜ', 577 => 'ꜝ', 578 => 'ꜞ', 579 => 'ꜟ', 580 => '꜠', 581 => '꜡', 582 => 'ꞈ', 583 => '꞉', 584 => '꞊', 585 => '°', 586 => '҂', 587 => '؈', 588 => '؎', 589 => '؏', 590 => '۞', 591 => '۩', 592 => '﷽', 593 => '߶', 594 => '৺', 595 => '୰', 596 => '௳', 597 => '௴', 598 => '௵', 599 => '௶', 600 => '௷', 601 => '௸', 602 => '௺', 603 => '౿', 604 => '൹', 605 => '꠨', 606 => '꠩', 607 => '꠪', 608 => '꠫', 609 => '꠶', 610 => '꠷', 611 => '꠹', 612 => '๏', 613 => '༁', 614 => '༂', 615 => '༃', 616 => '༓', 617 => '༕', 618 => '༖', 619 => '༗', 620 => '༚', 621 => '༛', 622 => '༜', 623 => '༝', 624 => '༞', 625 => '༟', 626 => '༴', 627 => '༶', 628 => '༸', 629 => '྾', 630 => '྿', 631 => '࿀', 632 => '࿁', 633 => '࿂', 634 => '࿃', 635 => '࿄', 636 => '࿅', 637 => '࿇', 638 => '࿈', 639 => '࿉', 640 => '࿊', 641 => '࿋', 642 => '࿌', 643 => '࿎', 644 => '࿏', 645 => '࿕', 646 => '࿖', 647 => '࿗', 648 => '࿘', 649 => '᧠', 650 => '᧡', 651 => '᧢', 652 => '᧣', 653 => '᧤', 654 => '᧥', 655 => '᧦', 656 => '᧧', 657 => '᧨', 658 => '᧩', 659 => '᧪', 660 => '᧫', 661 => '᧬', 662 => '᧭', 663 => '᧮', 664 => '᧯', 665 => '᧰', 666 => '᧱', 667 => '᧲', 668 => '᧳', 669 => '᧴', 670 => '᧵', 671 => '᧶', 672 => '᧷', 673 => '᧸', 674 => '᧹', 675 => '᧺', 676 => '᧻', 677 => '᧼', 678 => '᧽', 679 => '᧾', 680 => '᧿', 681 => '᭡', 682 => '᭢', 683 => '᭣', 684 => '᭤', 685 => '᭥', 686 => '᭦', 687 => '᭧', 688 => '᭨', 689 => '᭩', 690 => '᭪', 691 => '᭴', 692 => '᭵', 693 => '᭶', 694 => '᭷', 695 => '᭸', 696 => '᭹', 697 => '᭺', 698 => '᭻', 699 => '᭼', 700 => '℄', 701 => '℈', 702 => '℔', 703 => '℗', 704 => '℘', 705 => '℞', 706 => '℟', 707 => '℣', 708 => '℥', 709 => '℧', 710 => '℩', 711 => '℮', 712 => '℺', 713 => '⅁', 714 => '⅂', 715 => '⅃', 716 => '⅄', 717 => '⅊', 718 => '⅌', 719 => '⅍', 720 => '⅏', 721 => '←', 722 => '→', 723 => '↑', 724 => '↓', 725 => '↔', 726 => '↕', 727 => '↖', 728 => '↗', 729 => '↘', 730 => '↙', 731 => '↜', 732 => '↝', 733 => '↞', 734 => '↟', 735 => '↠', 736 => '↡', 737 => '↢', 738 => '↣', 739 => '↤', 740 => '↥', 741 => '↦', 742 => '↧', 743 => '↨', 744 => '↩', 745 => '↪', 746 => '↫', 747 => '↬', 748 => '↭', 749 => '↯', 750 => '↰', 751 => '↱', 752 => '↲', 753 => '↳', 754 => '↴', 755 => '↵', 756 => '↶', 757 => '↷', 758 => '↸', 759 => '↹', 760 => '↺', 761 => '↻', 762 => '↼', 763 => '↽', 764 => '↾', 765 => '↿', 766 => '⇀', 767 => '⇁', 768 => '⇂', 769 => '⇃', 770 => '⇄', 771 => '⇅', 772 => '⇆', 773 => '⇇', 774 => '⇈', 775 => '⇉', 776 => '⇊', 777 => '⇋', 778 => '⇌', 779 => '⇐', 780 => '⇑', 781 => '⇒', 782 => '⇓', 783 => '⇔', 784 => '⇕', 785 => '⇖', 786 => '⇗', 787 => '⇘', 788 => '⇙', 789 => '⇚', 790 => '⇛', 791 => '⇜', 792 => '⇝', 793 => '⇞', 794 => '⇟', 795 => '⇠', 796 => '⇡', 797 => '⇢', 798 => '⇣', 799 => '⇤', 800 => '⇥', 801 => '⇦', 802 => '⇧', 803 => '⇨', 804 => '⇩', 805 => '⇪', 806 => '⇫', 807 => '⇬', 808 => '⇭', 809 => '⇮', 810 => '⇯', 811 => '⇰', 812 => '⇱', 813 => '⇲', 814 => '⇳', 815 => '⇴', 816 => '⇵', 817 => '⇶', 818 => '⇷', 819 => '⇸', 820 => '⇹', 821 => '⇺', 822 => '⇻', 823 => '⇼', 824 => '⇽', 825 => '⇾', 826 => '⇿', 827 => '∀', 828 => '∁', 829 => '∂', 830 => '∃', 831 => '∅', 832 => '∆', 833 => '∇', 834 => '∈', 835 => '∊', 836 => '∋', 837 => '∍', 838 => '϶', 839 => '∎', 840 => '∏', 841 => '∐', 842 => '∑', 843 => '+', 844 => '±', 845 => '÷', 846 => '×', 847 => '<', 848 => '=', 849 => '>', 850 => '¬', 851 => '|', 852 => '¦', 853 => '‖', 854 => '~', 855 => '−', 856 => '∓', 857 => '∔', 858 => '∕', 859 => '∖', 860 => '∗', 861 => '∘', 862 => '∙', 863 => '√', 864 => '∛', 865 => '؆', 866 => '∜', 867 => '؇', 868 => '∝', 869 => '∞', 870 => '∟', 871 => '∠', 872 => '∡', 873 => '∢', 874 => '∣', 875 => '∥', 876 => '∧', 877 => '∨', 878 => '∩', 879 => '∪', 880 => '∫', 881 => '∮', 882 => '∱', 883 => '∲', 884 => '∳', 885 => '∴', 886 => '∵', 887 => '∶', 888 => '∷', 889 => '∸', 890 => '∹', 891 => '∺', 892 => '∻', 893 => '∼', 894 => '∽', 895 => '∾', 896 => '∿', 897 => '≀', 898 => '≂', 899 => '≃', 900 => '≅', 901 => '≆', 902 => '≈', 903 => '≊', 904 => '≋', 905 => '≌', 906 => '≍', 907 => '≎', 908 => '≏', 909 => '≐', 910 => '≑', 911 => '≒', 912 => '≓', 913 => '≔', 914 => '≕', 915 => '≖', 916 => '≗', 917 => '≘', 918 => '≙', 919 => '≚', 920 => '≛', 921 => '≜', 922 => '≝', 923 => '≞', 924 => '≟', 925 => '≡', 926 => '≣', 927 => '≤', 928 => '≥', 929 => '≦', 930 => '≧', 931 => '≨', 932 => '≩', 933 => '≪', 934 => '≫', 935 => '≬', 936 => '≲', 937 => '≳', 938 => '≶', 939 => '≷', 940 => '≺', 941 => '≻', 942 => '≼', 943 => '≽', 944 => '≾', 945 => '≿', 946 => '⊂', 947 => '⊃', 948 => '⊆', 949 => '⊇', 950 => '⊊', 951 => '⊋', 952 => '⊌', 953 => '⊍', 954 => '⊎', 955 => '⊏', 956 => '⊐', 957 => '⊑', 958 => '⊒', 959 => '⊓', 960 => '⊔', 961 => '⊕', 962 => '⊖', 963 => '⊗', 964 => '⊘', 965 => '⊙', 966 => '⊚', 967 => '⊛', 968 => '⊜', 969 => '⊝', 970 => '⊞', 971 => '⊟', 972 => '⊠', 973 => '⊡', 974 => '⊢', 975 => '⊣', 976 => '⊤', 977 => '⊥', 978 => '⊦', 979 => '⊧', 980 => '⊨', 981 => '⊩', 982 => '⊪', 983 => '⊫', 984 => '⊰', 985 => '⊱', 986 => '⊲', 987 => '⊳', 988 => '⊴', 989 => '⊵', 990 => '⊶', 991 => '⊷', 992 => '⊸', 993 => '⊹', 994 => '⊺', 995 => '⊻', 996 => '⊼', 997 => '⊽', 998 => '⊾', 999 => '⊿', 1000 => '⋀', 1001 => '⋁', 1002 => '⋂', 1003 => '⋃', 1004 => '⋄', 1005 => '⋅', 1006 => '⋆', 1007 => '⋇', 1008 => '⋈', 1009 => '⋉', 1010 => '⋊', 1011 => '⋋', 1012 => '⋌', 1013 => '⋍', 1014 => '⋎', 1015 => '⋏', 1016 => '⋐', 1017 => '⋑', 1018 => '⋒', 1019 => '⋓', 1020 => '⋔', 1021 => '⋕', 1022 => '⋖', 1023 => '⋗', 1024 => '⋘', 1025 => '⋙', 1026 => '⋚', 1027 => '⋛', 1028 => '⋜', 1029 => '⋝', 1030 => '⋞', 1031 => '⋟', 1032 => '⋤', 1033 => '⋥', 1034 => '⋦', 1035 => '⋧', 1036 => '⋨', 1037 => '⋩', 1038 => '⋮', 1039 => '⋯', 1040 => '⋰', 1041 => '⋱', 1042 => '⋲', 1043 => '⋳', 1044 => '⋴', 1045 => '⋵', 1046 => '⋶', 1047 => '⋷', 1048 => '⋸', 1049 => '⋹', 1050 => '⋺', 1051 => '⋻', 1052 => '⋼', 1053 => '⋽', 1054 => '⋾', 1055 => '⋿', 1056 => '⌀', 1057 => '⌁', 1058 => '⌂', 1059 => '⌃', 1060 => '⌄', 1061 => '⌅', 1062 => '⌆', 1063 => '⌇', 1064 => '⌈', 1065 => '⌉', 1066 => '⌊', 1067 => '⌋', 1068 => '⌌', 1069 => '⌍', 1070 => '⌎', 1071 => '⌏', 1072 => '⌐', 1073 => '⌑', 1074 => '⌒', 1075 => '⌓', 1076 => '⌔', 1077 => '⌕', 1078 => '⌖', 1079 => '⌗', 1080 => '⌘', 1081 => '⌙', 1082 => '⌚', 1083 => '⌛', 1084 => '⌜', 1085 => '⌝', 1086 => '⌞', 1087 => '⌟', 1088 => '⌠', 1089 => '⌡', 1090 => '⌢', 1091 => '⌣', 1092 => '⌤', 1093 => '⌥', 1094 => '⌦', 1095 => '⌧', 1096 => '⌨', 1097 => '⌫', 1098 => '⌬', 1099 => '⌭', 1100 => '⌮', 1101 => '⌯', 1102 => '⌰', 1103 => '⌱', 1104 => '⌲', 1105 => '⌳', 1106 => '⌴', 1107 => '⌵', 1108 => '⌶', 1109 => '⌷', 1110 => '⌸', 1111 => '⌹', 1112 => '⌺', 1113 => '⌻', 1114 => '⌼', 1115 => '⌽', 1116 => '⌾', 1117 => '⌿', 1118 => '⍀', 1119 => '⍁', 1120 => '⍂', 1121 => '⍃', 1122 => '⍄', 1123 => '⍅', 1124 => '⍆', 1125 => '⍇', 1126 => '⍈', 1127 => '⍉', 1128 => '⍊', 1129 => '⍋', 1130 => '⍌', 1131 => '⍍', 1132 => '⍎', 1133 => '⍏', 1134 => '⍐', 1135 => '⍑', 1136 => '⍒', 1137 => '⍓', 1138 => '⍔', 1139 => '⍕', 1140 => '⍖', 1141 => '⍗', 1142 => '⍘', 1143 => '⍙', 1144 => '⍚', 1145 => '⍛', 1146 => '⍜', 1147 => '⍝', 1148 => '⍞', 1149 => '⍟', 1150 => '⍠', 1151 => '⍡', 1152 => '⍢', 1153 => '⍣', 1154 => '⍤', 1155 => '⍥', 1156 => '⍦', 1157 => '⍧', 1158 => '⍨', 1159 => '⍩', 1160 => '⍪', 1161 => '⍫', 1162 => '⍬', 1163 => '⍭', 1164 => '⍮', 1165 => '⍯', 1166 => '⍰', 1167 => '⍱', 1168 => '⍲', 1169 => '⍳', 1170 => '⍴', 1171 => '⍵', 1172 => '⍶', 1173 => '⍷', 1174 => '⍸', 1175 => '⍹', 1176 => '⍺', 1177 => '⍻', 1178 => '⍼', 1179 => '⍽', 1180 => '⍾', 1181 => '⍿', 1182 => '⎀', 1183 => '⎁', 1184 => '⎂', 1185 => '⎃', 1186 => '⎄', 1187 => '⎅', 1188 => '⎆', 1189 => '⎇', 1190 => '⎈', 1191 => '⎉', 1192 => '⎊', 1193 => '⎋', 1194 => '⎌', 1195 => '⎍', 1196 => '⎎', 1197 => '⎏', 1198 => '⎐', 1199 => '⎑', 1200 => '⎒', 1201 => '⎓', 1202 => '⎔', 1203 => '⎕', 1204 => '⎖', 1205 => '⎗', 1206 => '⎘', 1207 => '⎙', 1208 => '⎚', 1209 => '⎛', 1210 => '⎜', 1211 => '⎝', 1212 => '⎞', 1213 => '⎟', 1214 => '⎠', 1215 => '⎡', 1216 => '⎢', 1217 => '⎣', 1218 => '⎤', 1219 => '⎥', 1220 => '⎦', 1221 => '⎧', 1222 => '⎨', 1223 => '⎩', 1224 => '⎪', 1225 => '⎫', 1226 => '⎬', 1227 => '⎭', 1228 => '⎮', 1229 => '⎯', 1230 => '⎰', 1231 => '⎱', 1232 => '⎲', 1233 => '⎳', 1234 => '⎴', 1235 => '⎵', 1236 => '⎶', 1237 => '⎷', 1238 => '⎸', 1239 => '⎹', 1240 => '⎺', 1241 => '⎻', 1242 => '⎼', 1243 => '⎽', 1244 => '⎾', 1245 => '⎿', 1246 => '⏀', 1247 => '⏁', 1248 => '⏂', 1249 => '⏃', 1250 => '⏄', 1251 => '⏅', 1252 => '⏆', 1253 => '⏇', 1254 => '⏈', 1255 => '⏉', 1256 => '⏊', 1257 => '⏋', 1258 => '⏌', 1259 => '⏍', 1260 => '⏎', 1261 => '⏏', 1262 => '⏐', 1263 => '⏑', 1264 => '⏒', 1265 => '⏓', 1266 => '⏔', 1267 => '⏕', 1268 => '⏖', 1269 => '⏗', 1270 => '⏘', 1271 => '⏙', 1272 => '⏚', 1273 => '⏛', 1274 => '⏜', 1275 => '⏝', 1276 => '⏞', 1277 => '⏟', 1278 => '⏠', 1279 => '⏡', 1280 => '⏢', 1281 => '⏣', 1282 => '⏤', 1283 => '⏥', 1284 => '⏦', 1285 => '⏧', 1286 => '⏨', 1287 => '⏩', 1288 => '⏪', 1289 => '⏫', 1290 => '⏬', 1291 => '⏭', 1292 => '⏮', 1293 => '⏯', 1294 => '⏰', 1295 => '⏱', 1296 => '⏲', 1297 => '⏳', 1298 => '␀', 1299 => '␁', 1300 => '␂', 1301 => '␃', 1302 => '␄', 1303 => '␅', 1304 => '␆', 1305 => '␇', 1306 => '␈', 1307 => '␉', 1308 => '␊', 1309 => '␋', 1310 => '␌', 1311 => '␍', 1312 => '␎', 1313 => '␏', 1314 => '␐', 1315 => '␑', 1316 => '␒', 1317 => '␓', 1318 => '␔', 1319 => '␕', 1320 => '␖', 1321 => '␗', 1322 => '␘', 1323 => '␙', 1324 => '␚', 1325 => '␛', 1326 => '␜', 1327 => '␝', 1328 => '␞', 1329 => '␟', 1330 => '␠', 1331 => '␡', 1332 => '␢', 1333 => '␣', 1334 => '␤', 1335 => '␥', 1336 => '␦', 1337 => '⑀', 1338 => '⑁', 1339 => '⑂', 1340 => '⑃', 1341 => '⑄', 1342 => '⑅', 1343 => '⑆', 1344 => '⑇', 1345 => '⑈', 1346 => '⑉', 1347 => '⑊', 1348 => '─', 1349 => '━', 1350 => '│', 1351 => '┃', 1352 => '┄', 1353 => '┅', 1354 => '┆', 1355 => '┇', 1356 => '┈', 1357 => '┉', 1358 => '┊', 1359 => '┋', 1360 => '┌', 1361 => '┍', 1362 => '┎', 1363 => '┏', 1364 => '┐', 1365 => '┑', 1366 => '┒', 1367 => '┓', 1368 => '└', 1369 => '┕', 1370 => '┖', 1371 => '┗', 1372 => '┘', 1373 => '┙', 1374 => '┚', 1375 => '┛', 1376 => '├', 1377 => '┝', 1378 => '┞', 1379 => '┟', 1380 => '┠', 1381 => '┡', 1382 => '┢', 1383 => '┣', 1384 => '┤', 1385 => '┥', 1386 => '┦', 1387 => '┧', 1388 => '┨', 1389 => '┩', 1390 => '┪', 1391 => '┫', 1392 => '┬', 1393 => '┭', 1394 => '┮', 1395 => '┯', 1396 => '┰', 1397 => '┱', 1398 => '┲', 1399 => '┳', 1400 => '┴', 1401 => '┵', 1402 => '┶', 1403 => '┷', 1404 => '┸', 1405 => '┹', 1406 => '┺', 1407 => '┻', 1408 => '┼', 1409 => '┽', 1410 => '┾', 1411 => '┿', 1412 => '╀', 1413 => '╁', 1414 => '╂', 1415 => '╃', 1416 => '╄', 1417 => '╅', 1418 => '╆', 1419 => '╇', 1420 => '╈', 1421 => '╉', 1422 => '╊', 1423 => '╋', 1424 => '╌', 1425 => '╍', 1426 => '╎', 1427 => '╏', 1428 => '═', 1429 => '║', 1430 => '╒', 1431 => '╓', 1432 => '╔', 1433 => '╕', 1434 => '╖', 1435 => '╗', 1436 => '╘', 1437 => '╙', 1438 => '╚', 1439 => '╛', 1440 => '╜', 1441 => '╝', 1442 => '╞', 1443 => '╟', 1444 => '╠', 1445 => '╡', 1446 => '╢', 1447 => '╣', 1448 => '╤', 1449 => '╥', 1450 => '╦', 1451 => '╧', 1452 => '╨', 1453 => '╩', 1454 => '╪', 1455 => '╫', 1456 => '╬', 1457 => '╭', 1458 => '╮', 1459 => '╯', 1460 => '╰', 1461 => '╱', 1462 => '╲', 1463 => '╳', 1464 => '╴', 1465 => '╵', 1466 => '╶', 1467 => '╷', 1468 => '╸', 1469 => '╹', 1470 => '╺', 1471 => '╻', 1472 => '╼', 1473 => '╽', 1474 => '╾', 1475 => '╿', 1476 => '▀', 1477 => '▁', 1478 => '▂', 1479 => '▃', 1480 => '▄', 1481 => '▅', 1482 => '▆', 1483 => '▇', 1484 => '█', 1485 => '▉', 1486 => '▊', 1487 => '▋', 1488 => '▌', 1489 => '▍', 1490 => '▎', 1491 => '▏', 1492 => '▐', 1493 => '░', 1494 => '▒', 1495 => '▓', 1496 => '▔', 1497 => '▕', 1498 => '▖', 1499 => '▗', 1500 => '▘', 1501 => '▙', 1502 => '▚', 1503 => '▛', 1504 => '▜', 1505 => '▝', 1506 => '▞', 1507 => '▟', 1508 => '■', 1509 => '□', 1510 => '▢', 1511 => '▣', 1512 => '▤', 1513 => '▥', 1514 => '▦', 1515 => '▧', 1516 => '▨', 1517 => '▩', 1518 => '▪', 1519 => '▫', 1520 => '▬', 1521 => '▭', 1522 => '▮', 1523 => '▯', 1524 => '▰', 1525 => '▱', 1526 => '▲', 1527 => '△', 1528 => '▴', 1529 => '▵', 1530 => '▶', 1531 => '▷', 1532 => '▸', 1533 => '▹', 1534 => '►', 1535 => '▻', 1536 => '▼', 1537 => '▽', 1538 => '▾', 1539 => '▿', 1540 => '◀', 1541 => '◁', 1542 => '◂', 1543 => '◃', 1544 => '◄', 1545 => '◅', 1546 => '◆', 1547 => '◇', 1548 => '◈', 1549 => '◉', 1550 => '◊', 1551 => '○', 1552 => '◌', 1553 => '◍', 1554 => '◎', 1555 => '●', 1556 => '◐', 1557 => '◑', 1558 => '◒', 1559 => '◓', 1560 => '◔', 1561 => '◕', 1562 => '◖', 1563 => '◗', 1564 => '◘', 1565 => '◙', 1566 => '◚', 1567 => '◛', 1568 => '◜', 1569 => '◝', 1570 => '◞', 1571 => '◟', 1572 => '◠', 1573 => '◡', 1574 => '◢', 1575 => '◣', 1576 => '◤', 1577 => '◥', 1578 => '◦', 1579 => '◧', 1580 => '◨', 1581 => '◩', 1582 => '◪', 1583 => '◫', 1584 => '◬', 1585 => '◭', 1586 => '◮', 1587 => '◯', 1588 => '◰', 1589 => '◱', 1590 => '◲', 1591 => '◳', 1592 => '◴', 1593 => '◵', 1594 => '◶', 1595 => '◷', 1596 => '◸', 1597 => '◹', 1598 => '◺', 1599 => '◻', 1600 => '◼', 1601 => '◽', 1602 => '◾', 1603 => '◿', 1604 => '☀', 1605 => '☁', 1606 => '☂', 1607 => '☃', 1608 => '☄', 1609 => '★', 1610 => '☆', 1611 => '☇', 1612 => '☈', 1613 => '☉', 1614 => '☊', 1615 => '☋', 1616 => '☌', 1617 => '☍', 1618 => '☎', 1619 => '☏', 1620 => '☐', 1621 => '☑', 1622 => '☒', 1623 => '☓', 1624 => '☔', 1625 => '☕', 1626 => '☖', 1627 => '☗', 1628 => '☘', 1629 => '☙', 1630 => '☚', 1631 => '☛', 1632 => '☜', 1633 => '☝', 1634 => '☞', 1635 => '☟', 1636 => '☠', 1637 => '☡', 1638 => '☢', 1639 => '☣', 1640 => '☤', 1641 => '☥', 1642 => '☦', 1643 => '☧', 1644 => '☨', 1645 => '☩', 1646 => '☪', 1647 => '☫', 1648 => '☬', 1649 => '☭', 1650 => '☮', 1651 => '☯', 1652 => '☸', 1653 => '☹', 1654 => '☺', 1655 => '☻', 1656 => '☼', 1657 => '☽', 1658 => '☾', 1659 => '☿', 1660 => '♀', 1661 => '♁', 1662 => '♂', 1663 => '♃', 1664 => '♄', 1665 => '♅', 1666 => '♆', 1667 => '♇', 1668 => '♈', 1669 => '♉', 1670 => '♊', 1671 => '♋', 1672 => '♌', 1673 => '♍', 1674 => '♎', 1675 => '♏', 1676 => '♐', 1677 => '♑', 1678 => '♒', 1679 => '♓', 1680 => '♔', 1681 => '♕', 1682 => '♖', 1683 => '♗', 1684 => '♘', 1685 => '♙', 1686 => '♚', 1687 => '♛', 1688 => '♜', 1689 => '♝', 1690 => '♞', 1691 => '♟', 1692 => '♠', 1693 => '♡', 1694 => '♢', 1695 => '♣', 1696 => '♤', 1697 => '♥', 1698 => '♦', 1699 => '♧', 1700 => '♨', 1701 => '♩', 1702 => '♪', 1703 => '♫', 1704 => '♬', 1705 => '♰', 1706 => '♱', 1707 => '♲', 1708 => '♳', 1709 => '♴', 1710 => '♵', 1711 => '♶', 1712 => '♷', 1713 => '♸', 1714 => '♹', 1715 => '♺', 1716 => '♻', 1717 => '♼', 1718 => '♽', 1719 => '♾', 1720 => '♿', 1721 => '⚀', 1722 => '⚁', 1723 => '⚂', 1724 => '⚃', 1725 => '⚄', 1726 => '⚅', 1727 => '⚆', 1728 => '⚇', 1729 => '⚈', 1730 => '⚉', 1731 => '⚐', 1732 => '⚑', 1733 => '⚒', 1734 => '⚓', 1735 => '⚔', 1736 => '⚕', 1737 => '⚖', 1738 => '⚗', 1739 => '⚘', 1740 => '⚙', 1741 => '⚚', 1742 => '⚛', 1743 => '⚜', 1744 => '⚝', 1745 => '⚞', 1746 => '⚟', 1747 => '⚠', 1748 => '⚡', 1749 => '⚢', 1750 => '⚣', 1751 => '⚤', 1752 => '⚥', 1753 => '⚦', 1754 => '⚧', 1755 => '⚨', 1756 => '⚩', 1757 => '⚪', 1758 => '⚫', 1759 => '⚬', 1760 => '⚭', 1761 => '⚮', 1762 => '⚯', 1763 => '⚰', 1764 => '⚱', 1765 => '⚲', 1766 => '⚳', 1767 => '⚴', 1768 => '⚵', 1769 => '⚶', 1770 => '⚷', 1771 => '⚸', 1772 => '⚹', 1773 => '⚺', 1774 => '⚻', 1775 => '⚼', 1776 => '⚽', 1777 => '⚾', 1778 => '⚿', 1779 => '⛀', 1780 => '⛁', 1781 => '⛂', 1782 => '⛃', 1783 => '⛄', 1784 => '⛅', 1785 => '⛆', 1786 => '⛇', 1787 => '⛈', 1788 => '⛉', 1789 => '⛊', 1790 => '⛋', 1791 => '⛌', 1792 => '⛍', 1793 => '⛎', 1794 => '⛏', 1795 => '⛐', 1796 => '⛑', 1797 => '⛒', 1798 => '⛓', 1799 => '⛔', 1800 => '⛕', 1801 => '⛖', 1802 => '⛗', 1803 => '⛘', 1804 => '⛙', 1805 => '⛚', 1806 => '⛛', 1807 => '⛜', 1808 => '⛝', 1809 => '⛞', 1810 => '⛟', 1811 => '⛠', 1812 => '⛡', 1813 => '⛢', 1814 => '⛣', 1815 => '⛤', 1816 => '⛥', 1817 => '⛦', 1818 => '⛧', 1819 => '⛨', 1820 => '⛩', 1821 => '⛪', 1822 => '⛫', 1823 => '⛬', 1824 => '⛭', 1825 => '⛮', 1826 => '⛯', 1827 => '⛰', 1828 => '⛱', 1829 => '⛲', 1830 => '⛳', 1831 => '⛴', 1832 => '⛵', 1833 => '⛶', 1834 => '⛷', 1835 => '⛸', 1836 => '⛹', 1837 => '⛺', 1838 => '⛻', 1839 => '⛼', 1840 => '⛽', 1841 => '⛾', 1842 => '⛿', 1843 => '✁', 1844 => '✂', 1845 => '✃', 1846 => '✄', 1847 => '✅', 1848 => '✆', 1849 => '✇', 1850 => '✈', 1851 => '✉', 1852 => '✊', 1853 => '✋', 1854 => '✌', 1855 => '✍', 1856 => '✎', 1857 => '✏', 1858 => '✐', 1859 => '✑', 1860 => '✒', 1861 => '✓', 1862 => '✔', 1863 => '✕', 1864 => '✖', 1865 => '✗', 1866 => '✘', 1867 => '✙', 1868 => '✚', 1869 => '✛', 1870 => '✜', 1871 => '✝', 1872 => '✞', 1873 => '✟', 1874 => '✠', 1875 => '✡', 1876 => '✢', 1877 => '✣', 1878 => '✤', 1879 => '✥', 1880 => '✦', 1881 => '✧', 1882 => '✨', 1883 => '✩', 1884 => '✪', 1885 => '✫', 1886 => '✬', 1887 => '✭', 1888 => '✮', 1889 => '✯', 1890 => '✰', 1891 => '✱', 1892 => '✲', 1893 => '✳', 1894 => '✴', 1895 => '✵', 1896 => '✶', 1897 => '✷', 1898 => '✸', 1899 => '✹', 1900 => '✺', 1901 => '✻', 1902 => '✼', 1903 => '✽', 1904 => '✾', 1905 => '✿', 1906 => '❀', 1907 => '❁', 1908 => '❂', 1909 => '❃', 1910 => '❄', 1911 => '❅', 1912 => '❆', 1913 => '❇', 1914 => '❈', 1915 => '❉', 1916 => '❊', 1917 => '❋', 1918 => '❌', 1919 => '❍', 1920 => '❎', 1921 => '❏', 1922 => '❐', 1923 => '❑', 1924 => '❒', 1925 => '❓', 1926 => '❔', 1927 => '❕', 1928 => '❖', 1929 => '❗', 1930 => '❘', 1931 => '❙', 1932 => '❚', 1933 => '❛', 1934 => '❜', 1935 => '❝', 1936 => '❞', 1937 => '❟', 1938 => '❠', 1939 => '❡', 1940 => '❢', 1941 => '❣', 1942 => '❤', 1943 => '❥', 1944 => '❦', 1945 => '❧', 1946 => '❨', 1947 => '❩', 1948 => '❪', 1949 => '❫', 1950 => '❬', 1951 => '❭', 1952 => '❮', 1953 => '❯', 1954 => '❰', 1955 => '❱', 1956 => '❲', 1957 => '❳', 1958 => '❴', 1959 => '❵', 1960 => '➔', 1961 => '➕', 1962 => '➖', 1963 => '➗', 1964 => '➘', 1965 => '➙', 1966 => '➚', 1967 => '➛', 1968 => '➜', 1969 => '➝', 1970 => '➞', 1971 => '➟', 1972 => '➠', 1973 => '➡', 1974 => '➢', 1975 => '➣', 1976 => '➤', 1977 => '➥', 1978 => '➦', 1979 => '➧', 1980 => '➨', 1981 => '➩', 1982 => '➪', 1983 => '➫', 1984 => '➬', 1985 => '➭', 1986 => '➮', 1987 => '➯', 1988 => '➰', 1989 => '➱', 1990 => '➲', 1991 => '➳', 1992 => '➴', 1993 => '➵', 1994 => '➶', 1995 => '➷', 1996 => '➸', 1997 => '➹', 1998 => '➺', 1999 => '➻', 2000 => '➼', 2001 => '➽', 2002 => '➾', 2003 => '➿', 2004 => '⟀', 2005 => '⟁', 2006 => '⟂', 2007 => '⟃', 2008 => '⟄', 2009 => '⟅', 2010 => '⟆', 2011 => '⟇', 2012 => '⟈', 2013 => '⟉', 2014 => '⟊', 2015 => '⟌', 2016 => '⟎', 2017 => '⟏', 2018 => '⟐', 2019 => '⟑', 2020 => '⟒', 2021 => '⟓', 2022 => '⟔', 2023 => '⟕', 2024 => '⟖', 2025 => '⟗', 2026 => '⟘', 2027 => '⟙', 2028 => '⟚', 2029 => '⟛', 2030 => '⟜', 2031 => '⟝', 2032 => '⟞', 2033 => '⟟', 2034 => '⟠', 2035 => '⟡', 2036 => '⟢', 2037 => '⟣', 2038 => '⟤', 2039 => '⟥', 2040 => '⟦', 2041 => '⟧', 2042 => '⟨', 2043 => '⟩', 2044 => '⟪', 2045 => '⟫', 2046 => '⟰', 2047 => '⟱', 2048 => '⟲', 2049 => '⟳', 2050 => '⟴', 2051 => '⟵', 2052 => '⟶', 2053 => '⟷', 2054 => '⟸', 2055 => '⟹', 2056 => '⟺', 2057 => '⟻', 2058 => '⟼', 2059 => '⟽', 2060 => '⟾', 2061 => '⟿', 2062 => '⤀', 2063 => '⤁', 2064 => '⤂', 2065 => '⤃', 2066 => '⤄', 2067 => '⤅', 2068 => '⤆', 2069 => '⤇', 2070 => '⤈', 2071 => '⤉', 2072 => '⤊', 2073 => '⤋', 2074 => '⤌', 2075 => '⤍', 2076 => '⤎', 2077 => '⤏', 2078 => '⤐', 2079 => '⤑', 2080 => '⤒', 2081 => '⤓', 2082 => '⤔', 2083 => '⤕', 2084 => '⤖', 2085 => '⤗', 2086 => '⤘', 2087 => '⤙', 2088 => '⤚', 2089 => '⤛', 2090 => '⤜', 2091 => '⤝', 2092 => '⤞', 2093 => '⤟', 2094 => '⤠', 2095 => '⤡', 2096 => '⤢', 2097 => '⤣', 2098 => '⤤', 2099 => '⤥', 2100 => '⤦', 2101 => '⤧', 2102 => '⤨', 2103 => '⤩', 2104 => '⤪', 2105 => '⤫', 2106 => '⤬', 2107 => '⤭', 2108 => '⤮', 2109 => '⤯', 2110 => '⤰', 2111 => '⤱', 2112 => '⤲', 2113 => '⤳', 2114 => '⤴', 2115 => '⤵', 2116 => '⤶', 2117 => '⤷', 2118 => '⤸', 2119 => '⤹', 2120 => '⤺', 2121 => '⤻', 2122 => '⤼', 2123 => '⤽', 2124 => '⤾', 2125 => '⤿', 2126 => '⥀', 2127 => '⥁', 2128 => '⥂', 2129 => '⥃', 2130 => '⥄', 2131 => '⥅', 2132 => '⥆', 2133 => '⥇', 2134 => '⥈', 2135 => '⥉', 2136 => '⥊', 2137 => '⥋', 2138 => '⥌', 2139 => '⥍', 2140 => '⥎', 2141 => '⥏', 2142 => '⥐', 2143 => '⥑', 2144 => '⥒', 2145 => '⥓', 2146 => '⥔', 2147 => '⥕', 2148 => '⥖', 2149 => '⥗', 2150 => '⥘', 2151 => '⥙', 2152 => '⥚', 2153 => '⥛', 2154 => '⥜', 2155 => '⥝', 2156 => '⥞', 2157 => '⥟', 2158 => '⥠', 2159 => '⥡', 2160 => '⥢', 2161 => '⥣', 2162 => '⥤', 2163 => '⥥', 2164 => '⥦', 2165 => '⥧', 2166 => '⥨', 2167 => '⥩', 2168 => '⥪', 2169 => '⥫', 2170 => '⥬', 2171 => '⥭', 2172 => '⥮', 2173 => '⥯', 2174 => '⥰', 2175 => '⥱', 2176 => '⥲', 2177 => '⥳', 2178 => '⥴', 2179 => '⥵', 2180 => '⥶', 2181 => '⥷', 2182 => '⥸', 2183 => '⥹', 2184 => '⥺', 2185 => '⥻', 2186 => '⥼', 2187 => '⥽', 2188 => '⥾', 2189 => '⥿', 2190 => '⦀', 2191 => '⦁', 2192 => '⦂', 2193 => '⦙', 2194 => '⦚', 2195 => '⦛', 2196 => '⦜', 2197 => '⦝', 2198 => '⦞', 2199 => '⦟', 2200 => '⦠', 2201 => '⦡', 2202 => '⦢', 2203 => '⦣', 2204 => '⦤', 2205 => '⦥', 2206 => '⦦', 2207 => '⦧', 2208 => '⦨', 2209 => '⦩', 2210 => '⦪', 2211 => '⦫', 2212 => '⦬', 2213 => '⦭', 2214 => '⦮', 2215 => '⦯', 2216 => '⦰', 2217 => '⦱', 2218 => '⦲', 2219 => '⦳', 2220 => '⦴', 2221 => '⦵', 2222 => '⦶', 2223 => '⦷', 2224 => '⦸', 2225 => '⦹', 2226 => '⦺', 2227 => '⦻', 2228 => '⦼', 2229 => '⦽', 2230 => '⦾', 2231 => '⦿', 2232 => '⧀', 2233 => '⧁', 2234 => '⧂', 2235 => '⧃', 2236 => '⧄', 2237 => '⧅', 2238 => '⧆', 2239 => '⧇', 2240 => '⧈', 2241 => '⧉', 2242 => '⧊', 2243 => '⧋', 2244 => '⧌', 2245 => '⧍', 2246 => '⧎', 2247 => '⧏', 2248 => '⧐', 2249 => '⧑', 2250 => '⧒', 2251 => '⧓', 2252 => '⧔', 2253 => '⧕', 2254 => '⧖', 2255 => '⧗', 2256 => '⧘', 2257 => '⧙', 2258 => '⧚', 2259 => '⧛', 2260 => '⧜', 2261 => '⧝', 2262 => '⧞', 2263 => '⧟', 2264 => '⧠', 2265 => '⧡', 2266 => '⧢', 2267 => '⧣', 2268 => '⧤', 2269 => '⧥', 2270 => '⧦', 2271 => '⧧', 2272 => '⧨', 2273 => '⧩', 2274 => '⧪', 2275 => '⧫', 2276 => '⧬', 2277 => '⧭', 2278 => '⧮', 2279 => '⧯', 2280 => '⧰', 2281 => '⧱', 2282 => '⧲', 2283 => '⧳', 2284 => '⧴', 2285 => '⧵', 2286 => '⧶', 2287 => '⧷', 2288 => '⧸', 2289 => '⧹', 2290 => '⧺', 2291 => '⧻', 2292 => '⧾', 2293 => '⧿', 2294 => '⨀', 2295 => '⨁', 2296 => '⨂', 2297 => '⨃', 2298 => '⨄', 2299 => '⨅', 2300 => '⨆', 2301 => '⨇', 2302 => '⨈', 2303 => '⨉', 2304 => '⨊', 2305 => '⨋', 2306 => '⨍', 2307 => '⨎', 2308 => '⨏', 2309 => '⨐', 2310 => '⨑', 2311 => '⨒', 2312 => '⨓', 2313 => '⨔', 2314 => '⨕', 2315 => '⨖', 2316 => '⨗', 2317 => '⨘', 2318 => '⨙', 2319 => '⨚', 2320 => '⨛', 2321 => '⨜', 2322 => '⨝', 2323 => '⨞', 2324 => '⨟', 2325 => '⨠', 2326 => '⨡', 2327 => '⨢', 2328 => '⨣', 2329 => '⨤', 2330 => '⨥', 2331 => '⨦', 2332 => '⨧', 2333 => '⨨', 2334 => '⨩', 2335 => '⨪', 2336 => '⨫', 2337 => '⨬', 2338 => '⨭', 2339 => '⨮', 2340 => '⨯', 2341 => '⨰', 2342 => '⨱', 2343 => '⨲', 2344 => '⨳', 2345 => '⨴', 2346 => '⨵', 2347 => '⨶', 2348 => '⨷', 2349 => '⨸', 2350 => '⨹', 2351 => '⨺', 2352 => '⨻', 2353 => '⨼', 2354 => '⨽', 2355 => '⨾', 2356 => '⨿', 2357 => '⩀', 2358 => '⩁', 2359 => '⩂', 2360 => '⩃', 2361 => '⩄', 2362 => '⩅', 2363 => '⩆', 2364 => '⩇', 2365 => '⩈', 2366 => '⩉', 2367 => '⩊', 2368 => '⩋', 2369 => '⩌', 2370 => '⩍', 2371 => '⩎', 2372 => '⩏', 2373 => '⩐', 2374 => '⩑', 2375 => '⩒', 2376 => '⩓', 2377 => '⩔', 2378 => '⩕', 2379 => '⩖', 2380 => '⩗', 2381 => '⩘', 2382 => '⩙', 2383 => '⩚', 2384 => '⩛', 2385 => '⩜', 2386 => '⩝', 2387 => '⩞', 2388 => '⩟', 2389 => '⩠', 2390 => '⩡', 2391 => '⩢', 2392 => '⩣', 2393 => '⩤', 2394 => '⩥', 2395 => '⩦', 2396 => '⩧', 2397 => '⩨', 2398 => '⩩', 2399 => '⩪', 2400 => '⩫', 2401 => '⩬', 2402 => '⩭', 2403 => '⩮', 2404 => '⩯', 2405 => '⩰', 2406 => '⩱', 2407 => '⩲', 2408 => '⩳', 2409 => '⩷', 2410 => '⩸', 2411 => '⩹', 2412 => '⩺', 2413 => '⩻', 2414 => '⩼', 2415 => '⩽', 2416 => '⩾', 2417 => '⩿', 2418 => '⪀', 2419 => '⪁', 2420 => '⪂', 2421 => '⪃', 2422 => '⪄', 2423 => '⪅', 2424 => '⪆', 2425 => '⪇', 2426 => '⪈', 2427 => '⪉', 2428 => '⪊', 2429 => '⪋', 2430 => '⪌', 2431 => '⪍', 2432 => '⪎', 2433 => '⪏', 2434 => '⪐', 2435 => '⪑', 2436 => '⪒', 2437 => '⪓', 2438 => '⪔', 2439 => '⪕', 2440 => '⪖', 2441 => '⪗', 2442 => '⪘', 2443 => '⪙', 2444 => '⪚', 2445 => '⪛', 2446 => '⪜', 2447 => '⪝', 2448 => '⪞', 2449 => '⪟', 2450 => '⪠', 2451 => '⪡', 2452 => '⪢', 2453 => '⪣', 2454 => '⪤', 2455 => '⪥', 2456 => '⪦', 2457 => '⪧', 2458 => '⪨', 2459 => '⪩', 2460 => '⪪', 2461 => '⪫', 2462 => '⪬', 2463 => '⪭', 2464 => '⪮', 2465 => '⪯', 2466 => '⪰', 2467 => '⪱', 2468 => '⪲', 2469 => '⪳', 2470 => '⪴', 2471 => '⪵', 2472 => '⪶', 2473 => '⪷', 2474 => '⪸', 2475 => '⪹', 2476 => '⪺', 2477 => '⪻', 2478 => '⪼', 2479 => '⪽', 2480 => '⪾', 2481 => '⪿', 2482 => '⫀', 2483 => '⫁', 2484 => '⫂', 2485 => '⫃', 2486 => '⫄', 2487 => '⫅', 2488 => '⫆', 2489 => '⫇', 2490 => '⫈', 2491 => '⫉', 2492 => '⫊', 2493 => '⫋', 2494 => '⫌', 2495 => '⫍', 2496 => '⫎', 2497 => '⫏', 2498 => '⫐', 2499 => '⫑', 2500 => '⫒', 2501 => '⫓', 2502 => '⫔', 2503 => '⫕', 2504 => '⫖', 2505 => '⫗', 2506 => '⫘', 2507 => '⫙', 2508 => '⫚', 2509 => '⫛', 2510 => '⫝', 2511 => '⫞', 2512 => '⫟', 2513 => '⫠', 2514 => '⫡', 2515 => '⫢', 2516 => '⫣', 2517 => '⫤', 2518 => '⫥', 2519 => '⫦', 2520 => '⫧', 2521 => '⫨', 2522 => '⫩', 2523 => '⫪', 2524 => '⫫', 2525 => '⫬', 2526 => '⫭', 2527 => '⫮', 2528 => '⫯', 2529 => '⫰', 2530 => '⫱', 2531 => '⫲', 2532 => '⫳', 2533 => '⫴', 2534 => '⫵', 2535 => '⫶', 2536 => '⫷', 2537 => '⫸', 2538 => '⫹', 2539 => '⫺', 2540 => '⫻', 2541 => '⫼', 2542 => '⫽', 2543 => '⫾', 2544 => '⫿', 2545 => '⬀', 2546 => '⬁', 2547 => '⬂', 2548 => '⬃', 2549 => '⬄', 2550 => '⬅', 2551 => '⬆', 2552 => '⬇', 2553 => '⬈', 2554 => '⬉', 2555 => '⬊', 2556 => '⬋', 2557 => '⬌', 2558 => '⬍', 2559 => '⬎', 2560 => '⬏', 2561 => '⬐', 2562 => '⬑', 2563 => '⬒', 2564 => '⬓', 2565 => '⬔', 2566 => '⬕', 2567 => '⬖', 2568 => '⬗', 2569 => '⬘', 2570 => '⬙', 2571 => '⬚', 2572 => '⬛', 2573 => '⬜', 2574 => '⬝', 2575 => '⬞', 2576 => '⬟', 2577 => '⬠', 2578 => '⬡', 2579 => '⬢', 2580 => '⬣', 2581 => '⬤', 2582 => '⬥', 2583 => '⬦', 2584 => '⬧', 2585 => '⬨', 2586 => '⬩', 2587 => '⬪', 2588 => '⬫', 2589 => '⬬', 2590 => '⬭', 2591 => '⬮', 2592 => '⬯', 2593 => '⬰', 2594 => '⬱', 2595 => '⬲', 2596 => '⬳', 2597 => '⬴', 2598 => '⬵', 2599 => '⬶', 2600 => '⬷', 2601 => '⬸', 2602 => '⬹', 2603 => '⬺', 2604 => '⬻', 2605 => '⬼', 2606 => '⬽', 2607 => '⬾', 2608 => '⬿', 2609 => '⭀', 2610 => '⭁', 2611 => '⭂', 2612 => '⭃', 2613 => '⭄', 2614 => '⭅', 2615 => '⭆', 2616 => '⭇', 2617 => '⭈', 2618 => '⭉', 2619 => '⭊', 2620 => '⭋', 2621 => '⭌', 2622 => '⭐', 2623 => '⭑', 2624 => '⭒', 2625 => '⭓', 2626 => '⭔', 2627 => '⭕', 2628 => '⭖', 2629 => '⭗', 2630 => '⭘', 2631 => '⭙', 2632 => '⳥', 2633 => '⳦', 2634 => '⳧', 2635 => '⳨', 2636 => '⳩', 2637 => '⳪', 2638 => '⠀', 2639 => '⠁', 2640 => '⠂', 2641 => '⠃', 2642 => '⠄', 2643 => '⠅', 2644 => '⠆', 2645 => '⠇', 2646 => '⠈', 2647 => '⠉', 2648 => '⠊', 2649 => '⠋', 2650 => '⠌', 2651 => '⠍', 2652 => '⠎', 2653 => '⠏', 2654 => '⠐', 2655 => '⠑', 2656 => '⠒', 2657 => '⠓', 2658 => '⠔', 2659 => '⠕', 2660 => '⠖', 2661 => '⠗', 2662 => '⠘', 2663 => '⠙', 2664 => '⠚', 2665 => '⠛', 2666 => '⠜', 2667 => '⠝', 2668 => '⠞', 2669 => '⠟', 2670 => '⠠', 2671 => '⠡', 2672 => '⠢', 2673 => '⠣', 2674 => '⠤', 2675 => '⠥', 2676 => '⠦', 2677 => '⠧', 2678 => '⠨', 2679 => '⠩', 2680 => '⠪', 2681 => '⠫', 2682 => '⠬', 2683 => '⠭', 2684 => '⠮', 2685 => '⠯', 2686 => '⠰', 2687 => '⠱', 2688 => '⠲', 2689 => '⠳', 2690 => '⠴', 2691 => '⠵', 2692 => '⠶', 2693 => '⠷', 2694 => '⠸', 2695 => '⠹', 2696 => '⠺', 2697 => '⠻', 2698 => '⠼', 2699 => '⠽', 2700 => '⠾', 2701 => '⠿', 2702 => '⡀', 2703 => '⡁', 2704 => '⡂', 2705 => '⡃', 2706 => '⡄', 2707 => '⡅', 2708 => '⡆', 2709 => '⡇', 2710 => '⡈', 2711 => '⡉', 2712 => '⡊', 2713 => '⡋', 2714 => '⡌', 2715 => '⡍', 2716 => '⡎', 2717 => '⡏', 2718 => '⡐', 2719 => '⡑', 2720 => '⡒', 2721 => '⡓', 2722 => '⡔', 2723 => '⡕', 2724 => '⡖', 2725 => '⡗', 2726 => '⡘', 2727 => '⡙', 2728 => '⡚', 2729 => '⡛', 2730 => '⡜', 2731 => '⡝', 2732 => '⡞', 2733 => '⡟', 2734 => '⡠', 2735 => '⡡', 2736 => '⡢', 2737 => '⡣', 2738 => '⡤', 2739 => '⡥', 2740 => '⡦', 2741 => '⡧', 2742 => '⡨', 2743 => '⡩', 2744 => '⡪', 2745 => '⡫', 2746 => '⡬', 2747 => '⡭', 2748 => '⡮', 2749 => '⡯', 2750 => '⡰', 2751 => '⡱', 2752 => '⡲', 2753 => '⡳', 2754 => '⡴', 2755 => '⡵', 2756 => '⡶', 2757 => '⡷', 2758 => '⡸', 2759 => '⡹', 2760 => '⡺', 2761 => '⡻', 2762 => '⡼', 2763 => '⡽', 2764 => '⡾', 2765 => '⡿', 2766 => '⢀', 2767 => '⢁', 2768 => '⢂', 2769 => '⢃', 2770 => '⢄', 2771 => '⢅', 2772 => '⢆', 2773 => '⢇', 2774 => '⢈', 2775 => '⢉', 2776 => '⢊', 2777 => '⢋', 2778 => '⢌', 2779 => '⢍', 2780 => '⢎', 2781 => '⢏', 2782 => '⢐', 2783 => '⢑', 2784 => '⢒', 2785 => '⢓', 2786 => '⢔', 2787 => '⢕', 2788 => '⢖', 2789 => '⢗', 2790 => '⢘', 2791 => '⢙', 2792 => '⢚', 2793 => '⢛', 2794 => '⢜', 2795 => '⢝', 2796 => '⢞', 2797 => '⢟', 2798 => '⢠', 2799 => '⢡', 2800 => '⢢', 2801 => '⢣', 2802 => '⢤', 2803 => '⢥', 2804 => '⢦', 2805 => '⢧', 2806 => '⢨', 2807 => '⢩', 2808 => '⢪', 2809 => '⢫', 2810 => '⢬', 2811 => '⢭', 2812 => '⢮', 2813 => '⢯', 2814 => '⢰', 2815 => '⢱', 2816 => '⢲', 2817 => '⢳', 2818 => '⢴', 2819 => '⢵', 2820 => '⢶', 2821 => '⢷', 2822 => '⢸', 2823 => '⢹', 2824 => '⢺', 2825 => '⢻', 2826 => '⢼', 2827 => '⢽', 2828 => '⢾', 2829 => '⢿', 2830 => '⣀', 2831 => '⣁', 2832 => '⣂', 2833 => '⣃', 2834 => '⣄', 2835 => '⣅', 2836 => '⣆', 2837 => '⣇', 2838 => '⣈', 2839 => '⣉', 2840 => '⣊', 2841 => '⣋', 2842 => '⣌', 2843 => '⣍', 2844 => '⣎', 2845 => '⣏', 2846 => '⣐', 2847 => '⣑', 2848 => '⣒', 2849 => '⣓', 2850 => '⣔', 2851 => '⣕', 2852 => '⣖', 2853 => '⣗', 2854 => '⣘', 2855 => '⣙', 2856 => '⣚', 2857 => '⣛', 2858 => '⣜', 2859 => '⣝', 2860 => '⣞', 2861 => '⣟', 2862 => '⣠', 2863 => '⣡', 2864 => '⣢', 2865 => '⣣', 2866 => '⣤', 2867 => '⣥', 2868 => '⣦', 2869 => '⣧', 2870 => '⣨', 2871 => '⣩', 2872 => '⣪', 2873 => '⣫', 2874 => '⣬', 2875 => '⣭', 2876 => '⣮', 2877 => '⣯', 2878 => '⣰', 2879 => '⣱', 2880 => '⣲', 2881 => '⣳', 2882 => '⣴', 2883 => '⣵', 2884 => '⣶', 2885 => '⣷', 2886 => '⣸', 2887 => '⣹', 2888 => '⣺', 2889 => '⣻', 2890 => '⣼', 2891 => '⣽', 2892 => '⣾', 2893 => '⣿', 2894 => '⚊', 2895 => '⚋', 2896 => '⚌', 2897 => '⚍', 2898 => '⚎', 2899 => '⚏', 2900 => '☰', 2901 => '☱', 2902 => '☲', 2903 => '☳', 2904 => '☴', 2905 => '☵', 2906 => '☶', 2907 => '☷', 2908 => '䷀', 2909 => '䷁', 2910 => '䷂', 2911 => '䷃', 2912 => '䷄', 2913 => '䷅', 2914 => '䷆', 2915 => '䷇', 2916 => '䷈', 2917 => '䷉', 2918 => '䷊', 2919 => '䷋', 2920 => '䷌', 2921 => '䷍', 2922 => '䷎', 2923 => '䷏', 2924 => '䷐', 2925 => '䷑', 2926 => '䷒', 2927 => '䷓', 2928 => '䷔', 2929 => '䷕', 2930 => '䷖', 2931 => '䷗', 2932 => '䷘', 2933 => '䷙', 2934 => '䷚', 2935 => '䷛', 2936 => '䷜', 2937 => '䷝', 2938 => '䷞', 2939 => '䷟', 2940 => '䷠', 2941 => '䷡', 2942 => '䷢', 2943 => '䷣', 2944 => '䷤', 2945 => '䷥', 2946 => '䷦', 2947 => '䷧', 2948 => '䷨', 2949 => '䷩', 2950 => '䷪', 2951 => '䷫', 2952 => '䷬', 2953 => '䷭', 2954 => '䷮', 2955 => '䷯', 2956 => '䷰', 2957 => '䷱', 2958 => '䷲', 2959 => '䷳', 2960 => '䷴', 2961 => '䷵', 2962 => '䷶', 2963 => '䷷', 2964 => '䷸', 2965 => '䷹', 2966 => '䷺', 2967 => '䷻', 2968 => '䷼', 2969 => '䷽', 2970 => '䷾', 2971 => '䷿', 2972 => '𝌀', 2973 => '𝌁', 2974 => '𝌂', 2975 => '𝌃', 2976 => '𝌄', 2977 => '𝌅', 2978 => '𝌆', 2979 => '𝌇', 2980 => '𝌈', 2981 => '𝌉', 2982 => '𝌊', 2983 => '𝌋', 2984 => '𝌌', 2985 => '𝌍', 2986 => '𝌎', 2987 => '𝌏', 2988 => '𝌐', 2989 => '𝌑', 2990 => '𝌒', 2991 => '𝌓', 2992 => '𝌔', 2993 => '𝌕', 2994 => '𝌖', 2995 => '𝌗', 2996 => '𝌘', 2997 => '𝌙', 2998 => '𝌚', 2999 => '𝌛', 3000 => '𝌜', 3001 => '𝌝', 3002 => '𝌞', 3003 => '𝌟', 3004 => '𝌠', 3005 => '𝌡', 3006 => '𝌢', 3007 => '𝌣', 3008 => '𝌤', 3009 => '𝌥', 3010 => '𝌦', 3011 => '𝌧', 3012 => '𝌨', 3013 => '𝌩', 3014 => '𝌪', 3015 => '𝌫', 3016 => '𝌬', 3017 => '𝌭', 3018 => '𝌮', 3019 => '𝌯', 3020 => '𝌰', 3021 => '𝌱', 3022 => '𝌲', 3023 => '𝌳', 3024 => '𝌴', 3025 => '𝌵', 3026 => '𝌶', 3027 => '𝌷', 3028 => '𝌸', 3029 => '𝌹', 3030 => '𝌺', 3031 => '𝌻', 3032 => '𝌼', 3033 => '𝌽', 3034 => '𝌾', 3035 => '𝌿', 3036 => '𝍀', 3037 => '𝍁', 3038 => '𝍂', 3039 => '𝍃', 3040 => '𝍄', 3041 => '𝍅', 3042 => '𝍆', 3043 => '𝍇', 3044 => '𝍈', 3045 => '𝍉', 3046 => '𝍊', 3047 => '𝍋', 3048 => '𝍌', 3049 => '𝍍', 3050 => '𝍎', 3051 => '𝍏', 3052 => '𝍐', 3053 => '𝍑', 3054 => '𝍒', 3055 => '𝍓', 3056 => '𝍔', 3057 => '𝍕', 3058 => '𝍖', 3059 => '꒐', 3060 => '꒑', 3061 => '꒒', 3062 => '꒓', 3063 => '꒔', 3064 => '꒕', 3065 => '꒖', 3066 => '꒗', 3067 => '꒘', 3068 => '꒙', 3069 => '꒚', 3070 => '꒛', 3071 => '꒜', 3072 => '꒝', 3073 => '꒞', 3074 => '꒟', 3075 => '꒠', 3076 => '꒡', 3077 => '꒢', 3078 => '꒣', 3079 => '꒤', 3080 => '꒥', 3081 => '꒦', 3082 => '꒧', 3083 => '꒨', 3084 => '꒩', 3085 => '꒪', 3086 => '꒫', 3087 => '꒬', 3088 => '꒭', 3089 => '꒮', 3090 => '꒯', 3091 => '꒰', 3092 => '꒱', 3093 => '꒲', 3094 => '꒳', 3095 => '꒴', 3096 => '꒵', 3097 => '꒶', 3098 => '꒷', 3099 => '꒸', 3100 => '꒹', 3101 => '꒺', 3102 => '꒻', 3103 => '꒼', 3104 => '꒽', 3105 => '꒾', 3106 => '꒿', 3107 => '꓀', 3108 => '꓁', 3109 => '꓂', 3110 => '꓃', 3111 => '꓄', 3112 => '꓅', 3113 => '꓆', 3114 => '𐄷', 3115 => '𐄸', 3116 => '𐄹', 3117 => '𐄺', 3118 => '𐄻', 3119 => '𐄼', 3120 => '𐄽', 3121 => '𐄾', 3122 => '𐄿', 3123 => '𐅹', 3124 => '𐅺', 3125 => '𐅻', 3126 => '𐅼', 3127 => '𐅽', 3128 => '𐅾', 3129 => '𐅿', 3130 => '𐆀', 3131 => '𐆁', 3132 => '𐆂', 3133 => '𐆃', 3134 => '𐆄', 3135 => '𐆅', 3136 => '𐆆', 3137 => '𐆇', 3138 => '𐆈', 3139 => '𐆉', 3140 => '𐆐', 3141 => '𐆑', 3142 => '𐆒', 3143 => '𐆓', 3144 => '𐆔', 3145 => '𐆕', 3146 => '𐆖', 3147 => '𐆗', 3148 => '𐆘', 3149 => '𐆙', 3150 => '𐆚', 3151 => '𐆛', 3152 => '𐇐', 3153 => '𐇑', 3154 => '𐇒', 3155 => '𐇓', 3156 => '𐇔', 3157 => '𐇕', 3158 => '𐇖', 3159 => '𐇗', 3160 => '𐇘', 3161 => '𐇙', 3162 => '𐇚', 3163 => '𐇛', 3164 => '𐇜', 3165 => '𐇝', 3166 => '𐇞', 3167 => '𐇟', 3168 => '𐇠', 3169 => '𐇡', 3170 => '𐇢', 3171 => '𐇣', 3172 => '𐇤', 3173 => '𐇥', 3174 => '𐇦', 3175 => '𐇧', 3176 => '𐇨', 3177 => '𐇩', 3178 => '𐇪', 3179 => '𐇫', 3180 => '𐇬', 3181 => '𐇭', 3182 => '𐇮', 3183 => '𐇯', 3184 => '𐇰', 3185 => '𐇱', 3186 => '𐇲', 3187 => '𐇳', 3188 => '𐇴', 3189 => '𐇵', 3190 => '𐇶', 3191 => '𐇷', 3192 => '𐇸', 3193 => '𐇹', 3194 => '𐇺', 3195 => '𐇻', 3196 => '𐇼', 3197 => '𝀀', 3198 => '𝀁', 3199 => '𝀂', 3200 => '𝀃', 3201 => '𝀄', 3202 => '𝀅', 3203 => '𝀆', 3204 => '𝀇', 3205 => '𝀈', 3206 => '𝀉', 3207 => '𝀊', 3208 => '𝀋', 3209 => '𝀌', 3210 => '𝀍', 3211 => '𝀎', 3212 => '𝀏', 3213 => '𝀐', 3214 => '𝀑', 3215 => '𝀒', 3216 => '𝀓', 3217 => '𝀔', 3218 => '𝀕', 3219 => '𝀖', 3220 => '𝀗', 3221 => '𝀘', 3222 => '𝀙', 3223 => '𝀚', 3224 => '𝀛', 3225 => '𝀜', 3226 => '𝀝', 3227 => '𝀞', 3228 => '𝀟', 3229 => '𝀠', 3230 => '𝀡', 3231 => '𝀢', 3232 => '𝀣', 3233 => '𝀤', 3234 => '𝀥', 3235 => '𝀦', 3236 => '𝀧', 3237 => '𝀨', 3238 => '𝀩', 3239 => '𝀪', 3240 => '𝀫', 3241 => '𝀬', 3242 => '𝀭', 3243 => '𝀮', 3244 => '𝀯', 3245 => '𝀰', 3246 => '𝀱', 3247 => '𝀲', 3248 => '𝀳', 3249 => '𝀴', 3250 => '𝀵', 3251 => '𝀶', 3252 => '𝀷', 3253 => '𝀸', 3254 => '𝀹', 3255 => '𝀺', 3256 => '𝀻', 3257 => '𝀼', 3258 => '𝀽', 3259 => '𝀾', 3260 => '𝀿', 3261 => '𝁀', 3262 => '𝁁', 3263 => '𝁂', 3264 => '𝁃', 3265 => '𝁄', 3266 => '𝁅', 3267 => '𝁆', 3268 => '𝁇', 3269 => '𝁈', 3270 => '𝁉', 3271 => '𝁊', 3272 => '𝁋', 3273 => '𝁌', 3274 => '𝁍', 3275 => '𝁎', 3276 => '𝁏', 3277 => '𝁐', 3278 => '𝁑', 3279 => '𝁒', 3280 => '𝁓', 3281 => '𝁔', 3282 => '𝁕', 3283 => '𝁖', 3284 => '𝁗', 3285 => '𝁘', 3286 => '𝁙', 3287 => '𝁚', 3288 => '𝁛', 3289 => '𝁜', 3290 => '𝁝', 3291 => '𝁞', 3292 => '𝁟', 3293 => '𝁠', 3294 => '𝁡', 3295 => '𝁢', 3296 => '𝁣', 3297 => '𝁤', 3298 => '𝁥', 3299 => '𝁦', 3300 => '𝁧', 3301 => '𝁨', 3302 => '𝁩', 3303 => '𝁪', 3304 => '𝁫', 3305 => '𝁬', 3306 => '𝁭', 3307 => '𝁮', 3308 => '𝁯', 3309 => '𝁰', 3310 => '𝁱', 3311 => '𝁲', 3312 => '𝁳', 3313 => '𝁴', 3314 => '𝁵', 3315 => '𝁶', 3316 => '𝁷', 3317 => '𝁸', 3318 => '𝁹', 3319 => '𝁺', 3320 => '𝁻', 3321 => '𝁼', 3322 => '𝁽', 3323 => '𝁾', 3324 => '𝁿', 3325 => '𝂀', 3326 => '𝂁', 3327 => '𝂂', 3328 => '𝂃', 3329 => '𝂄', 3330 => '𝂅', 3331 => '𝂆', 3332 => '𝂇', 3333 => '𝂈', 3334 => '𝂉', 3335 => '𝂊', 3336 => '𝂋', 3337 => '𝂌', 3338 => '𝂍', 3339 => '𝂎', 3340 => '𝂏', 3341 => '𝂐', 3342 => '𝂑', 3343 => '𝂒', 3344 => '𝂓', 3345 => '𝂔', 3346 => '𝂕', 3347 => '𝂖', 3348 => '𝂗', 3349 => '𝂘', 3350 => '𝂙', 3351 => '𝂚', 3352 => '𝂛', 3353 => '𝂜', 3354 => '𝂝', 3355 => '𝂞', 3356 => '𝂟', 3357 => '𝂠', 3358 => '𝂡', 3359 => '𝂢', 3360 => '𝂣', 3361 => '𝂤', 3362 => '𝂥', 3363 => '𝂦', 3364 => '𝂧', 3365 => '𝂨', 3366 => '𝂩', 3367 => '𝂪', 3368 => '𝂫', 3369 => '𝂬', 3370 => '𝂭', 3371 => '𝂮', 3372 => '𝂯', 3373 => '𝂰', 3374 => '𝂱', 3375 => '𝂲', 3376 => '𝂳', 3377 => '𝂴', 3378 => '𝂵', 3379 => '𝂶', 3380 => '𝂷', 3381 => '𝂸', 3382 => '𝂹', 3383 => '𝂺', 3384 => '𝂻', 3385 => '𝂼', 3386 => '𝂽', 3387 => '𝂾', 3388 => '𝂿', 3389 => '𝃀', 3390 => '𝃁', 3391 => '𝃂', 3392 => '𝃃', 3393 => '𝃄', 3394 => '𝃅', 3395 => '𝃆', 3396 => '𝃇', 3397 => '𝃈', 3398 => '𝃉', 3399 => '𝃊', 3400 => '𝃋', 3401 => '𝃌', 3402 => '𝃍', 3403 => '𝃎', 3404 => '𝃏', 3405 => '𝃐', 3406 => '𝃑', 3407 => '𝃒', 3408 => '𝃓', 3409 => '𝃔', 3410 => '𝃕', 3411 => '𝃖', 3412 => '𝃗', 3413 => '𝃘', 3414 => '𝃙', 3415 => '𝃚', 3416 => '𝃛', 3417 => '𝃜', 3418 => '𝃝', 3419 => '𝃞', 3420 => '𝃟', 3421 => '𝃠', 3422 => '𝃡', 3423 => '𝃢', 3424 => '𝃣', 3425 => '𝃤', 3426 => '𝃥', 3427 => '𝃦', 3428 => '𝃧', 3429 => '𝃨', 3430 => '𝃩', 3431 => '𝃪', 3432 => '𝃫', 3433 => '𝃬', 3434 => '𝃭', 3435 => '𝃮', 3436 => '𝃯', 3437 => '𝃰', 3438 => '𝃱', 3439 => '𝃲', 3440 => '𝃳', 3441 => '𝃴', 3442 => '𝃵', 3443 => '𝄀', 3444 => '𝄁', 3445 => '𝄂', 3446 => '𝄃', 3447 => '𝄄', 3448 => '𝄅', 3449 => '𝄆', 3450 => '𝄇', 3451 => '𝄈', 3452 => '𝄉', 3453 => '𝄊', 3454 => '𝄋', 3455 => '𝄌', 3456 => '𝄍', 3457 => '𝄎', 3458 => '𝄏', 3459 => '𝄐', 3460 => '𝄑', 3461 => '𝄒', 3462 => '𝄓', 3463 => '𝄔', 3464 => '𝄕', 3465 => '𝄖', 3466 => '𝄗', 3467 => '𝄘', 3468 => '𝄙', 3469 => '𝄚', 3470 => '𝄛', 3471 => '𝄜', 3472 => '𝄝', 3473 => '𝄞', 3474 => '𝄟', 3475 => '𝄠', 3476 => '𝄡', 3477 => '𝄢', 3478 => '𝄣', 3479 => '𝄤', 3480 => '𝄥', 3481 => '𝄦', 3482 => '♭', 3483 => '♮', 3484 => '♯', 3485 => '𝄪', 3486 => '𝄫', 3487 => '𝄬', 3488 => '𝄭', 3489 => '𝄮', 3490 => '𝄯', 3491 => '𝄰', 3492 => '𝄱', 3493 => '𝄲', 3494 => '𝄳', 3495 => '𝄴', 3496 => '𝄵', 3497 => '𝄶', 3498 => '𝄷', 3499 => '𝄸', 3500 => '𝄹', 3501 => '𝄩', 3502 => '𝄺', 3503 => '𝄻', 3504 => '𝄼', 3505 => '𝄽', 3506 => '𝄾', 3507 => '𝄿', 3508 => '𝅀', 3509 => '𝅁', 3510 => '𝅂', 3511 => '𝅃', 3512 => '𝅄', 3513 => '𝅅', 3514 => '𝅆', 3515 => '𝅇', 3516 => '𝅈', 3517 => '𝅉', 3518 => '𝅊', 3519 => '𝅋', 3520 => '𝅌', 3521 => '𝅍', 3522 => '𝅎', 3523 => '𝅏', 3524 => '𝅐', 3525 => '𝅑', 3526 => '𝅒', 3527 => '𝅓', 3528 => '𝅔', 3529 => '𝅕', 3530 => '𝅖', 3531 => '𝅗', 3532 => '𝅘', 3533 => '𝅙', 3534 => '𝅚', 3535 => '𝅛', 3536 => '𝅜', 3537 => '𝅝', 3538 => '𝅪', 3539 => '𝅫', 3540 => '𝅬', 3541 => '𝆃', 3542 => '𝆄', 3543 => '𝆌', 3544 => '𝆍', 3545 => '𝆎', 3546 => '𝆏', 3547 => '𝆐', 3548 => '𝆑', 3549 => '𝆒', 3550 => '𝆓', 3551 => '𝆔', 3552 => '𝆕', 3553 => '𝆖', 3554 => '𝆗', 3555 => '𝆘', 3556 => '𝆙', 3557 => '𝆚', 3558 => '𝆛', 3559 => '𝆜', 3560 => '𝆝', 3561 => '𝆞', 3562 => '𝆟', 3563 => '𝆠', 3564 => '𝆡', 3565 => '𝆢', 3566 => '𝆣', 3567 => '𝆤', 3568 => '𝆥', 3569 => '𝆦', 3570 => '𝆧', 3571 => '𝆨', 3572 => '𝆩', 3573 => '𝆮', 3574 => '𝆯', 3575 => '𝆰', 3576 => '𝆱', 3577 => '𝆲', 3578 => '𝆳', 3579 => '𝆴', 3580 => '𝆵', 3581 => '𝆶', 3582 => '𝆷', 3583 => '𝆸', 3584 => '𝆹', 3585 => '𝆺', 3586 => '𝇁', 3587 => '𝇂', 3588 => '𝇃', 3589 => '𝇄', 3590 => '𝇅', 3591 => '𝇆', 3592 => '𝇇', 3593 => '𝇈', 3594 => '𝇉', 3595 => '𝇊', 3596 => '𝇋', 3597 => '𝇌', 3598 => '𝇍', 3599 => '𝇎', 3600 => '𝇏', 3601 => '𝇐', 3602 => '𝇑', 3603 => '𝇒', 3604 => '𝇓', 3605 => '𝇔', 3606 => '𝇕', 3607 => '𝇖', 3608 => '𝇗', 3609 => '𝇘', 3610 => '𝇙', 3611 => '𝇚', 3612 => '𝇛', 3613 => '𝇜', 3614 => '𝇝', 3615 => '𝈀', 3616 => '𝈁', 3617 => '𝈂', 3618 => '𝈃', 3619 => '𝈄', 3620 => '𝈅', 3621 => '𝈆', 3622 => '𝈇', 3623 => '𝈈', 3624 => '𝈉', 3625 => '𝈊', 3626 => '𝈋', 3627 => '𝈌', 3628 => '𝈍', 3629 => '𝈎', 3630 => '𝈏', 3631 => '𝈐', 3632 => '𝈑', 3633 => '𝈒', 3634 => '𝈓', 3635 => '𝈔', 3636 => '𝈕', 3637 => '𝈖', 3638 => '𝈗', 3639 => '𝈘', 3640 => '𝈙', 3641 => '𝈚', 3642 => '𝈛', 3643 => '𝈜', 3644 => '𝈝', 3645 => '𝈞', 3646 => '𝈟', 3647 => '𝈠', 3648 => '𝈡', 3649 => '𝈢', 3650 => '𝈣', 3651 => '𝈤', 3652 => '𝈥', 3653 => '𝈦', 3654 => '𝈧', 3655 => '𝈨', 3656 => '𝈩', 3657 => '𝈪', 3658 => '𝈫', 3659 => '𝈬', 3660 => '𝈭', 3661 => '𝈮', 3662 => '𝈯', 3663 => '𝈰', 3664 => '𝈱', 3665 => '𝈲', 3666 => '𝈳', 3667 => '𝈴', 3668 => '𝈵', 3669 => '𝈶', 3670 => '𝈷', 3671 => '𝈸', 3672 => '𝈹', 3673 => '𝈺', 3674 => '𝈻', 3675 => '𝈼', 3676 => '𝈽', 3677 => '𝈾', 3678 => '𝈿', 3679 => '𝉀', 3680 => '𝉁', 3681 => '𝉅', 3682 => '🀀', 3683 => '🀁', 3684 => '🀂', 3685 => '🀃', 3686 => '🀄', 3687 => '🀅', 3688 => '🀆', 3689 => '🀇', 3690 => '🀈', 3691 => '🀉', 3692 => '🀊', 3693 => '🀋', 3694 => '🀌', 3695 => '🀍', 3696 => '🀎', 3697 => '🀏', 3698 => '🀐', 3699 => '🀑', 3700 => '🀒', 3701 => '🀓', 3702 => '🀔', 3703 => '🀕', 3704 => '🀖', 3705 => '🀗', 3706 => '🀘', 3707 => '🀙', 3708 => '🀚', 3709 => '🀛', 3710 => '🀜', 3711 => '🀝', 3712 => '🀞', 3713 => '🀟', 3714 => '🀠', 3715 => '🀡', 3716 => '🀢', 3717 => '🀣', 3718 => '🀤', 3719 => '🀥', 3720 => '🀦', 3721 => '🀧', 3722 => '🀨', 3723 => '🀩', 3724 => '🀪', 3725 => '🀫', 3726 => '🀰', 3727 => '🀱', 3728 => '🀲', 3729 => '🀳', 3730 => '🀴', 3731 => '🀵', 3732 => '🀶', 3733 => '🀷', 3734 => '🀸', 3735 => '🀹', 3736 => '🀺', 3737 => '🀻', 3738 => '🀼', 3739 => '🀽', 3740 => '🀾', 3741 => '🀿', 3742 => '🁀', 3743 => '🁁', 3744 => '🁂', 3745 => '🁃', 3746 => '🁄', 3747 => '🁅', 3748 => '🁆', 3749 => '🁇', 3750 => '🁈', 3751 => '🁉', 3752 => '🁊', 3753 => '🁋', 3754 => '🁌', 3755 => '🁍', 3756 => '🁎', 3757 => '🁏', 3758 => '🁐', 3759 => '🁑', 3760 => '🁒', 3761 => '🁓', 3762 => '🁔', 3763 => '🁕', 3764 => '🁖', 3765 => '🁗', 3766 => '🁘', 3767 => '🁙', 3768 => '🁚', 3769 => '🁛', 3770 => '🁜', 3771 => '🁝', 3772 => '🁞', 3773 => '🁟', 3774 => '🁠', 3775 => '🁡', 3776 => '🁢', 3777 => '🁣', 3778 => '🁤', 3779 => '🁥', 3780 => '🁦', 3781 => '🁧', 3782 => '🁨', 3783 => '🁩', 3784 => '🁪', 3785 => '🁫', 3786 => '🁬', 3787 => '🁭', 3788 => '🁮', 3789 => '🁯', 3790 => '🁰', 3791 => '🁱', 3792 => '🁲', 3793 => '🁳', 3794 => '🁴', 3795 => '🁵', 3796 => '🁶', 3797 => '🁷', 3798 => '🁸', 3799 => '🁹', 3800 => '🁺', 3801 => '🁻', 3802 => '🁼', 3803 => '🁽', 3804 => '🁾', 3805 => '🁿', 3806 => '🂀', 3807 => '🂁', 3808 => '🂂', 3809 => '🂃', 3810 => '🂄', 3811 => '🂅', 3812 => '🂆', 3813 => '🂇', 3814 => '🂈', 3815 => '🂉', 3816 => '🂊', 3817 => '🂋', 3818 => '🂌', 3819 => '🂍', 3820 => '🂎', 3821 => '🂏', 3822 => '🂐', 3823 => '🂑', 3824 => '🂒', 3825 => '🂓', 3826 => '🂠', 3827 => '🂡', 3828 => '🂢', 3829 => '🂣', 3830 => '🂤', 3831 => '🂥', 3832 => '🂦', 3833 => '🂧', 3834 => '🂨', 3835 => '🂩', 3836 => '🂪', 3837 => '🂫', 3838 => '🂬', 3839 => '🂭', 3840 => '🂮', 3841 => '🂱', 3842 => '🂲', 3843 => '🂳', 3844 => '🂴', 3845 => '🂵', 3846 => '🂶', 3847 => '🂷', 3848 => '🂸', 3849 => '🂹', 3850 => '🂺', 3851 => '🂻', 3852 => '🂼', 3853 => '🂽', 3854 => '🂾', 3855 => '🃁', 3856 => '🃂', 3857 => '🃃', 3858 => '🃄', 3859 => '🃅', 3860 => '🃆', 3861 => '🃇', 3862 => '🃈', 3863 => '🃉', 3864 => '🃊', 3865 => '🃋', 3866 => '🃌', 3867 => '🃍', 3868 => '🃎', 3869 => '🃏', 3870 => '🃑', 3871 => '🃒', 3872 => '🃓', 3873 => '🃔', 3874 => '🃕', 3875 => '🃖', 3876 => '🃗', 3877 => '🃘', 3878 => '🃙', 3879 => '🃚', 3880 => '🃛', 3881 => '🃜', 3882 => '🃝', 3883 => '🃞', 3884 => '🃟', 3885 => '🌀', 3886 => '🌁', 3887 => '🌂', 3888 => '🌃', 3889 => '🌄', 3890 => '🌅', 3891 => '🌆', 3892 => '🌇', 3893 => '🌈', 3894 => '🌉', 3895 => '🌊', 3896 => '🌋', 3897 => '🌌', 3898 => '🌍', 3899 => '🌎', 3900 => '🌏', 3901 => '🌐', 3902 => '🌑', 3903 => '🌒', 3904 => '🌓', 3905 => '🌔', 3906 => '🌕', 3907 => '🌖', 3908 => '🌗', 3909 => '🌘', 3910 => '🌙', 3911 => '🌚', 3912 => '🌛', 3913 => '🌜', 3914 => '🌝', 3915 => '🌞', 3916 => '🌟', 3917 => '🌠', 3918 => '🌰', 3919 => '🌱', 3920 => '🌲', 3921 => '🌳', 3922 => '🌴', 3923 => '🌵', 3924 => '🌷', 3925 => '🌸', 3926 => '🌹', 3927 => '🌺', 3928 => '🌻', 3929 => '🌼', 3930 => '🌽', 3931 => '🌾', 3932 => '🌿', 3933 => '🍀', 3934 => '🍁', 3935 => '🍂', 3936 => '🍃', 3937 => '🍄', 3938 => '🍅', 3939 => '🍆', 3940 => '🍇', 3941 => '🍈', 3942 => '🍉', 3943 => '🍊', 3944 => '🍋', 3945 => '🍌', 3946 => '🍍', 3947 => '🍎', 3948 => '🍏', 3949 => '🍐', 3950 => '🍑', 3951 => '🍒', 3952 => '🍓', 3953 => '🍔', 3954 => '🍕', 3955 => '🍖', 3956 => '🍗', 3957 => '🍘', 3958 => '🍙', 3959 => '🍚', 3960 => '🍛', 3961 => '🍜', 3962 => '🍝', 3963 => '🍞', 3964 => '🍟', 3965 => '🍠', 3966 => '🍡', 3967 => '🍢', 3968 => '🍣', 3969 => '🍤', 3970 => '🍥', 3971 => '🍦', 3972 => '🍧', 3973 => '🍨', 3974 => '🍩', 3975 => '🍪', 3976 => '🍫', 3977 => '🍬', 3978 => '🍭', 3979 => '🍮', 3980 => '🍯', 3981 => '🍰', 3982 => '🍱', 3983 => '🍲', 3984 => '🍳', 3985 => '🍴', 3986 => '🍵', 3987 => '🍶', 3988 => '🍷', 3989 => '🍸', 3990 => '🍹', 3991 => '🍺', 3992 => '🍻', 3993 => '🍼', 3994 => '🎀', 3995 => '🎁', 3996 => '🎂', 3997 => '🎃', 3998 => '🎄', 3999 => '🎅', 4000 => '🎆', 4001 => '🎇', 4002 => '🎈', 4003 => '🎉', 4004 => '🎊', 4005 => '🎋', 4006 => '🎌', 4007 => '🎍', 4008 => '🎎', 4009 => '🎏', 4010 => '🎐', 4011 => '🎑', 4012 => '🎒', 4013 => '🎓', 4014 => '🎠', 4015 => '🎡', 4016 => '🎢', 4017 => '🎣', 4018 => '🎤', 4019 => '🎥', 4020 => '🎦', 4021 => '🎧', 4022 => '🎨', 4023 => '🎩', 4024 => '🎪', 4025 => '🎫', 4026 => '🎬', 4027 => '🎭', 4028 => '🎮', 4029 => '🎯', 4030 => '🎰', 4031 => '🎱', 4032 => '🎲', 4033 => '🎳', 4034 => '🎴', 4035 => '🎵', 4036 => '🎶', 4037 => '🎷', 4038 => '🎸', 4039 => '🎹', 4040 => '🎺', 4041 => '🎻', 4042 => '🎼', 4043 => '🎽', 4044 => '🎾', 4045 => '🎿', 4046 => '🏀', 4047 => '🏁', 4048 => '🏂', 4049 => '🏃', 4050 => '🏄', 4051 => '🏆', 4052 => '🏇', 4053 => '🏈', 4054 => '🏉', 4055 => '🏊', 4056 => '🏠', 4057 => '🏡', 4058 => '🏢', 4059 => '🏣', 4060 => '🏤', 4061 => '🏥', 4062 => '🏦', 4063 => '🏧', 4064 => '🏨', 4065 => '🏩', 4066 => '🏪', 4067 => '🏫', 4068 => '🏬', 4069 => '🏭', 4070 => '🏮', 4071 => '🏯', 4072 => '🏰', 4073 => '🐀', 4074 => '🐁', 4075 => '🐂', 4076 => '🐃', 4077 => '🐄', 4078 => '🐅', 4079 => '🐆', 4080 => '🐇', 4081 => '🐈', 4082 => '🐉', 4083 => '🐊', 4084 => '🐋', 4085 => '🐌', 4086 => '🐍', 4087 => '🐎', 4088 => '🐏', 4089 => '🐐', 4090 => '🐑', 4091 => '🐒', 4092 => '🐓', 4093 => '🐔', 4094 => '🐕', 4095 => '🐖', 4096 => '🐗', 4097 => '🐘', 4098 => '🐙', 4099 => '🐚', 4100 => '🐛', 4101 => '🐜', 4102 => '🐝', 4103 => '🐞', 4104 => '🐟', 4105 => '🐠', 4106 => '🐡', 4107 => '🐢', 4108 => '🐣', 4109 => '🐤', 4110 => '🐥', 4111 => '🐦', 4112 => '🐧', 4113 => '🐨', 4114 => '🐩', 4115 => '🐪', 4116 => '🐫', 4117 => '🐬', 4118 => '🐭', 4119 => '🐮', 4120 => '🐯', 4121 => '🐰', 4122 => '🐱', 4123 => '🐲', 4124 => '🐳', 4125 => '🐴', 4126 => '🐵', 4127 => '🐶', 4128 => '🐷', 4129 => '🐸', 4130 => '🐹', 4131 => '🐺', 4132 => '🐻', 4133 => '🐼', 4134 => '🐽', 4135 => '🐾', 4136 => '👀', 4137 => '👂', 4138 => '👃', 4139 => '👄', 4140 => '👅', 4141 => '👆', 4142 => '👇', 4143 => '👈', 4144 => '👉', 4145 => '👊', 4146 => '👋', 4147 => '👌', 4148 => '👍', 4149 => '👎', 4150 => '👏', 4151 => '👐', 4152 => '👑', 4153 => '👒', 4154 => '👓', 4155 => '👔', 4156 => '👕', 4157 => '👖', 4158 => '👗', 4159 => '👘', 4160 => '👙', 4161 => '👚', 4162 => '👛', 4163 => '👜', 4164 => '👝', 4165 => '👞', 4166 => '👟', 4167 => '👠', 4168 => '👡', 4169 => '👢', 4170 => '👣', 4171 => '👤', 4172 => '👥', 4173 => '👦', 4174 => '👧', 4175 => '👨', 4176 => '👩', 4177 => '👪', 4178 => '👫', 4179 => '👬', 4180 => '👭', 4181 => '👮', 4182 => '👯', 4183 => '👰', 4184 => '👱', 4185 => '👲', 4186 => '👳', 4187 => '👴', 4188 => '👵', 4189 => '👶', 4190 => '👷', 4191 => '👸', 4192 => '👹', 4193 => '👺', 4194 => '👻', 4195 => '👼', 4196 => '👽', 4197 => '👾', 4198 => '👿', 4199 => '💀', 4200 => '💁', 4201 => '💂', 4202 => '💃', 4203 => '💄', 4204 => '💅', 4205 => '💆', 4206 => '💇', 4207 => '💈', 4208 => '💉', 4209 => '💊', 4210 => '💋', 4211 => '💌', 4212 => '💍', 4213 => '💎', 4214 => '💏', 4215 => '💐', 4216 => '💑', 4217 => '💒', 4218 => '💓', 4219 => '💔', 4220 => '💕', 4221 => '💖', 4222 => '💗', 4223 => '💘', 4224 => '💙', 4225 => '💚', 4226 => '💛', 4227 => '💜', 4228 => '💝', 4229 => '💞', 4230 => '💟', 4231 => '💠', 4232 => '💡', 4233 => '💢', 4234 => '💣', 4235 => '💤', 4236 => '💥', 4237 => '💦', 4238 => '💧', 4239 => '💨', 4240 => '💩', 4241 => '💪', 4242 => '💫', 4243 => '💬', 4244 => '💭', 4245 => '💮', 4246 => '💯', 4247 => '💰', 4248 => '💱', 4249 => '💲', 4250 => '💳', 4251 => '💴', 4252 => '💵', 4253 => '💶', 4254 => '💷', 4255 => '💸', 4256 => '💹', 4257 => '💺', 4258 => '💻', 4259 => '💼', 4260 => '💽', 4261 => '💾', 4262 => '💿', 4263 => '📀', 4264 => '📁', 4265 => '📂', 4266 => '📃', 4267 => '📄', 4268 => '📅', 4269 => '📆', 4270 => '📇', 4271 => '📈', 4272 => '📉', 4273 => '📊', 4274 => '📋', 4275 => '📌', 4276 => '📍', 4277 => '📎', 4278 => '📏', 4279 => '📐', 4280 => '📑', 4281 => '📒', 4282 => '📓', 4283 => '📔', 4284 => '📕', 4285 => '📖', 4286 => '📗', 4287 => '📘', 4288 => '📙', 4289 => '📚', 4290 => '📛', 4291 => '📜', 4292 => '📝', 4293 => '📞', 4294 => '📟', 4295 => '📠', 4296 => '📡', 4297 => '📢', 4298 => '📣', 4299 => '📤', 4300 => '📥', 4301 => '📦', 4302 => '📧', 4303 => '📨', 4304 => '📩', 4305 => '📪', 4306 => '📫', 4307 => '📬', 4308 => '📭', 4309 => '📮', 4310 => '📯', 4311 => '📰', 4312 => '📱', 4313 => '📲', 4314 => '📳', 4315 => '📴', 4316 => '📵', 4317 => '📶', 4318 => '📷', 4319 => '📹', 4320 => '📺', 4321 => '📻', 4322 => '📼', 4323 => '🔀', 4324 => '🔁', 4325 => '🔂', 4326 => '🔃', 4327 => '🔄', 4328 => '🔅', 4329 => '🔆', 4330 => '🔇', 4331 => '🔈', 4332 => '🔉', 4333 => '🔊', 4334 => '🔋', 4335 => '🔌', 4336 => '🔍', 4337 => '🔎', 4338 => '🔏', 4339 => '🔐', 4340 => '🔑', 4341 => '🔒', 4342 => '🔓', 4343 => '🔔', 4344 => '🔕', 4345 => '🔖', 4346 => '🔗', 4347 => '🔘', 4348 => '🔙', 4349 => '🔚', 4350 => '🔛', 4351 => '🔜', 4352 => '🔝', 4353 => '🔞', 4354 => '🔟', 4355 => '🔠', 4356 => '🔡', 4357 => '🔢', 4358 => '🔣', 4359 => '🔤', 4360 => '🔥', 4361 => '🔦', 4362 => '🔧', 4363 => '🔨', 4364 => '🔩', 4365 => '🔪', 4366 => '🔫', 4367 => '🔬', 4368 => '🔭', 4369 => '🔮', 4370 => '🔯', 4371 => '🔰', 4372 => '🔱', 4373 => '🔲', 4374 => '🔳', 4375 => '🔴', 4376 => '🔵', 4377 => '🔶', 4378 => '🔷', 4379 => '🔸', 4380 => '🔹', 4381 => '🔺', 4382 => '🔻', 4383 => '🔼', 4384 => '🔽', 4385 => '🕐', 4386 => '🕑', 4387 => '🕒', 4388 => '🕓', 4389 => '🕔', 4390 => '🕕', 4391 => '🕖', 4392 => '🕗', 4393 => '🕘', 4394 => '🕙', 4395 => '🕚', 4396 => '🕛', 4397 => '🕜', 4398 => '🕝', 4399 => '🕞', 4400 => '🕟', 4401 => '🕠', 4402 => '🕡', 4403 => '🕢', 4404 => '🕣', 4405 => '🕤', 4406 => '🕥', 4407 => '🕦', 4408 => '🕧', 4409 => '🗻', 4410 => '🗼', 4411 => '🗽', 4412 => '🗾', 4413 => '🗿', 4414 => '😁', 4415 => '😂', 4416 => '😃', 4417 => '😄', 4418 => '😅', 4419 => '😆', 4420 => '😇', 4421 => '😈', 4422 => '😉', 4423 => '😊', 4424 => '😋', 4425 => '😌', 4426 => '😍', 4427 => '😎', 4428 => '😏', 4429 => '😐', 4430 => '😒', 4431 => '😓', 4432 => '😔', 4433 => '😖', 4434 => '😘', 4435 => '😚', 4436 => '😜', 4437 => '😝', 4438 => '😞', 4439 => '😠', 4440 => '😡', 4441 => '😢', 4442 => '😣', 4443 => '😤', 4444 => '😥', 4445 => '😨', 4446 => '😩', 4447 => '😪', 4448 => '😫', 4449 => '😭', 4450 => '😰', 4451 => '😱', 4452 => '😲', 4453 => '😳', 4454 => '😵', 4455 => '😶', 4456 => '😷', 4457 => '😸', 4458 => '😹', 4459 => '😺', 4460 => '😻', 4461 => '😼', 4462 => '😽', 4463 => '😾', 4464 => '😿', 4465 => '🙀', 4466 => '🙅', 4467 => '🙆', 4468 => '🙇', 4469 => '🙈', 4470 => '🙉', 4471 => '🙊', 4472 => '🙋', 4473 => '🙌', 4474 => '🙍', 4475 => '🙎', 4476 => '🙏', 4477 => '🚀', 4478 => '🚁', 4479 => '🚂', 4480 => '🚃', 4481 => '🚄', 4482 => '🚅', 4483 => '🚆', 4484 => '🚇', 4485 => '🚈', 4486 => '🚉', 4487 => '🚊', 4488 => '🚋', 4489 => '🚌', 4490 => '🚍', 4491 => '🚎', 4492 => '🚏', 4493 => '🚐', 4494 => '🚑', 4495 => '🚒', 4496 => '🚓', 4497 => '🚔', 4498 => '🚕', 4499 => '🚖', 4500 => '🚗', 4501 => '🚘', 4502 => '🚙', 4503 => '🚚', 4504 => '🚛', 4505 => '🚜', 4506 => '🚝', 4507 => '🚞', 4508 => '🚟', 4509 => '🚠', 4510 => '🚡', 4511 => '🚢', 4512 => '🚣', 4513 => '🚤', 4514 => '🚥', 4515 => '🚦', 4516 => '🚧', 4517 => '🚨', 4518 => '🚩', 4519 => '🚪', 4520 => '🚫', 4521 => '🚬', 4522 => '🚭', 4523 => '🚮', 4524 => '🚯', 4525 => '🚰', 4526 => '🚱', 4527 => '🚲', 4528 => '🚳', 4529 => '🚴', 4530 => '🚵', 4531 => '🚶', 4532 => '🚷', 4533 => '🚸', 4534 => '🚹', 4535 => '🚺', 4536 => '🚻', 4537 => '🚼', 4538 => '🚽', 4539 => '🚾', 4540 => '🚿', 4541 => '🛀', 4542 => '🛁', 4543 => '🛂', 4544 => '🛃', 4545 => '🛄', 4546 => '🛅', 4547 => '🜀', 4548 => '🜁', 4549 => '🜂', 4550 => '🜃', 4551 => '🜄', 4552 => '🜅', 4553 => '🜆', 4554 => '🜇', 4555 => '🜈', 4556 => '🜉', 4557 => '🜊', 4558 => '🜋', 4559 => '🜌', 4560 => '🜍', 4561 => '🜎', 4562 => '🜏', 4563 => '🜐', 4564 => '🜑', 4565 => '🜒', 4566 => '🜓', 4567 => '🜔', 4568 => '🜕', 4569 => '🜖', 4570 => '🜗', 4571 => '🜘', 4572 => '🜙', 4573 => '🜚', 4574 => '🜛', 4575 => '🜜', 4576 => '🜝', 4577 => '🜞', 4578 => '🜟', 4579 => '🜠', 4580 => '🜡', 4581 => '🜢', 4582 => '🜣', 4583 => '🜤', 4584 => '🜥', 4585 => '🜦', 4586 => '🜧', 4587 => '🜨', 4588 => '🜩', 4589 => '🜪', 4590 => '🜫', 4591 => '🜬', 4592 => '🜭', 4593 => '🜮', 4594 => '🜯', 4595 => '🜰', 4596 => '🜱', 4597 => '🜲', 4598 => '🜳', 4599 => '🜴', 4600 => '🜵', 4601 => '🜶', 4602 => '🜷', 4603 => '🜸', 4604 => '🜹', 4605 => '🜺', 4606 => '🜻', 4607 => '🜼', 4608 => '🜽', 4609 => '🜾', 4610 => '🜿', 4611 => '🝀', 4612 => '🝁', 4613 => '🝂', 4614 => '🝃', 4615 => '🝄', 4616 => '🝅', 4617 => '🝆', 4618 => '🝇', 4619 => '🝈', 4620 => '🝉', 4621 => '🝊', 4622 => '🝋', 4623 => '🝌', 4624 => '🝍', 4625 => '🝎', 4626 => '🝏', 4627 => '🝐', 4628 => '🝑', 4629 => '🝒', 4630 => '🝓', 4631 => '🝔', 4632 => '🝕', 4633 => '🝖', 4634 => '🝗', 4635 => '🝘', 4636 => '🝙', 4637 => '🝚', 4638 => '🝛', 4639 => '🝜', 4640 => '🝝', 4641 => '🝞', 4642 => '🝟', 4643 => '🝠', 4644 => '🝡', 4645 => '🝢', 4646 => '🝣', 4647 => '🝤', 4648 => '🝥', 4649 => '🝦', 4650 => '🝧', 4651 => '🝨', 4652 => '🝩', 4653 => '🝪', 4654 => '🝫', 4655 => '🝬', 4656 => '🝭', 4657 => '🝮', 4658 => '🝯', 4659 => '🝰', 4660 => '🝱', 4661 => '🝲', 4662 => '🝳', 4663 => '㆐', 4664 => '㆑', 4665 => '', 4666 => '�', 4667 => '৴', 4668 => '৵', 4669 => '৶', 4670 => '৷', 4671 => '৸', 4672 => '৹', 4673 => '୲', 4674 => '୳', 4675 => '୴', 4676 => '୵', 4677 => '୶', 4678 => '୷', 4679 => '꠰', 4680 => '꠱', 4681 => '꠲', 4682 => '꠳', 4683 => '꠴', 4684 => '꠵', 4685 => '௰', 4686 => '௱', 4687 => '௲', 4688 => '൰', 4689 => '൱', 4690 => '൲', 4691 => '൳', 4692 => '൴', 4693 => '൵', 4694 => '፲', 4695 => '፳', 4696 => '፴', 4697 => '፵', 4698 => '፶', 4699 => '፷', 4700 => '፸', 4701 => '፹', 4702 => '፺', 4703 => '፻', 4704 => '፼', 4705 => 'ↀ', 4706 => 'ↁ', 4707 => 'ↂ', 4708 => 'ↆ', 4709 => 'ↇ', 4710 => 'ↈ', 4711 => '𐹩', 4712 => '𐹪', 4713 => '𐹫', 4714 => '𐹬', 4715 => '𐹭', 4716 => '𐹮', 4717 => '𐹯', 4718 => '𐹰', 4719 => '𐹱', 4720 => '𐹲', 4721 => '𐹳', 4722 => '𐹴', 4723 => '𐹵', 4724 => '𐹶', 4725 => '𐹷', 4726 => '𐹸', 4727 => '𐹹', 4728 => '𐹺', 4729 => '𐹻', 4730 => '𐹼', 4731 => '𐹽', 4732 => '𐹾', 4733 => '⳽', 4734 => '𐌢', 4735 => '𐌣', 4736 => '𐄐', 4737 => '𐄑', 4738 => '𐄒', 4739 => '𐄓', 4740 => '𐄔', 4741 => '𐄕', 4742 => '𐄖', 4743 => '𐄗', 4744 => '𐄘', 4745 => '𐄙', 4746 => '𐄚', 4747 => '𐄛', 4748 => '𐄜', 4749 => '𐄝', 4750 => '𐄞', 4751 => '𐄟', 4752 => '𐄠', 4753 => '𐄡', 4754 => '𐄢', 4755 => '𐄣', 4756 => '𐄤', 4757 => '𐄥', 4758 => '𐄦', 4759 => '𐄧', 4760 => '𐄨', 4761 => '𐄩', 4762 => '𐄪', 4763 => '𐄫', 4764 => '𐄬', 4765 => '𐄭', 4766 => '𐄮', 4767 => '𐄯', 4768 => '𐄰', 4769 => '𐄱', 4770 => '𐄲', 4771 => '𐄳', 4772 => '𐅀', 4773 => '𐅁', 4774 => '𐅄', 4775 => '𐅅', 4776 => '𐅆', 4777 => '𐅇', 4778 => '𐅉', 4779 => '𐅊', 4780 => '𐅋', 4781 => '𐅌', 4782 => '𐅍', 4783 => '𐅎', 4784 => '𐅐', 4785 => '𐅑', 4786 => '𐅒', 4787 => '𐅓', 4788 => '𐅔', 4789 => '𐅕', 4790 => '𐅖', 4791 => '𐅗', 4792 => '𐅠', 4793 => '𐅡', 4794 => '𐅢', 4795 => '𐅣', 4796 => '𐅤', 4797 => '𐅥', 4798 => '𐅦', 4799 => '𐅧', 4800 => '𐅨', 4801 => '𐅩', 4802 => '𐅪', 4803 => '𐅫', 4804 => '𐅬', 4805 => '𐅭', 4806 => '𐅮', 4807 => '𐅯', 4808 => '𐅰', 4809 => '𐅱', 4810 => '𐅲', 4811 => '𐅴', 4812 => '𐅵', 4813 => '𐅶', 4814 => '𐅷', 4815 => '𐅸', 4816 => '𐏓', 4817 => '𐏔', 4818 => '𐏕', 4819 => '𐩾', 4820 => '𐩿', 4821 => '𐤗', 4822 => '𐤘', 4823 => '𐤙', 4824 => '𐡛', 4825 => '𐡜', 4826 => '𐡝', 4827 => '𐡞', 4828 => '𐡟', 4829 => '𐭜', 4830 => '𐭝', 4831 => '𐭞', 4832 => '𐭟', 4833 => '𐭼', 4834 => '𐭽', 4835 => '𐭾', 4836 => '𐭿', 4837 => '𑁛', 4838 => '𑁜', 4839 => '𑁝', 4840 => '𑁞', 4841 => '𑁟', 4842 => '𑁠', 4843 => '𑁡', 4844 => '𑁢', 4845 => '𑁣', 4846 => '𑁤', 4847 => '𑁥', 4848 => '𐩄', 4849 => '𐩅', 4850 => '𐩆', 4851 => '𐩇', 4852 => '𒐲', 4853 => '𒐳', 4854 => '𒑖', 4855 => '𒑗', 4856 => '𒑚', 4857 => '𒑛', 4858 => '𒑜', 4859 => '𒑝', 4860 => '𒑞', 4861 => '𒑟', 4862 => '𒑠', 4863 => '𒑡', 4864 => '𒑢', 4865 => '𝍩', 4866 => '𝍪', 4867 => '𝍫', 4868 => '𝍬', 4869 => '𝍭', 4870 => '𝍮', 4871 => '𝍯', 4872 => '𝍰', 4873 => '𝍱', 4874 => 'ː', 4875 => 'ˑ', 4876 => 'ॱ', 4877 => 'ๆ', 4878 => 'ໆ', 4879 => 'ᪧ', 4880 => 'ꧏ', 4881 => 'ꩰ', 4882 => 'ꫝ', 4883 => 'ゝ', 4884 => 'ー', 4885 => 'ヽ', 4886 => '¤', 4887 => '¢', 4888 => '$', 4889 => '£', 4890 => '¥', 4891 => '؋', 4892 => '৲', 4893 => '৳', 4894 => '৻', 4895 => '૱', 4896 => '꠸', 4897 => '௹', 4898 => '฿', 4899 => '៛', 4900 => '₠', 4901 => '₡', 4902 => '₢', 4903 => '₣', 4904 => '₤', 4905 => '₥', 4906 => '₦', 4907 => '₧', 4908 => '₩', 4909 => '₪', 4910 => '₫', 4911 => '€', 4912 => '₭', 4913 => '₮', 4914 => '₯', 4915 => '₰', 4916 => '₱', 4917 => '₲', 4918 => '₳', 4919 => '₴', 4920 => '₵', 4921 => '₶', 4922 => '₷', 4923 => '₸', 4924 => '₹', 4925 => '0', 4926 => '1', 4927 => '2', 4928 => '3', 4929 => '4', 4930 => '5', 4931 => '6', 4932 => '7', 4933 => '8', 4934 => '9', 4935 => 'A', 4936 => 'ᴀ', 4937 => 'Ⱥ', 4938 => 'ᶏ', 4939 => 'ᴁ', 4940 => 'ᴂ', 4941 => 'Ɐ', 4942 => 'Ɑ', 4943 => 'ᶐ', 4944 => 'Ɒ', 4945 => 'B', 4946 => 'ʙ', 4947 => 'Ƀ', 4948 => 'ᴯ', 4949 => 'ᴃ', 4950 => 'ᵬ', 4951 => 'ᶀ', 4952 => 'Ɓ', 4953 => 'Ƃ', 4954 => 'C', 4955 => 'ᴄ', 4956 => 'Ȼ', 4957 => 'Ƈ', 4958 => 'ɕ', 4959 => 'Ↄ', 4960 => 'Ꜿ', 4961 => 'D', 4962 => 'ᴅ', 4963 => 'ᴆ', 4964 => 'ᵭ', 4965 => 'ᶁ', 4966 => 'Ɖ', 4967 => 'Ɗ', 4968 => 'ᶑ', 4969 => 'Ƌ', 4970 => 'ȡ', 4971 => 'ꝱ', 4972 => 'ẟ', 4973 => 'E', 4974 => 'ᴇ', 4975 => 'Ɇ', 4976 => 'ᶒ', 4977 => 'ⱸ', 4978 => 'Ǝ', 4979 => 'ⱻ', 4980 => 'Ə', 4981 => 'ᶕ', 4982 => 'Ɛ', 4983 => 'ᶓ', 4984 => 'ɘ', 4985 => 'ɚ', 4986 => 'ɜ', 4987 => 'ᶔ', 4988 => 'ᴈ', 4989 => 'ɝ', 4990 => 'ɞ', 4991 => 'ʚ', 4992 => 'ɤ', 4993 => 'F', 4994 => 'ꜰ', 4995 => 'ᵮ', 4996 => 'ᶂ', 4997 => 'Ƒ', 4998 => 'Ⅎ', 4999 => 'ꟻ', 5000 => 'G', 5001 => 'ɡ', 5002 => 'ɢ', 5003 => 'Ǥ', 5004 => 'ᶃ', 5005 => 'Ɠ', 5006 => 'ʛ', 5007 => 'ᵷ', 5008 => 'Ꝿ', 5009 => 'Ɣ', 5010 => 'Ƣ', 5011 => 'H', 5012 => 'ʜ', 5013 => 'Ƕ', 5014 => 'ɦ', 5015 => 'Ⱨ', 5016 => 'Ⱶ', 5017 => 'Ꜧ', 5018 => 'ɧ', 5019 => 'ʻ', 5020 => 'ʽ', 5021 => 'I', 5022 => 'ı', 5023 => 'ɪ', 5024 => 'ꟾ', 5025 => 'ᴉ', 5026 => 'Ɨ', 5027 => 'ᵻ', 5028 => 'ᶖ', 5029 => 'Ɩ', 5030 => 'ᵼ', 5031 => 'J', 5032 => 'ȷ', 5033 => 'ᴊ', 5034 => 'Ɉ', 5035 => 'ʝ', 5036 => 'ɟ', 5037 => 'ʄ', 5038 => 'K', 5039 => 'ᴋ', 5040 => 'ᶄ', 5041 => 'Ƙ', 5042 => 'Ⱪ', 5043 => 'Ꝁ', 5044 => 'Ꝃ', 5045 => 'Ꝅ', 5046 => 'ʞ', 5047 => 'L', 5048 => 'ʟ', 5049 => 'Ꝇ', 5050 => 'ᴌ', 5051 => 'Ꝉ', 5052 => 'Ƚ', 5053 => 'Ⱡ', 5054 => 'Ɫ', 5055 => 'ɬ', 5056 => 'ᶅ', 5057 => 'ɭ', 5058 => 'ꞎ', 5059 => 'ȴ', 5060 => 'ꝲ', 5061 => 'ɮ', 5062 => 'Ꞁ', 5063 => 'ƛ', 5064 => 'ʎ', 5065 => 'M', 5066 => 'ᴍ', 5067 => 'ᵯ', 5068 => 'ᶆ', 5069 => 'Ɱ', 5070 => 'ꟽ', 5071 => 'ꟿ', 5072 => 'ꝳ', 5073 => 'N', 5074 => 'ɴ', 5075 => 'ᴻ', 5076 => 'ᴎ', 5077 => 'ᵰ', 5078 => 'Ɲ', 5079 => 'Ƞ', 5080 => 'Ꞑ', 5081 => 'ᶇ', 5082 => 'ɳ', 5083 => 'ȵ', 5084 => 'ꝴ', 5085 => 'Ŋ', 5086 => 'O', 5087 => 'ᴏ', 5088 => 'ᴑ', 5089 => 'ɶ', 5090 => 'ᴔ', 5091 => 'ᴓ', 5092 => 'Ɔ', 5093 => 'ᴐ', 5094 => 'ᴒ', 5095 => 'ᶗ', 5096 => 'Ꝍ', 5097 => 'ᴖ', 5098 => 'ᴗ', 5099 => 'ⱺ', 5100 => 'Ɵ', 5101 => 'Ꝋ', 5102 => 'ɷ', 5103 => 'Ȣ', 5104 => 'ᴕ', 5105 => 'P', 5106 => 'ᴘ', 5107 => 'Ᵽ', 5108 => 'Ꝑ', 5109 => 'ᵱ', 5110 => 'ᶈ', 5111 => 'Ƥ', 5112 => 'Ꝓ', 5113 => 'Ꝕ', 5114 => 'ꟼ', 5115 => 'ɸ', 5116 => 'ⱷ', 5117 => 'Q', 5118 => 'Ꝗ', 5119 => 'Ꝙ', 5120 => 'ʠ', 5121 => 'Ɋ', 5122 => 'ĸ', 5123 => 'R', 5124 => 'Ʀ', 5125 => 'Ꝛ', 5126 => 'ᴙ', 5127 => 'Ɍ', 5128 => 'ᵲ', 5129 => 'ɹ', 5130 => 'ᴚ', 5131 => 'ɺ', 5132 => 'ᶉ', 5133 => 'ɻ', 5134 => 'ⱹ', 5135 => 'ɼ', 5136 => 'Ɽ', 5137 => 'ɾ', 5138 => 'ᵳ', 5139 => 'ɿ', 5140 => 'ʁ', 5141 => 'ꝵ', 5142 => 'ꝶ', 5143 => 'Ꝝ', 5144 => 'S', 5145 => 'ꜱ', 5146 => 'ᵴ', 5147 => 'ᶊ', 5148 => 'ʂ', 5149 => 'Ȿ', 5150 => 'ẜ', 5151 => 'ẝ', 5152 => 'Ʃ', 5153 => 'ᶋ', 5154 => 'ƪ', 5155 => 'ʅ', 5156 => 'ᶘ', 5157 => 'ʆ', 5158 => 'T', 5159 => 'ᴛ', 5160 => 'Ŧ', 5161 => 'Ⱦ', 5162 => 'ᵵ', 5163 => 'ƫ', 5164 => 'Ƭ', 5165 => 'Ʈ', 5166 => 'ȶ', 5167 => 'ꝷ', 5168 => 'ʇ', 5169 => 'U', 5170 => 'ᴜ', 5171 => 'ᴝ', 5172 => 'ᴞ', 5173 => 'ᵫ', 5174 => 'Ʉ', 5175 => 'ᵾ', 5176 => 'ᶙ', 5177 => 'Ɥ', 5178 => 'ʮ', 5179 => 'ʯ', 5180 => 'Ɯ', 5181 => 'ꟺ', 5182 => 'ᴟ', 5183 => 'ɰ', 5184 => 'Ʊ', 5185 => 'ᵿ', 5186 => 'V', 5187 => 'ᴠ', 5188 => 'Ꝟ', 5189 => 'ᶌ', 5190 => 'Ʋ', 5191 => 'ⱱ', 5192 => 'ⱴ', 5193 => 'Ỽ', 5194 => 'Ʌ', 5195 => 'W', 5196 => 'ᴡ', 5197 => 'Ⱳ', 5198 => 'ʍ', 5199 => 'X', 5200 => 'ᶍ', 5201 => 'Y', 5202 => 'ʏ', 5203 => 'Ɏ', 5204 => 'Ƴ', 5205 => 'Ỿ', 5206 => 'Z', 5207 => 'ᴢ', 5208 => 'Ƶ', 5209 => 'ᵶ', 5210 => 'ᶎ', 5211 => 'Ȥ', 5212 => 'ʐ', 5213 => 'ʑ', 5214 => 'Ɀ', 5215 => 'Ⱬ', 5216 => 'Ꝣ', 5217 => 'Ʒ', 5218 => 'ᴣ', 5219 => 'Ƹ', 5220 => 'ᶚ', 5221 => 'ƺ', 5222 => 'ʓ', 5223 => 'Ȝ', 5224 => 'Þ', 5225 => 'Ꝥ', 5226 => 'Ꝧ', 5227 => 'Ƿ', 5228 => 'Ꝩ', 5229 => 'Ꝫ', 5230 => 'Ꝭ', 5231 => 'Ꝯ', 5232 => 'ꝸ', 5233 => 'ƻ', 5234 => 'Ꜫ', 5235 => 'Ꜭ', 5236 => 'Ꜯ', 5237 => 'Ƨ', 5238 => 'Ƽ', 5239 => 'Ƅ', 5240 => 'ʔ', 5241 => 'Ɂ', 5242 => 'ˀ', 5243 => 'ʼ', 5244 => 'ˮ', 5245 => 'ʾ', 5246 => 'Ꜣ', 5247 => 'Ꞌ', 5248 => 'ʕ', 5249 => 'ʿ', 5250 => 'ˁ', 5251 => 'ᴤ', 5252 => 'ᴥ', 5253 => 'Ꜥ', 5254 => 'ʡ', 5255 => 'ʢ', 5256 => 'ʖ', 5257 => 'ǀ', 5258 => 'ǁ', 5259 => 'ǂ', 5260 => 'ǃ', 5261 => 'ʗ', 5262 => 'ʘ', 5263 => 'ʬ', 5264 => 'ʭ', 5265 => 'Α', 5266 => 'Β', 5267 => 'Γ', 5268 => 'ᴦ', 5269 => 'Δ', 5270 => 'Ε', 5271 => 'Ϝ', 5272 => 'Ͷ', 5273 => 'Ϛ', 5274 => 'Ζ', 5275 => 'Ͱ', 5276 => 'Η', 5277 => 'Θ', 5278 => 'Ι', 5279 => 'ϳ', 5280 => 'Κ', 5281 => 'Λ', 5282 => 'ᴧ', 5283 => 'Μ', 5284 => 'Ν', 5285 => 'Ξ', 5286 => 'Ο', 5287 => 'Π', 5288 => 'ᴨ', 5289 => 'Ϻ', 5290 => 'Ϟ', 5291 => 'Ϙ', 5292 => 'Ρ', 5293 => 'ᴩ', 5294 => 'ϼ', 5295 => 'Σ', 5296 => 'Ͼ', 5297 => 'Ͻ', 5298 => 'Ͽ', 5299 => 'Τ', 5300 => 'Υ', 5301 => 'Φ', 5302 => 'Χ', 5303 => 'Ψ', 5304 => 'ᴪ', 5305 => 'Ω', 5306 => 'Ϡ', 5307 => 'Ͳ', 5308 => 'Ϸ', 5309 => 'Ⲁ', 5310 => 'Ⲃ', 5311 => 'Ⲅ', 5312 => 'Ⲇ', 5313 => 'Ⲉ', 5314 => 'Ⲷ', 5315 => 'Ⲋ', 5316 => 'Ⲍ', 5317 => 'Ⲏ', 5318 => 'Ⲑ', 5319 => 'Ⲓ', 5320 => 'Ⲕ', 5321 => 'Ⲹ', 5322 => 'Ⲗ', 5323 => 'Ⲙ', 5324 => 'Ⲛ', 5325 => 'Ⲻ', 5326 => 'Ⲽ', 5327 => 'Ⲝ', 5328 => 'Ⲟ', 5329 => 'Ⲡ', 5330 => 'Ⲣ', 5331 => 'Ⲥ', 5332 => 'Ⲧ', 5333 => 'Ⲩ', 5334 => 'Ⲫ', 5335 => 'Ⲭ', 5336 => 'Ⲯ', 5337 => 'Ⲱ', 5338 => 'Ⲿ', 5339 => 'Ⳁ', 5340 => 'Ϣ', 5341 => 'Ⳬ', 5342 => 'Ⳃ', 5343 => 'Ⳅ', 5344 => 'Ⳇ', 5345 => 'Ϥ', 5346 => 'Ϧ', 5347 => 'Ⳉ', 5348 => 'Ϩ', 5349 => 'Ⳋ', 5350 => 'Ⳍ', 5351 => 'Ⳏ', 5352 => 'Ⳑ', 5353 => 'Ⳓ', 5354 => 'Ⳕ', 5355 => 'Ϫ', 5356 => 'Ⳮ', 5357 => 'Ⳗ', 5358 => 'Ϭ', 5359 => 'Ⳙ', 5360 => 'Ⳛ', 5361 => 'Ⳝ', 5362 => 'Ϯ', 5363 => 'Ⲳ', 5364 => 'Ⲵ', 5365 => 'Ⳟ', 5366 => 'Ⳡ', 5367 => 'Ⳣ', 5368 => 'А', 5369 => 'Ӑ', 5370 => 'Ӓ', 5371 => 'Ә', 5372 => 'Ӛ', 5373 => 'Ӕ', 5374 => 'Б', 5375 => 'В', 5376 => 'Г', 5377 => 'Ғ', 5378 => 'Ӻ', 5379 => 'Ҕ', 5380 => 'Ӷ', 5381 => 'Д', 5382 => 'Ԁ', 5383 => 'Ꚁ', 5384 => 'Ђ', 5385 => 'Ꙣ', 5386 => 'Ԃ', 5387 => 'Ѓ', 5388 => 'Ҙ', 5389 => 'Е', 5390 => 'Ӗ', 5391 => 'Є', 5392 => 'Ж', 5393 => 'Ꚅ', 5394 => 'Ӝ', 5395 => 'Җ', 5396 => 'З', 5397 => 'Ꙁ', 5398 => 'Ԅ', 5399 => 'Ԑ', 5400 => 'Ӟ', 5401 => 'Ꙃ', 5402 => 'Ѕ', 5403 => 'Ꙅ', 5404 => 'Ӡ', 5405 => 'Ꚉ', 5406 => 'Ԇ', 5407 => 'Ꚃ', 5408 => 'И', 5409 => 'Ҋ', 5410 => 'Ӥ', 5411 => 'І', 5412 => 'Ꙇ', 5413 => 'Ї', 5414 => 'Й', 5415 => 'Ј', 5416 => 'Ꙉ', 5417 => 'К', 5418 => 'Қ', 5419 => 'Ӄ', 5420 => 'Ҡ', 5421 => 'Ҟ', 5422 => 'Ҝ', 5423 => 'Ԟ', 5424 => 'Ԛ', 5425 => 'Л', 5426 => 'ᴫ', 5427 => 'Ӆ', 5428 => 'Ԓ', 5429 => 'Ԡ', 5430 => 'Љ', 5431 => 'Ꙥ', 5432 => 'Ԉ', 5433 => 'Ԕ', 5434 => 'М', 5435 => 'Ӎ', 5436 => 'Ꙧ', 5437 => 'Н', 5438 => 'Ӊ', 5439 => 'Ң', 5440 => 'Ӈ', 5441 => 'Ԣ', 5442 => 'Ҥ', 5443 => 'Њ', 5444 => 'Ԋ', 5445 => 'О', 5446 => 'Ӧ', 5447 => 'Ө', 5448 => 'Ӫ', 5449 => 'П', 5450 => 'Ԥ', 5451 => 'Ҧ', 5452 => 'Ҁ', 5453 => 'Р', 5454 => 'Ҏ', 5455 => 'Ԗ', 5456 => 'С', 5457 => 'Ԍ', 5458 => 'Ҫ', 5459 => 'Т', 5460 => 'Ꚍ', 5461 => 'Ԏ', 5462 => 'Ҭ', 5463 => 'Ꚋ', 5464 => 'Ћ', 5465 => 'Ќ', 5466 => 'У', 5467 => 'Ў', 5468 => 'Ӱ', 5469 => 'Ӳ', 5470 => 'Ү', 5471 => 'Ұ', 5472 => 'Ꙋ', 5473 => 'Ѹ', 5474 => 'Ф', 5475 => 'Х', 5476 => 'Ӽ', 5477 => 'Ӿ', 5478 => 'Ҳ', 5479 => 'Һ', 5480 => 'Ԧ', 5481 => 'Ꚕ', 5482 => 'Ѡ', 5483 => 'Ѿ', 5484 => 'Ꙍ', 5485 => 'Ѽ', 5486 => 'Ѻ', 5487 => 'Ц', 5488 => 'Ꙡ', 5489 => 'Ꚏ', 5490 => 'Ҵ', 5491 => 'Ꚑ', 5492 => 'Ч', 5493 => 'Ꚓ', 5494 => 'Ӵ', 5495 => 'Ҷ', 5496 => 'Ӌ', 5497 => 'Ҹ', 5498 => 'Ꚇ', 5499 => 'Ҽ', 5500 => 'Ҿ', 5501 => 'Џ', 5502 => 'Ш', 5503 => 'Ꚗ', 5504 => 'Щ', 5505 => 'Ꙏ', 5506 => 'ⸯ', 5507 => 'ꙿ', 5508 => 'Ъ', 5509 => 'Ꙑ', 5510 => 'Ы', 5511 => 'Ӹ', 5512 => 'Ь', 5513 => 'Ҍ', 5514 => 'Ѣ', 5515 => 'Ꙓ', 5516 => 'Э', 5517 => 'Ӭ', 5518 => 'Ю', 5519 => 'Ꙕ', 5520 => 'Ꙗ', 5521 => 'Я', 5522 => 'Ԙ', 5523 => 'Ѥ', 5524 => 'Ѧ', 5525 => 'Ꙙ', 5526 => 'Ѫ', 5527 => 'Ꙛ', 5528 => 'Ѩ', 5529 => 'Ꙝ', 5530 => 'Ѭ', 5531 => 'Ѯ', 5532 => 'Ѱ', 5533 => 'Ѳ', 5534 => 'Ѵ', 5535 => 'Ѷ', 5536 => 'Ꙟ', 5537 => 'Ҩ', 5538 => 'Ԝ', 5539 => 'Ӏ', 5540 => 'Ⰰ', 5541 => 'Ⰱ', 5542 => 'Ⰲ', 5543 => 'Ⰳ', 5544 => 'Ⰴ', 5545 => 'Ⰵ', 5546 => 'Ⰶ', 5547 => 'Ⰷ', 5548 => 'Ⰸ', 5549 => 'Ⰹ', 5550 => 'Ⰺ', 5551 => 'Ⰻ', 5552 => 'Ⰼ', 5553 => 'Ⰽ', 5554 => 'Ⰾ', 5555 => 'Ⰿ', 5556 => 'Ⱀ', 5557 => 'Ⱁ', 5558 => 'Ⱂ', 5559 => 'Ⱃ', 5560 => 'Ⱄ', 5561 => 'Ⱅ', 5562 => 'Ⱆ', 5563 => 'Ⱇ', 5564 => 'Ⱈ', 5565 => 'Ⱉ', 5566 => 'Ⱊ', 5567 => 'Ⱋ', 5568 => 'Ⱌ', 5569 => 'Ⱍ', 5570 => 'Ⱎ', 5571 => 'Ⱏ', 5572 => 'Ⱐ', 5573 => 'Ⱑ', 5574 => 'Ⱒ', 5575 => 'Ⱓ', 5576 => 'Ⱔ', 5577 => 'Ⱕ', 5578 => 'Ⱖ', 5579 => 'Ⱗ', 5580 => 'Ⱘ', 5581 => 'Ⱙ', 5582 => 'Ⱚ', 5583 => 'Ⱛ', 5584 => 'Ⱜ', 5585 => 'Ⱝ', 5586 => 'Ⱞ', 5587 => 'ა', 5588 => 'Ⴀ', 5589 => 'ბ', 5590 => 'Ⴁ', 5591 => 'გ', 5592 => 'Ⴂ', 5593 => 'დ', 5594 => 'Ⴃ', 5595 => 'ე', 5596 => 'Ⴄ', 5597 => 'ვ', 5598 => 'Ⴅ', 5599 => 'ზ', 5600 => 'Ⴆ', 5601 => 'ჱ', 5602 => 'Ⴡ', 5603 => 'თ', 5604 => 'Ⴇ', 5605 => 'ი', 5606 => 'Ⴈ', 5607 => 'კ', 5608 => 'Ⴉ', 5609 => 'ლ', 5610 => 'Ⴊ', 5611 => 'მ', 5612 => 'Ⴋ', 5613 => 'ნ', 5614 => 'Ⴌ', 5615 => 'ჲ', 5616 => 'Ⴢ', 5617 => 'ო', 5618 => 'Ⴍ', 5619 => 'პ', 5620 => 'Ⴎ', 5621 => 'ჟ', 5622 => 'Ⴏ', 5623 => 'რ', 5624 => 'Ⴐ', 5625 => 'ს', 5626 => 'Ⴑ', 5627 => 'ტ', 5628 => 'Ⴒ', 5629 => 'ჳ', 5630 => 'Ⴣ', 5631 => 'უ', 5632 => 'Ⴓ', 5633 => 'ფ', 5634 => 'Ⴔ', 5635 => 'ქ', 5636 => 'Ⴕ', 5637 => 'ღ', 5638 => 'Ⴖ', 5639 => 'ყ', 5640 => 'Ⴗ', 5641 => 'შ', 5642 => 'Ⴘ', 5643 => 'ჩ', 5644 => 'Ⴙ', 5645 => 'ც', 5646 => 'Ⴚ', 5647 => 'ძ', 5648 => 'Ⴛ', 5649 => 'წ', 5650 => 'Ⴜ', 5651 => 'ჭ', 5652 => 'Ⴝ', 5653 => 'ხ', 5654 => 'Ⴞ', 5655 => 'ჴ', 5656 => 'Ⴤ', 5657 => 'ჯ', 5658 => 'Ⴟ', 5659 => 'ჰ', 5660 => 'Ⴠ', 5661 => 'ჵ', 5662 => 'Ⴥ', 5663 => 'ჶ', 5664 => 'ჷ', 5665 => 'ჸ', 5666 => 'ჹ', 5667 => 'ჺ', 5668 => 'Ա', 5669 => 'Բ', 5670 => 'Գ', 5671 => 'Դ', 5672 => 'Ե', 5673 => 'Զ', 5674 => 'Է', 5675 => 'Ը', 5676 => 'Թ', 5677 => 'Ժ', 5678 => 'Ի', 5679 => 'Լ', 5680 => 'Խ', 5681 => 'Ծ', 5682 => 'Կ', 5683 => 'Հ', 5684 => 'Ձ', 5685 => 'Ղ', 5686 => 'Ճ', 5687 => 'Մ', 5688 => 'Յ', 5689 => 'Ն', 5690 => 'Շ', 5691 => 'Ո', 5692 => 'Չ', 5693 => 'Պ', 5694 => 'Ջ', 5695 => 'Ռ', 5696 => 'Ս', 5697 => 'Վ', 5698 => 'Տ', 5699 => 'Ր', 5700 => 'Ց', 5701 => 'Ւ', 5702 => 'Փ', 5703 => 'Ք', 5704 => 'Օ', 5705 => 'Ֆ', 5706 => 'ՙ', 5707 => 'א', 5708 => 'ב', 5709 => 'ג', 5710 => 'ד', 5711 => 'ה', 5712 => 'ו', 5713 => 'ז', 5714 => 'ח', 5715 => 'ט', 5716 => 'י', 5717 => 'ך', 5718 => 'ל', 5719 => 'ם', 5720 => 'ן', 5721 => 'ס', 5722 => 'ע', 5723 => 'ף', 5724 => 'ץ', 5725 => 'ק', 5726 => 'ר', 5727 => 'ש', 5728 => 'ת', 5729 => '𐤀', 5730 => '𐤁', 5731 => '𐤂', 5732 => '𐤃', 5733 => '𐤄', 5734 => '𐤅', 5735 => '𐤆', 5736 => '𐤇', 5737 => '𐤈', 5738 => '𐤉', 5739 => '𐤊', 5740 => '𐤋', 5741 => '𐤌', 5742 => '𐤍', 5743 => '𐤎', 5744 => '𐤏', 5745 => '𐤐', 5746 => '𐤑', 5747 => '𐤒', 5748 => '𐤓', 5749 => '𐤔', 5750 => '𐤕', 5751 => 'ࠀ', 5752 => 'ࠁ', 5753 => 'ࠂ', 5754 => 'ࠃ', 5755 => 'ࠄ', 5756 => 'ࠅ', 5757 => 'ࠆ', 5758 => 'ࠇ', 5759 => 'ࠈ', 5760 => 'ࠉ', 5761 => 'ࠊ', 5762 => 'ࠋ', 5763 => 'ࠌ', 5764 => 'ࠍ', 5765 => 'ࠎ', 5766 => 'ࠏ', 5767 => 'ࠐ', 5768 => 'ࠑ', 5769 => 'ࠒ', 5770 => 'ࠓ', 5771 => 'ࠔ', 5772 => 'ࠕ', 5773 => 'ࠚ', 5774 => 'ء', 5775 => 'آ', 5776 => 'أ', 5777 => 'ٲ', 5778 => 'ٱ', 5779 => 'ؤ', 5780 => 'إ', 5781 => 'ٳ', 5782 => 'ݳ', 5783 => 'ݴ', 5784 => 'ئ', 5785 => 'ا', 5786 => 'ٮ', 5787 => 'ب', 5788 => 'ٻ', 5789 => 'پ', 5790 => 'ڀ', 5791 => 'ݐ', 5792 => 'ݑ', 5793 => 'ݒ', 5794 => 'ݓ', 5795 => 'ݔ', 5796 => 'ݕ', 5797 => 'ݖ', 5798 => 'ة', 5799 => 'ت', 5800 => 'ث', 5801 => 'ٹ', 5802 => 'ٺ', 5803 => 'ټ', 5804 => 'ٽ', 5805 => 'ٿ', 5806 => 'ج', 5807 => 'ڃ', 5808 => 'ڄ', 5809 => 'چ', 5810 => 'ڿ', 5811 => 'ڇ', 5812 => 'ح', 5813 => 'خ', 5814 => 'ځ', 5815 => 'ڂ', 5816 => 'څ', 5817 => 'ݗ', 5818 => 'ݘ', 5819 => 'ݮ', 5820 => 'ݯ', 5821 => 'ݲ', 5822 => 'ݼ', 5823 => 'د', 5824 => 'ذ', 5825 => 'ڈ', 5826 => 'ډ', 5827 => 'ڊ', 5828 => 'ڋ', 5829 => 'ڌ', 5830 => 'ڍ', 5831 => 'ڎ', 5832 => 'ڏ', 5833 => 'ڐ', 5834 => 'ۮ', 5835 => 'ݙ', 5836 => 'ݚ', 5837 => 'ر', 5838 => 'ز', 5839 => 'ڑ', 5840 => 'ڒ', 5841 => 'ړ', 5842 => 'ڔ', 5843 => 'ڕ', 5844 => 'ږ', 5845 => 'ڗ', 5846 => 'ژ', 5847 => 'ڙ', 5848 => 'ۯ', 5849 => 'ݛ', 5850 => 'ݫ', 5851 => 'ݬ', 5852 => 'ݱ', 5853 => 'س', 5854 => 'ش', 5855 => 'ښ', 5856 => 'ڛ', 5857 => 'ڜ', 5858 => 'ۺ', 5859 => 'ݜ', 5860 => 'ݭ', 5861 => 'ݰ', 5862 => 'ݽ', 5863 => 'ݾ', 5864 => 'ص', 5865 => 'ض', 5866 => 'ڝ', 5867 => 'ڞ', 5868 => 'ۻ', 5869 => 'ط', 5870 => 'ظ', 5871 => 'ڟ', 5872 => 'ع', 5873 => 'غ', 5874 => 'ڠ', 5875 => 'ۼ', 5876 => 'ݝ', 5877 => 'ݞ', 5878 => 'ݟ', 5879 => 'ف', 5880 => 'ڡ', 5881 => 'ڢ', 5882 => 'ڣ', 5883 => 'ڤ', 5884 => 'ڥ', 5885 => 'ڦ', 5886 => 'ݠ', 5887 => 'ݡ', 5888 => 'ٯ', 5889 => 'ق', 5890 => 'ڧ', 5891 => 'ڨ', 5892 => 'ك', 5893 => 'ک', 5894 => 'ڪ', 5895 => 'ګ', 5896 => 'ڬ', 5897 => 'ݿ', 5898 => 'ڭ', 5899 => 'ڮ', 5900 => 'گ', 5901 => 'ڰ', 5902 => 'ڱ', 5903 => 'ڲ', 5904 => 'ڳ', 5905 => 'ڴ', 5906 => 'ݢ', 5907 => 'ػ', 5908 => 'ؼ', 5909 => 'ݣ', 5910 => 'ݤ', 5911 => 'ل', 5912 => 'ڵ', 5913 => 'ڶ', 5914 => 'ڷ', 5915 => 'ڸ', 5916 => 'ݪ', 5917 => 'م', 5918 => 'ݥ', 5919 => 'ݦ', 5920 => 'ن', 5921 => 'ں', 5922 => 'ڻ', 5923 => 'ڼ', 5924 => 'ڽ', 5925 => 'ڹ', 5926 => 'ݧ', 5927 => 'ݨ', 5928 => 'ݩ', 5929 => 'ه', 5930 => 'ھ', 5931 => 'ہ', 5932 => 'ۃ', 5933 => 'ۿ', 5934 => 'ە', 5935 => 'و', 5936 => 'ۄ', 5937 => 'ۅ', 5938 => 'ۆ', 5939 => 'ۇ', 5940 => 'ۈ', 5941 => 'ۉ', 5942 => 'ۊ', 5943 => 'ۋ', 5944 => 'ۏ', 5945 => 'ݸ', 5946 => 'ݹ', 5947 => 'ى', 5948 => 'ي', 5949 => 'ی', 5950 => 'ۍ', 5951 => 'ێ', 5952 => 'ې', 5953 => 'ۑ', 5954 => 'ؽ', 5955 => 'ؾ', 5956 => 'ؿ', 5957 => 'ؠ', 5958 => 'ݵ', 5959 => 'ݶ', 5960 => 'ݷ', 5961 => 'ے', 5962 => 'ݺ', 5963 => 'ݻ', 5964 => 'ܐ', 5965 => 'ܒ', 5966 => 'ܓ', 5967 => 'ܖ', 5968 => 'ܕ', 5969 => 'ܗ', 5970 => 'ܘ', 5971 => 'ܙ', 5972 => 'ݍ', 5973 => 'ܚ', 5974 => 'ܛ', 5975 => 'ܝ', 5976 => 'ܞ', 5977 => 'ܟ', 5978 => 'ݎ', 5979 => 'ܠ', 5980 => 'ܡ', 5981 => 'ܢ', 5982 => 'ܣ', 5983 => 'ܥ', 5984 => 'ܦ', 5985 => 'ݏ', 5986 => 'ܨ', 5987 => 'ܩ', 5988 => 'ܪ', 5989 => 'ܫ', 5990 => 'ܬ', 5991 => 'ࡀ', 5992 => 'ࡁ', 5993 => 'ࡂ', 5994 => 'ࡃ', 5995 => 'ࡄ', 5996 => 'ࡅ', 5997 => 'ࡆ', 5998 => 'ࡇ', 5999 => 'ࡈ', 6000 => 'ࡉ', 6001 => 'ࡊ', 6002 => 'ࡋ', 6003 => 'ࡌ', 6004 => 'ࡍ', 6005 => 'ࡎ', 6006 => 'ࡏ', 6007 => 'ࡐ', 6008 => 'ࡑ', 6009 => 'ࡒ', 6010 => 'ࡓ', 6011 => 'ࡔ', 6012 => 'ࡕ', 6013 => 'ࡖ', 6014 => 'ࡗ', 6015 => 'ࡘ', 6016 => 'ހ', 6017 => 'ޙ', 6018 => 'ޚ', 6019 => 'ށ', 6020 => 'ނ', 6021 => 'ރ', 6022 => 'ޜ', 6023 => 'ބ', 6024 => 'ޅ', 6025 => 'ކ', 6026 => 'އ', 6027 => 'ޢ', 6028 => 'ޣ', 6029 => 'ވ', 6030 => 'ޥ', 6031 => 'މ', 6032 => 'ފ', 6033 => 'ދ', 6034 => 'ޛ', 6035 => 'ތ', 6036 => 'ޘ', 6037 => 'ޠ', 6038 => 'ޡ', 6039 => 'ލ', 6040 => 'ގ', 6041 => 'ޤ', 6042 => 'ޏ', 6043 => 'ސ', 6044 => 'ޝ', 6045 => 'ޞ', 6046 => 'ޟ', 6047 => 'ޑ', 6048 => 'ޒ', 6049 => 'ޓ', 6050 => 'ޔ', 6051 => 'ޕ', 6052 => 'ޖ', 6053 => 'ޗ', 6054 => 'ޱ', 6055 => 'ߊ', 6056 => 'ߋ', 6057 => 'ߌ', 6058 => 'ߍ', 6059 => 'ߎ', 6060 => 'ߏ', 6061 => 'ߐ', 6062 => 'ߑ', 6063 => 'ߒ', 6064 => 'ߓ', 6065 => 'ߔ', 6066 => 'ߕ', 6067 => 'ߖ', 6068 => 'ߗ', 6069 => 'ߘ', 6070 => 'ߙ', 6071 => 'ߚ', 6072 => 'ߛ', 6073 => 'ߜ', 6074 => 'ߝ', 6075 => 'ߞ', 6076 => 'ߟ', 6077 => 'ߠ', 6078 => 'ߡ', 6079 => 'ߢ', 6080 => 'ߣ', 6081 => 'ߤ', 6082 => 'ߥ', 6083 => 'ߦ', 6084 => 'ߧ', 6085 => 'ߴ', 6086 => 'ߵ', 6087 => 'ⴰ', 6088 => 'ⴱ', 6089 => 'ⴲ', 6090 => 'ⴳ', 6091 => 'ⴴ', 6092 => 'ⴵ', 6093 => 'ⴶ', 6094 => 'ⴷ', 6095 => 'ⴸ', 6096 => 'ⴹ', 6097 => 'ⴺ', 6098 => 'ⴻ', 6099 => 'ⴼ', 6100 => 'ⴽ', 6101 => 'ⴾ', 6102 => 'ⴿ', 6103 => 'ⵀ', 6104 => 'ⵁ', 6105 => 'ⵂ', 6106 => 'ⵃ', 6107 => 'ⵄ', 6108 => 'ⵅ', 6109 => 'ⵆ', 6110 => 'ⵇ', 6111 => 'ⵈ', 6112 => 'ⵉ', 6113 => 'ⵊ', 6114 => 'ⵋ', 6115 => 'ⵌ', 6116 => 'ⵍ', 6117 => 'ⵎ', 6118 => 'ⵏ', 6119 => 'ⵐ', 6120 => 'ⵑ', 6121 => 'ⵒ', 6122 => 'ⵓ', 6123 => 'ⵔ', 6124 => 'ⵕ', 6125 => 'ⵖ', 6126 => 'ⵗ', 6127 => 'ⵘ', 6128 => 'ⵙ', 6129 => 'ⵚ', 6130 => 'ⵛ', 6131 => 'ⵜ', 6132 => 'ⵝ', 6133 => 'ⵞ', 6134 => 'ⵟ', 6135 => 'ⵠ', 6136 => 'ⵡ', 6137 => 'ⵢ', 6138 => 'ⵣ', 6139 => 'ⵤ', 6140 => 'ⵥ', 6141 => 'ⵯ', 6142 => 'ሀ', 6143 => 'ሁ', 6144 => 'ሂ', 6145 => 'ሃ', 6146 => 'ሄ', 6147 => 'ህ', 6148 => 'ሆ', 6149 => 'ሇ', 6150 => 'ለ', 6151 => 'ሉ', 6152 => 'ሊ', 6153 => 'ላ', 6154 => 'ሌ', 6155 => 'ል', 6156 => 'ሎ', 6157 => 'ሏ', 6158 => 'ⶀ', 6159 => 'ሐ', 6160 => 'ሑ', 6161 => 'ሒ', 6162 => 'ሓ', 6163 => 'ሔ', 6164 => 'ሕ', 6165 => 'ሖ', 6166 => 'ሗ', 6167 => 'መ', 6168 => 'ሙ', 6169 => 'ሚ', 6170 => 'ማ', 6171 => 'ሜ', 6172 => 'ም', 6173 => 'ሞ', 6174 => 'ሟ', 6175 => 'ᎀ', 6176 => 'ᎁ', 6177 => 'ᎂ', 6178 => 'ᎃ', 6179 => 'ⶁ', 6180 => 'ሠ', 6181 => 'ሡ', 6182 => 'ሢ', 6183 => 'ሣ', 6184 => 'ሤ', 6185 => 'ሥ', 6186 => 'ሦ', 6187 => 'ሧ', 6188 => 'ረ', 6189 => 'ሩ', 6190 => 'ሪ', 6191 => 'ራ', 6192 => 'ሬ', 6193 => 'ር', 6194 => 'ሮ', 6195 => 'ሯ', 6196 => 'ⶂ', 6197 => 'ሰ', 6198 => 'ሱ', 6199 => 'ሲ', 6200 => 'ሳ', 6201 => 'ሴ', 6202 => 'ስ', 6203 => 'ሶ', 6204 => 'ሷ', 6205 => 'ⶃ', 6206 => 'ꬁ', 6207 => 'ꬂ', 6208 => 'ꬃ', 6209 => 'ꬄ', 6210 => 'ꬅ', 6211 => 'ꬆ', 6212 => 'ሸ', 6213 => 'ሹ', 6214 => 'ሺ', 6215 => 'ሻ', 6216 => 'ሼ', 6217 => 'ሽ', 6218 => 'ሾ', 6219 => 'ሿ', 6220 => 'ⶄ', 6221 => 'ቀ', 6222 => 'ቁ', 6223 => 'ቂ', 6224 => 'ቃ', 6225 => 'ቄ', 6226 => 'ቅ', 6227 => 'ቆ', 6228 => 'ቇ', 6229 => 'ቈ', 6230 => 'ቊ', 6231 => 'ቋ', 6232 => 'ቌ', 6233 => 'ቍ', 6234 => 'ቐ', 6235 => 'ቑ', 6236 => 'ቒ', 6237 => 'ቓ', 6238 => 'ቔ', 6239 => 'ቕ', 6240 => 'ቖ', 6241 => 'ቘ', 6242 => 'ቚ', 6243 => 'ቛ', 6244 => 'ቜ', 6245 => 'ቝ', 6246 => 'በ', 6247 => 'ቡ', 6248 => 'ቢ', 6249 => 'ባ', 6250 => 'ቤ', 6251 => 'ብ', 6252 => 'ቦ', 6253 => 'ቧ', 6254 => 'ᎄ', 6255 => 'ᎅ', 6256 => 'ᎆ', 6257 => 'ᎇ', 6258 => 'ⶅ', 6259 => 'ቨ', 6260 => 'ቩ', 6261 => 'ቪ', 6262 => 'ቫ', 6263 => 'ቬ', 6264 => 'ቭ', 6265 => 'ቮ', 6266 => 'ቯ', 6267 => 'ተ', 6268 => 'ቱ', 6269 => 'ቲ', 6270 => 'ታ', 6271 => 'ቴ', 6272 => 'ት', 6273 => 'ቶ', 6274 => 'ቷ', 6275 => 'ⶆ', 6276 => 'ቸ', 6277 => 'ቹ', 6278 => 'ቺ', 6279 => 'ቻ', 6280 => 'ቼ', 6281 => 'ች', 6282 => 'ቾ', 6283 => 'ቿ', 6284 => 'ⶇ', 6285 => 'ኀ', 6286 => 'ኁ', 6287 => 'ኂ', 6288 => 'ኃ', 6289 => 'ኄ', 6290 => 'ኅ', 6291 => 'ኆ', 6292 => 'ኇ', 6293 => 'ኈ', 6294 => 'ኊ', 6295 => 'ኋ', 6296 => 'ኌ', 6297 => 'ኍ', 6298 => 'ነ', 6299 => 'ኑ', 6300 => 'ኒ', 6301 => 'ና', 6302 => 'ኔ', 6303 => 'ን', 6304 => 'ኖ', 6305 => 'ኗ', 6306 => 'ⶈ', 6307 => 'ኘ', 6308 => 'ኙ', 6309 => 'ኚ', 6310 => 'ኛ', 6311 => 'ኜ', 6312 => 'ኝ', 6313 => 'ኞ', 6314 => 'ኟ', 6315 => 'ⶉ', 6316 => 'አ', 6317 => 'ኡ', 6318 => 'ኢ', 6319 => 'ኣ', 6320 => 'ኤ', 6321 => 'እ', 6322 => 'ኦ', 6323 => 'ኧ', 6324 => 'ⶊ', 6325 => 'ከ', 6326 => 'ኩ', 6327 => 'ኪ', 6328 => 'ካ', 6329 => 'ኬ', 6330 => 'ክ', 6331 => 'ኮ', 6332 => 'ኯ', 6333 => 'ኰ', 6334 => 'ኲ', 6335 => 'ኳ', 6336 => 'ኴ', 6337 => 'ኵ', 6338 => 'ኸ', 6339 => 'ኹ', 6340 => 'ኺ', 6341 => 'ኻ', 6342 => 'ኼ', 6343 => 'ኽ', 6344 => 'ኾ', 6345 => 'ዀ', 6346 => 'ዂ', 6347 => 'ዃ', 6348 => 'ዄ', 6349 => 'ዅ', 6350 => 'ወ', 6351 => 'ዉ', 6352 => 'ዊ', 6353 => 'ዋ', 6354 => 'ዌ', 6355 => 'ው', 6356 => 'ዎ', 6357 => 'ዏ', 6358 => 'ዐ', 6359 => 'ዑ', 6360 => 'ዒ', 6361 => 'ዓ', 6362 => 'ዔ', 6363 => 'ዕ', 6364 => 'ዖ', 6365 => 'ዘ', 6366 => 'ዙ', 6367 => 'ዚ', 6368 => 'ዛ', 6369 => 'ዜ', 6370 => 'ዝ', 6371 => 'ዞ', 6372 => 'ዟ', 6373 => 'ⶋ', 6374 => 'ꬑ', 6375 => 'ꬒ', 6376 => 'ꬓ', 6377 => 'ꬔ', 6378 => 'ꬕ', 6379 => 'ꬖ', 6380 => 'ዠ', 6381 => 'ዡ', 6382 => 'ዢ', 6383 => 'ዣ', 6384 => 'ዤ', 6385 => 'ዥ', 6386 => 'ዦ', 6387 => 'ዧ', 6388 => 'የ', 6389 => 'ዩ', 6390 => 'ዪ', 6391 => 'ያ', 6392 => 'ዬ', 6393 => 'ይ', 6394 => 'ዮ', 6395 => 'ዯ', 6396 => 'ደ', 6397 => 'ዱ', 6398 => 'ዲ', 6399 => 'ዳ', 6400 => 'ዴ', 6401 => 'ድ', 6402 => 'ዶ', 6403 => 'ዷ', 6404 => 'ⶌ', 6405 => 'ꬉ', 6406 => 'ꬊ', 6407 => 'ꬋ', 6408 => 'ꬌ', 6409 => 'ꬍ', 6410 => 'ꬎ', 6411 => 'ዸ', 6412 => 'ዹ', 6413 => 'ዺ', 6414 => 'ዻ', 6415 => 'ዼ', 6416 => 'ዽ', 6417 => 'ዾ', 6418 => 'ዿ', 6419 => 'ⶍ', 6420 => 'ጀ', 6421 => 'ጁ', 6422 => 'ጂ', 6423 => 'ጃ', 6424 => 'ጄ', 6425 => 'ጅ', 6426 => 'ጆ', 6427 => 'ጇ', 6428 => 'ⶎ', 6429 => 'ገ', 6430 => 'ጉ', 6431 => 'ጊ', 6432 => 'ጋ', 6433 => 'ጌ', 6434 => 'ግ', 6435 => 'ጎ', 6436 => 'ጏ', 6437 => 'ጐ', 6438 => 'ጒ', 6439 => 'ጓ', 6440 => 'ጔ', 6441 => 'ጕ', 6442 => 'ጘ', 6443 => 'ጙ', 6444 => 'ጚ', 6445 => 'ጛ', 6446 => 'ጜ', 6447 => 'ጝ', 6448 => 'ጞ', 6449 => 'ጟ', 6450 => 'ⶓ', 6451 => 'ⶔ', 6452 => 'ⶕ', 6453 => 'ⶖ', 6454 => 'ጠ', 6455 => 'ጡ', 6456 => 'ጢ', 6457 => 'ጣ', 6458 => 'ጤ', 6459 => 'ጥ', 6460 => 'ጦ', 6461 => 'ጧ', 6462 => 'ⶏ', 6463 => 'ጨ', 6464 => 'ጩ', 6465 => 'ጪ', 6466 => 'ጫ', 6467 => 'ጬ', 6468 => 'ጭ', 6469 => 'ጮ', 6470 => 'ጯ', 6471 => 'ⶐ', 6472 => 'ꬠ', 6473 => 'ꬡ', 6474 => 'ꬢ', 6475 => 'ꬣ', 6476 => 'ꬤ', 6477 => 'ꬥ', 6478 => 'ꬦ', 6479 => 'ጰ', 6480 => 'ጱ', 6481 => 'ጲ', 6482 => 'ጳ', 6483 => 'ጴ', 6484 => 'ጵ', 6485 => 'ጶ', 6486 => 'ጷ', 6487 => 'ⶑ', 6488 => 'ጸ', 6489 => 'ጹ', 6490 => 'ጺ', 6491 => 'ጻ', 6492 => 'ጼ', 6493 => 'ጽ', 6494 => 'ጾ', 6495 => 'ጿ', 6496 => 'ꬨ', 6497 => 'ꬩ', 6498 => 'ꬪ', 6499 => 'ꬫ', 6500 => 'ꬬ', 6501 => 'ꬭ', 6502 => 'ꬮ', 6503 => 'ፀ', 6504 => 'ፁ', 6505 => 'ፂ', 6506 => 'ፃ', 6507 => 'ፄ', 6508 => 'ፅ', 6509 => 'ፆ', 6510 => 'ፇ', 6511 => 'ፈ', 6512 => 'ፉ', 6513 => 'ፊ', 6514 => 'ፋ', 6515 => 'ፌ', 6516 => 'ፍ', 6517 => 'ፎ', 6518 => 'ፏ', 6519 => 'ᎈ', 6520 => 'ᎉ', 6521 => 'ᎊ', 6522 => 'ᎋ', 6523 => 'ፐ', 6524 => 'ፑ', 6525 => 'ፒ', 6526 => 'ፓ', 6527 => 'ፔ', 6528 => 'ፕ', 6529 => 'ፖ', 6530 => 'ፗ', 6531 => 'ᎌ', 6532 => 'ᎍ', 6533 => 'ᎎ', 6534 => 'ᎏ', 6535 => 'ⶒ', 6536 => 'ፘ', 6537 => 'ፙ', 6538 => 'ፚ', 6539 => 'ⶠ', 6540 => 'ⶡ', 6541 => 'ⶢ', 6542 => 'ⶣ', 6543 => 'ⶤ', 6544 => 'ⶥ', 6545 => 'ⶦ', 6546 => 'ⶨ', 6547 => 'ⶩ', 6548 => 'ⶪ', 6549 => 'ⶫ', 6550 => 'ⶬ', 6551 => 'ⶭ', 6552 => 'ⶮ', 6553 => 'ⶰ', 6554 => 'ⶱ', 6555 => 'ⶲ', 6556 => 'ⶳ', 6557 => 'ⶴ', 6558 => 'ⶵ', 6559 => 'ⶶ', 6560 => 'ⶸ', 6561 => 'ⶹ', 6562 => 'ⶺ', 6563 => 'ⶻ', 6564 => 'ⶼ', 6565 => 'ⶽ', 6566 => 'ⶾ', 6567 => 'ⷀ', 6568 => 'ⷁ', 6569 => 'ⷂ', 6570 => 'ⷃ', 6571 => 'ⷄ', 6572 => 'ⷅ', 6573 => 'ⷆ', 6574 => 'ⷈ', 6575 => 'ⷉ', 6576 => 'ⷊ', 6577 => 'ⷋ', 6578 => 'ⷌ', 6579 => 'ⷍ', 6580 => 'ⷎ', 6581 => 'ⷐ', 6582 => 'ⷑ', 6583 => 'ⷒ', 6584 => 'ⷓ', 6585 => 'ⷔ', 6586 => 'ⷕ', 6587 => 'ⷖ', 6588 => 'ⷘ', 6589 => 'ⷙ', 6590 => 'ⷚ', 6591 => 'ⷛ', 6592 => 'ⷜ', 6593 => 'ⷝ', 6594 => 'ⷞ', 6595 => 'ॐ', 6596 => 'ॲ', 6597 => 'ऄ', 6598 => 'अ', 6599 => 'आ', 6600 => 'ॳ', 6601 => 'ॴ', 6602 => 'ॵ', 6603 => 'ॶ', 6604 => 'ॷ', 6605 => 'इ', 6606 => 'ई', 6607 => 'उ', 6608 => 'ऊ', 6609 => 'ऋ', 6610 => 'ॠ', 6611 => 'ऌ', 6612 => 'ॡ', 6613 => 'ऍ', 6614 => 'ऎ', 6615 => 'ए', 6616 => 'ऐ', 6617 => 'ऑ', 6618 => 'ऒ', 6619 => 'ओ', 6620 => 'औ', 6621 => 'क', 6622 => 'ख', 6623 => 'ग', 6624 => 'ॻ', 6625 => 'घ', 6626 => 'ङ', 6627 => 'च', 6628 => 'छ', 6629 => 'ज', 6630 => 'ॹ', 6631 => 'ॼ', 6632 => 'झ', 6633 => 'ञ', 6634 => 'ट', 6635 => 'ठ', 6636 => 'ड', 6637 => 'ॾ', 6638 => 'ढ', 6639 => 'ण', 6640 => 'त', 6641 => 'थ', 6642 => 'द', 6643 => 'ध', 6644 => 'न', 6645 => 'प', 6646 => 'फ', 6647 => 'ब', 6648 => 'ॿ', 6649 => 'भ', 6650 => 'म', 6651 => 'य', 6652 => 'ॺ', 6653 => 'र', 6654 => 'ल', 6655 => 'ळ', 6656 => 'व', 6657 => 'श', 6658 => 'ष', 6659 => 'स', 6660 => 'ह', 6661 => 'ऽ', 6662 => 'ॽ', 6663 => 'ᳩ', 6664 => 'ꣲ', 6665 => 'ꣻ', 6666 => 'অ', 6667 => 'আ', 6668 => 'ই', 6669 => 'ঈ', 6670 => 'উ', 6671 => 'ঊ', 6672 => 'ঋ', 6673 => 'ৠ', 6674 => 'ঌ', 6675 => 'ৡ', 6676 => 'এ', 6677 => 'ঐ', 6678 => 'ও', 6679 => 'ঔ', 6680 => 'ক', 6681 => 'খ', 6682 => 'গ', 6683 => 'ঘ', 6684 => 'ঙ', 6685 => 'চ', 6686 => 'ছ', 6687 => 'জ', 6688 => 'ঝ', 6689 => 'ঞ', 6690 => 'ট', 6691 => 'ঠ', 6692 => 'ড', 6693 => 'ঢ', 6694 => 'ণ', 6695 => 'ত', 6696 => 'থ', 6697 => 'দ', 6698 => 'ধ', 6699 => 'ন', 6700 => 'প', 6701 => 'ফ', 6702 => 'ব', 6703 => 'ভ', 6704 => 'ম', 6705 => 'য', 6706 => 'র', 6707 => 'ৰ', 6708 => 'ল', 6709 => 'ৱ', 6710 => 'শ', 6711 => 'ষ', 6712 => 'স', 6713 => 'হ', 6714 => 'ঽ', 6715 => 'ੴ', 6716 => 'ੳ', 6717 => 'ਉ', 6718 => 'ਊ', 6719 => 'ਓ', 6720 => 'ਅ', 6721 => 'ਆ', 6722 => 'ਐ', 6723 => 'ਔ', 6724 => 'ੲ', 6725 => 'ਇ', 6726 => 'ਈ', 6727 => 'ਏ', 6728 => 'ਸ', 6729 => 'ਹ', 6730 => 'ਕ', 6731 => 'ਖ', 6732 => 'ਗ', 6733 => 'ਘ', 6734 => 'ਙ', 6735 => 'ਚ', 6736 => 'ਛ', 6737 => 'ਜ', 6738 => 'ਝ', 6739 => 'ਞ', 6740 => 'ਟ', 6741 => 'ਠ', 6742 => 'ਡ', 6743 => 'ਢ', 6744 => 'ਣ', 6745 => 'ਤ', 6746 => 'ਥ', 6747 => 'ਦ', 6748 => 'ਧ', 6749 => 'ਨ', 6750 => 'ਪ', 6751 => 'ਫ', 6752 => 'ਬ', 6753 => 'ਭ', 6754 => 'ਮ', 6755 => 'ਯ', 6756 => 'ਰ', 6757 => 'ਲ', 6758 => 'ਵ', 6759 => 'ੜ', 6760 => 'ૐ', 6761 => 'અ', 6762 => 'આ', 6763 => 'ઇ', 6764 => 'ઈ', 6765 => 'ઉ', 6766 => 'ઊ', 6767 => 'ઋ', 6768 => 'ૠ', 6769 => 'ઌ', 6770 => 'ૡ', 6771 => 'ઍ', 6772 => 'એ', 6773 => 'ઐ', 6774 => 'ઑ', 6775 => 'ઓ', 6776 => 'ઔ', 6777 => 'ક', 6778 => 'ખ', 6779 => 'ગ', 6780 => 'ઘ', 6781 => 'ઙ', 6782 => 'ચ', 6783 => 'છ', 6784 => 'જ', 6785 => 'ઝ', 6786 => 'ઞ', 6787 => 'ટ', 6788 => 'ઠ', 6789 => 'ડ', 6790 => 'ઢ', 6791 => 'ણ', 6792 => 'ત', 6793 => 'થ', 6794 => 'દ', 6795 => 'ધ', 6796 => 'ન', 6797 => 'પ', 6798 => 'ફ', 6799 => 'બ', 6800 => 'ભ', 6801 => 'મ', 6802 => 'ય', 6803 => 'ર', 6804 => 'લ', 6805 => 'વ', 6806 => 'શ', 6807 => 'ષ', 6808 => 'સ', 6809 => 'હ', 6810 => 'ળ', 6811 => 'ઽ', 6812 => 'ଅ', 6813 => 'ଆ', 6814 => 'ଇ', 6815 => 'ଈ', 6816 => 'ଉ', 6817 => 'ଊ', 6818 => 'ଋ', 6819 => 'ୠ', 6820 => 'ଌ', 6821 => 'ୡ', 6822 => 'ଏ', 6823 => 'ଐ', 6824 => 'ଓ', 6825 => 'ଔ', 6826 => 'କ', 6827 => 'ଖ', 6828 => 'ଗ', 6829 => 'ଘ', 6830 => 'ଙ', 6831 => 'ଚ', 6832 => 'ଛ', 6833 => 'ଜ', 6834 => 'ଝ', 6835 => 'ଞ', 6836 => 'ଟ', 6837 => 'ଠ', 6838 => 'ଡ', 6839 => 'ଢ', 6840 => 'ଣ', 6841 => 'ତ', 6842 => 'ଥ', 6843 => 'ଦ', 6844 => 'ଧ', 6845 => 'ନ', 6846 => 'ପ', 6847 => 'ଫ', 6848 => 'ବ', 6849 => 'ଭ', 6850 => 'ମ', 6851 => 'ଯ', 6852 => 'ୟ', 6853 => 'ର', 6854 => 'ଲ', 6855 => 'ଳ', 6856 => 'ଵ', 6857 => 'ୱ', 6858 => 'ଶ', 6859 => 'ଷ', 6860 => 'ସ', 6861 => 'ହ', 6862 => 'ଽ', 6863 => 'ௐ', 6864 => 'அ', 6865 => 'ஆ', 6866 => 'இ', 6867 => 'ஈ', 6868 => 'உ', 6869 => 'ஊ', 6870 => 'எ', 6871 => 'ஏ', 6872 => 'ஐ', 6873 => 'ஒ', 6874 => 'ஓ', 6875 => 'ஔ', 6876 => 'ஃ', 6877 => 'க', 6878 => 'ங', 6879 => 'ச', 6880 => 'ஞ', 6881 => 'ட', 6882 => 'ண', 6883 => 'த', 6884 => 'ந', 6885 => 'ப', 6886 => 'ம', 6887 => 'ய', 6888 => 'ர', 6889 => 'ல', 6890 => 'வ', 6891 => 'ழ', 6892 => 'ள', 6893 => 'ற', 6894 => 'ன', 6895 => 'ஜ', 6896 => 'ஶ', 6897 => 'ஷ', 6898 => 'ஸ', 6899 => 'ஹ', 6900 => 'అ', 6901 => 'ఆ', 6902 => 'ఇ', 6903 => 'ఈ', 6904 => 'ఉ', 6905 => 'ఊ', 6906 => 'ఋ', 6907 => 'ౠ', 6908 => 'ఌ', 6909 => 'ౡ', 6910 => 'ఎ', 6911 => 'ఏ', 6912 => 'ఐ', 6913 => 'ఒ', 6914 => 'ఓ', 6915 => 'ఔ', 6916 => 'క', 6917 => 'ఖ', 6918 => 'గ', 6919 => 'ఘ', 6920 => 'ఙ', 6921 => 'చ', 6922 => 'ౘ', 6923 => 'ఛ', 6924 => 'జ', 6925 => 'ౙ', 6926 => 'ఝ', 6927 => 'ఞ', 6928 => 'ట', 6929 => 'ఠ', 6930 => 'డ', 6931 => 'ఢ', 6932 => 'ణ', 6933 => 'త', 6934 => 'థ', 6935 => 'ద', 6936 => 'ధ', 6937 => 'న', 6938 => 'ప', 6939 => 'ఫ', 6940 => 'బ', 6941 => 'భ', 6942 => 'మ', 6943 => 'య', 6944 => 'ర', 6945 => 'ఱ', 6946 => 'ల', 6947 => 'వ', 6948 => 'శ', 6949 => 'ష', 6950 => 'స', 6951 => 'హ', 6952 => 'ళ', 6953 => 'ఽ', 6954 => 'ಅ', 6955 => 'ಆ', 6956 => 'ಇ', 6957 => 'ಈ', 6958 => 'ಉ', 6959 => 'ಊ', 6960 => 'ಋ', 6961 => 'ೠ', 6962 => 'ಌ', 6963 => 'ೡ', 6964 => 'ಎ', 6965 => 'ಏ', 6966 => 'ಐ', 6967 => 'ಒ', 6968 => 'ಓ', 6969 => 'ಔ', 6970 => 'ಕ', 6971 => 'ಖ', 6972 => 'ಗ', 6973 => 'ಘ', 6974 => 'ಙ', 6975 => 'ಚ', 6976 => 'ಛ', 6977 => 'ಜ', 6978 => 'ಝ', 6979 => 'ಞ', 6980 => 'ಟ', 6981 => 'ಠ', 6982 => 'ಡ', 6983 => 'ಢ', 6984 => 'ಣ', 6985 => 'ತ', 6986 => 'ಥ', 6987 => 'ದ', 6988 => 'ಧ', 6989 => 'ನ', 6990 => 'ಪ', 6991 => 'ಫ', 6992 => 'ಬ', 6993 => 'ಭ', 6994 => 'ಮ', 6995 => 'ಯ', 6996 => 'ರ', 6997 => 'ಱ', 6998 => 'ಲ', 6999 => 'ವ', 7000 => 'ಶ', 7001 => 'ಷ', 7002 => 'ಸ', 7003 => 'ಹ', 7004 => 'ಳ', 7005 => 'ೞ', 7006 => 'ಽ', 7007 => 'ೱ', 7008 => 'ೲ', 7009 => 'അ', 7010 => 'ആ', 7011 => 'ഇ', 7012 => 'ഈ', 7013 => 'ഉ', 7014 => 'ഊ', 7015 => 'ഋ', 7016 => 'ൠ', 7017 => 'ഌ', 7018 => 'ൡ', 7019 => 'എ', 7020 => 'ഏ', 7021 => 'ഐ', 7022 => 'ഒ', 7023 => 'ഓ', 7024 => 'ഔ', 7025 => 'ക', 7026 => 'ഖ', 7027 => 'ഗ', 7028 => 'ഘ', 7029 => 'ങ', 7030 => 'ച', 7031 => 'ഛ', 7032 => 'ജ', 7033 => 'ഝ', 7034 => 'ഞ', 7035 => 'ട', 7036 => 'ഠ', 7037 => 'ഡ', 7038 => 'ഢ', 7039 => 'ണ', 7040 => 'ത', 7041 => 'ഥ', 7042 => 'ദ', 7043 => 'ധ', 7044 => 'ന', 7045 => 'ഩ', 7046 => 'പ', 7047 => 'ഫ', 7048 => 'ബ', 7049 => 'ഭ', 7050 => 'മ', 7051 => 'യ', 7052 => 'ര', 7053 => 'ല', 7054 => 'വ', 7055 => 'ശ', 7056 => 'ഷ', 7057 => 'സ', 7058 => 'ഹ', 7059 => 'ള', 7060 => 'ഴ', 7061 => 'റ', 7062 => 'ഺ', 7063 => 'ഽ', 7064 => 'අ', 7065 => 'ආ', 7066 => 'ඇ', 7067 => 'ඈ', 7068 => 'ඉ', 7069 => 'ඊ', 7070 => 'උ', 7071 => 'ඌ', 7072 => 'ඍ', 7073 => 'ඎ', 7074 => 'ඏ', 7075 => 'ඐ', 7076 => 'එ', 7077 => 'ඒ', 7078 => 'ඓ', 7079 => 'ඔ', 7080 => 'ඕ', 7081 => 'ඖ', 7082 => 'ක', 7083 => 'ඛ', 7084 => 'ග', 7085 => 'ඝ', 7086 => 'ඞ', 7087 => 'ඟ', 7088 => 'ච', 7089 => 'ඡ', 7090 => 'ජ', 7091 => 'ඣ', 7092 => 'ඤ', 7093 => 'ඥ', 7094 => 'ඦ', 7095 => 'ට', 7096 => 'ඨ', 7097 => 'ඩ', 7098 => 'ඪ', 7099 => 'ණ', 7100 => 'ඬ', 7101 => 'ත', 7102 => 'ථ', 7103 => 'ද', 7104 => 'ධ', 7105 => 'න', 7106 => 'ඳ', 7107 => 'ප', 7108 => 'ඵ', 7109 => 'බ', 7110 => 'භ', 7111 => 'ම', 7112 => 'ඹ', 7113 => 'ය', 7114 => 'ර', 7115 => 'ල', 7116 => 'ව', 7117 => 'ශ', 7118 => 'ෂ', 7119 => 'ස', 7120 => 'හ', 7121 => 'ළ', 7122 => 'ෆ', 7123 => 'ꯀ', 7124 => 'ꯁ', 7125 => 'ꯂ', 7126 => 'ꯃ', 7127 => 'ꯄ', 7128 => 'ꯅ', 7129 => 'ꯆ', 7130 => 'ꯇ', 7131 => 'ꯈ', 7132 => 'ꯉ', 7133 => 'ꯊ', 7134 => 'ꯋ', 7135 => 'ꯌ', 7136 => 'ꯍ', 7137 => 'ꯎ', 7138 => 'ꯏ', 7139 => 'ꯐ', 7140 => 'ꯑ', 7141 => 'ꯒ', 7142 => 'ꯓ', 7143 => 'ꯔ', 7144 => 'ꯕ', 7145 => 'ꯖ', 7146 => 'ꯗ', 7147 => 'ꯘ', 7148 => 'ꯙ', 7149 => 'ꯚ', 7150 => 'ꯛ', 7151 => 'ꯜ', 7152 => 'ꯝ', 7153 => 'ꯞ', 7154 => 'ꯟ', 7155 => 'ꯠ', 7156 => 'ꯡ', 7157 => 'ꯢ', 7158 => 'ꠀ', 7159 => 'ꠁ', 7160 => 'ꠃ', 7161 => 'ꠄ', 7162 => 'ꠅ', 7163 => 'ꠇ', 7164 => 'ꠈ', 7165 => 'ꠉ', 7166 => 'ꠊ', 7167 => 'ꠌ', 7168 => 'ꠍ', 7169 => 'ꠎ', 7170 => 'ꠏ', 7171 => 'ꠐ', 7172 => 'ꠑ', 7173 => 'ꠒ', 7174 => 'ꠓ', 7175 => 'ꠔ', 7176 => 'ꠕ', 7177 => 'ꠖ', 7178 => 'ꠗ', 7179 => 'ꠘ', 7180 => 'ꠙ', 7181 => 'ꠚ', 7182 => 'ꠛ', 7183 => 'ꠜ', 7184 => 'ꠝ', 7185 => 'ꠞ', 7186 => 'ꠟ', 7187 => 'ꠠ', 7188 => 'ꠡ', 7189 => 'ꠢ', 7190 => 'ꢂ', 7191 => 'ꢃ', 7192 => 'ꢄ', 7193 => 'ꢅ', 7194 => 'ꢆ', 7195 => 'ꢇ', 7196 => 'ꢈ', 7197 => 'ꢉ', 7198 => 'ꢊ', 7199 => 'ꢋ', 7200 => 'ꢌ', 7201 => 'ꢍ', 7202 => 'ꢎ', 7203 => 'ꢏ', 7204 => 'ꢐ', 7205 => 'ꢑ', 7206 => 'ꢒ', 7207 => 'ꢓ', 7208 => 'ꢔ', 7209 => 'ꢕ', 7210 => 'ꢖ', 7211 => 'ꢗ', 7212 => 'ꢘ', 7213 => 'ꢙ', 7214 => 'ꢚ', 7215 => 'ꢛ', 7216 => 'ꢜ', 7217 => 'ꢝ', 7218 => 'ꢞ', 7219 => 'ꢟ', 7220 => 'ꢠ', 7221 => 'ꢡ', 7222 => 'ꢢ', 7223 => 'ꢣ', 7224 => 'ꢤ', 7225 => 'ꢥ', 7226 => 'ꢦ', 7227 => 'ꢧ', 7228 => 'ꢨ', 7229 => 'ꢩ', 7230 => 'ꢪ', 7231 => 'ꢫ', 7232 => 'ꢬ', 7233 => 'ꢭ', 7234 => 'ꢮ', 7235 => 'ꢯ', 7236 => 'ꢰ', 7237 => 'ꢱ', 7238 => 'ꢲ', 7239 => 'ꢳ', 7240 => '𑂃', 7241 => '𑂄', 7242 => '𑂅', 7243 => '𑂆', 7244 => '𑂇', 7245 => '𑂈', 7246 => '𑂉', 7247 => '𑂊', 7248 => '𑂋', 7249 => '𑂌', 7250 => '𑂍', 7251 => '𑂎', 7252 => '𑂏', 7253 => '𑂐', 7254 => '𑂑', 7255 => '𑂒', 7256 => '𑂓', 7257 => '𑂔', 7258 => '𑂕', 7259 => '𑂖', 7260 => '𑂗', 7261 => '𑂘', 7262 => '𑂙', 7263 => '𑂛', 7264 => '𑂝', 7265 => '𑂞', 7266 => '𑂟', 7267 => '𑂠', 7268 => '𑂡', 7269 => '𑂢', 7270 => '𑂣', 7271 => '𑂤', 7272 => '𑂥', 7273 => '𑂦', 7274 => '𑂧', 7275 => '𑂨', 7276 => '𑂩', 7277 => '𑂪', 7278 => '𑂬', 7279 => '𑂭', 7280 => '𑂮', 7281 => '𑂯', 7282 => 'ᮃ', 7283 => 'ᮄ', 7284 => 'ᮅ', 7285 => 'ᮆ', 7286 => 'ᮇ', 7287 => 'ᮈ', 7288 => 'ᮉ', 7289 => 'ᮊ', 7290 => 'ᮮ', 7291 => 'ᮋ', 7292 => 'ᮌ', 7293 => 'ᮍ', 7294 => 'ᮎ', 7295 => 'ᮏ', 7296 => 'ᮐ', 7297 => 'ᮑ', 7298 => 'ᮒ', 7299 => 'ᮓ', 7300 => 'ᮔ', 7301 => 'ᮕ', 7302 => 'ᮖ', 7303 => 'ᮗ', 7304 => 'ᮘ', 7305 => 'ᮙ', 7306 => 'ᮚ', 7307 => 'ᮛ', 7308 => 'ᮜ', 7309 => 'ᮝ', 7310 => 'ᮞ', 7311 => 'ᮟ', 7312 => 'ᮯ', 7313 => 'ᮠ', 7314 => '𑀅', 7315 => '𑀆', 7316 => '𑀇', 7317 => '𑀈', 7318 => '𑀉', 7319 => '𑀊', 7320 => '𑀋', 7321 => '𑀌', 7322 => '𑀍', 7323 => '𑀎', 7324 => '𑀏', 7325 => '𑀐', 7326 => '𑀑', 7327 => '𑀒', 7328 => '𑀓', 7329 => '𑀔', 7330 => '𑀕', 7331 => '𑀖', 7332 => '𑀗', 7333 => '𑀘', 7334 => '𑀙', 7335 => '𑀚', 7336 => '𑀛', 7337 => '𑀜', 7338 => '𑀝', 7339 => '𑀞', 7340 => '𑀟', 7341 => '𑀠', 7342 => '𑀡', 7343 => '𑀢', 7344 => '𑀣', 7345 => '𑀤', 7346 => '𑀥', 7347 => '𑀦', 7348 => '𑀧', 7349 => '𑀨', 7350 => '𑀩', 7351 => '𑀪', 7352 => '𑀫', 7353 => '𑀬', 7354 => '𑀭', 7355 => '𑀮', 7356 => '𑀯', 7357 => '𑀰', 7358 => '𑀱', 7359 => '𑀲', 7360 => '𑀳', 7361 => '𑀃', 7362 => '𑀄', 7363 => '𑀴', 7364 => '𑀵', 7365 => '𑀶', 7366 => '𑀷', 7367 => '𐨀', 7368 => '𐨐', 7369 => '𐨑', 7370 => '𐨒', 7371 => '𐨓', 7372 => '𐨕', 7373 => '𐨖', 7374 => '𐨗', 7375 => '𐨙', 7376 => '𐨚', 7377 => '𐨛', 7378 => '𐨜', 7379 => '𐨝', 7380 => '𐨞', 7381 => '𐨟', 7382 => '𐨠', 7383 => '𐨡', 7384 => '𐨢', 7385 => '𐨣', 7386 => '𐨤', 7387 => '𐨥', 7388 => '𐨦', 7389 => '𐨧', 7390 => '𐨨', 7391 => '𐨩', 7392 => '𐨪', 7393 => '𐨫', 7394 => '𐨬', 7395 => '𐨭', 7396 => '𐨮', 7397 => '𐨯', 7398 => '𐨰', 7399 => '𐨱', 7400 => '𐨲', 7401 => '𐨳', 7402 => 'ก', 7403 => 'ข', 7404 => 'ฃ', 7405 => 'ค', 7406 => 'ฅ', 7407 => 'ฆ', 7408 => 'ง', 7409 => 'จ', 7410 => 'ฉ', 7411 => 'ช', 7412 => 'ซ', 7413 => 'ฌ', 7414 => 'ญ', 7415 => 'ฎ', 7416 => 'ฏ', 7417 => 'ฐ', 7418 => 'ฑ', 7419 => 'ฒ', 7420 => 'ณ', 7421 => 'ด', 7422 => 'ต', 7423 => 'ถ', 7424 => 'ท', 7425 => 'ธ', 7426 => 'น', 7427 => 'บ', 7428 => 'ป', 7429 => 'ผ', 7430 => 'ฝ', 7431 => 'พ', 7432 => 'ฟ', 7433 => 'ภ', 7434 => 'ม', 7435 => 'ย', 7436 => 'ร', 7437 => 'ฤ', 7438 => 'ล', 7439 => 'ฦ', 7440 => 'ว', 7441 => 'ศ', 7442 => 'ษ', 7443 => 'ส', 7444 => 'ห', 7445 => 'ฬ', 7446 => 'อ', 7447 => 'ฮ', 7448 => 'ฯ', 7449 => 'ะ', 7450 => 'า', 7451 => 'ำ', 7452 => 'เ', 7453 => 'แ', 7454 => 'โ', 7455 => 'ใ', 7456 => 'ไ', 7457 => 'ๅ', 7458 => 'ກ', 7459 => 'ຂ', 7460 => 'ຄ', 7461 => 'ງ', 7462 => 'ຈ', 7463 => 'ສ', 7464 => 'ຊ', 7465 => 'ຍ', 7466 => 'ດ', 7467 => 'ຕ', 7468 => 'ຖ', 7469 => 'ທ', 7470 => 'ນ', 7471 => 'ບ', 7472 => 'ປ', 7473 => 'ຜ', 7474 => 'ຝ', 7475 => 'ພ', 7476 => 'ຟ', 7477 => 'ມ', 7478 => 'ຢ', 7479 => 'ຣ', 7480 => 'ລ', 7481 => 'ວ', 7482 => 'ຫ', 7483 => 'ອ', 7484 => 'ຮ', 7485 => 'ຯ', 7486 => 'ະ', 7487 => 'າ', 7488 => 'ຳ', 7489 => 'ຽ', 7490 => 'ເ', 7491 => 'ແ', 7492 => 'ໂ', 7493 => 'ໃ', 7494 => 'ໄ', 7495 => 'ꪀ', 7496 => 'ꪁ', 7497 => 'ꪂ', 7498 => 'ꪃ', 7499 => 'ꪄ', 7500 => 'ꪅ', 7501 => 'ꪆ', 7502 => 'ꪇ', 7503 => 'ꪈ', 7504 => 'ꪉ', 7505 => 'ꪊ', 7506 => 'ꪋ', 7507 => 'ꪌ', 7508 => 'ꪍ', 7509 => 'ꪎ', 7510 => 'ꪏ', 7511 => 'ꪐ', 7512 => 'ꪑ', 7513 => 'ꪒ', 7514 => 'ꪓ', 7515 => 'ꪔ', 7516 => 'ꪕ', 7517 => 'ꪖ', 7518 => 'ꪗ', 7519 => 'ꪘ', 7520 => 'ꪙ', 7521 => 'ꪚ', 7522 => 'ꪛ', 7523 => 'ꪜ', 7524 => 'ꪝ', 7525 => 'ꪞ', 7526 => 'ꪟ', 7527 => 'ꪠ', 7528 => 'ꪡ', 7529 => 'ꪢ', 7530 => 'ꪣ', 7531 => 'ꪤ', 7532 => 'ꪥ', 7533 => 'ꪦ', 7534 => 'ꪧ', 7535 => 'ꪨ', 7536 => 'ꪩ', 7537 => 'ꪪ', 7538 => 'ꪫ', 7539 => 'ꪬ', 7540 => 'ꪭ', 7541 => 'ꪮ', 7542 => 'ꪯ', 7543 => 'ꪱ', 7544 => 'ꪵ', 7545 => 'ꪶ', 7546 => 'ꪹ', 7547 => 'ꪺ', 7548 => 'ꪻ', 7549 => 'ꪼ', 7550 => 'ꪽ', 7551 => 'ꫀ', 7552 => 'ꫂ', 7553 => 'ꫛ', 7554 => 'ꫜ', 7555 => 'ཀ', 7556 => 'ཫ', 7557 => 'ཁ', 7558 => 'ག', 7559 => 'ང', 7560 => 'ཅ', 7561 => 'ཆ', 7562 => 'ཇ', 7563 => 'ཉ', 7564 => 'ཊ', 7565 => 'ཋ', 7566 => 'ཌ', 7567 => 'ཎ', 7568 => 'ཏ', 7569 => 'ཐ', 7570 => 'ད', 7571 => 'ན', 7572 => 'པ', 7573 => 'ཕ', 7574 => 'བ', 7575 => 'མ', 7576 => 'ཙ', 7577 => 'ཚ', 7578 => 'ཛ', 7579 => 'ཝ', 7580 => 'ཞ', 7581 => 'ཟ', 7582 => 'འ', 7583 => 'ཡ', 7584 => 'ར', 7585 => 'ཬ', 7586 => 'ལ', 7587 => 'ཤ', 7588 => 'ཥ', 7589 => 'ས', 7590 => 'ཧ', 7591 => 'ཨ', 7592 => 'ྈ', 7593 => 'ྉ', 7594 => 'ྌ', 7595 => 'ྊ', 7596 => 'ྋ', 7597 => 'ᰀ', 7598 => 'ᰁ', 7599 => 'ᰂ', 7600 => 'ᰃ', 7601 => 'ᰄ', 7602 => 'ᰅ', 7603 => 'ᰆ', 7604 => 'ᰇ', 7605 => 'ᰈ', 7606 => 'ᰉ', 7607 => 'ᱍ', 7608 => 'ᱎ', 7609 => 'ᱏ', 7610 => 'ᰊ', 7611 => 'ᰋ', 7612 => 'ᰌ', 7613 => 'ᰍ', 7614 => 'ᰎ', 7615 => 'ᰏ', 7616 => 'ᰐ', 7617 => 'ᰑ', 7618 => 'ᰒ', 7619 => 'ᰓ', 7620 => 'ᰔ', 7621 => 'ᰕ', 7622 => 'ᰖ', 7623 => 'ᰗ', 7624 => 'ᰘ', 7625 => 'ᰙ', 7626 => 'ᰚ', 7627 => 'ᰛ', 7628 => 'ᰜ', 7629 => 'ᰝ', 7630 => 'ᰞ', 7631 => 'ᰟ', 7632 => 'ᰠ', 7633 => 'ᰡ', 7634 => 'ᰢ', 7635 => 'ᰣ', 7636 => 'ꡀ', 7637 => 'ꡁ', 7638 => 'ꡂ', 7639 => 'ꡃ', 7640 => 'ꡄ', 7641 => 'ꡅ', 7642 => 'ꡆ', 7643 => 'ꡇ', 7644 => 'ꡩ', 7645 => 'ꡪ', 7646 => 'ꡫ', 7647 => 'ꡬ', 7648 => 'ꡈ', 7649 => 'ꡉ', 7650 => 'ꡊ', 7651 => 'ꡋ', 7652 => 'ꡌ', 7653 => 'ꡍ', 7654 => 'ꡎ', 7655 => 'ꡏ', 7656 => 'ꡐ', 7657 => 'ꡑ', 7658 => 'ꡒ', 7659 => 'ꡓ', 7660 => 'ꡧ', 7661 => 'ꡔ', 7662 => 'ꡕ', 7663 => 'ꡖ', 7664 => 'ꡗ', 7665 => 'ꡨ', 7666 => 'ꡭ', 7667 => 'ꡘ', 7668 => 'ꡱ', 7669 => 'ꡲ', 7670 => 'ꡙ', 7671 => 'ꡚ', 7672 => 'ꡮ', 7673 => 'ꡛ', 7674 => 'ꡜ', 7675 => 'ꡯ', 7676 => 'ꡰ', 7677 => 'ꡝ', 7678 => 'ꡢ', 7679 => 'ꡣ', 7680 => 'ꡤ', 7681 => 'ꡥ', 7682 => 'ꡞ', 7683 => 'ꡟ', 7684 => 'ꡠ', 7685 => 'ꡡ', 7686 => 'ꡦ', 7687 => 'ꡳ', 7688 => 'ᤀ', 7689 => 'ᤁ', 7690 => 'ᤂ', 7691 => 'ᤃ', 7692 => 'ᤄ', 7693 => 'ᤅ', 7694 => 'ᤆ', 7695 => 'ᤇ', 7696 => 'ᤈ', 7697 => 'ᤉ', 7698 => 'ᤊ', 7699 => 'ᤋ', 7700 => 'ᤌ', 7701 => 'ᤍ', 7702 => 'ᤎ', 7703 => 'ᤏ', 7704 => 'ᤐ', 7705 => 'ᤑ', 7706 => 'ᤒ', 7707 => 'ᤓ', 7708 => 'ᤔ', 7709 => 'ᤕ', 7710 => 'ᤖ', 7711 => 'ᤗ', 7712 => 'ᤘ', 7713 => 'ᤙ', 7714 => 'ᤚ', 7715 => 'ᤛ', 7716 => 'ᤜ', 7717 => 'ᜀ', 7718 => 'ᜁ', 7719 => 'ᜂ', 7720 => 'ᜃ', 7721 => 'ᜄ', 7722 => 'ᜅ', 7723 => 'ᜆ', 7724 => 'ᜇ', 7725 => 'ᜈ', 7726 => 'ᜉ', 7727 => 'ᜊ', 7728 => 'ᜋ', 7729 => 'ᜌ', 7730 => 'ᜎ', 7731 => 'ᜏ', 7732 => 'ᜐ', 7733 => 'ᜑ', 7734 => 'ᜠ', 7735 => 'ᜡ', 7736 => 'ᜢ', 7737 => 'ᜣ', 7738 => 'ᜤ', 7739 => 'ᜥ', 7740 => 'ᜦ', 7741 => 'ᜧ', 7742 => 'ᜨ', 7743 => 'ᜩ', 7744 => 'ᜪ', 7745 => 'ᜫ', 7746 => 'ᜬ', 7747 => 'ᜭ', 7748 => 'ᜮ', 7749 => 'ᜯ', 7750 => 'ᜰ', 7751 => 'ᜱ', 7752 => 'ᝀ', 7753 => 'ᝁ', 7754 => 'ᝂ', 7755 => 'ᝃ', 7756 => 'ᝄ', 7757 => 'ᝅ', 7758 => 'ᝆ', 7759 => 'ᝇ', 7760 => 'ᝈ', 7761 => 'ᝉ', 7762 => 'ᝊ', 7763 => 'ᝋ', 7764 => 'ᝌ', 7765 => 'ᝍ', 7766 => 'ᝎ', 7767 => 'ᝏ', 7768 => 'ᝐ', 7769 => 'ᝑ', 7770 => 'ᝠ', 7771 => 'ᝡ', 7772 => 'ᝢ', 7773 => 'ᝣ', 7774 => 'ᝤ', 7775 => 'ᝥ', 7776 => 'ᝦ', 7777 => 'ᝧ', 7778 => 'ᝨ', 7779 => 'ᝩ', 7780 => 'ᝪ', 7781 => 'ᝫ', 7782 => 'ᝬ', 7783 => 'ᝮ', 7784 => 'ᝯ', 7785 => 'ᝰ', 7786 => 'ᨀ', 7787 => 'ᨁ', 7788 => 'ᨂ', 7789 => 'ᨃ', 7790 => 'ᨄ', 7791 => 'ᨅ', 7792 => 'ᨆ', 7793 => 'ᨇ', 7794 => 'ᨈ', 7795 => 'ᨉ', 7796 => 'ᨊ', 7797 => 'ᨋ', 7798 => 'ᨌ', 7799 => 'ᨍ', 7800 => 'ᨎ', 7801 => 'ᨏ', 7802 => 'ᨐ', 7803 => 'ᨑ', 7804 => 'ᨒ', 7805 => 'ᨓ', 7806 => 'ᨔ', 7807 => 'ᨕ', 7808 => 'ᨖ', 7809 => 'ᯀ', 7810 => 'ᯂ', 7811 => 'ᯅ', 7812 => 'ᯇ', 7813 => 'ᯉ', 7814 => 'ᯋ', 7815 => 'ᯎ', 7816 => 'ᯐ', 7817 => 'ᯑ', 7818 => 'ᯒ', 7819 => 'ᯔ', 7820 => 'ᯖ', 7821 => 'ᯘ', 7822 => 'ᯛ', 7823 => 'ᯝ', 7824 => 'ᯞ', 7825 => 'ᯠ', 7826 => 'ᯡ', 7827 => 'ᯢ', 7828 => 'ᯣ', 7829 => 'ᯤ', 7830 => 'ᯥ', 7831 => 'ꤰ', 7832 => 'ꤱ', 7833 => 'ꤲ', 7834 => 'ꤳ', 7835 => 'ꤴ', 7836 => 'ꤵ', 7837 => 'ꤶ', 7838 => 'ꤷ', 7839 => 'ꤸ', 7840 => 'ꤹ', 7841 => 'ꤺ', 7842 => 'ꤻ', 7843 => 'ꤼ', 7844 => 'ꤽ', 7845 => 'ꤾ', 7846 => 'ꤿ', 7847 => 'ꥀ', 7848 => 'ꥁ', 7849 => 'ꥂ', 7850 => 'ꥃ', 7851 => 'ꥄ', 7852 => 'ꥅ', 7853 => 'ꥆ', 7854 => 'ꤊ', 7855 => 'ꤋ', 7856 => 'ꤌ', 7857 => 'ꤍ', 7858 => 'ꤎ', 7859 => 'ꤏ', 7860 => 'ꤐ', 7861 => 'ꤑ', 7862 => 'ꤒ', 7863 => 'ꤓ', 7864 => 'ꤔ', 7865 => 'ꤕ', 7866 => 'ꤖ', 7867 => 'ꤗ', 7868 => 'ꤘ', 7869 => 'ꤙ', 7870 => 'ꤚ', 7871 => 'ꤛ', 7872 => 'ꤜ', 7873 => 'ꤝ', 7874 => 'ꤞ', 7875 => 'ꤟ', 7876 => 'ꤠ', 7877 => 'ꤡ', 7878 => 'ꤢ', 7879 => 'ꤣ', 7880 => 'ꤤ', 7881 => 'ꤥ', 7882 => 'က', 7883 => 'ၵ', 7884 => 'ခ', 7885 => 'ၶ', 7886 => 'ဂ', 7887 => 'ၷ', 7888 => 'ꩠ', 7889 => 'ဃ', 7890 => 'င', 7891 => 'ၚ', 7892 => 'စ', 7893 => 'ၸ', 7894 => 'ꩡ', 7895 => 'ဆ', 7896 => 'ꩢ', 7897 => 'ဇ', 7898 => 'ꩣ', 7899 => 'ၹ', 7900 => 'ꩲ', 7901 => 'ဈ', 7902 => 'ၛ', 7903 => 'ꩤ', 7904 => 'ၡ', 7905 => 'ဉ', 7906 => 'ၺ', 7907 => 'ꩥ', 7908 => 'ည', 7909 => 'ဋ', 7910 => 'ꩦ', 7911 => 'ဌ', 7912 => 'ꩧ', 7913 => 'ဍ', 7914 => 'ꩨ', 7915 => 'ဎ', 7916 => 'ꩩ', 7917 => 'ဏ', 7918 => 'ၮ', 7919 => 'တ', 7920 => 'ထ', 7921 => 'ဒ', 7922 => 'ၻ', 7923 => 'ဓ', 7924 => 'ꩪ', 7925 => 'န', 7926 => 'ၼ', 7927 => 'ꩫ', 7928 => 'ပ', 7929 => 'ဖ', 7930 => 'ၽ', 7931 => 'ၾ', 7932 => 'ꩯ', 7933 => 'ႎ', 7934 => 'ဗ', 7935 => 'ၿ', 7936 => 'ဘ', 7937 => 'မ', 7938 => 'ယ', 7939 => 'ရ', 7940 => 'ꩳ', 7941 => 'ꩺ', 7942 => 'လ', 7943 => 'ဝ', 7944 => 'ႀ', 7945 => 'ၐ', 7946 => 'ၑ', 7947 => 'ၥ', 7948 => 'သ', 7949 => 'ꩬ', 7950 => 'ဟ', 7951 => 'ႁ', 7952 => 'ꩭ', 7953 => 'ꩮ', 7954 => 'ꩱ', 7955 => 'ဠ', 7956 => 'ၜ', 7957 => 'ၝ', 7958 => 'ၯ', 7959 => 'ၰ', 7960 => 'ၦ', 7961 => 'အ', 7962 => 'ဢ', 7963 => 'ဣ', 7964 => 'ဤ', 7965 => 'ဥ', 7966 => 'ဦ', 7967 => 'ၒ', 7968 => 'ၓ', 7969 => 'ၔ', 7970 => 'ၕ', 7971 => 'ဧ', 7972 => 'ဨ', 7973 => 'ဩ', 7974 => 'ဪ', 7975 => 'ꩴ', 7976 => 'ꩵ', 7977 => 'ꩶ', 7978 => 'ក', 7979 => 'ខ', 7980 => 'គ', 7981 => 'ឃ', 7982 => 'ង', 7983 => 'ច', 7984 => 'ឆ', 7985 => 'ជ', 7986 => 'ឈ', 7987 => 'ញ', 7988 => 'ដ', 7989 => 'ឋ', 7990 => 'ឌ', 7991 => 'ឍ', 7992 => 'ណ', 7993 => 'ត', 7994 => 'ថ', 7995 => 'ទ', 7996 => 'ធ', 7997 => 'ន', 7998 => 'ប', 7999 => 'ផ', 8000 => 'ព', 8001 => 'ភ', 8002 => 'ម', 8003 => 'យ', 8004 => 'រ', 8005 => 'ល', 8006 => 'វ', 8007 => 'ឝ', 8008 => 'ឞ', 8009 => 'ស', 8010 => 'ហ', 8011 => 'ឡ', 8012 => 'អ', 8013 => 'ៜ', 8014 => 'ឣ', 8015 => 'ឤ', 8016 => 'ឥ', 8017 => 'ឦ', 8018 => 'ឧ', 8019 => 'ឨ', 8020 => 'ឩ', 8021 => 'ឪ', 8022 => 'ឫ', 8023 => 'ឬ', 8024 => 'ឭ', 8025 => 'ឮ', 8026 => 'ឯ', 8027 => 'ឰ', 8028 => 'ឱ', 8029 => 'ឲ', 8030 => 'ឳ', 8031 => 'ᥐ', 8032 => 'ᥑ', 8033 => 'ᥒ', 8034 => 'ᥓ', 8035 => 'ᥔ', 8036 => 'ᥕ', 8037 => 'ᥖ', 8038 => 'ᥗ', 8039 => 'ᥘ', 8040 => 'ᥙ', 8041 => 'ᥚ', 8042 => 'ᥛ', 8043 => 'ᥜ', 8044 => 'ᥝ', 8045 => 'ᥞ', 8046 => 'ᥟ', 8047 => 'ᥠ', 8048 => 'ᥡ', 8049 => 'ᥢ', 8050 => 'ᥣ', 8051 => 'ᥤ', 8052 => 'ᥥ', 8053 => 'ᥦ', 8054 => 'ᥧ', 8055 => 'ᥨ', 8056 => 'ᥩ', 8057 => 'ᥪ', 8058 => 'ᥫ', 8059 => 'ᥬ', 8060 => 'ᥭ', 8061 => 'ᥰ', 8062 => 'ᥱ', 8063 => 'ᥲ', 8064 => 'ᥳ', 8065 => 'ᥴ', 8066 => 'ᦀ', 8067 => 'ᦁ', 8068 => 'ᦂ', 8069 => 'ᦃ', 8070 => 'ᦄ', 8071 => 'ᦅ', 8072 => 'ᦆ', 8073 => 'ᦇ', 8074 => 'ᦈ', 8075 => 'ᦉ', 8076 => 'ᦊ', 8077 => 'ᦋ', 8078 => 'ᦌ', 8079 => 'ᦍ', 8080 => 'ᦎ', 8081 => 'ᦏ', 8082 => 'ᦐ', 8083 => 'ᦑ', 8084 => 'ᦒ', 8085 => 'ᦓ', 8086 => 'ᦔ', 8087 => 'ᦕ', 8088 => 'ᦖ', 8089 => 'ᦗ', 8090 => 'ᦘ', 8091 => 'ᦙ', 8092 => 'ᦚ', 8093 => 'ᦛ', 8094 => 'ᦜ', 8095 => 'ᦝ', 8096 => 'ᦞ', 8097 => 'ᦟ', 8098 => 'ᦠ', 8099 => 'ᦡ', 8100 => 'ᦢ', 8101 => 'ᦣ', 8102 => 'ᦤ', 8103 => 'ᦥ', 8104 => 'ᦦ', 8105 => 'ᦧ', 8106 => 'ᦨ', 8107 => 'ᦩ', 8108 => 'ᦪ', 8109 => 'ᦫ', 8110 => 'ᧁ', 8111 => 'ᧂ', 8112 => 'ᧃ', 8113 => 'ᧄ', 8114 => 'ᧅ', 8115 => 'ᧆ', 8116 => 'ᧇ', 8117 => 'ᨠ', 8118 => 'ᨡ', 8119 => 'ᨢ', 8120 => 'ᨣ', 8121 => 'ᨤ', 8122 => 'ᨥ', 8123 => 'ᨦ', 8124 => 'ᨧ', 8125 => 'ᨨ', 8126 => 'ᨩ', 8127 => 'ᨪ', 8128 => 'ᨫ', 8129 => 'ᨬ', 8130 => 'ᨭ', 8131 => 'ᨮ', 8132 => 'ᨯ', 8133 => 'ᨰ', 8134 => 'ᨱ', 8135 => 'ᨲ', 8136 => 'ᨳ', 8137 => 'ᨴ', 8138 => 'ᨵ', 8139 => 'ᨶ', 8140 => 'ᨷ', 8141 => 'ᨸ', 8142 => 'ᨹ', 8143 => 'ᨺ', 8144 => 'ᨻ', 8145 => 'ᨼ', 8146 => 'ᨽ', 8147 => 'ᨾ', 8148 => 'ᨿ', 8149 => 'ᩀ', 8150 => 'ᩁ', 8151 => 'ᩂ', 8152 => 'ᩃ', 8153 => 'ᩄ', 8154 => 'ᩅ', 8155 => 'ᩆ', 8156 => 'ᩇ', 8157 => 'ᩈ', 8158 => 'ᩉ', 8159 => 'ᩊ', 8160 => 'ᩋ', 8161 => 'ᩌ', 8162 => 'ᩓ', 8163 => 'ᩍ', 8164 => 'ᩎ', 8165 => 'ᩏ', 8166 => 'ᩐ', 8167 => 'ᩑ', 8168 => 'ᩒ', 8169 => 'ꨀ', 8170 => 'ꨁ', 8171 => 'ꨂ', 8172 => 'ꨃ', 8173 => 'ꨄ', 8174 => 'ꨅ', 8175 => 'ꨆ', 8176 => 'ꨇ', 8177 => 'ꨈ', 8178 => 'ꨉ', 8179 => 'ꨊ', 8180 => 'ꨋ', 8181 => 'ꨌ', 8182 => 'ꨍ', 8183 => 'ꨎ', 8184 => 'ꨏ', 8185 => 'ꨐ', 8186 => 'ꨑ', 8187 => 'ꨒ', 8188 => 'ꨓ', 8189 => 'ꨔ', 8190 => 'ꨕ', 8191 => 'ꨖ', 8192 => 'ꨗ', 8193 => 'ꨘ', 8194 => 'ꨙ', 8195 => 'ꨚ', 8196 => 'ꨛ', 8197 => 'ꨜ', 8198 => 'ꨝ', 8199 => 'ꨞ', 8200 => 'ꨟ', 8201 => 'ꨠ', 8202 => 'ꨡ', 8203 => 'ꨢ', 8204 => 'ꨣ', 8205 => 'ꨤ', 8206 => 'ꨥ', 8207 => 'ꨦ', 8208 => 'ꨧ', 8209 => 'ꨨ', 8210 => 'ꩀ', 8211 => 'ꩁ', 8212 => 'ꩂ', 8213 => 'ꩄ', 8214 => 'ꩅ', 8215 => 'ꩆ', 8216 => 'ꩇ', 8217 => 'ꩈ', 8218 => 'ꩉ', 8219 => 'ꩊ', 8220 => 'ꩋ', 8221 => 'ᬅ', 8222 => 'ᬆ', 8223 => 'ᬇ', 8224 => 'ᬈ', 8225 => 'ᬉ', 8226 => 'ᬊ', 8227 => 'ᬋ', 8228 => 'ᬌ', 8229 => 'ᬍ', 8230 => 'ᬎ', 8231 => 'ᬏ', 8232 => 'ᬐ', 8233 => 'ᬑ', 8234 => 'ᬒ', 8235 => 'ᬓ', 8236 => 'ᭅ', 8237 => 'ᭆ', 8238 => 'ᬔ', 8239 => 'ᬕ', 8240 => 'ᬖ', 8241 => 'ᬗ', 8242 => 'ᬘ', 8243 => 'ᬙ', 8244 => 'ᬚ', 8245 => 'ᬛ', 8246 => 'ᬜ', 8247 => 'ᬝ', 8248 => 'ᬞ', 8249 => 'ᬟ', 8250 => 'ᬠ', 8251 => 'ᬡ', 8252 => 'ᬢ', 8253 => 'ᭇ', 8254 => 'ᬣ', 8255 => 'ᬤ', 8256 => 'ᬥ', 8257 => 'ᬦ', 8258 => 'ᬧ', 8259 => 'ᭈ', 8260 => 'ᬨ', 8261 => 'ᬩ', 8262 => 'ᬪ', 8263 => 'ᬫ', 8264 => 'ᬬ', 8265 => 'ᬭ', 8266 => 'ᬮ', 8267 => 'ᬯ', 8268 => 'ᭉ', 8269 => 'ᬰ', 8270 => 'ᬱ', 8271 => 'ᬲ', 8272 => 'ᭊ', 8273 => 'ᭋ', 8274 => 'ᬳ', 8275 => 'ꦄ', 8276 => 'ꦅ', 8277 => 'ꦆ', 8278 => 'ꦇ', 8279 => 'ꦈ', 8280 => 'ꦉ', 8281 => 'ꦊ', 8282 => 'ꦋ', 8283 => 'ꦌ', 8284 => 'ꦍ', 8285 => 'ꦎ', 8286 => 'ꦏ', 8287 => 'ꦐ', 8288 => 'ꦑ', 8289 => 'ꦒ', 8290 => 'ꦓ', 8291 => 'ꦔ', 8292 => 'ꦕ', 8293 => 'ꦖ', 8294 => 'ꦗ', 8295 => 'ꦘ', 8296 => 'ꦙ', 8297 => 'ꦚ', 8298 => 'ꦛ', 8299 => 'ꦜ', 8300 => 'ꦝ', 8301 => 'ꦞ', 8302 => 'ꦟ', 8303 => 'ꦠ', 8304 => 'ꦡ', 8305 => 'ꦢ', 8306 => 'ꦣ', 8307 => 'ꦤ', 8308 => 'ꦥ', 8309 => 'ꦦ', 8310 => 'ꦧ', 8311 => 'ꦨ', 8312 => 'ꦩ', 8313 => 'ꦪ', 8314 => 'ꦫ', 8315 => 'ꦭ', 8316 => 'ꦮ', 8317 => 'ꦯ', 8318 => 'ꦰ', 8319 => 'ꦱ', 8320 => 'ꦲ', 8321 => 'ᢀ', 8322 => 'ᢁ', 8323 => 'ᢂ', 8324 => 'ᢃ', 8325 => 'ᢄ', 8326 => 'ᢅ', 8327 => 'ᢆ', 8328 => 'ᡃ', 8329 => 'ᠠ', 8330 => 'ᢇ', 8331 => 'ᠡ', 8332 => 'ᡄ', 8333 => 'ᡝ', 8334 => 'ᠢ', 8335 => 'ᡅ', 8336 => 'ᡞ', 8337 => 'ᡳ', 8338 => 'ᢈ', 8339 => 'ᡟ', 8340 => 'ᠣ', 8341 => 'ᡆ', 8342 => 'ᠤ', 8343 => 'ᡇ', 8344 => 'ᡡ', 8345 => 'ᠥ', 8346 => 'ᡈ', 8347 => 'ᠦ', 8348 => 'ᡉ', 8349 => 'ᡠ', 8350 => 'ᠧ', 8351 => 'ᠨ', 8352 => 'ᠩ', 8353 => 'ᡊ', 8354 => 'ᡢ', 8355 => 'ᢊ', 8356 => 'ᢛ', 8357 => 'ᠪ', 8358 => 'ᡋ', 8359 => 'ᠫ', 8360 => 'ᡌ', 8361 => 'ᡦ', 8362 => 'ᠬ', 8363 => 'ᡍ', 8364 => 'ᠭ', 8365 => 'ᡎ', 8366 => 'ᡤ', 8367 => 'ᢚ', 8368 => 'ᡥ', 8369 => 'ᠮ', 8370 => 'ᡏ', 8371 => 'ᠯ', 8372 => 'ᠰ', 8373 => 'ᠱ', 8374 => 'ᡧ', 8375 => 'ᢜ', 8376 => 'ᢝ', 8377 => 'ᢢ', 8378 => 'ᢤ', 8379 => 'ᢥ', 8380 => 'ᠲ', 8381 => 'ᡐ', 8382 => 'ᡨ', 8383 => 'ᠳ', 8384 => 'ᡑ', 8385 => 'ᡩ', 8386 => 'ᠴ', 8387 => 'ᡒ', 8388 => 'ᡱ', 8389 => 'ᡜ', 8390 => 'ᢋ', 8391 => 'ᠵ', 8392 => 'ᡓ', 8393 => 'ᡪ', 8394 => 'ᡷ', 8395 => 'ᠶ', 8396 => 'ᡕ', 8397 => 'ᡲ', 8398 => 'ᠷ', 8399 => 'ᡵ', 8400 => 'ᠸ', 8401 => 'ᡖ', 8402 => 'ᠹ', 8403 => 'ᡫ', 8404 => 'ᡶ', 8405 => 'ᠺ', 8406 => 'ᡗ', 8407 => 'ᡣ', 8408 => 'ᡴ', 8409 => 'ᢉ', 8410 => 'ᠻ', 8411 => 'ᠼ', 8412 => 'ᡔ', 8413 => 'ᡮ', 8414 => 'ᠽ', 8415 => 'ᡯ', 8416 => 'ᡘ', 8417 => 'ᡬ', 8418 => 'ᠾ', 8419 => 'ᡙ', 8420 => 'ᡭ', 8421 => 'ᠿ', 8422 => 'ᡀ', 8423 => 'ᡁ', 8424 => 'ᡂ', 8425 => 'ᡚ', 8426 => 'ᡛ', 8427 => 'ᡰ', 8428 => 'ᢌ', 8429 => 'ᢞ', 8430 => 'ᢍ', 8431 => 'ᢎ', 8432 => 'ᢟ', 8433 => 'ᢏ', 8434 => 'ᢐ', 8435 => 'ᢘ', 8436 => 'ᢠ', 8437 => 'ᢑ', 8438 => 'ᢡ', 8439 => 'ᢒ', 8440 => 'ᢓ', 8441 => 'ᢨ', 8442 => 'ᢔ', 8443 => 'ᢣ', 8444 => 'ᢕ', 8445 => 'ᢙ', 8446 => 'ᢖ', 8447 => 'ᢗ', 8448 => 'ᢦ', 8449 => 'ᢧ', 8450 => 'ᢪ', 8451 => 'ᱚ', 8452 => 'ᱛ', 8453 => 'ᱜ', 8454 => 'ᱝ', 8455 => 'ᱞ', 8456 => 'ᱟ', 8457 => 'ᱠ', 8458 => 'ᱡ', 8459 => 'ᱢ', 8460 => 'ᱣ', 8461 => 'ᱤ', 8462 => 'ᱥ', 8463 => 'ᱦ', 8464 => 'ᱧ', 8465 => 'ᱨ', 8466 => 'ᱩ', 8467 => 'ᱪ', 8468 => 'ᱫ', 8469 => 'ᱬ', 8470 => 'ᱭ', 8471 => 'ᱮ', 8472 => 'ᱯ', 8473 => 'ᱰ', 8474 => 'ᱱ', 8475 => 'ᱲ', 8476 => 'ᱳ', 8477 => 'ᱴ', 8478 => 'ᱵ', 8479 => 'ᱶ', 8480 => 'ᱷ', 8481 => 'ᱸ', 8482 => 'ᱹ', 8483 => 'ᱺ', 8484 => 'ᱻ', 8485 => 'ᱼ', 8486 => 'ᱽ', 8487 => 'Ꭰ', 8488 => 'Ꭱ', 8489 => 'Ꭲ', 8490 => 'Ꭳ', 8491 => 'Ꭴ', 8492 => 'Ꭵ', 8493 => 'Ꭶ', 8494 => 'Ꭷ', 8495 => 'Ꭸ', 8496 => 'Ꭹ', 8497 => 'Ꭺ', 8498 => 'Ꭻ', 8499 => 'Ꭼ', 8500 => 'Ꭽ', 8501 => 'Ꭾ', 8502 => 'Ꭿ', 8503 => 'Ꮀ', 8504 => 'Ꮁ', 8505 => 'Ꮂ', 8506 => 'Ꮃ', 8507 => 'Ꮄ', 8508 => 'Ꮅ', 8509 => 'Ꮆ', 8510 => 'Ꮇ', 8511 => 'Ꮈ', 8512 => 'Ꮉ', 8513 => 'Ꮊ', 8514 => 'Ꮋ', 8515 => 'Ꮌ', 8516 => 'Ꮍ', 8517 => 'Ꮎ', 8518 => 'Ꮏ', 8519 => 'Ꮐ', 8520 => 'Ꮑ', 8521 => 'Ꮒ', 8522 => 'Ꮓ', 8523 => 'Ꮔ', 8524 => 'Ꮕ', 8525 => 'Ꮖ', 8526 => 'Ꮗ', 8527 => 'Ꮘ', 8528 => 'Ꮙ', 8529 => 'Ꮚ', 8530 => 'Ꮛ', 8531 => 'Ꮜ', 8532 => 'Ꮝ', 8533 => 'Ꮞ', 8534 => 'Ꮟ', 8535 => 'Ꮠ', 8536 => 'Ꮡ', 8537 => 'Ꮢ', 8538 => 'Ꮣ', 8539 => 'Ꮤ', 8540 => 'Ꮥ', 8541 => 'Ꮦ', 8542 => 'Ꮧ', 8543 => 'Ꮨ', 8544 => 'Ꮩ', 8545 => 'Ꮪ', 8546 => 'Ꮫ', 8547 => 'Ꮬ', 8548 => 'Ꮭ', 8549 => 'Ꮮ', 8550 => 'Ꮯ', 8551 => 'Ꮰ', 8552 => 'Ꮱ', 8553 => 'Ꮲ', 8554 => 'Ꮳ', 8555 => 'Ꮴ', 8556 => 'Ꮵ', 8557 => 'Ꮶ', 8558 => 'Ꮷ', 8559 => 'Ꮸ', 8560 => 'Ꮹ', 8561 => 'Ꮺ', 8562 => 'Ꮻ', 8563 => 'Ꮼ', 8564 => 'Ꮽ', 8565 => 'Ꮾ', 8566 => 'Ꮿ', 8567 => 'Ᏸ', 8568 => 'Ᏹ', 8569 => 'Ᏺ', 8570 => 'Ᏻ', 8571 => 'Ᏼ', 8572 => 'ᐁ', 8573 => 'ᐂ', 8574 => 'ᐃ', 8575 => 'ᐄ', 8576 => 'ᐅ', 8577 => 'ᐆ', 8578 => 'ᐇ', 8579 => 'ᐈ', 8580 => 'ᐉ', 8581 => 'ᐊ', 8582 => 'ᐋ', 8583 => 'ᐌ', 8584 => 'ᐍ', 8585 => 'ᐎ', 8586 => 'ᐏ', 8587 => 'ᐐ', 8588 => 'ᐑ', 8589 => 'ᐒ', 8590 => 'ᐓ', 8591 => 'ᐔ', 8592 => 'ᐕ', 8593 => 'ᐖ', 8594 => 'ᐗ', 8595 => 'ᐘ', 8596 => 'ᐙ', 8597 => 'ᐚ', 8598 => 'ᐛ', 8599 => 'ᐜ', 8600 => 'ᐝ', 8601 => 'ᐞ', 8602 => 'ᐟ', 8603 => 'ᐠ', 8604 => 'ᐡ', 8605 => 'ᐢ', 8606 => 'ᐣ', 8607 => 'ᐤ', 8608 => 'ᐥ', 8609 => 'ᐦ', 8610 => 'ᐧ', 8611 => 'ᐨ', 8612 => 'ᐩ', 8613 => 'ᐪ', 8614 => 'ᐫ', 8615 => 'ᐬ', 8616 => 'ᐭ', 8617 => 'ᐮ', 8618 => 'ᐯ', 8619 => 'ᐰ', 8620 => 'ᐱ', 8621 => 'ᐲ', 8622 => 'ᐳ', 8623 => 'ᐴ', 8624 => 'ᐵ', 8625 => 'ᐶ', 8626 => 'ᐷ', 8627 => 'ᐸ', 8628 => 'ᐹ', 8629 => 'ᐺ', 8630 => 'ᐻ', 8631 => 'ᐼ', 8632 => 'ᐽ', 8633 => 'ᐾ', 8634 => 'ᐿ', 8635 => 'ᑀ', 8636 => 'ᑁ', 8637 => 'ᑂ', 8638 => 'ᑃ', 8639 => 'ᑄ', 8640 => 'ᑅ', 8641 => 'ᑆ', 8642 => 'ᑇ', 8643 => 'ᑈ', 8644 => 'ᑉ', 8645 => 'ᑊ', 8646 => 'ᑋ', 8647 => 'ᑌ', 8648 => 'ᑍ', 8649 => 'ᑎ', 8650 => 'ᑏ', 8651 => 'ᑐ', 8652 => 'ᑑ', 8653 => 'ᑒ', 8654 => 'ᑓ', 8655 => 'ᑔ', 8656 => 'ᑕ', 8657 => 'ᑖ', 8658 => 'ᑗ', 8659 => 'ᑘ', 8660 => 'ᑙ', 8661 => 'ᑚ', 8662 => 'ᑛ', 8663 => 'ᑜ', 8664 => 'ᑝ', 8665 => 'ᑞ', 8666 => 'ᑟ', 8667 => 'ᑠ', 8668 => 'ᑡ', 8669 => 'ᑢ', 8670 => 'ᑣ', 8671 => 'ᑤ', 8672 => 'ᑥ', 8673 => 'ᑦ', 8674 => 'ᑧ', 8675 => 'ᑨ', 8676 => 'ᑩ', 8677 => 'ᑪ', 8678 => 'ᑫ', 8679 => 'ᑬ', 8680 => 'ᑭ', 8681 => 'ᑮ', 8682 => 'ᑯ', 8683 => 'ᑰ', 8684 => 'ᑱ', 8685 => 'ᑲ', 8686 => 'ᑳ', 8687 => 'ᑴ', 8688 => 'ᑵ', 8689 => 'ᑶ', 8690 => 'ᑷ', 8691 => 'ᑸ', 8692 => 'ᑹ', 8693 => 'ᑺ', 8694 => 'ᑻ', 8695 => 'ᑼ', 8696 => 'ᑽ', 8697 => 'ᑾ', 8698 => 'ᑿ', 8699 => 'ᒀ', 8700 => 'ᒁ', 8701 => 'ᒂ', 8702 => 'ᒃ', 8703 => 'ᒄ', 8704 => 'ᒅ', 8705 => 'ᒆ', 8706 => 'ᒇ', 8707 => 'ᒈ', 8708 => 'ᒉ', 8709 => 'ᒊ', 8710 => 'ᒋ', 8711 => 'ᒌ', 8712 => 'ᒍ', 8713 => 'ᒎ', 8714 => 'ᒏ', 8715 => 'ᒐ', 8716 => 'ᒑ', 8717 => 'ᒒ', 8718 => 'ᒓ', 8719 => 'ᒔ', 8720 => 'ᒕ', 8721 => 'ᒖ', 8722 => 'ᒗ', 8723 => 'ᒘ', 8724 => 'ᒙ', 8725 => 'ᒚ', 8726 => 'ᒛ', 8727 => 'ᒜ', 8728 => 'ᒝ', 8729 => 'ᒞ', 8730 => 'ᒟ', 8731 => 'ᒠ', 8732 => 'ᒡ', 8733 => 'ᒢ', 8734 => 'ᒣ', 8735 => 'ᒤ', 8736 => 'ᒥ', 8737 => 'ᒦ', 8738 => 'ᒧ', 8739 => 'ᒨ', 8740 => 'ᒩ', 8741 => 'ᒪ', 8742 => 'ᒫ', 8743 => 'ᒬ', 8744 => 'ᒭ', 8745 => 'ᒮ', 8746 => 'ᒯ', 8747 => 'ᒰ', 8748 => 'ᒱ', 8749 => 'ᒲ', 8750 => 'ᒳ', 8751 => 'ᒴ', 8752 => 'ᒵ', 8753 => 'ᒶ', 8754 => 'ᒷ', 8755 => 'ᒸ', 8756 => 'ᒹ', 8757 => 'ᒺ', 8758 => 'ᒻ', 8759 => 'ᒼ', 8760 => 'ᒽ', 8761 => 'ᒾ', 8762 => 'ᒿ', 8763 => 'ᓀ', 8764 => 'ᓁ', 8765 => 'ᓂ', 8766 => 'ᓃ', 8767 => 'ᓄ', 8768 => 'ᓅ', 8769 => 'ᓆ', 8770 => 'ᓇ', 8771 => 'ᓈ', 8772 => 'ᓉ', 8773 => 'ᓊ', 8774 => 'ᓋ', 8775 => 'ᓌ', 8776 => 'ᓍ', 8777 => 'ᓎ', 8778 => 'ᓏ', 8779 => 'ᓐ', 8780 => 'ᓑ', 8781 => 'ᓒ', 8782 => 'ᓓ', 8783 => 'ᓔ', 8784 => 'ᓕ', 8785 => 'ᓖ', 8786 => 'ᓗ', 8787 => 'ᓘ', 8788 => 'ᓙ', 8789 => 'ᓚ', 8790 => 'ᓛ', 8791 => 'ᓜ', 8792 => 'ᓝ', 8793 => 'ᓞ', 8794 => 'ᓟ', 8795 => 'ᓠ', 8796 => 'ᓡ', 8797 => 'ᓢ', 8798 => 'ᓣ', 8799 => 'ᓤ', 8800 => 'ᓥ', 8801 => 'ᓦ', 8802 => 'ᓧ', 8803 => 'ᓨ', 8804 => 'ᓩ', 8805 => 'ᓪ', 8806 => 'ᓫ', 8807 => 'ᓬ', 8808 => 'ᓭ', 8809 => 'ᓮ', 8810 => 'ᓯ', 8811 => 'ᓰ', 8812 => 'ᓱ', 8813 => 'ᓲ', 8814 => 'ᓳ', 8815 => 'ᓴ', 8816 => 'ᓵ', 8817 => 'ᓶ', 8818 => 'ᓷ', 8819 => 'ᓸ', 8820 => 'ᓹ', 8821 => 'ᓺ', 8822 => 'ᓻ', 8823 => 'ᓼ', 8824 => 'ᓽ', 8825 => 'ᓾ', 8826 => 'ᓿ', 8827 => 'ᔀ', 8828 => 'ᔁ', 8829 => 'ᔂ', 8830 => 'ᔃ', 8831 => 'ᔄ', 8832 => 'ᔅ', 8833 => 'ᔆ', 8834 => 'ᔇ', 8835 => 'ᔈ', 8836 => 'ᔉ', 8837 => 'ᔊ', 8838 => 'ᔋ', 8839 => 'ᔌ', 8840 => 'ᔍ', 8841 => 'ᔎ', 8842 => 'ᔏ', 8843 => 'ᔐ', 8844 => 'ᔑ', 8845 => 'ᔒ', 8846 => 'ᔓ', 8847 => 'ᔔ', 8848 => 'ᔕ', 8849 => 'ᔖ', 8850 => 'ᔗ', 8851 => 'ᔘ', 8852 => 'ᔙ', 8853 => 'ᔚ', 8854 => 'ᔛ', 8855 => 'ᔜ', 8856 => 'ᔝ', 8857 => 'ᔞ', 8858 => 'ᔟ', 8859 => 'ᔠ', 8860 => 'ᔡ', 8861 => 'ᔢ', 8862 => 'ᔣ', 8863 => 'ᔤ', 8864 => 'ᔥ', 8865 => 'ᔦ', 8866 => 'ᔧ', 8867 => 'ᔨ', 8868 => 'ᔩ', 8869 => 'ᔪ', 8870 => 'ᔫ', 8871 => 'ᔬ', 8872 => 'ᔭ', 8873 => 'ᔮ', 8874 => 'ᔯ', 8875 => 'ᔰ', 8876 => 'ᔱ', 8877 => 'ᔲ', 8878 => 'ᔳ', 8879 => 'ᔴ', 8880 => 'ᔵ', 8881 => 'ᔶ', 8882 => 'ᔷ', 8883 => 'ᔸ', 8884 => 'ᔹ', 8885 => 'ᔺ', 8886 => 'ᔻ', 8887 => 'ᔼ', 8888 => 'ᔽ', 8889 => 'ᔾ', 8890 => 'ᔿ', 8891 => 'ᕀ', 8892 => 'ᕁ', 8893 => 'ᕂ', 8894 => 'ᕃ', 8895 => 'ᕄ', 8896 => 'ᕅ', 8897 => 'ᕆ', 8898 => 'ᕇ', 8899 => 'ᕈ', 8900 => 'ᕉ', 8901 => 'ᕊ', 8902 => 'ᕋ', 8903 => 'ᕌ', 8904 => 'ᕍ', 8905 => 'ᕎ', 8906 => 'ᕏ', 8907 => 'ᕐ', 8908 => 'ᕑ', 8909 => 'ᕒ', 8910 => 'ᕓ', 8911 => 'ᕔ', 8912 => 'ᕕ', 8913 => 'ᕖ', 8914 => 'ᕗ', 8915 => 'ᕘ', 8916 => 'ᕙ', 8917 => 'ᕚ', 8918 => 'ᕛ', 8919 => 'ᕜ', 8920 => 'ᕝ', 8921 => 'ᕞ', 8922 => 'ᕟ', 8923 => 'ᕠ', 8924 => 'ᕡ', 8925 => 'ᕢ', 8926 => 'ᕣ', 8927 => 'ᕤ', 8928 => 'ᕥ', 8929 => 'ᕦ', 8930 => 'ᕧ', 8931 => 'ᕨ', 8932 => 'ᕩ', 8933 => 'ᕪ', 8934 => 'ᕫ', 8935 => 'ᕬ', 8936 => 'ᕭ', 8937 => 'ᕮ', 8938 => 'ᕯ', 8939 => 'ᕰ', 8940 => 'ᕱ', 8941 => 'ᕲ', 8942 => 'ᕳ', 8943 => 'ᕴ', 8944 => 'ᕵ', 8945 => 'ᕶ', 8946 => 'ᕷ', 8947 => 'ᕸ', 8948 => 'ᕹ', 8949 => 'ᕺ', 8950 => 'ᕻ', 8951 => 'ᕽ', 8952 => 'ᙯ', 8953 => 'ᕾ', 8954 => 'ᕿ', 8955 => 'ᖀ', 8956 => 'ᖁ', 8957 => 'ᖂ', 8958 => 'ᖃ', 8959 => 'ᖄ', 8960 => 'ᖅ', 8961 => 'ᖆ', 8962 => 'ᖇ', 8963 => 'ᖈ', 8964 => 'ᖉ', 8965 => 'ᖊ', 8966 => 'ᖋ', 8967 => 'ᖌ', 8968 => 'ᖍ', 8969 => 'ᙰ', 8970 => 'ᖎ', 8971 => 'ᖏ', 8972 => 'ᖐ', 8973 => 'ᖑ', 8974 => 'ᖒ', 8975 => 'ᖓ', 8976 => 'ᖔ', 8977 => 'ᖕ', 8978 => 'ᙱ', 8979 => 'ᙲ', 8980 => 'ᙳ', 8981 => 'ᙴ', 8982 => 'ᙵ', 8983 => 'ᙶ', 8984 => 'ᖖ', 8985 => 'ᖗ', 8986 => 'ᖘ', 8987 => 'ᖙ', 8988 => 'ᖚ', 8989 => 'ᖛ', 8990 => 'ᖜ', 8991 => 'ᖝ', 8992 => 'ᖞ', 8993 => 'ᖟ', 8994 => 'ᖠ', 8995 => 'ᖡ', 8996 => 'ᖢ', 8997 => 'ᖣ', 8998 => 'ᖤ', 8999 => 'ᖥ', 9000 => 'ᖦ', 9001 => 'ᕼ', 9002 => 'ᖧ', 9003 => 'ᖨ', 9004 => 'ᖩ', 9005 => 'ᖪ', 9006 => 'ᖫ', 9007 => 'ᖬ', 9008 => 'ᖭ', 9009 => 'ᖮ', 9010 => 'ᖯ', 9011 => 'ᖰ', 9012 => 'ᖱ', 9013 => 'ᖲ', 9014 => 'ᖳ', 9015 => 'ᖴ', 9016 => 'ᖵ', 9017 => 'ᖶ', 9018 => 'ᖷ', 9019 => 'ᖸ', 9020 => 'ᖹ', 9021 => 'ᖺ', 9022 => 'ᖻ', 9023 => 'ᖼ', 9024 => 'ᖽ', 9025 => 'ᖾ', 9026 => 'ᖿ', 9027 => 'ᗀ', 9028 => 'ᗁ', 9029 => 'ᗂ', 9030 => 'ᗃ', 9031 => 'ᗄ', 9032 => 'ᗅ', 9033 => 'ᗆ', 9034 => 'ᗇ', 9035 => 'ᗈ', 9036 => 'ᗉ', 9037 => 'ᗊ', 9038 => 'ᗋ', 9039 => 'ᗌ', 9040 => 'ᗍ', 9041 => 'ᗎ', 9042 => 'ᗏ', 9043 => 'ᗐ', 9044 => 'ᗑ', 9045 => 'ᗒ', 9046 => 'ᗓ', 9047 => 'ᗔ', 9048 => 'ᗕ', 9049 => 'ᗖ', 9050 => 'ᗗ', 9051 => 'ᗘ', 90