MediaWiki  master
Parser.php
Go to the documentation of this file.
1 <?php
57 use Psr\Log\LoggerInterface;
58 use Wikimedia\IPUtils;
59 use Wikimedia\Parsoid\Core\SectionMetadata;
60 use Wikimedia\Parsoid\Core\TOCData;
61 use Wikimedia\ScopedCallback;
62 
103 #[AllowDynamicProperties]
104 class Parser {
105 
106  # Flags for Parser::setFunctionHook
107  public const SFH_NO_HASH = 1;
108  public const SFH_OBJECT_ARGS = 2;
109 
110  # Constants needed for external link processing
111  # Everything except bracket, space, or control characters
112  # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
113  # as well as U+3000 is IDEOGRAPHIC SPACE for T21052
114  # \x{FFFD} is the Unicode replacement character, which the HTML5 spec
115  # uses to replace invalid HTML characters.
116  public const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]';
117  # Simplified expression to match an IPv4 or IPv6 address, or
118  # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
119  // phpcs:ignore Generic.Files.LineLength
120  private const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
121  # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
122  // phpcs:ignore Generic.Files.LineLength
123  private const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
124  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
125 
126  # Regular expression for a non-newline space
127  private const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
128 
133  public const PTD_FOR_INCLUSION = Preprocessor::DOM_FOR_INCLUSION;
134 
135  # Allowed values for $this->mOutputType
136  # Parameter to startExternalParse().
137  public const OT_HTML = 1; # like parse()
138  public const OT_WIKI = 2; # like preSaveTransform()
139  public const OT_PREPROCESS = 3; # like preprocess()
140  public const OT_MSG = 3;
141  # like extractSections() - portions of the original are returned unchanged.
142  public const OT_PLAIN = 4;
143 
161  public const MARKER_SUFFIX = "-QINU`\"'\x7f";
162  public const MARKER_PREFIX = "\x7f'\"`UNIQ-";
163 
176  public const TOC_START = '<mw:toc>';
177 
183  public const TOC_END = '</mw:toc>';
184 
202  public const TOC_PLACEHOLDER = '<meta property="mw:PageProp/toc" />';
203 
211  private const TOC_PLACEHOLDER_REGEX = '/<meta\\b[^>]*\\bproperty\\s*=\\s*"mw:PageProp\\/toc"[^>]*\\/>/';
212 
213  # Persistent:
214  private $mTagHooks = [];
215  private $mFunctionHooks = [];
216  private $mFunctionSynonyms = [ 0 => [], 1 => [] ];
217  private $mStripList = [];
218  private $mVarCache = [];
219  private $mImageParams = [];
220  private $mImageParamsMagicArray = [];
222  public $mMarkerIndex = 0;
223 
224  # Initialised by initializeVariables()
225 
229  private $mVariables;
230 
234  private $mSubstWords;
235 
236  # Initialised in constructor
237  private $mExtLinkBracketedRegex;
238 
244  private $urlUtils;
245 
246  # Initialized in constructor
250  private $mPreprocessor;
251 
252  # Cleared with clearState():
256  private $mOutput;
257  private $mAutonumber;
258 
262  private $mStripState;
263 
267  private $mLinkHolders;
268 
272  private $mLinkID;
273  private $mIncludeSizes;
278  private $mTplRedirCache;
280  public $mHeadings;
284  private $mDoubleUnderscores;
286  public $mExpensiveFunctionCount; # number of expensive parser function calls
287  private $mShowToc;
288  private $mForceTocPosition;
290  private $mTplDomCache;
291 
295  private $mUser;
296 
297  # Temporary
298  # These are variables reset at least once per parse regardless of $clearState
299 
304  public $mOptions;
305 
313  public $mTitle; # Title context, used for self-link rendering and similar things
314  private $mOutputType; # Output type, one of the OT_xxx constants
316  public $ot; # Shortcut alias, see setOutputType()
317  private $mRevisionId; # ID to display in {{REVISIONID}} tags
318  private $mRevisionTimestamp; # The timestamp of the specified revision ID
319  private $mRevisionUser; # User to display in {{REVISIONUSER}} tag
320  private $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
321  private $mInputSize = false; # For {{PAGESIZE}} on current page.
322 
324  private $mRevisionRecordObject;
325 
331  private $mLangLinkLanguages;
332 
339  private $currentRevisionCache;
340 
345  private $mInParse = false;
346 
348  private $mProfiler;
349 
353  private $mLinkRenderer;
354 
356  private $magicWordFactory;
357 
359  private $contLang;
360 
362  private $languageConverterFactory;
363 
365  private $factory;
366 
368  private $specialPageFactory;
369 
371  private $titleFormatter;
372 
380  private $svcOptions;
381 
383  private $linkRendererFactory;
384 
386  private $nsInfo;
387 
389  private $logger;
390 
392  private $badFileLookup;
393 
395  private $hookContainer;
396 
398  private $hookRunner;
399 
401  private $tidy;
402 
404  private $userOptionsLookup;
405 
407  private $userFactory;
408 
410  private $httpRequestFactory;
411 
413  private $trackingCategories;
414 
416  private $signatureValidatorFactory;
417 
419  private $userNameUtils;
420 
424  public const CONSTRUCTOR_OPTIONS = [
425  // See documentation for the corresponding config options
426  // Many of these are only used in (eg) CoreMagicVariables
427  MainConfigNames::AllowDisplayTitle,
428  MainConfigNames::AllowSlowParserFunctions,
429  MainConfigNames::ArticlePath,
430  MainConfigNames::EnableScaryTranscluding,
431  MainConfigNames::ExtraInterlanguageLinkPrefixes,
432  MainConfigNames::FragmentMode,
433  MainConfigNames::Localtimezone,
434  MainConfigNames::MaxSigChars,
435  MainConfigNames::MaxTocLevel,
436  MainConfigNames::MiserMode,
437  MainConfigNames::RawHtml,
438  MainConfigNames::ScriptPath,
439  MainConfigNames::Server,
440  MainConfigNames::ServerName,
441  MainConfigNames::ShowHostnames,
442  MainConfigNames::SignatureValidation,
443  MainConfigNames::Sitename,
444  MainConfigNames::StylePath,
445  MainConfigNames::TranscludeCacheExpiry,
446  MainConfigNames::PreprocessorCacheThreshold,
447  ];
448 
475  public function __construct(
476  ServiceOptions $svcOptions,
477  MagicWordFactory $magicWordFactory,
478  Language $contLang,
479  ParserFactory $factory,
480  UrlUtils $urlUtils,
481  SpecialPageFactory $spFactory,
482  LinkRendererFactory $linkRendererFactory,
483  NamespaceInfo $nsInfo,
484  LoggerInterface $logger,
485  BadFileLookup $badFileLookup,
486  LanguageConverterFactory $languageConverterFactory,
487  HookContainer $hookContainer,
488  TidyDriverBase $tidy,
489  WANObjectCache $wanCache,
490  UserOptionsLookup $userOptionsLookup,
491  UserFactory $userFactory,
492  TitleFormatter $titleFormatter,
493  HttpRequestFactory $httpRequestFactory,
494  TrackingCategories $trackingCategories,
495  SignatureValidatorFactory $signatureValidatorFactory,
496  UserNameUtils $userNameUtils
497  ) {
498  if ( ParserFactory::$inParserFactory === 0 ) {
499  // Direct construction of Parser was deprecated in 1.34 and
500  // removed in 1.36; use a ParserFactory instead.
501  throw new MWException( 'Direct construction of Parser not allowed' );
502  }
503  $svcOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
504  $this->svcOptions = $svcOptions;
505 
506  $this->urlUtils = $urlUtils;
507  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->urlUtils->validProtocols() . ')' .
508  self::EXT_LINK_ADDR .
509  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*)\]/Su';
510 
511  $this->magicWordFactory = $magicWordFactory;
512 
513  $this->contLang = $contLang;
514 
515  $this->factory = $factory;
516  $this->specialPageFactory = $spFactory;
517  $this->linkRendererFactory = $linkRendererFactory;
518  $this->nsInfo = $nsInfo;
519  $this->logger = $logger;
520  $this->badFileLookup = $badFileLookup;
521 
522  $this->languageConverterFactory = $languageConverterFactory;
523 
524  $this->hookContainer = $hookContainer;
525  $this->hookRunner = new HookRunner( $hookContainer );
526 
527  $this->tidy = $tidy;
528 
529  $this->mPreprocessor = new Preprocessor_Hash(
530  $this,
531  $wanCache,
532  [
533  'cacheThreshold' => $svcOptions->get( MainConfigNames::PreprocessorCacheThreshold ),
534  'disableLangConversion' => $languageConverterFactory->isConversionDisabled(),
535  ]
536  );
537 
538  $this->userOptionsLookup = $userOptionsLookup;
539  $this->userFactory = $userFactory;
540  $this->titleFormatter = $titleFormatter;
541  $this->httpRequestFactory = $httpRequestFactory;
542  $this->trackingCategories = $trackingCategories;
543  $this->signatureValidatorFactory = $signatureValidatorFactory;
544  $this->userNameUtils = $userNameUtils;
545 
546  // These steps used to be done in "::firstCallInit()"
547  // (if you're chasing a reference from some old code)
549  $this,
551  );
553  $this,
555  );
556  $this->initializeVariables();
557 
558  $this->hookRunner->onParserFirstCallInit( $this );
559  }
560 
564  public function __destruct() {
565  if ( isset( $this->mLinkHolders ) ) {
566  // @phan-suppress-next-line PhanTypeObjectUnsetDeclaredProperty
567  unset( $this->mLinkHolders );
568  }
569  // @phan-suppress-next-line PhanTypeSuspiciousNonTraversableForeach
570  foreach ( $this as $name => $value ) {
571  unset( $this->$name );
572  }
573  }
574 
578  public function __clone() {
579  $this->mInParse = false;
580 
581  // T58226: When you create a reference "to" an object field, that
582  // makes the object field itself be a reference too (until the other
583  // reference goes out of scope). When cloning, any field that's a
584  // reference is copied as a reference in the new object. Both of these
585  // are defined PHP5 behaviors, as inconvenient as it is for us when old
586  // hooks from PHP4 days are passing fields by reference.
587  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
588  // Make a non-reference copy of the field, then rebind the field to
589  // reference the new copy.
590  $tmp = $this->$k;
591  $this->$k =& $tmp;
592  unset( $tmp );
593  }
594 
595  $this->mPreprocessor = clone $this->mPreprocessor;
596  $this->mPreprocessor->resetParser( $this );
597 
598  $this->hookRunner->onParserCloned( $this );
599  }
600 
608  public function firstCallInit() {
609  /*
610  * This method should be hard-deprecated once remaining calls are
611  * removed; it no longer does anything.
612  */
613  }
614 
620  public function clearState() {
621  $this->resetOutput();
622  $this->mAutonumber = 0;
623  $this->mLinkHolders = new LinkHolderArray(
624  $this,
625  $this->getContentLanguageConverter(),
626  $this->getHookContainer()
627  );
628  $this->mLinkID = 0;
629  $this->mRevisionTimestamp = null;
630  $this->mRevisionId = null;
631  $this->mRevisionUser = null;
632  $this->mRevisionSize = null;
633  $this->mRevisionRecordObject = null;
634  $this->mVarCache = [];
635  $this->mUser = null;
636  $this->mLangLinkLanguages = [];
637  $this->currentRevisionCache = null;
638 
639  $this->mStripState = new StripState( $this );
640 
641  # Clear these on every parse, T6549
642  $this->mTplRedirCache = [];
643  $this->mTplDomCache = [];
644 
645  $this->mShowToc = true;
646  $this->mForceTocPosition = false;
647  $this->mIncludeSizes = [
648  'post-expand' => 0,
649  'arg' => 0,
650  ];
651  $this->mPPNodeCount = 0;
652  $this->mHighestExpansionDepth = 0;
653  $this->mHeadings = [];
654  $this->mDoubleUnderscores = [];
655  $this->mExpensiveFunctionCount = 0;
656 
657  $this->mProfiler = new SectionProfiler();
658 
659  $this->hookRunner->onParserClearState( $this );
660  }
661 
666  public function resetOutput() {
667  $this->mOutput = new ParserOutput;
668  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
669  }
670 
689  public function parse(
690  $text, PageReference $page, ParserOptions $options,
691  $linestart = true, $clearState = true, $revid = null
692  ) {
693  if ( $clearState ) {
694  // We use U+007F DELETE to construct strip markers, so we have to make
695  // sure that this character does not occur in the input text.
696  $text = strtr( $text, "\x7f", "?" );
697  $magicScopeVariable = $this->lock();
698  }
699  // Strip U+0000 NULL (T159174)
700  $text = str_replace( "\000", '', $text );
701 
702  $this->startParse( $page, $options, self::OT_HTML, $clearState );
703 
704  $this->currentRevisionCache = null;
705  $this->mInputSize = strlen( $text );
706  $this->mOutput->resetParseStartTime();
707 
708  $oldRevisionId = $this->mRevisionId;
709  $oldRevisionRecordObject = $this->mRevisionRecordObject;
710  $oldRevisionTimestamp = $this->mRevisionTimestamp;
711  $oldRevisionUser = $this->mRevisionUser;
712  $oldRevisionSize = $this->mRevisionSize;
713  if ( $revid !== null ) {
714  $this->mRevisionId = $revid;
715  $this->mRevisionRecordObject = null;
716  $this->mRevisionTimestamp = null;
717  $this->mRevisionUser = null;
718  $this->mRevisionSize = null;
719  }
720 
721  $text = $this->internalParse( $text );
722  $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
723 
724  $text = $this->internalParseHalfParsed( $text, true, $linestart );
725 
733  if ( !$options->getDisableTitleConversion()
734  && !isset( $this->mDoubleUnderscores['nocontentconvert'] )
735  && !isset( $this->mDoubleUnderscores['notitleconvert'] )
736  && $this->mOutput->getDisplayTitle() === false
737  ) {
738  $titleText = $this->getTargetLanguageConverter()->getConvRuleTitle();
739  if ( $titleText !== false ) {
740  $titleText = Sanitizer::removeSomeTags( $titleText );
741  } else {
742  [ $nsText, $nsSeparator, $mainText ] = $this->getTargetLanguageConverter()->convertSplitTitle( $page );
743  // In the future, those three pieces could be stored separately rather than joined into $titleText,
744  // and OutputPage would format them and join them together, to resolve T314399.
745  $titleText = self::formatPageTitle( $nsText, $nsSeparator, $mainText );
746  }
747  $this->mOutput->setTitleText( $titleText );
748  }
749 
750  # Compute runtime adaptive expiry if set
751  $this->mOutput->finalizeAdaptiveCacheExpiry();
752 
753  # Warn if too many heavyweight parser functions were used
754  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
755  $this->limitationWarn( 'expensive-parserfunction',
756  $this->mExpensiveFunctionCount,
757  $this->mOptions->getExpensiveParserFunctionLimit()
758  );
759  }
760 
761  # Information on limits, for the benefit of users who try to skirt them
762  if ( MediaWikiServices::getInstance()->getMainConfig()->get(
763  MainConfigNames::EnableParserLimitReporting ) ) {
764  $this->makeLimitReport();
765  }
766 
767  # Wrap non-interface parser output in a <div> so it can be targeted
768  # with CSS (T37247)
769  $class = $this->mOptions->getWrapOutputClass();
770  if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
771  $this->mOutput->addWrapperDivClass( $class );
772  }
773 
774  $this->mOutput->setText( $text );
775 
776  $this->mRevisionId = $oldRevisionId;
777  $this->mRevisionRecordObject = $oldRevisionRecordObject;
778  $this->mRevisionTimestamp = $oldRevisionTimestamp;
779  $this->mRevisionUser = $oldRevisionUser;
780  $this->mRevisionSize = $oldRevisionSize;
781  $this->mInputSize = false;
782  $this->currentRevisionCache = null;
783 
784  return $this->mOutput;
785  }
786 
790  protected function makeLimitReport() {
791  $maxIncludeSize = $this->mOptions->getMaxIncludeSize();
792 
793  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
794  if ( $cpuTime !== null ) {
795  $this->mOutput->setLimitReportData( 'limitreport-cputime',
796  sprintf( "%.3f", $cpuTime )
797  );
798  }
799 
800  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
801  $this->mOutput->setLimitReportData( 'limitreport-walltime',
802  sprintf( "%.3f", $wallTime )
803  );
804 
805  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
806  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
807  );
808  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
809  [ $this->mIncludeSizes['post-expand'], $maxIncludeSize ]
810  );
811  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
812  [ $this->mIncludeSizes['arg'], $maxIncludeSize ]
813  );
814  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
815  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
816  );
817  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
818  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
819  );
820 
821  foreach ( $this->mStripState->getLimitReport() as [ $key, $value ] ) {
822  $this->mOutput->setLimitReportData( $key, $value );
823  }
824 
825  $this->hookRunner->onParserLimitReportPrepare( $this, $this->mOutput );
826 
827  // Add on template profiling data in human/machine readable way
828  $dataByFunc = $this->mProfiler->getFunctionStats();
829  uasort( $dataByFunc, static function ( $a, $b ) {
830  return $b['real'] <=> $a['real']; // descending order
831  } );
832  $profileReport = [];
833  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
834  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
835  $item['%real'], $item['real'], $item['calls'],
836  htmlspecialchars( $item['name'] ) );
837  }
838 
839  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
840 
841  // Add other cache related metadata
842  if ( $this->svcOptions->get( MainConfigNames::ShowHostnames ) ) {
843  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
844  }
845  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
846  $this->mOutput->getCacheTime() );
847  $this->mOutput->setLimitReportData( 'cachereport-ttl',
848  $this->mOutput->getCacheExpiry() );
849  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
850  $this->mOutput->hasReducedExpiry() );
851  }
852 
878  public function recursiveTagParse( $text, $frame = false ) {
879  $text = $this->internalParse( $text, false, $frame );
880  return $text;
881  }
882 
902  public function recursiveTagParseFully( $text, $frame = false ) {
903  $text = $this->recursiveTagParse( $text, $frame );
904  $text = $this->internalParseHalfParsed( $text, false );
905  return $text;
906  }
907 
927  public function parseExtensionTagAsTopLevelDoc( $text ) {
928  $text = $this->recursiveTagParse( $text );
929  $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
930  $text = $this->internalParseHalfParsed( $text, true );
931  return $text;
932  }
933 
946  public function preprocess(
947  $text,
948  ?PageReference $page,
949  ParserOptions $options,
950  $revid = null,
951  $frame = false
952  ) {
953  $magicScopeVariable = $this->lock();
954  $this->startParse( $page, $options, self::OT_PREPROCESS, true );
955  if ( $revid !== null ) {
956  $this->mRevisionId = $revid;
957  }
958  $this->hookRunner->onParserBeforePreprocess( $this, $text, $this->mStripState );
959  $text = $this->replaceVariables( $text, $frame );
960  $text = $this->mStripState->unstripBoth( $text );
961  return $text;
962  }
963 
973  public function recursivePreprocess( $text, $frame = false ) {
974  $text = $this->replaceVariables( $text, $frame );
975  $text = $this->mStripState->unstripBoth( $text );
976  return $text;
977  }
978 
993  public function getPreloadText( $text, PageReference $page, ParserOptions $options, $params = [] ) {
994  $msg = new RawMessage( $text );
995  $text = $msg->params( $params )->plain();
996 
997  # Parser (re)initialisation
998  $magicScopeVariable = $this->lock();
999  $this->startParse( $page, $options, self::OT_PLAIN, true );
1000 
1002  $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
1003  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
1004  $text = $this->mStripState->unstripBoth( $text );
1005  return $text;
1006  }
1007 
1015  public function setUser( ?UserIdentity $user ) {
1016  $this->mUser = $user;
1017  }
1018 
1026  public function setTitle( Title $t = null ) {
1027  $this->setPage( $t );
1028  }
1029 
1035  public function getTitle(): Title {
1036  if ( !$this->mTitle ) {
1037  $this->mTitle = Title::makeTitle( NS_SPECIAL, 'Badtitle/Parser' );
1038  }
1039  return $this->mTitle;
1040  }
1041 
1048  public function setPage( ?PageReference $t = null ) {
1049  if ( !$t ) {
1050  $t = Title::makeTitle( NS_SPECIAL, 'Badtitle/Parser' );
1051  } else {
1052  // For now (early 1.37 alpha), always convert to Title, so we don't have to do it over
1053  // and over again in other methods. Eventually, we will no longer need to have a Title
1054  // instance internally.
1056  }
1057 
1058  if ( $t->hasFragment() ) {
1059  # Strip the fragment to avoid various odd effects
1060  $this->mTitle = $t->createFragmentTarget( '' );
1061  } else {
1062  $this->mTitle = $t;
1063  }
1064  }
1065 
1071  public function getPage(): ?PageReference {
1072  return $this->mTitle;
1073  }
1074 
1080  public function getOutputType(): int {
1081  return $this->mOutputType;
1082  }
1083 
1089  public function setOutputType( $ot ): void {
1090  $this->mOutputType = $ot;
1091  # Shortcut alias
1092  $this->ot = [
1093  'html' => $ot == self::OT_HTML,
1094  'wiki' => $ot == self::OT_WIKI,
1095  'pre' => $ot == self::OT_PREPROCESS,
1096  'plain' => $ot == self::OT_PLAIN,
1097  ];
1098  }
1099 
1107  public function OutputType( $x = null ) {
1108  wfDeprecated( __METHOD__, '1.35' );
1109  return wfSetVar( $this->mOutputType, $x );
1110  }
1111 
1116  public function getOutput() {
1117  return $this->mOutput;
1118  }
1119 
1124  public function getOptions() {
1125  return $this->mOptions;
1126  }
1127 
1133  public function setOptions( ParserOptions $options ): void {
1134  $this->mOptions = $options;
1135  }
1136 
1144  public function Options( $x = null ) {
1145  wfDeprecated( __METHOD__, '1.35' );
1146  return wfSetVar( $this->mOptions, $x );
1147  }
1148 
1153  public function nextLinkID() {
1154  return $this->mLinkID++;
1155  }
1156 
1161  public function setLinkID( $id ) {
1162  $this->mLinkID = $id;
1163  }
1164 
1171  public function getFunctionLang() {
1172  wfDeprecated( __METHOD__, '1.40' );
1173  return $this->getTargetLanguage();
1174  }
1175 
1184  public function getTargetLanguage() {
1185  $target = $this->mOptions->getTargetLanguage();
1186 
1187  if ( $target !== null ) {
1188  return $target;
1189  } elseif ( $this->mOptions->getInterfaceMessage() ) {
1190  return $this->mOptions->getUserLangObj();
1191  }
1192 
1193  return $this->getTitle()->getPageLanguage();
1194  }
1195 
1203  public function getUserIdentity(): UserIdentity {
1204  return $this->mUser ?? $this->getOptions()->getUserIdentity();
1205  }
1206 
1213  public function getPreprocessor() {
1214  return $this->mPreprocessor;
1215  }
1216 
1223  public function getLinkRenderer() {
1224  // XXX We make the LinkRenderer with current options and then cache it forever
1225  if ( !$this->mLinkRenderer ) {
1226  $this->mLinkRenderer = $this->linkRendererFactory->create();
1227  }
1228 
1229  return $this->mLinkRenderer;
1230  }
1231 
1238  public function getMagicWordFactory() {
1239  return $this->magicWordFactory;
1240  }
1241 
1248  public function getContentLanguage() {
1249  return $this->contLang;
1250  }
1251 
1258  public function getBadFileLookup() {
1259  return $this->badFileLookup;
1260  }
1261 
1281  public static function extractTagsAndParams( array $elements, $text, &$matches ) {
1282  static $n = 1;
1283  $stripped = '';
1284  $matches = [];
1285 
1286  $taglist = implode( '|', $elements );
1287  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i";
1288 
1289  while ( $text != '' ) {
1290  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1291  $stripped .= $p[0];
1292  if ( count( $p ) < 5 ) {
1293  break;
1294  }
1295  if ( count( $p ) > 5 ) {
1296  # comment
1297  $element = $p[4];
1298  $attributes = '';
1299  $close = '';
1300  $inside = $p[5];
1301  } else {
1302  # tag
1303  [ , $element, $attributes, $close, $inside ] = $p;
1304  }
1305 
1306  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
1307  $stripped .= $marker;
1308 
1309  if ( $close === '/>' ) {
1310  # Empty element tag, <tag />
1311  $content = null;
1312  $text = $inside;
1313  $tail = null;
1314  } else {
1315  if ( $element === '!--' ) {
1316  $end = '/(-->)/';
1317  } else {
1318  $end = "/(<\\/$element\\s*>)/i";
1319  }
1320  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1321  $content = $q[0];
1322  if ( count( $q ) < 3 ) {
1323  # No end tag -- let it run out to the end of the text.
1324  $tail = '';
1325  $text = '';
1326  } else {
1327  [ , $tail, $text ] = $q;
1328  }
1329  }
1330 
1331  $matches[$marker] = [ $element,
1332  $content,
1333  Sanitizer::decodeTagAttributes( $attributes ),
1334  "<$element$attributes$close$content$tail" ];
1335  }
1336  return $stripped;
1337  }
1338 
1344  public function getStripList() {
1345  return $this->mStripList;
1346  }
1347 
1352  public function getStripState() {
1353  return $this->mStripState;
1354  }
1355 
1365  public function insertStripItem( $text ) {
1366  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1367  $this->mMarkerIndex++;
1368  $this->mStripState->addGeneral( $marker, $text );
1369  return $marker;
1370  }
1371 
1378  private function handleTables( $text ) {
1379  $lines = StringUtils::explode( "\n", $text );
1380  $out = '';
1381  $td_history = []; # Is currently a td tag open?
1382  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1383  $tr_history = []; # Is currently a tr tag open?
1384  $tr_attributes = []; # history of tr attributes
1385  $has_opened_tr = []; # Did this table open a <tr> element?
1386  $indent_level = 0; # indent level of the table
1387 
1388  foreach ( $lines as $outLine ) {
1389  $line = trim( $outLine );
1390 
1391  if ( $line === '' ) { # empty line, go to next line
1392  $out .= $outLine . "\n";
1393  continue;
1394  }
1395 
1396  $first_character = $line[0];
1397  $first_two = substr( $line, 0, 2 );
1398  $matches = [];
1399 
1400  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1401  # First check if we are starting a new table
1402  $indent_level = strlen( $matches[1] );
1403 
1404  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1405  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1406 
1407  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1408  array_push( $td_history, false );
1409  array_push( $last_tag_history, '' );
1410  array_push( $tr_history, false );
1411  array_push( $tr_attributes, '' );
1412  array_push( $has_opened_tr, false );
1413  } elseif ( count( $td_history ) == 0 ) {
1414  # Don't do any of the following
1415  $out .= $outLine . "\n";
1416  continue;
1417  } elseif ( $first_two === '|}' ) {
1418  # We are ending a table
1419  $line = '</table>' . substr( $line, 2 );
1420  $last_tag = array_pop( $last_tag_history );
1421 
1422  if ( !array_pop( $has_opened_tr ) ) {
1423  $line = "<tr><td></td></tr>{$line}";
1424  }
1425 
1426  if ( array_pop( $tr_history ) ) {
1427  $line = "</tr>{$line}";
1428  }
1429 
1430  if ( array_pop( $td_history ) ) {
1431  $line = "</{$last_tag}>{$line}";
1432  }
1433  array_pop( $tr_attributes );
1434  if ( $indent_level > 0 ) {
1435  $outLine = rtrim( $line ) . str_repeat( '</dd></dl>', $indent_level );
1436  } else {
1437  $outLine = $line;
1438  }
1439  } elseif ( $first_two === '|-' ) {
1440  # Now we have a table row
1441  $line = preg_replace( '#^\|-+#', '', $line );
1442 
1443  # Whats after the tag is now only attributes
1444  $attributes = $this->mStripState->unstripBoth( $line );
1445  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1446  array_pop( $tr_attributes );
1447  array_push( $tr_attributes, $attributes );
1448 
1449  $line = '';
1450  $last_tag = array_pop( $last_tag_history );
1451  array_pop( $has_opened_tr );
1452  array_push( $has_opened_tr, true );
1453 
1454  if ( array_pop( $tr_history ) ) {
1455  $line = '</tr>';
1456  }
1457 
1458  if ( array_pop( $td_history ) ) {
1459  $line = "</{$last_tag}>{$line}";
1460  }
1461 
1462  $outLine = $line;
1463  array_push( $tr_history, false );
1464  array_push( $td_history, false );
1465  array_push( $last_tag_history, '' );
1466  } elseif ( $first_character === '|'
1467  || $first_character === '!'
1468  || $first_two === '|+'
1469  ) {
1470  # This might be cell elements, td, th or captions
1471  if ( $first_two === '|+' ) {
1472  $first_character = '+';
1473  $line = substr( $line, 2 );
1474  } else {
1475  $line = substr( $line, 1 );
1476  }
1477 
1478  // Implies both are valid for table headings.
1479  if ( $first_character === '!' ) {
1480  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1481  }
1482 
1483  # Split up multiple cells on the same line.
1484  # FIXME : This can result in improper nesting of tags processed
1485  # by earlier parser steps.
1486  $cells = explode( '||', $line );
1487 
1488  $outLine = '';
1489 
1490  # Loop through each table cell
1491  foreach ( $cells as $cell ) {
1492  $previous = '';
1493  if ( $first_character !== '+' ) {
1494  $tr_after = array_pop( $tr_attributes );
1495  if ( !array_pop( $tr_history ) ) {
1496  $previous = "<tr{$tr_after}>\n";
1497  }
1498  array_push( $tr_history, true );
1499  array_push( $tr_attributes, '' );
1500  array_pop( $has_opened_tr );
1501  array_push( $has_opened_tr, true );
1502  }
1503 
1504  $last_tag = array_pop( $last_tag_history );
1505 
1506  if ( array_pop( $td_history ) ) {
1507  $previous = "</{$last_tag}>\n{$previous}";
1508  }
1509 
1510  if ( $first_character === '|' ) {
1511  $last_tag = 'td';
1512  } elseif ( $first_character === '!' ) {
1513  $last_tag = 'th';
1514  } elseif ( $first_character === '+' ) {
1515  $last_tag = 'caption';
1516  } else {
1517  $last_tag = '';
1518  }
1519 
1520  array_push( $last_tag_history, $last_tag );
1521 
1522  # A cell could contain both parameters and data
1523  $cell_data = explode( '|', $cell, 2 );
1524 
1525  # T2553: Note that a '|' inside an invalid link should not
1526  # be mistaken as delimiting cell parameters
1527  # Bug T153140: Neither should language converter markup.
1528  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1529  $cell = "{$previous}<{$last_tag}>" . trim( $cell );
1530  } elseif ( count( $cell_data ) == 1 ) {
1531  // Whitespace in cells is trimmed
1532  $cell = "{$previous}<{$last_tag}>" . trim( $cell_data[0] );
1533  } else {
1534  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1535  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1536  // Whitespace in cells is trimmed
1537  $cell = "{$previous}<{$last_tag}{$attributes}>" . trim( $cell_data[1] );
1538  }
1539 
1540  $outLine .= $cell;
1541  array_push( $td_history, true );
1542  }
1543  }
1544  $out .= $outLine . "\n";
1545  }
1546 
1547  # Closing open td, tr && table
1548  while ( count( $td_history ) > 0 ) {
1549  if ( array_pop( $td_history ) ) {
1550  $out .= "</td>\n";
1551  }
1552  if ( array_pop( $tr_history ) ) {
1553  $out .= "</tr>\n";
1554  }
1555  if ( !array_pop( $has_opened_tr ) ) {
1556  $out .= "<tr><td></td></tr>\n";
1557  }
1558 
1559  $out .= "</table>\n";
1560  }
1561 
1562  # Remove trailing line-ending (b/c)
1563  if ( substr( $out, -1 ) === "\n" ) {
1564  $out = substr( $out, 0, -1 );
1565  }
1566 
1567  # special case: don't return empty table
1568  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1569  $out = '';
1570  }
1571 
1572  return $out;
1573  }
1574 
1588  public function internalParse( $text, $isMain = true, $frame = false ) {
1589  $origText = $text;
1590 
1591  # Hook to suspend the parser in this state
1592  if ( !$this->hookRunner->onParserBeforeInternalParse( $this, $text, $this->mStripState ) ) {
1593  return $text;
1594  }
1595 
1596  # if $frame is provided, then use $frame for replacing any variables
1597  if ( $frame ) {
1598  # use frame depth to infer how include/noinclude tags should be handled
1599  # depth=0 means this is the top-level document; otherwise it's an included document
1600  if ( !$frame->depth ) {
1601  $flag = 0;
1602  } else {
1604  }
1605  $dom = $this->preprocessToDom( $text, $flag );
1606  $text = $frame->expand( $dom );
1607  } else {
1608  # if $frame is not provided, then use old-style replaceVariables
1609  $text = $this->replaceVariables( $text );
1610  }
1611 
1613  $text,
1614  // Callback from the Sanitizer for expanding items found in
1615  // HTML attribute values, so they can be safely tested and escaped.
1616  function ( &$text, $frame = false ) {
1617  $text = $this->replaceVariables( $text, $frame );
1618  $text = $this->mStripState->unstripBoth( $text );
1619  },
1620  false,
1621  [],
1622  []
1623  );
1624  $this->hookRunner->onInternalParseBeforeLinks( $this, $text, $this->mStripState );
1625 
1626  # Tables need to come after variable replacement for things to work
1627  # properly; putting them before other transformations should keep
1628  # exciting things like link expansions from showing up in surprising
1629  # places.
1630  $text = $this->handleTables( $text );
1631 
1632  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1633 
1634  $text = $this->handleDoubleUnderscore( $text );
1635 
1636  $text = $this->handleHeadings( $text );
1637  $text = $this->handleInternalLinks( $text );
1638  $text = $this->handleAllQuotes( $text );
1639  $text = $this->handleExternalLinks( $text );
1640 
1641  # handleInternalLinks may sometimes leave behind
1642  # absolute URLs, which have to be masked to hide them from handleExternalLinks
1643  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1644 
1645  $text = $this->handleMagicLinks( $text );
1646  $text = $this->finalizeHeadings( $text, $origText, $isMain );
1647 
1648  return $text;
1649  }
1650 
1658  return $this->languageConverterFactory->getLanguageConverter(
1659  $this->getTargetLanguage()
1660  );
1661  }
1662 
1668  private function getContentLanguageConverter(): ILanguageConverter {
1669  return $this->languageConverterFactory->getLanguageConverter(
1670  $this->getContentLanguage()
1671  );
1672  }
1673 
1681  protected function getHookContainer() {
1682  return $this->hookContainer;
1683  }
1684 
1693  protected function getHookRunner() {
1694  return $this->hookRunner;
1695  }
1696 
1706  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1707  $text = $this->mStripState->unstripGeneral( $text );
1708 
1709  $text = BlockLevelPass::doBlockLevels( $text, $linestart );
1710 
1711  $this->replaceLinkHoldersPrivate( $text );
1712 
1720  if ( !( $this->mOptions->getDisableContentConversion()
1721  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1722  && !$this->mOptions->getInterfaceMessage()
1723  ) {
1724  # The position of the convert() call should not be changed. it
1725  # assumes that the links are all replaced and the only thing left
1726  # is the <nowiki> mark.
1727  $text = $this->getTargetLanguageConverter()->convert( $text );
1728  // Record information necessary for language conversion of TOC.
1729  $this->mOutput->setExtensionData(
1730  // T303329: this should migrate out of extension data
1731  'core:target-lang',
1732  $this->getTargetLanguage()->getCode()
1733  );
1734  $this->mOutput->setExtensionData(
1735  // T303329: this should migrate out of extension data
1736  'core:target-lang-variant',
1737  $this->getTargetLanguageConverter()->getPreferredVariant()
1738  );
1739  } else {
1740  $this->mOutput->setOutputFlag( ParserOutputFlags::NO_TOC_CONVERSION );
1741  }
1742 
1743  $text = $this->mStripState->unstripNoWiki( $text );
1744 
1745  $text = $this->mStripState->unstripGeneral( $text );
1746 
1747  $text = $this->tidy->tidy( $text, [ Sanitizer::class, 'armorFrenchSpaces' ] );
1748 
1749  if ( $isMain ) {
1750  $this->hookRunner->onParserAfterTidy( $this, $text );
1751  }
1752 
1753  return $text;
1754  }
1755 
1766  private function handleMagicLinks( $text ) {
1767  $prots = $this->urlUtils->validAbsoluteProtocols();
1768  $urlChar = self::EXT_LINK_URL_CLASS;
1769  $addr = self::EXT_LINK_ADDR;
1770  $space = self::SPACE_NOT_NL; # non-newline space
1771  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1772  $spaces = "$space++"; # possessive match of 1 or more spaces
1773  $text = preg_replace_callback(
1774  '!(?: # Start cases
1775  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1776  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1777  (\b # m[3]: Free external links
1778  (?i:$prots)
1779  ($addr$urlChar*) # m[4]: Post-protocol path
1780  ) |
1781  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1782  ([0-9]+)\b |
1783  \bISBN $spaces ( # m[6]: ISBN, capture number
1784  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1785  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1786  [0-9Xx] # check digit
1787  )\b
1788  )!xu",
1789  [ $this, 'magicLinkCallback' ],
1790  $text
1791  );
1792  return $text;
1793  }
1794 
1800  private function magicLinkCallback( array $m ) {
1801  if ( isset( $m[1] ) && $m[1] !== '' ) {
1802  # Skip anchor
1803  return $m[0];
1804  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1805  # Skip HTML element
1806  return $m[0];
1807  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1808  # Free external link
1809  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1810  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1811  # RFC or PMID
1812  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1813  if ( !$this->mOptions->getMagicRFCLinks() ) {
1814  return $m[0];
1815  }
1816  $keyword = 'RFC';
1817  $urlmsg = 'rfcurl';
1818  $cssClass = 'mw-magiclink-rfc';
1819  $trackingCat = 'magiclink-tracking-rfc';
1820  $id = $m[5];
1821  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1822  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1823  return $m[0];
1824  }
1825  $keyword = 'PMID';
1826  $urlmsg = 'pubmedurl';
1827  $cssClass = 'mw-magiclink-pmid';
1828  $trackingCat = 'magiclink-tracking-pmid';
1829  $id = $m[5];
1830  } else {
1831  // Should never happen
1832  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1833  substr( $m[0], 0, 20 ) . '"' );
1834  }
1835  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1836  $this->addTrackingCategory( $trackingCat );
1837  return Linker::makeExternalLink(
1838  $url,
1839  "{$keyword} {$id}",
1840  true,
1841  $cssClass,
1842  [],
1843  $this->getTitle()
1844  );
1845  } elseif ( isset( $m[6] ) && $m[6] !== ''
1846  && $this->mOptions->getMagicISBNLinks()
1847  ) {
1848  # ISBN
1849  $isbn = $m[6];
1850  $space = self::SPACE_NOT_NL; # non-newline space
1851  $isbn = preg_replace( "/$space/", ' ', $isbn );
1852  $num = strtr( $isbn, [
1853  '-' => '',
1854  ' ' => '',
1855  'x' => 'X',
1856  ] );
1857  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1858  return $this->getLinkRenderer()->makeKnownLink(
1859  SpecialPage::getTitleFor( 'Booksources', $num ),
1860  "ISBN $isbn",
1861  [
1862  'class' => 'internal mw-magiclink-isbn',
1863  'title' => false // suppress title attribute
1864  ]
1865  );
1866  } else {
1867  return $m[0];
1868  }
1869  }
1870 
1880  private function makeFreeExternalLink( $url, $numPostProto ) {
1881  $trail = '';
1882 
1883  # The characters '<' and '>' (which were escaped by
1884  # internalRemoveHtmlTags()) should not be included in
1885  # URLs, per RFC 2396.
1886  # Make &nbsp; terminate a URL as well (bug T84937)
1887  $m2 = [];
1888  if ( preg_match(
1889  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1890  $url,
1891  $m2,
1892  PREG_OFFSET_CAPTURE
1893  ) ) {
1894  $trail = substr( $url, $m2[0][1] ) . $trail;
1895  $url = substr( $url, 0, $m2[0][1] );
1896  }
1897 
1898  # Move trailing punctuation to $trail
1899  $sep = ',;\.:!?';
1900  # If there is no left bracket, then consider right brackets fair game too
1901  if ( strpos( $url, '(' ) === false ) {
1902  $sep .= ')';
1903  }
1904 
1905  $urlRev = strrev( $url );
1906  $numSepChars = strspn( $urlRev, $sep );
1907  # Don't break a trailing HTML entity by moving the ; into $trail
1908  # This is in hot code, so use substr_compare to avoid having to
1909  # create a new string object for the comparison
1910  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1911  # more optimization: instead of running preg_match with a $
1912  # anchor, which can be slow, do the match on the reversed
1913  # string starting at the desired offset.
1914  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1915  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1916  $numSepChars--;
1917  }
1918  }
1919  if ( $numSepChars ) {
1920  $trail = substr( $url, -$numSepChars ) . $trail;
1921  $url = substr( $url, 0, -$numSepChars );
1922  }
1923 
1924  # Verify that we still have a real URL after trail removal, and
1925  # not just lone protocol
1926  if ( strlen( $trail ) >= $numPostProto ) {
1927  return $url . $trail;
1928  }
1929 
1930  $url = Sanitizer::cleanUrl( $url );
1931 
1932  # Is this an external image?
1933  $text = $this->maybeMakeExternalImage( $url );
1934  if ( $text === false ) {
1935  # Not an image, make a link
1936  $text = Linker::makeExternalLink(
1937  $url,
1938  $this->getTargetLanguageConverter()->markNoConversion( $url ),
1939  true,
1940  'free',
1941  $this->getExternalLinkAttribs( $url ),
1942  $this->getTitle()
1943  );
1944  # Register it in the output object...
1945  $this->mOutput->addExternalLink( $url );
1946  }
1947  return $text . $trail;
1948  }
1949 
1956  private function handleHeadings( $text ) {
1957  for ( $i = 6; $i >= 1; --$i ) {
1958  $h = str_repeat( '=', $i );
1959  // Trim non-newline whitespace from headings
1960  // Using \s* will break for: "==\n===\n" and parse as <h2>=</h2>
1961  $text = preg_replace( "/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m", "<h$i>\\1</h$i>", $text );
1962  }
1963  return $text;
1964  }
1965 
1973  private function handleAllQuotes( $text ) {
1974  $outtext = '';
1975  $lines = StringUtils::explode( "\n", $text );
1976  foreach ( $lines as $line ) {
1977  $outtext .= $this->doQuotes( $line ) . "\n";
1978  }
1979  $outtext = substr( $outtext, 0, -1 );
1980  return $outtext;
1981  }
1982 
1991  public function doQuotes( $text ) {
1992  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1993  $countarr = count( $arr );
1994  if ( $countarr == 1 ) {
1995  return $text;
1996  }
1997 
1998  // First, do some preliminary work. This may shift some apostrophes from
1999  // being mark-up to being text. It also counts the number of occurrences
2000  // of bold and italics mark-ups.
2001  $numbold = 0;
2002  $numitalics = 0;
2003  for ( $i = 1; $i < $countarr; $i += 2 ) {
2004  $thislen = strlen( $arr[$i] );
2005  // If there are ever four apostrophes, assume the first is supposed to
2006  // be text, and the remaining three constitute mark-up for bold text.
2007  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
2008  if ( $thislen == 4 ) {
2009  $arr[$i - 1] .= "'";
2010  $arr[$i] = "'''";
2011  $thislen = 3;
2012  } elseif ( $thislen > 5 ) {
2013  // If there are more than 5 apostrophes in a row, assume they're all
2014  // text except for the last 5.
2015  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
2016  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
2017  $arr[$i] = "'''''";
2018  $thislen = 5;
2019  }
2020  // Count the number of occurrences of bold and italics mark-ups.
2021  if ( $thislen == 2 ) {
2022  $numitalics++;
2023  } elseif ( $thislen == 3 ) {
2024  $numbold++;
2025  } elseif ( $thislen == 5 ) {
2026  $numitalics++;
2027  $numbold++;
2028  }
2029  }
2030 
2031  // If there is an odd number of both bold and italics, it is likely
2032  // that one of the bold ones was meant to be an apostrophe followed
2033  // by italics. Which one we cannot know for certain, but it is more
2034  // likely to be one that has a single-letter word before it.
2035  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
2036  $firstsingleletterword = -1;
2037  $firstmultiletterword = -1;
2038  $firstspace = -1;
2039  for ( $i = 1; $i < $countarr; $i += 2 ) {
2040  if ( strlen( $arr[$i] ) == 3 ) {
2041  $x1 = substr( $arr[$i - 1], -1 );
2042  $x2 = substr( $arr[$i - 1], -2, 1 );
2043  if ( $x1 === ' ' ) {
2044  if ( $firstspace == -1 ) {
2045  $firstspace = $i;
2046  }
2047  } elseif ( $x2 === ' ' ) {
2048  $firstsingleletterword = $i;
2049  // if $firstsingleletterword is set, we don't
2050  // look at the other options, so we can bail early.
2051  break;
2052  } elseif ( $firstmultiletterword == -1 ) {
2053  $firstmultiletterword = $i;
2054  }
2055  }
2056  }
2057 
2058  // If there is a single-letter word, use it!
2059  if ( $firstsingleletterword > -1 ) {
2060  $arr[$firstsingleletterword] = "''";
2061  $arr[$firstsingleletterword - 1] .= "'";
2062  } elseif ( $firstmultiletterword > -1 ) {
2063  // If not, but there's a multi-letter word, use that one.
2064  $arr[$firstmultiletterword] = "''";
2065  $arr[$firstmultiletterword - 1] .= "'";
2066  } elseif ( $firstspace > -1 ) {
2067  // ... otherwise use the first one that has neither.
2068  // (notice that it is possible for all three to be -1 if, for example,
2069  // there is only one pentuple-apostrophe in the line)
2070  $arr[$firstspace] = "''";
2071  $arr[$firstspace - 1] .= "'";
2072  }
2073  }
2074 
2075  // Now let's actually convert our apostrophic mush to HTML!
2076  $output = '';
2077  $buffer = '';
2078  $state = '';
2079  $i = 0;
2080  foreach ( $arr as $r ) {
2081  if ( ( $i % 2 ) == 0 ) {
2082  if ( $state === 'both' ) {
2083  $buffer .= $r;
2084  } else {
2085  $output .= $r;
2086  }
2087  } else {
2088  $thislen = strlen( $r );
2089  if ( $thislen == 2 ) {
2090  // two quotes - open or close italics
2091  if ( $state === 'i' ) {
2092  $output .= '</i>';
2093  $state = '';
2094  } elseif ( $state === 'bi' ) {
2095  $output .= '</i>';
2096  $state = 'b';
2097  } elseif ( $state === 'ib' ) {
2098  $output .= '</b></i><b>';
2099  $state = 'b';
2100  } elseif ( $state === 'both' ) {
2101  $output .= '<b><i>' . $buffer . '</i>';
2102  $state = 'b';
2103  } else { // $state can be 'b' or ''
2104  $output .= '<i>';
2105  $state .= 'i';
2106  }
2107  } elseif ( $thislen == 3 ) {
2108  // three quotes - open or close bold
2109  if ( $state === 'b' ) {
2110  $output .= '</b>';
2111  $state = '';
2112  } elseif ( $state === 'bi' ) {
2113  $output .= '</i></b><i>';
2114  $state = 'i';
2115  } elseif ( $state === 'ib' ) {
2116  $output .= '</b>';
2117  $state = 'i';
2118  } elseif ( $state === 'both' ) {
2119  $output .= '<i><b>' . $buffer . '</b>';
2120  $state = 'i';
2121  } else { // $state can be 'i' or ''
2122  $output .= '<b>';
2123  $state .= 'b';
2124  }
2125  } elseif ( $thislen == 5 ) {
2126  // five quotes - open or close both separately
2127  if ( $state === 'b' ) {
2128  $output .= '</b><i>';
2129  $state = 'i';
2130  } elseif ( $state === 'i' ) {
2131  $output .= '</i><b>';
2132  $state = 'b';
2133  } elseif ( $state === 'bi' ) {
2134  $output .= '</i></b>';
2135  $state = '';
2136  } elseif ( $state === 'ib' ) {
2137  $output .= '</b></i>';
2138  $state = '';
2139  } elseif ( $state === 'both' ) {
2140  $output .= '<i><b>' . $buffer . '</b></i>';
2141  $state = '';
2142  } else { // ($state == '')
2143  $buffer = '';
2144  $state = 'both';
2145  }
2146  }
2147  }
2148  $i++;
2149  }
2150  // Now close all remaining tags. Notice that the order is important.
2151  if ( $state === 'b' || $state === 'ib' ) {
2152  $output .= '</b>';
2153  }
2154  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
2155  $output .= '</i>';
2156  }
2157  if ( $state === 'bi' ) {
2158  $output .= '</b>';
2159  }
2160  // There might be lonely ''''', so make sure we have a buffer
2161  if ( $state === 'both' && $buffer ) {
2162  $output .= '<b><i>' . $buffer . '</i></b>';
2163  }
2164  return $output;
2165  }
2166 
2176  private function handleExternalLinks( $text ) {
2177  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
2178  // @phan-suppress-next-line PhanTypeComparisonFromArray See phan issue #3161
2179  if ( $bits === false ) {
2180  throw new RuntimeException( "PCRE failure" );
2181  }
2182  $s = array_shift( $bits );
2183 
2184  $i = 0;
2185  while ( $i < count( $bits ) ) {
2186  $url = $bits[$i++];
2187  $i++; // protocol
2188  $text = $bits[$i++];
2189  $trail = $bits[$i++];
2190 
2191  # The characters '<' and '>' (which were escaped by
2192  # internalRemoveHtmlTags()) should not be included in
2193  # URLs, per RFC 2396.
2194  $m2 = [];
2195  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
2196  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
2197  $url = substr( $url, 0, $m2[0][1] );
2198  }
2199 
2200  # If the link text is an image URL, replace it with an <img> tag
2201  # This happened by accident in the original parser, but some people used it extensively
2202  $img = $this->maybeMakeExternalImage( $text );
2203  if ( $img !== false ) {
2204  $text = $img;
2205  }
2206 
2207  $dtrail = '';
2208 
2209  # Set linktype for CSS
2210  $linktype = 'text';
2211 
2212  # No link text, e.g. [http://domain.tld/some.link]
2213  if ( $text == '' ) {
2214  # Autonumber
2215  $langObj = $this->getTargetLanguage();
2216  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
2217  $linktype = 'autonumber';
2218  } else {
2219  # Have link text, e.g. [http://domain.tld/some.link text]s
2220  # Check for trail
2221  [ $dtrail, $trail ] = Linker::splitTrail( $trail );
2222  }
2223 
2224  // Excluding protocol-relative URLs may avoid many false positives.
2225  if ( preg_match( '/^(?:' . $this->urlUtils->validAbsoluteProtocols() . ')/', $text ) ) {
2226  $text = $this->getTargetLanguageConverter()->markNoConversion( $text );
2227  }
2228 
2229  $url = Sanitizer::cleanUrl( $url );
2230 
2231  # Use the encoded URL
2232  # This means that users can paste URLs directly into the text
2233  # Funny characters like ö aren't valid in URLs anyway
2234  # This was changed in August 2004
2235  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
2236  $this->getExternalLinkAttribs( $url ), $this->getTitle() ) . $dtrail . $trail;
2237 
2238  # Register link in the output object.
2239  $this->mOutput->addExternalLink( $url );
2240  }
2241 
2242  // @phan-suppress-next-line PhanTypeMismatchReturnNullable False positive from array_shift
2243  return $s;
2244  }
2245 
2256  public static function getExternalLinkRel( $url = false, LinkTarget $title = null ) {
2257  $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
2258  $noFollowLinks = $mainConfig->get( MainConfigNames::NoFollowLinks );
2259  $noFollowNsExceptions = $mainConfig->get( MainConfigNames::NoFollowNsExceptions );
2260  $noFollowDomainExceptions = $mainConfig->get( MainConfigNames::NoFollowDomainExceptions );
2261  $ns = $title ? $title->getNamespace() : false;
2262  if ( $noFollowLinks && !in_array( $ns, $noFollowNsExceptions )
2263  && !wfMatchesDomainList( $url, $noFollowDomainExceptions )
2264  ) {
2265  return 'nofollow';
2266  }
2267  return null;
2268  }
2269 
2281  public function getExternalLinkAttribs( $url ) {
2282  $attribs = [];
2283  $rel = self::getExternalLinkRel( $url, $this->getTitle() );
2284 
2285  $target = $this->mOptions->getExternalLinkTarget();
2286  if ( $target ) {
2287  $attribs['target'] = $target;
2288  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
2289  // T133507. New windows can navigate parent cross-origin.
2290  // Including noreferrer due to lacking browser
2291  // support of noopener. Eventually noreferrer should be removed.
2292  if ( $rel !== '' ) {
2293  $rel .= ' ';
2294  }
2295  $rel .= 'noreferrer noopener';
2296  }
2297  }
2298  $attribs['rel'] = $rel;
2299  return $attribs;
2300  }
2301 
2312  public static function normalizeLinkUrl( $url ) {
2313  # Test for RFC 3986 IPv6 syntax
2314  $scheme = '[a-z][a-z0-9+.-]*:';
2315  $userinfo = '(?:[a-z0-9\-._~!$&\'()*+,;=:]|%[0-9a-f]{2})*';
2316  $ipv6Host = '\\[((?:[0-9a-f:]|%3[0-A]|%[46][1-6])+)\\]';
2317  if ( preg_match( "<^(?:{$scheme})?//(?:{$userinfo}@)?{$ipv6Host}(?:[:/?#].*|)$>i", $url, $m ) &&
2318  IPUtils::isValid( rawurldecode( $m[1] ) )
2319  ) {
2320  $isIPv6 = rawurldecode( $m[1] );
2321  } else {
2322  $isIPv6 = false;
2323  }
2324 
2325  # Make sure unsafe characters are encoded
2326  $url = preg_replace_callback(
2327  '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
2328  static function ( $m ) {
2329  return rawurlencode( $m[0] );
2330  },
2331  $url
2332  );
2333 
2334  $ret = '';
2335  $end = strlen( $url );
2336 
2337  # Fragment part - 'fragment'
2338  $start = strpos( $url, '#' );
2339  if ( $start !== false && $start < $end ) {
2340  $ret = self::normalizeUrlComponent(
2341  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
2342  $end = $start;
2343  }
2344 
2345  # Query part - 'query' minus &=+;
2346  $start = strpos( $url, '?' );
2347  if ( $start !== false && $start < $end ) {
2348  $ret = self::normalizeUrlComponent(
2349  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
2350  $end = $start;
2351  }
2352 
2353  # Scheme and path part - 'pchar'
2354  # (we assume no userinfo or encoded colons in the host)
2355  $ret = self::normalizeUrlComponent(
2356  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
2357 
2358  # Fix IPv6 syntax
2359  if ( $isIPv6 !== false ) {
2360  $ipv6Host = "%5B({$isIPv6})%5D";
2361  $ret = preg_replace(
2362  "<^((?:{$scheme})?//(?:{$userinfo}@)?){$ipv6Host}(?=[:/?#]|$)>i",
2363  "$1[$2]",
2364  $ret
2365  );
2366  }
2367 
2368  return $ret;
2369  }
2370 
2371  private static function normalizeUrlComponent( $component, $unsafe ) {
2372  $callback = static function ( $matches ) use ( $unsafe ) {
2373  $char = urldecode( $matches[0] );
2374  $ord = ord( $char );
2375  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2376  # Unescape it
2377  return $char;
2378  } else {
2379  # Leave it escaped, but use uppercase for a-f
2380  return strtoupper( $matches[0] );
2381  }
2382  };
2383  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2384  }
2385 
2394  private function maybeMakeExternalImage( $url ) {
2395  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2396  $imagesexception = !empty( $imagesfrom );
2397  $text = false;
2398  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2399  if ( $imagesexception && is_array( $imagesfrom ) ) {
2400  $imagematch = false;
2401  foreach ( $imagesfrom as $match ) {
2402  if ( strpos( $url, $match ) === 0 ) {
2403  $imagematch = true;
2404  break;
2405  }
2406  }
2407  } elseif ( $imagesexception ) {
2408  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2409  } else {
2410  $imagematch = false;
2411  }
2412 
2413  if ( $this->mOptions->getAllowExternalImages()
2414  || ( $imagesexception && $imagematch )
2415  ) {
2416  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2417  # Image found
2418  $text = Linker::makeExternalImage( $url );
2419  }
2420  }
2421  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2422  && preg_match( self::EXT_IMAGE_REGEX, $url )
2423  ) {
2424  $whitelist = explode(
2425  "\n",
2426  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2427  );
2428 
2429  foreach ( $whitelist as $entry ) {
2430  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2431  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2432  continue;
2433  }
2434  // @phan-suppress-next-line SecurityCheck-ReDoS preg_quote is not wanted here
2435  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2436  # Image matches a whitelist entry
2437  $text = Linker::makeExternalImage( $url );
2438  break;
2439  }
2440  }
2441  }
2442  return $text;
2443  }
2444 
2452  private function handleInternalLinks( $text ) {
2453  $this->mLinkHolders->merge( $this->handleInternalLinks2( $text ) );
2454  return $text;
2455  }
2456 
2462  private function handleInternalLinks2( &$s ) {
2463  static $tc = false, $e1, $e1_img;
2464  # the % is needed to support urlencoded titles as well
2465  if ( !$tc ) {
2466  $tc = Title::legalChars() . '#%';
2467  # Match a link having the form [[namespace:link|alternate]]trail
2468  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2469  # Match cases where there is no "]]", which might still be images
2470  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2471  }
2472 
2473  $holders = new LinkHolderArray(
2474  $this,
2475  $this->getContentLanguageConverter(),
2476  $this->getHookContainer() );
2477 
2478  # split the entire text string on occurrences of [[
2479  $a = StringUtils::explode( '[[', ' ' . $s );
2480  # get the first element (all text up to first [[), and remove the space we added
2481  $s = $a->current();
2482  $a->next();
2483  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2484  $s = substr( $s, 1 );
2485 
2486  $nottalk = !$this->getTitle()->isTalkPage();
2487 
2488  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2489  $e2 = null;
2490  if ( $useLinkPrefixExtension ) {
2491  # Match the end of a line for a word that's not followed by whitespace,
2492  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2493  $charset = $this->contLang->linkPrefixCharset();
2494  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2495  $m = [];
2496  if ( preg_match( $e2, $s, $m ) ) {
2497  $first_prefix = $m[2];
2498  } else {
2499  $first_prefix = false;
2500  }
2501  $prefix = false;
2502  } else {
2503  $first_prefix = false;
2504  $prefix = '';
2505  }
2506 
2507  # Some namespaces don't allow subpages
2508  $useSubpages = $this->nsInfo->hasSubpages(
2509  $this->getTitle()->getNamespace()
2510  );
2511 
2512  # Loop for each link
2513  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2514  # Check for excessive memory usage
2515  if ( $holders->isBig() ) {
2516  # Too big
2517  # Do the existence check, replace the link holders and clear the array
2518  $holders->replace( $s );
2519  $holders->clear();
2520  }
2521 
2522  if ( $useLinkPrefixExtension ) {
2523  // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal $e2 is set under this condition
2524  if ( preg_match( $e2, $s, $m ) ) {
2525  [ , $s, $prefix ] = $m;
2526  } else {
2527  $prefix = '';
2528  }
2529  # first link
2530  if ( $first_prefix ) {
2531  $prefix = $first_prefix;
2532  $first_prefix = false;
2533  }
2534  }
2535 
2536  $might_be_img = false;
2537 
2538  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2539  $text = $m[2];
2540  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2541  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2542  # the real problem is with the $e1 regex
2543  # See T1500.
2544  # Still some problems for cases where the ] is meant to be outside punctuation,
2545  # and no image is in sight. See T4095.
2546  if ( $text !== ''
2547  && substr( $m[3], 0, 1 ) === ']'
2548  && strpos( $text, '[' ) !== false
2549  ) {
2550  $text .= ']'; # so that handleExternalLinks($text) works later
2551  $m[3] = substr( $m[3], 1 );
2552  }
2553  # fix up urlencoded title texts
2554  if ( strpos( $m[1], '%' ) !== false ) {
2555  # Should anchors '#' also be rejected?
2556  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2557  }
2558  $trail = $m[3];
2559  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2560  # Invalid, but might be an image with a link in its caption
2561  $might_be_img = true;
2562  $text = $m[2];
2563  if ( strpos( $m[1], '%' ) !== false ) {
2564  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2565  }
2566  $trail = "";
2567  } else { # Invalid form; output directly
2568  $s .= $prefix . '[[' . $line;
2569  continue;
2570  }
2571 
2572  // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset preg_match success when reached here
2573  $origLink = ltrim( $m[1], ' ' );
2574 
2575  # Don't allow internal links to pages containing
2576  # PROTO: where PROTO is a valid URL protocol; these
2577  # should be external links.
2578  if ( preg_match( '/^(?i:' . $this->urlUtils->validProtocols() . ')/', $origLink ) ) {
2579  $s .= $prefix . '[[' . $line;
2580  continue;
2581  }
2582 
2583  # Make subpage if necessary
2584  if ( $useSubpages ) {
2585  $link = Linker::normalizeSubpageLink(
2586  $this->getTitle(), $origLink, $text
2587  );
2588  } else {
2589  $link = $origLink;
2590  }
2591 
2592  // \x7f isn't a default legal title char, so most likely strip
2593  // markers will force us into the "invalid form" path above. But,
2594  // just in case, let's assert that xmlish tags aren't valid in
2595  // the title position.
2596  $unstrip = $this->mStripState->killMarkers( $link );
2597  $noMarkers = ( $unstrip === $link );
2598 
2599  $nt = $noMarkers ? Title::newFromText( $link ) : null;
2600  if ( $nt === null ) {
2601  $s .= $prefix . '[[' . $line;
2602  continue;
2603  }
2604 
2605  $ns = $nt->getNamespace();
2606  $iw = $nt->getInterwiki();
2607 
2608  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2609 
2610  if ( $might_be_img ) { # if this is actually an invalid link
2611  if ( $ns === NS_FILE && $noforce ) { # but might be an image
2612  $found = false;
2613  while ( true ) {
2614  # look at the next 'line' to see if we can close it there
2615  $a->next();
2616  $next_line = $a->current();
2617  if ( $next_line === false || $next_line === null ) {
2618  break;
2619  }
2620  $m = explode( ']]', $next_line, 3 );
2621  if ( count( $m ) == 3 ) {
2622  # the first ]] closes the inner link, the second the image
2623  $found = true;
2624  $text .= "[[{$m[0]}]]{$m[1]}";
2625  $trail = $m[2];
2626  break;
2627  } elseif ( count( $m ) == 2 ) {
2628  # if there's exactly one ]] that's fine, we'll keep looking
2629  $text .= "[[{$m[0]}]]{$m[1]}";
2630  } else {
2631  # if $next_line is invalid too, we need look no further
2632  $text .= '[[' . $next_line;
2633  break;
2634  }
2635  }
2636  if ( !$found ) {
2637  # we couldn't find the end of this imageLink, so output it raw
2638  # but don't ignore what might be perfectly normal links in the text we've examined
2639  $holders->merge( $this->handleInternalLinks2( $text ) );
2640  $s .= "{$prefix}[[$link|$text";
2641  # note: no $trail, because without an end, there *is* no trail
2642  continue;
2643  }
2644  } else { # it's not an image, so output it raw
2645  $s .= "{$prefix}[[$link|$text";
2646  # note: no $trail, because without an end, there *is* no trail
2647  continue;
2648  }
2649  }
2650 
2651  $wasblank = ( $text == '' );
2652  if ( $wasblank ) {
2653  $text = $link;
2654  if ( !$noforce ) {
2655  # Strip off leading ':'
2656  $text = substr( $text, 1 );
2657  }
2658  } else {
2659  # T6598 madness. Handle the quotes only if they come from the alternate part
2660  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2661  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2662  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2663  $text = $this->doQuotes( $text );
2664  }
2665 
2666  # Link not escaped by : , create the various objects
2667  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2668  # Interwikis
2669  if (
2670  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2671  MediaWikiServices::getInstance()->getLanguageNameUtils()
2672  ->getLanguageName(
2673  $iw,
2674  LanguageNameUtils::AUTONYMS,
2675  LanguageNameUtils::DEFINED
2676  )
2677  || in_array( $iw, $this->svcOptions->get( MainConfigNames::ExtraInterlanguageLinkPrefixes ) )
2678  )
2679  ) {
2680  # T26502: filter duplicates
2681  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2682  $this->mLangLinkLanguages[$iw] = true;
2683  $this->mOutput->addLanguageLink( $nt->getFullText() );
2684  }
2685 
2689  $s = rtrim( $s . $prefix ) . $trail; # T175416
2690  continue;
2691  }
2692 
2693  if ( $ns === NS_FILE ) {
2694  if ( !$this->badFileLookup->isBadFile( $nt->getDBkey(), $this->getTitle() ) ) {
2695  if ( $wasblank ) {
2696  # if no parameters were passed, $text
2697  # becomes something like "File:Foo.png",
2698  # which we don't want to pass on to the
2699  # image generator
2700  $text = '';
2701  } else {
2702  # recursively parse links inside the image caption
2703  # actually, this will parse them in any other parameters, too,
2704  # but it might be hard to fix that, and it doesn't matter ATM
2705  $text = $this->handleExternalLinks( $text );
2706  $holders->merge( $this->handleInternalLinks2( $text ) );
2707  }
2708  # cloak any absolute URLs inside the image markup, so handleExternalLinks() won't touch them
2709  $s .= $prefix . $this->armorLinks(
2710  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2711  continue;
2712  }
2713  } elseif ( $ns === NS_CATEGORY ) {
2717  $s = rtrim( $s . $prefix ) . $trail; # T2087, T87753
2718 
2719  if ( $wasblank ) {
2720  $sortkey = $this->mOutput->getPageProperty( 'defaultsort' ) ?? '';
2721  } else {
2722  $sortkey = $text;
2723  }
2724  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2725  $sortkey = str_replace( "\n", '', $sortkey );
2726  $sortkey = $this->getTargetLanguageConverter()->convertCategoryKey( $sortkey );
2727  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2728 
2729  continue;
2730  }
2731  }
2732 
2733  # Self-link checking. For some languages, variants of the title are checked in
2734  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2735  # for linking to a different variant.
2736  if ( $ns !== NS_SPECIAL && $nt->equals( $this->getTitle() ) ) {
2737  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail, '',
2739  continue;
2740  }
2741 
2742  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2743  # @todo FIXME: Should do batch file existence checks, see comment below
2744  if ( $ns === NS_MEDIA ) {
2745  # Give extensions a chance to select the file revision for us
2746  $options = [];
2747  $descQuery = false;
2748  $this->hookRunner->onBeforeParserFetchFileAndTitle(
2749  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
2750  $this, $nt, $options, $descQuery
2751  );
2752  # Fetch and register the file (file title may be different via hooks)
2753  [ $file, $nt ] = $this->fetchFileAndTitle( $nt, $options );
2754  # Cloak with NOPARSE to avoid replacement in handleExternalLinks
2755  $s .= $prefix . $this->armorLinks(
2756  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2757  continue;
2758  }
2759 
2760  # Some titles, such as valid special pages or files in foreign repos, should
2761  # be shown as bluelinks even though they're not included in the page table
2762  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2763  # batch file existence checks for NS_FILE and NS_MEDIA
2764  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2765  $this->mOutput->addLink( $nt );
2766  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2767  } else {
2768  # Links will be added to the output link list after checking
2769  $s .= $holders->makeHolder( $nt, $text, $trail, $prefix );
2770  }
2771  }
2772  return $holders;
2773  }
2774 
2788  private function makeKnownLinkHolder( LinkTarget $nt, $text = '', $trail = '', $prefix = '' ) {
2789  [ $inside, $trail ] = Linker::splitTrail( $trail );
2790 
2791  if ( $text == '' ) {
2792  $text = htmlspecialchars( $this->titleFormatter->getPrefixedText( $nt ) );
2793  }
2794 
2795  $link = $this->getLinkRenderer()->makeKnownLink(
2796  $nt, new HtmlArmor( "$prefix$text$inside" )
2797  );
2798 
2799  return $this->armorLinks( $link ) . $trail;
2800  }
2801 
2812  private function armorLinks( $text ) {
2813  return preg_replace( '/\b((?i)' . $this->urlUtils->validProtocols() . ')/',
2814  self::MARKER_PREFIX . "NOPARSE$1", $text );
2815  }
2816 
2826  public function doBlockLevels( $text, $linestart ) {
2827  wfDeprecated( __METHOD__, '1.35' );
2828  return BlockLevelPass::doBlockLevels( $text, $linestart );
2829  }
2830 
2839  private function expandMagicVariable( $index, $frame = false ) {
2844  if ( isset( $this->mVarCache[$index] ) ) {
2845  return $this->mVarCache[$index];
2846  }
2847 
2848  $ts = new MWTimestamp( $this->mOptions->getTimestamp() /* TS_MW */ );
2849  if ( $this->hookContainer->isRegistered( 'ParserGetVariableValueTs' ) ) {
2850  $s = $ts->getTimestamp( TS_UNIX );
2851  $this->hookRunner->onParserGetVariableValueTs( $this, $s );
2852  $ts = new MWTimestamp( $s );
2853  }
2854 
2855  $value = CoreMagicVariables::expand(
2856  $this, $index, $ts, $this->nsInfo, $this->svcOptions, $this->logger
2857  );
2858 
2859  if ( $value === null ) {
2860  // Not a defined core magic word
2861  // Don't give this hook unrestricted access to mVarCache
2862  $fakeCache = [];
2863  $this->hookRunner->onParserGetVariableValueSwitch(
2864  // @phan-suppress-next-line PhanTypeMismatchArgument $value is passed as null but returned as string
2865  $this, $fakeCache, $index, $value, $frame
2866  );
2867  // Cache the value returned by the hook by falling through here.
2868  // Assert the the hook returned a non-null value for this MV
2869  '@phan-var string $value';
2870  }
2871 
2872  $this->mVarCache[$index] = $value;
2873 
2874  return $value;
2875  }
2876 
2881  private function initializeVariables() {
2882  $variableIDs = $this->magicWordFactory->getVariableIDs();
2883  $substIDs = $this->magicWordFactory->getSubstIDs();
2884 
2885  $this->mVariables = $this->magicWordFactory->newArray( $variableIDs );
2886  $this->mSubstWords = $this->magicWordFactory->newArray( $substIDs );
2887  }
2888 
2907  public function preprocessToDom( $text, $flags = 0 ) {
2908  return $this->getPreprocessor()->preprocessToObj( $text, $flags );
2909  }
2910 
2932  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2933  # Is there any text? Also, Prevent too big inclusions!
2934  $textSize = strlen( $text );
2935  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2936  return $text;
2937  }
2938 
2939  if ( $frame === false ) {
2940  $frame = $this->getPreprocessor()->newFrame();
2941  } elseif ( !( $frame instanceof PPFrame ) ) {
2942  $this->logger->debug(
2943  __METHOD__ . " called using plain parameters instead of " .
2944  "a PPFrame instance. Creating custom frame."
2945  );
2946  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2947  }
2948 
2949  $dom = $this->preprocessToDom( $text );
2950  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2951  $text = $frame->expand( $dom, $flags );
2952 
2953  return $text;
2954  }
2955 
2983  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2984  # does no harm if $current and $max are present but are unnecessary for the message
2985  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2986  # only during preview, and that would split the parser cache unnecessarily.
2987  $this->mOutput->addWarningMsg(
2988  "$limitationType-warning",
2989  Message::numParam( $current ),
2990  Message::numParam( $max )
2991  );
2992  $this->addTrackingCategory( "$limitationType-category" );
2993  }
2994 
3008  public function braceSubstitution( array $piece, PPFrame $frame ) {
3009  // Flags
3010 
3011  // $text has been filled
3012  $found = false;
3013  $text = '';
3014  // wiki markup in $text should be escaped
3015  $nowiki = false;
3016  // $text is HTML, armour it against wikitext transformation
3017  $isHTML = false;
3018  // Force interwiki transclusion to be done in raw mode not rendered
3019  $forceRawInterwiki = false;
3020  // $text is a DOM node needing expansion in a child frame
3021  $isChildObj = false;
3022  // $text is a DOM node needing expansion in the current frame
3023  $isLocalObj = false;
3024 
3025  # Title object, where $text came from
3026  $title = false;
3027 
3028  # $part1 is the bit before the first |, and must contain only title characters.
3029  # Various prefixes will be stripped from it later.
3030  $titleWithSpaces = $frame->expand( $piece['title'] );
3031  $part1 = trim( $titleWithSpaces );
3032  $titleText = false;
3033 
3034  # Original title text preserved for various purposes
3035  $originalTitle = $part1;
3036 
3037  # $args is a list of argument nodes, starting from index 0, not including $part1
3038  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3039  # below won't work b/c this $args isn't an object
3040  $args = ( $piece['parts'] == null ) ? [] : $piece['parts'];
3041 
3042  $profileSection = null; // profile templates
3043 
3044  $sawDeprecatedTemplateEquals = false; // T91154
3045 
3046  # SUBST
3047  // @phan-suppress-next-line PhanImpossibleCondition
3048  if ( !$found ) {
3049  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3050 
3051  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3052  # Decide whether to expand template or keep wikitext as-is.
3053  if ( $this->ot['wiki'] ) {
3054  if ( $substMatch === false ) {
3055  $literal = true; # literal when in PST with no prefix
3056  } else {
3057  $literal = false; # expand when in PST with subst: or safesubst:
3058  }
3059  } else {
3060  if ( $substMatch == 'subst' ) {
3061  $literal = true; # literal when not in PST with plain subst:
3062  } else {
3063  $literal = false; # expand when not in PST with safesubst: or no prefix
3064  }
3065  }
3066  if ( $literal ) {
3067  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3068  $isLocalObj = true;
3069  $found = true;
3070  }
3071  }
3072 
3073  # Variables
3074  if ( !$found && $args->getLength() == 0 ) {
3075  $id = $this->mVariables->matchStartToEnd( $part1 );
3076  if ( $id !== false ) {
3077  if ( strpos( $part1, ':' ) !== false ) {
3079  'Registering a magic variable with a name including a colon',
3080  '1.39', false, false
3081  );
3082  }
3083  $text = $this->expandMagicVariable( $id, $frame );
3084  if ( $this->magicWordFactory->getCacheTTL( $id ) > -1 ) {
3085  $this->mOutput->updateCacheExpiry(
3086  $this->magicWordFactory->getCacheTTL( $id ) );
3087  }
3088  $found = true;
3089  }
3090  }
3091 
3092  # MSG, MSGNW and RAW
3093  if ( !$found ) {
3094  # Check for MSGNW:
3095  $mwMsgnw = $this->magicWordFactory->get( 'msgnw' );
3096  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3097  $nowiki = true;
3098  } else {
3099  # Remove obsolete MSG:
3100  $mwMsg = $this->magicWordFactory->get( 'msg' );
3101  $mwMsg->matchStartAndRemove( $part1 );
3102  }
3103 
3104  # Check for RAW:
3105  $mwRaw = $this->magicWordFactory->get( 'raw' );
3106  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3107  $forceRawInterwiki = true;
3108  }
3109  }
3110 
3111  # Parser functions
3112  if ( !$found ) {
3113  $colonPos = strpos( $part1, ':' );
3114  if ( $colonPos !== false ) {
3115  $func = substr( $part1, 0, $colonPos );
3116  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3117  $argsLength = $args->getLength();
3118  for ( $i = 0; $i < $argsLength; $i++ ) {
3119  $funcArgs[] = $args->item( $i );
3120  }
3121 
3122  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3123 
3124  // Extract any forwarded flags
3125  if ( isset( $result['title'] ) ) {
3126  $title = $result['title'];
3127  }
3128  if ( isset( $result['found'] ) ) {
3129  $found = $result['found'];
3130  }
3131  if ( array_key_exists( 'text', $result ) ) {
3132  // a string or null
3133  $text = $result['text'];
3134  }
3135  if ( isset( $result['nowiki'] ) ) {
3136  $nowiki = $result['nowiki'];
3137  }
3138  if ( isset( $result['isHTML'] ) ) {
3139  $isHTML = $result['isHTML'];
3140  }
3141  if ( isset( $result['forceRawInterwiki'] ) ) {
3142  $forceRawInterwiki = $result['forceRawInterwiki'];
3143  }
3144  if ( isset( $result['isChildObj'] ) ) {
3145  $isChildObj = $result['isChildObj'];
3146  }
3147  if ( isset( $result['isLocalObj'] ) ) {
3148  $isLocalObj = $result['isLocalObj'];
3149  }
3150  }
3151  }
3152 
3153  # Finish mangling title and then check for loops.
3154  # Set $title to a Title object and $titleText to the PDBK
3155  if ( !$found ) {
3156  $ns = NS_TEMPLATE;
3157  # Split the title into page and subpage
3158  $subpage = '';
3159  $relative = Linker::normalizeSubpageLink(
3160  $this->getTitle(), $part1, $subpage
3161  );
3162  if ( $part1 !== $relative ) {
3163  $part1 = $relative;
3164  $ns = $this->getTitle()->getNamespace();
3165  }
3166  $title = Title::newFromText( $part1, $ns );
3167  if ( $title ) {
3168  $titleText = $title->getPrefixedText();
3169  # Check for language variants if the template is not found
3170  if ( $this->getTargetLanguageConverter()->hasVariants() && $title->getArticleID() == 0 ) {
3171  $this->getTargetLanguageConverter()->findVariantLink( $part1, $title, true );
3172  }
3173  # Do recursion depth check
3174  $limit = $this->mOptions->getMaxTemplateDepth();
3175  if ( $frame->depth >= $limit ) {
3176  $found = true;
3177  $text = '<span class="error">'
3178  . wfMessage( 'parser-template-recursion-depth-warning' )
3179  ->numParams( $limit )->inContentLanguage()->text()
3180  . '</span>';
3181  }
3182  }
3183  }
3184 
3185  # Load from database
3186  if ( !$found && $title ) {
3187  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3188  if ( !$title->isExternal() ) {
3189  if ( $title->isSpecialPage()
3190  && $this->mOptions->getAllowSpecialInclusion()
3191  && $this->ot['html']
3192  ) {
3193  $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() );
3194  // Pass the template arguments as URL parameters.
3195  // "uselang" will have no effect since the Language object
3196  // is forced to the one defined in ParserOptions.
3197  $pageArgs = [];
3198  $argsLength = $args->getLength();
3199  for ( $i = 0; $i < $argsLength; $i++ ) {
3200  $bits = $args->item( $i )->splitArg();
3201  if ( strval( $bits['index'] ) === '' ) {
3202  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3203  $value = trim( $frame->expand( $bits['value'] ) );
3204  $pageArgs[$name] = $value;
3205  }
3206  }
3207 
3208  // Create a new context to execute the special page
3209  $context = new RequestContext;
3210  $context->setTitle( $title );
3211  $context->setRequest( new FauxRequest( $pageArgs ) );
3212  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3213  $context->setUser( $this->userFactory->newFromUserIdentity( $this->getUserIdentity() ) );
3214  } else {
3215  // If this page is cached, then we better not be per user.
3216  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3217  }
3218  $context->setLanguage( $this->mOptions->getUserLangObj() );
3219  $ret = $this->specialPageFactory->capturePath( $title, $context, $this->getLinkRenderer() );
3220  if ( $ret ) {
3221  $text = $context->getOutput()->getHTML();
3222  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3223  $found = true;
3224  $isHTML = true;
3225  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3226  $this->mOutput->updateRuntimeAdaptiveExpiry(
3227  $specialPage->maxIncludeCacheTime()
3228  );
3229  }
3230  }
3231  } elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
3232  $found = false; # access denied
3233  $this->logger->debug(
3234  __METHOD__ .
3235  ": template inclusion denied for " . $title->getPrefixedDBkey()
3236  );
3237  } else {
3238  [ $text, $title ] = $this->getTemplateDom( $title );
3239  if ( $text !== false ) {
3240  $found = true;
3241  $isChildObj = true;
3242  if (
3243  $title->getNamespace() === NS_TEMPLATE &&
3244  $title->getDBkey() === '=' &&
3245  $originalTitle === '='
3246  ) {
3247  // Note that we won't get here if `=` is evaluated
3248  // (in the future) as a parser function, nor if
3249  // the Template namespace is given explicitly,
3250  // ie `{{Template:=}}`. Only `{{=}}` triggers.
3251  $sawDeprecatedTemplateEquals = true; // T91154
3252  }
3253  }
3254  }
3255 
3256  # If the title is valid but undisplayable, make a link to it
3257  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3258  $text = "[[:$titleText]]";
3259  $found = true;
3260  }
3261  } elseif ( $title->isTrans() ) {
3262  # Interwiki transclusion
3263  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3264  $text = $this->interwikiTransclude( $title, 'render' );
3265  $isHTML = true;
3266  } else {
3267  $text = $this->interwikiTransclude( $title, 'raw' );
3268  # Preprocess it like a template
3269  $text = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
3270  $isChildObj = true;
3271  }
3272  $found = true;
3273  }
3274 
3275  # Do infinite loop check
3276  # This has to be done after redirect resolution to avoid infinite loops via redirects
3277  if ( !$frame->loopCheck( $title ) ) {
3278  $found = true;
3279  $text = '<span class="error">'
3280  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3281  . '</span>';
3282  $this->addTrackingCategory( 'template-loop-category' );
3283  $this->mOutput->addWarningMsg(
3284  'template-loop-warning',
3285  Message::plaintextParam( $titleText )
3286  );
3287  $this->logger->debug( __METHOD__ . ": template loop broken at '$titleText'" );
3288  }
3289  }
3290 
3291  # If we haven't found text to substitute by now, we're done
3292  # Recover the source wikitext and return it
3293  if ( !$found ) {
3294  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3295  if ( $profileSection ) {
3296  $this->mProfiler->scopedProfileOut( $profileSection );
3297  }
3298  return [ 'object' => $text ];
3299  }
3300 
3301  # Expand DOM-style return values in a child frame
3302  if ( $isChildObj ) {
3303  # Clean up argument array
3304  $newFrame = $frame->newChild( $args, $title );
3305 
3306  if ( $nowiki ) {
3307  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3308  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3309  # Expansion is eligible for the empty-frame cache
3310  $text = $newFrame->cachedExpand( $titleText, $text );
3311  } else {
3312  # Uncached expansion
3313  $text = $newFrame->expand( $text );
3314  }
3315  }
3316  if ( $isLocalObj && $nowiki ) {
3317  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3318  $isLocalObj = false;
3319  }
3320 
3321  if ( $profileSection ) {
3322  $this->mProfiler->scopedProfileOut( $profileSection );
3323  }
3324  if (
3325  $sawDeprecatedTemplateEquals &&
3326  $this->mStripState->unstripBoth( $text ) !== '='
3327  ) {
3328  // T91154: {{=}} is deprecated when it doesn't expand to `=`;
3329  // use {{Template:=}} if you must.
3330  $this->addTrackingCategory( 'template-equals-category' );
3331  $this->mOutput->addWarningMsg( 'template-equals-warning' );
3332  }
3333 
3334  # Replace raw HTML by a placeholder
3335  if ( $isHTML ) {
3336  $text = $this->insertStripItem( $text );
3337  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3338  # Escape nowiki-style return values
3339  $text = wfEscapeWikiText( $text );
3340  } elseif ( is_string( $text )
3341  && !$piece['lineStart']
3342  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3343  ) {
3344  # T2529: if the template begins with a table or block-level
3345  # element, it should be treated as beginning a new line.
3346  # This behavior is somewhat controversial.
3347  $text = "\n" . $text;
3348  }
3349 
3350  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3351  # Error, oversize inclusion
3352  if ( $titleText !== false ) {
3353  # Make a working, properly escaped link if possible (T25588)
3354  $text = "[[:$titleText]]";
3355  } else {
3356  # This will probably not be a working link, but at least it may
3357  # provide some hint of where the problem is
3358  $originalTitle = preg_replace( '/^:/', '', $originalTitle );
3359  $text = "[[:$originalTitle]]";
3360  }
3361  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3362  . 'post-expand include size too large -->' );
3363  $this->limitationWarn( 'post-expand-template-inclusion' );
3364  }
3365 
3366  if ( $isLocalObj ) {
3367  $ret = [ 'object' => $text ];
3368  } else {
3369  $ret = [ 'text' => $text ];
3370  }
3371 
3372  return $ret;
3373  }
3374 
3393  public function callParserFunction( PPFrame $frame, $function, array $args = [] ) {
3394  # Case sensitive functions
3395  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3396  $function = $this->mFunctionSynonyms[1][$function];
3397  } else {
3398  # Case insensitive functions
3399  $function = $this->contLang->lc( $function );
3400  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3401  $function = $this->mFunctionSynonyms[0][$function];
3402  } else {
3403  return [ 'found' => false ];
3404  }
3405  }
3406 
3407  [ $callback, $flags ] = $this->mFunctionHooks[$function];
3408 
3409  $allArgs = [ $this ];
3410  if ( $flags & self::SFH_OBJECT_ARGS ) {
3411  # Convert arguments to PPNodes and collect for appending to $allArgs
3412  $funcArgs = [];
3413  foreach ( $args as $k => $v ) {
3414  if ( $v instanceof PPNode || $k === 0 ) {
3415  $funcArgs[] = $v;
3416  } else {
3417  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3418  }
3419  }
3420 
3421  # Add a frame parameter, and pass the arguments as an array
3422  $allArgs[] = $frame;
3423  $allArgs[] = $funcArgs;
3424  } else {
3425  # Convert arguments to plain text and append to $allArgs
3426  foreach ( $args as $k => $v ) {
3427  if ( $v instanceof PPNode ) {
3428  $allArgs[] = trim( $frame->expand( $v ) );
3429  } elseif ( is_int( $k ) && $k >= 0 ) {
3430  $allArgs[] = trim( $v );
3431  } else {
3432  $allArgs[] = trim( "$k=$v" );
3433  }
3434  }
3435  }
3436 
3437  $result = $callback( ...$allArgs );
3438 
3439  # The interface for function hooks allows them to return a wikitext
3440  # string or an array containing the string and any flags. This mungs
3441  # things around to match what this method should return.
3442  if ( !is_array( $result ) ) {
3443  $result = [
3444  'found' => true,
3445  'text' => $result,
3446  ];
3447  } else {
3448  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3449  $result['text'] = $result[0];
3450  }
3451  unset( $result[0] );
3452  $result += [
3453  'found' => true,
3454  ];
3455  }
3456 
3457  $noparse = true;
3458  $preprocessFlags = 0;
3459  if ( isset( $result['noparse'] ) ) {
3460  $noparse = $result['noparse'];
3461  }
3462  if ( isset( $result['preprocessFlags'] ) ) {
3463  $preprocessFlags = $result['preprocessFlags'];
3464  }
3465 
3466  if ( !$noparse ) {
3467  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3468  $result['isChildObj'] = true;
3469  }
3470 
3471  return $result;
3472  }
3473 
3483  public function getTemplateDom( LinkTarget $title ) {
3484  $cacheTitle = $title;
3485  $titleKey = CacheKeyHelper::getKeyForPage( $title );
3486 
3487  if ( isset( $this->mTplRedirCache[$titleKey] ) ) {
3488  [ $ns, $dbk ] = $this->mTplRedirCache[$titleKey];
3489  $title = Title::makeTitle( $ns, $dbk );
3490  $titleKey = CacheKeyHelper::getKeyForPage( $title );
3491  }
3492  if ( isset( $this->mTplDomCache[$titleKey] ) ) {
3493  return [ $this->mTplDomCache[$titleKey], $title ];
3494  }
3495 
3496  # Cache miss, go to the database
3497  [ $text, $title ] = $this->fetchTemplateAndTitle( $title );
3498 
3499  if ( $text === false ) {
3500  $this->mTplDomCache[$titleKey] = false;
3501  return [ false, $title ];
3502  }
3503 
3504  $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
3505  $this->mTplDomCache[$titleKey] = $dom;
3506 
3507  if ( !$title->isSamePageAs( $cacheTitle ) ) {
3508  $this->mTplRedirCache[ CacheKeyHelper::getKeyForPage( $cacheTitle ) ] =
3509  [ $title->getNamespace(), $title->getDBkey() ];
3510  }
3511 
3512  return [ $dom, $title ];
3513  }
3514 
3529  $cacheKey = CacheKeyHelper::getKeyForPage( $link );
3530  if ( !$this->currentRevisionCache ) {
3531  $this->currentRevisionCache = new MapCacheLRU( 100 );
3532  }
3533  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3534  $title = Title::castFromLinkTarget( $link ); // hook signature compat
3535  $revisionRecord =
3536  // Defaults to Parser::statelessFetchRevisionRecord()
3537  call_user_func(
3538  $this->mOptions->getCurrentRevisionRecordCallback(),
3539  $title,
3540  $this
3541  );
3542  if ( $revisionRecord === false ) {
3543  // Parser::statelessFetchRevisionRecord() can return false;
3544  // normalize it to null.
3545  $revisionRecord = null;
3546  }
3547  $this->currentRevisionCache->set( $cacheKey, $revisionRecord );
3548  }
3549  return $this->currentRevisionCache->get( $cacheKey );
3550  }
3551 
3558  public function isCurrentRevisionOfTitleCached( LinkTarget $link ) {
3559  $key = CacheKeyHelper::getKeyForPage( $link );
3560  return (
3561  $this->currentRevisionCache &&
3562  $this->currentRevisionCache->has( $key )
3563  );
3564  }
3565 
3574  public static function statelessFetchRevisionRecord( LinkTarget $link, $parser = null ) {
3575  if ( $link instanceof PageIdentity ) {
3576  // probably a Title, just use it.
3577  $page = $link;
3578  } else {
3579  // XXX: use RevisionStore::getPageForLink()!
3580  // ...but get the info for the current revision at the same time?
3581  // Should RevisionStore::getKnownCurrentRevision accept a LinkTarget?
3582  $page = Title::castFromLinkTarget( $link );
3583  }
3584 
3585  $revRecord = MediaWikiServices::getInstance()
3586  ->getRevisionLookup()
3587  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable castFrom does not return null here
3588  ->getKnownCurrentRevision( $page );
3589  return $revRecord;
3590  }
3591 
3598  public function fetchTemplateAndTitle( LinkTarget $link ) {
3599  // Use Title for compatibility with callbacks and return type
3600  $title = Title::castFromLinkTarget( $link );
3601 
3602  // Defaults to Parser::statelessFetchTemplate()
3603  $templateCb = $this->mOptions->getTemplateCallback();
3604  $stuff = $templateCb( $title, $this );
3605  $revRecord = $stuff['revision-record'] ?? null;
3606 
3607  $text = $stuff['text'];
3608  if ( is_string( $stuff['text'] ) ) {
3609  // We use U+007F DELETE to distinguish strip markers from regular text
3610  $text = strtr( $text, "\x7f", "?" );
3611  }
3612  $finalTitle = $stuff['finalTitle'] ?? $title;
3613  foreach ( ( $stuff['deps'] ?? [] ) as $dep ) {
3614  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3615  if ( $dep['title']->equals( $this->getTitle() ) && $revRecord instanceof RevisionRecord ) {
3616  // Self-transclusion; final result may change based on the new page version
3617  try {
3618  $sha1 = $revRecord->getSha1();
3619  } catch ( RevisionAccessException $e ) {
3620  $sha1 = null;
3621  }
3622  $this->setOutputFlag( ParserOutputFlags::VARY_REVISION_SHA1, 'Self transclusion' );
3623  $this->getOutput()->setRevisionUsedSha1Base36( $sha1 );
3624  }
3625  }
3626 
3627  return [ $text, $finalTitle ];
3628  }
3629 
3640  public static function statelessFetchTemplate( $page, $parser = false ) {
3641  $title = Title::castFromLinkTarget( $page ); // for compatibility with return type
3642  $text = $skip = false;
3643  $finalTitle = $title;
3644  $deps = [];
3645  $revRecord = null;
3646  $contextTitle = $parser ? $parser->getTitle() : null;
3647 
3648  # Loop to fetch the article, with up to 2 redirects
3649  $revLookup = MediaWikiServices::getInstance()->getRevisionLookup();
3650  for ( $i = 0; $i < 3 && is_object( $title ); $i++ ) {
3651  # Give extensions a chance to select the revision instead
3652  $revRecord = null; # Assume no hook
3653  $id = false; # Assume current
3654  $origTitle = $title;
3655  $titleChanged = false;
3656  Hooks::runner()->onBeforeParserFetchTemplateRevisionRecord(
3657  # The $title is a not a PageIdentity, as it may
3658  # contain fragments or even represent an attempt to transclude
3659  # a broken or otherwise-missing Title, which the hook may
3660  # fix up. Similarly, the $contextTitle may represent a special
3661  # page or other page which "exists" as a parsing context but
3662  # is not in the DB.
3663  $contextTitle, $title,
3664  $skip, $revRecord
3665  );
3666 
3667  if ( $skip ) {
3668  $text = false;
3669  $deps[] = [
3670  'title' => $title,
3671  'page_id' => $title->getArticleID(),
3672  'rev_id' => null
3673  ];
3674  break;
3675  }
3676  # Get the revision
3677  if ( !$revRecord ) {
3678  if ( $id ) {
3679  # Handle $id returned by deprecated legacy hook
3680  $revRecord = $revLookup->getRevisionById( $id );
3681  } elseif ( $parser ) {
3682  $revRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
3683  } else {
3684  $revRecord = $revLookup->getRevisionByTitle( $title );
3685  }
3686  }
3687  if ( $revRecord ) {
3688  # Update title, as $revRecord may have been changed by hook
3690  $revRecord->getPageAsLinkTarget()
3691  );
3692  $deps[] = [
3693  'title' => $title,
3694  'page_id' => $revRecord->getPageId(),
3695  'rev_id' => $revRecord->getId(),
3696  ];
3697  } else {
3698  $deps[] = [
3699  'title' => $title,
3700  'page_id' => $title->getArticleID(),
3701  'rev_id' => null,
3702  ];
3703  }
3704  if ( !$title->equals( $origTitle ) ) {
3705  # If we fetched a rev from a different title, register
3706  # the original title too...
3707  $deps[] = [
3708  'title' => $origTitle,
3709  'page_id' => $origTitle->getArticleID(),
3710  'rev_id' => null,
3711  ];
3712  $titleChanged = true;
3713  }
3714  # If there is no current revision, there is no page
3715  if ( $revRecord === null || $revRecord->getId() === null ) {
3716  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3717  $linkCache->addBadLinkObj( $title );
3718  }
3719  if ( $revRecord ) {
3720  if ( $titleChanged && !$revRecord->hasSlot( SlotRecord::MAIN ) ) {
3721  // We've added this (missing) title to the dependencies;
3722  // give the hook another chance to redirect it to an
3723  // actual page.
3724  $text = false;
3725  $finalTitle = $title;
3726  continue;
3727  }
3728  if ( $revRecord->hasSlot( SlotRecord::MAIN ) ) { // T276476
3729  $content = $revRecord->getContent( SlotRecord::MAIN );
3730  $text = $content ? $content->getWikitextForTransclusion() : null;
3731  } else {
3732  $text = false;
3733  }
3734 
3735  if ( $text === false || $text === null ) {
3736  $text = false;
3737  break;
3738  }
3739  } elseif ( $title->getNamespace() === NS_MEDIAWIKI ) {
3740  $message = wfMessage( MediaWikiServices::getInstance()->getContentLanguage()->
3741  lcfirst( $title->getText() ) )->inContentLanguage();
3742  if ( !$message->exists() ) {
3743  $text = false;
3744  break;
3745  }
3746  $text = $message->plain();
3747  break;
3748  } else {
3749  break;
3750  }
3751  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable Only reached when content is set
3752  if ( !$content ) {
3753  break;
3754  }
3755  # Redirect?
3756  $finalTitle = $title;
3757  $title = $content->getRedirectTarget();
3758  }
3759 
3760  $retValues = [
3761  // previously, when this also returned a Revision object, we set
3762  // 'revision-record' to false instead of null if it was unavailable,
3763  // so that callers to use isset and then rely on the revision-record
3764  // key instead of the revision key, even if there was no corresponding
3765  // object - we continue to set to false here for backwards compatability
3766  'revision-record' => $revRecord ?: false,
3767  'text' => $text,
3768  'finalTitle' => $finalTitle,
3769  'deps' => $deps
3770  ];
3771  return $retValues;
3772  }
3773 
3782  public function fetchFileAndTitle( LinkTarget $link, array $options = [] ) {
3783  $file = $this->fetchFileNoRegister( $link, $options );
3784 
3785  $time = $file ? $file->getTimestamp() : false;
3786  $sha1 = $file ? $file->getSha1() : false;
3787  # Register the file as a dependency...
3788  $this->mOutput->addImage( $link->getDBkey(), $time, $sha1 );
3789  if ( $file && !$link->isSameLinkAs( $file->getTitle() ) ) {
3790  # Update fetched file title after resolving redirects, etc.
3791  $link = $file->getTitle();
3792  $this->mOutput->addImage( $link->getDBkey(), $time, $sha1 );
3793  }
3794 
3795  $title = Title::castFromLinkTarget( $link ); // for return type compat
3796  return [ $file, $title ];
3797  }
3798 
3809  protected function fetchFileNoRegister( LinkTarget $link, array $options = [] ) {
3810  if ( isset( $options['broken'] ) ) {
3811  $file = false; // broken thumbnail forced by hook
3812  } else {
3813  $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
3814  if ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3815  $file = $repoGroup->findFileFromKey( $options['sha1'], $options );
3816  } else { // get by (name,timestamp)
3817  $file = $repoGroup->findFile( $link, $options );
3818  }
3819  }
3820  return $file;
3821  }
3822 
3832  public function interwikiTransclude( LinkTarget $link, $action ) {
3833  if ( !$this->svcOptions->get( MainConfigNames::EnableScaryTranscluding ) ) {
3834  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3835  }
3836 
3837  // TODO: extract relevant functionality from Title
3838  $title = Title::castFromLinkTarget( $link );
3839 
3840  $url = $title->getFullURL( [ 'action' => $action ] );
3841  if ( strlen( $url ) > 1024 ) {
3842  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3843  }
3844 
3845  $wikiId = $title->getTransWikiID(); // remote wiki ID or false
3846 
3847  $fname = __METHOD__;
3848  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
3849 
3850  $data = $cache->getWithSetCallback(
3851  $cache->makeGlobalKey(
3852  'interwiki-transclude',
3853  ( $wikiId !== false ) ? $wikiId : 'external',
3854  sha1( $url )
3855  ),
3856  $this->svcOptions->get( MainConfigNames::TranscludeCacheExpiry ),
3857  function ( $oldValue, &$ttl ) use ( $url, $fname, $cache ) {
3858  $req = $this->httpRequestFactory->create( $url, [], $fname );
3859 
3860  $status = $req->execute(); // Status object
3861  if ( !$status->isOK() ) {
3862  $ttl = $cache::TTL_UNCACHEABLE;
3863  } elseif ( $req->getResponseHeader( 'X-Database-Lagged' ) !== null ) {
3864  $ttl = min( $cache::TTL_LAGGED, $ttl );
3865  }
3866 
3867  return [
3868  'text' => $status->isOK() ? $req->getContent() : null,
3869  'code' => $req->getStatus()
3870  ];
3871  },
3872  [
3873  'checkKeys' => ( $wikiId !== false )
3874  ? [ $cache->makeGlobalKey( 'interwiki-page', $wikiId, $title->getDBkey() ) ]
3875  : [],
3876  'pcGroup' => 'interwiki-transclude:5',
3877  'pcTTL' => $cache::TTL_PROC_LONG
3878  ]
3879  );
3880 
3881  if ( is_string( $data['text'] ) ) {
3882  $text = $data['text'];
3883  } elseif ( $data['code'] != 200 ) {
3884  // Though we failed to fetch the content, this status is useless.
3885  $text = wfMessage( 'scarytranscludefailed-httpstatus' )
3886  ->params( $url, $data['code'] )->inContentLanguage()->text();
3887  } else {
3888  $text = wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3889  }
3890 
3891  return $text;
3892  }
3893 
3904  public function argSubstitution( array $piece, PPFrame $frame ) {
3905  $error = false;
3906  $parts = $piece['parts'];
3907  $nameWithSpaces = $frame->expand( $piece['title'] );
3908  $argName = trim( $nameWithSpaces );
3909  $object = false;
3910  $text = $frame->getArgument( $argName );
3911  if ( $text === false && $parts->getLength() > 0
3912  && ( $this->ot['html']
3913  || $this->ot['pre']
3914  || ( $this->ot['wiki'] && $frame->isTemplate() )
3915  )
3916  ) {
3917  # No match in frame, use the supplied default
3918  $object = $parts->item( 0 )->getChildren();
3919  }
3920  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3921  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3922  $this->limitationWarn( 'post-expand-template-argument' );
3923  }
3924 
3925  if ( $text === false && $object === false ) {
3926  # No match anywhere
3927  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3928  }
3929  if ( $error !== false ) {
3930  $text .= $error;
3931  }
3932  if ( $object !== false ) {
3933  $ret = [ 'object' => $object ];
3934  } else {
3935  $ret = [ 'text' => $text ];
3936  }
3937 
3938  return $ret;
3939  }
3940 
3945  public function tagNeedsNowikiStrippedInTagPF( string $lowerTagName ): bool {
3946  $parsoidSiteConfig = MediaWikiServices::getInstance()->getParsoidSiteConfig();
3947  return $parsoidSiteConfig->tagNeedsNowikiStrippedInTagPF( $lowerTagName );
3948  }
3949 
3971  public function extensionSubstitution( array $params, PPFrame $frame, bool $processNowiki = false ) {
3972  static $errorStr = '<span class="error">';
3973 
3974  $name = $frame->expand( $params['name'] );
3975  if ( str_starts_with( $name, $errorStr ) ) {
3976  // Probably expansion depth or node count exceeded. Just punt the
3977  // error up.
3978  return $name;
3979  }
3980 
3981  // Parse attributes from XML-like wikitext syntax
3982  $attrText = !isset( $params['attr'] ) ? '' : $frame->expand( $params['attr'] );
3983  if ( str_starts_with( $attrText, $errorStr ) ) {
3984  // See above
3985  return $attrText;
3986  }
3987 
3988  // We can't safely check if the expansion for $content resulted in an
3989  // error, because the content could happen to be the error string
3990  // (T149622).
3991  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3992 
3993  $marker = self::MARKER_PREFIX . "-$name-"
3994  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3995 
3996  $normalizedName = strtolower( $name );
3997  $isNowiki = $normalizedName === 'nowiki';
3998  $markerType = $isNowiki ? 'nowiki' : 'general';
3999  if ( $this->ot['html'] || ( $processNowiki && $isNowiki ) ) {
4000  $attributes = Sanitizer::decodeTagAttributes( $attrText );
4001  // Merge in attributes passed via {{#tag:}} parser function
4002  if ( isset( $params['attributes'] ) ) {
4003  $attributes += $params['attributes'];
4004  }
4005 
4006  if ( isset( $this->mTagHooks[$normalizedName] ) ) {
4007  // Note that $content may be null here, for example if the
4008  // tag is self-closed.
4009  $output = call_user_func_array( $this->mTagHooks[$normalizedName],
4010  [ $content, $attributes, $this, $frame ] );
4011  } else {
4012  $output = '<span class="error">Invalid tag extension name: ' .
4013  htmlspecialchars( $normalizedName ) . '</span>';
4014  }
4015 
4016  if ( is_array( $output ) ) {
4017  // Extract flags
4018  $flags = $output;
4019  $output = $flags[0];
4020  if ( isset( $flags['markerType'] ) ) {
4021  $markerType = $flags['markerType'];
4022  }
4023  }
4024  } else {
4025  // We're substituting a {{subst:#tag:}} parser function.
4026  // Convert the attributes it passed into the XML-like string.
4027  if ( isset( $params['attributes'] ) ) {
4028  foreach ( $params['attributes'] as $attrName => $attrValue ) {
4029  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
4030  htmlspecialchars( $attrValue, ENT_COMPAT ) . '"';
4031  }
4032  }
4033  if ( $content === null ) {
4034  $output = "<$name$attrText/>";
4035  } else {
4036  $close = $params['close'] === null ? '' : $frame->expand( $params['close'] );
4037  if ( str_starts_with( $close, $errorStr ) ) {
4038  // See above
4039  return $close;
4040  }
4041  $output = "<$name$attrText>$content$close";
4042  }
4043  }
4044 
4045  if ( $markerType === 'none' ) {
4046  return $output;
4047  } elseif ( $markerType === 'nowiki' ) {
4048  $this->mStripState->addNoWiki( $marker, $output );
4049  } elseif ( $markerType === 'general' ) {
4050  $this->mStripState->addGeneral( $marker, $output );
4051  } else {
4052  throw new MWException( __METHOD__ . ': invalid marker type' );
4053  }
4054  return $marker;
4055  }
4056 
4064  private function incrementIncludeSize( $type, $size ) {
4065  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
4066  return false;
4067  } else {
4068  $this->mIncludeSizes[$type] += $size;
4069  return true;
4070  }
4071  }
4072 
4078  $this->mExpensiveFunctionCount++;
4079  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
4080  }
4081 
4089  private function handleDoubleUnderscore( $text ) {
4090  # The position of __TOC__ needs to be recorded
4091  $mw = $this->magicWordFactory->get( 'toc' );
4092  if ( $mw->match( $text ) ) {
4093  $this->mShowToc = true;
4094  $this->mForceTocPosition = true;
4095 
4096  # Set a placeholder. At the end we'll fill it in with the TOC.
4097  $text = $mw->replace( self::TOC_PLACEHOLDER, $text, 1 );
4098 
4099  # Only keep the first one.
4100  $text = $mw->replace( '', $text );
4101  }
4102 
4103  # Now match and remove the rest of them
4104  $mwa = $this->magicWordFactory->getDoubleUnderscoreArray();
4105  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
4106 
4107  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
4108  $this->mOutput->setNoGallery( true );
4109  }
4110  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
4111  $this->mShowToc = false;
4112  }
4113  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
4114  && $this->getTitle()->getNamespace() === NS_CATEGORY
4115  ) {
4116  $this->addTrackingCategory( 'hidden-category-category' );
4117  }
4118  # (T10068) Allow control over whether robots index a page.
4119  # __INDEX__ always overrides __NOINDEX__, see T16899
4120  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->getTitle()->canUseNoindex() ) {
4121  $this->mOutput->setIndexPolicy( 'noindex' );
4122  $this->addTrackingCategory( 'noindex-category' );
4123  }
4124  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->getTitle()->canUseNoindex() ) {
4125  $this->mOutput->setIndexPolicy( 'index' );
4126  $this->addTrackingCategory( 'index-category' );
4127  }
4128 
4129  # Cache all double underscores in the database
4130  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4131  $this->mOutput->setPageProperty( $key, '' );
4132  }
4133 
4134  return $text;
4135  }
4136 
4143  public function addTrackingCategory( $msg ) {
4144  return $this->trackingCategories->addTrackingCategory(
4145  $this->mOutput, $msg, $this->getPage()
4146  );
4147  }
4148 
4162  public function msg( string $msg, ...$args ): Message {
4163  return wfMessage( $msg, ...$args )
4164  ->inLanguage( $this->getTargetLanguage() )
4165  ->page( $this->getPage() );
4166  }
4167 
4183  private function finalizeHeadings( $text, $origText, $isMain = true ) {
4184  # Inhibit editsection links if requested in the page
4185  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4186  $maybeShowEditLink = false;
4187  } else {
4188  $maybeShowEditLink = true; /* Actual presence will depend on post-cache transforms */
4189  }
4190 
4191  # Get all headlines for numbering them and adding funky stuff like [edit]
4192  # links - this is for later, but we need the number of headlines right now
4193  # NOTE: white space in headings have been trimmed in handleHeadings. They shouldn't
4194  # be trimmed here since whitespace in HTML headings is significant.
4195  $matches = [];
4196  $numMatches = preg_match_all(
4197  '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4198  $text,
4199  $matches
4200  );
4201 
4202  # if there are fewer than 4 headlines in the article, do not show TOC
4203  # unless it's been explicitly enabled.
4204  $enoughToc = $this->mShowToc &&
4205  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4206 
4207  # Allow user to stipulate that a page should have a "new section"
4208  # link added via __NEWSECTIONLINK__
4209  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4210  $this->mOutput->setNewSection( true );
4211  }
4212 
4213  # Allow user to remove the "new section"
4214  # link via __NONEWSECTIONLINK__
4215  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4216  $this->mOutput->setHideNewSection( true );
4217  }
4218 
4219  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4220  # override above conditions and always show TOC above first header
4221  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4222  $this->mShowToc = true;
4223  $enoughToc = true;
4224  }
4225 
4226  # headline counter
4227  $headlineCount = 0;
4228  $haveTocEntries = false;
4229 
4230  # Ugh .. the TOC should have neat indentation levels which can be
4231  # passed to the skin functions. These are determined here
4232  $full = '';
4233  $head = [];
4234  $level = 0;
4235  $tocData = new TOCData();
4236  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4237  $baseTitleText = $this->getTitle()->getPrefixedDBkey();
4238  $oldType = $this->mOutputType;
4239  $this->setOutputType( self::OT_WIKI );
4240  $frame = $this->getPreprocessor()->newFrame();
4241  $root = $this->preprocessToDom( $origText );
4242  $node = $root->getFirstChild();
4243  $byteOffset = 0;
4244  $refers = [];
4245 
4246  $headlines = $numMatches !== false ? $matches[3] : [];
4247 
4248  $maxTocLevel = $this->svcOptions->get( MainConfigNames::MaxTocLevel );
4249  foreach ( $headlines as $headline ) {
4250  $isTemplate = false;
4251  $titleText = false;
4252  $sectionIndex = false;
4253  $markerMatches = [];
4254  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4255  $serial = (int)$markerMatches[1];
4256  [ $titleText, $sectionIndex ] = $this->mHeadings[$serial];
4257  $isTemplate = ( $titleText != $baseTitleText );
4258  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4259  }
4260 
4261  $sectionMetadata = SectionMetadata::fromLegacy( [
4262  "fromtitle" => $titleText ?: null,
4263  "index" => $sectionIndex === false
4264  ? '' : ( ( $isTemplate ? 'T-' : '' ) . $sectionIndex )
4265  ] );
4266  $tocData->addSection( $sectionMetadata );
4267 
4268  $oldLevel = $level;
4269  $level = (int)$matches[1][$headlineCount];
4270  $tocData->processHeading( $oldLevel, $level, $sectionMetadata );
4271 
4272  if ( $tocData->getCurrentTOCLevel() < $maxTocLevel ) {
4273  $haveTocEntries = true;
4274  }
4275 
4276  // FIXME: Maybe move to Linker::tocLine?
4277  // This requires us to record target language there.
4278  //
4279  // Localize numbering
4280  $dot = '.';
4281  $pieces = explode( $dot, $sectionMetadata->number );
4282  $numbering = '';
4283  foreach ( $pieces as $i => $p ) {
4284  if ( $i > 0 ) {
4285  $numbering .= $dot;
4286  }
4287  $numbering .= $this->getTargetLanguage()->formatNum( $p );
4288  }
4289  $sectionMetadata->number = $numbering;
4290 
4291  # The safe header is a version of the header text safe to use for links
4292 
4293  # Remove link placeholders by the link text.
4294  # <!--LINK number-->
4295  # turns into
4296  # link text with suffix
4297  # Do this before unstrip since link text can contain strip markers
4298  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4299 
4300  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4301  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4302 
4303  # Remove any <style> or <script> tags (T198618)
4304  $safeHeadline = preg_replace(
4305  '#<(style|script)(?: [^>]*[^>/])?>.*?</\1>#is',
4306  '',
4307  $safeHeadline
4308  );
4309 
4310  # Strip out HTML (first regex removes any tag not allowed)
4311  # Allowed tags are:
4312  # * <sup> and <sub> (T10393)
4313  # * <i> (T28375)
4314  # * <b> (r105284)
4315  # * <bdi> (T74884)
4316  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4317  # * <s> and <strike> (T35715)
4318  # * <q> (T251672)
4319  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4320  # to allow setting directionality in toc items.
4321  $tocline = preg_replace(
4322  [
4323  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike|q)(?: [^>]*)?>).*?>#',
4324  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4325  ],
4326  [ '', '<$1>' ],
4327  $safeHeadline
4328  );
4329 
4330  # Strip '<span></span>', which is the result from the above if
4331  # <span id="foo"></span> is used to produce an additional anchor
4332  # for a section.
4333  $tocline = str_replace( '<span></span>', '', $tocline );
4334 
4335  $tocline = trim( $tocline );
4336 
4337  # For the anchor, strip out HTML-y stuff period
4338  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4339  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4340 
4341  # Save headline for section edit hint before it's escaped
4342  $headlineHint = $safeHeadline;
4343 
4344  # Decode HTML entities
4345  $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4346 
4347  $safeHeadline = self::normalizeSectionName( $safeHeadline );
4348 
4349  $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4350  $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4351  $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4352  if ( $fallbackHeadline === $safeHeadline ) {
4353  # No reason to have both (in fact, we can't)
4354  $fallbackHeadline = false;
4355  }
4356 
4357  # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4358  $arrayKey = strtolower( $safeHeadline );
4359  if ( $fallbackHeadline === false ) {
4360  $fallbackArrayKey = false;
4361  } else {
4362  $fallbackArrayKey = strtolower( $fallbackHeadline );
4363  }
4364 
4365  # Create the anchor for linking from the TOC to the section
4366  $anchor = $safeHeadline;
4367  $fallbackAnchor = $fallbackHeadline;
4368  if ( isset( $refers[$arrayKey] ) ) {
4369  for ( $i = 2; isset( $refers["{$arrayKey}_$i"] ); ++$i );
4370  $anchor .= "_$i";
4371  $linkAnchor .= "_$i";
4372  $refers["{$arrayKey}_$i"] = true;
4373  } else {
4374  $refers[$arrayKey] = true;
4375  }
4376  if ( $fallbackHeadline !== false && isset( $refers[$fallbackArrayKey] ) ) {
4377  for ( $i = 2; isset( $refers["{$fallbackArrayKey}_$i"] ); ++$i );
4378  $fallbackAnchor .= "_$i";
4379  $refers["{$fallbackArrayKey}_$i"] = true;
4380  } else {
4381  $refers[$fallbackArrayKey] = true;
4382  }
4383 
4384  # Add the section to the section tree
4385  # Find the DOM node for this header
4386  $noOffset = ( $isTemplate || $sectionIndex === false );
4387  while ( $node && !$noOffset ) {
4388  if ( $node->getName() === 'h' ) {
4389  $bits = $node->splitHeading();
4390  if ( $bits['i'] == $sectionIndex ) {
4391  break;
4392  }
4393  }
4394  $byteOffset += mb_strlen(
4395  $this->mStripState->unstripBoth(
4396  $frame->expand( $node, PPFrame::RECOVER_ORIG )
4397  )
4398  );
4399  $node = $node->getNextSibling();
4400  }
4401  $sectionMetadata->line = $tocline;
4402  $sectionMetadata->byteOffset = ( $noOffset ? null : $byteOffset );
4403  $sectionMetadata->anchor = $anchor;
4404  $sectionMetadata->linkAnchor = $linkAnchor;
4405 
4406  # give headline the correct <h#> tag
4407  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4408  // Output edit section links as markers with styles that can be customized by skins
4409  if ( $isTemplate ) {
4410  # Put a T flag in the section identifier, to indicate to extractSections()
4411  # that sections inside <includeonly> should be counted.
4412  $editsectionPage = $titleText;
4413  $editsectionSection = "T-$sectionIndex";
4414  } else {
4415  $editsectionPage = $this->getTitle()->getPrefixedText();
4416  $editsectionSection = $sectionIndex;
4417  }
4418  $editsectionContent = $headlineHint;
4419  // We use a bit of pesudo-xml for editsection markers. The
4420  // language converter is run later on. Using a UNIQ style marker
4421  // leads to the converter screwing up the tokens when it
4422  // converts stuff. And trying to insert strip tags fails too. At
4423  // this point all real inputted tags have already been escaped,
4424  // so we don't have to worry about a user trying to input one of
4425  // these markers directly. We use a page and section attribute
4426  // to stop the language converter from converting these
4427  // important bits of data, but put the headline hint inside a
4428  // content block because the language converter is supposed to
4429  // be able to convert that piece of data.
4430  // Gets replaced with html in ParserOutput::getText
4431  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage, ENT_COMPAT );
4432  $editlink .= '" section="' . htmlspecialchars( $editsectionSection, ENT_COMPAT ) . '"';
4433  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4434  } else {
4435  $editlink = '';
4436  }
4437  $head[$headlineCount] = Linker::makeHeadline(
4438  $level,
4439  $matches['attrib'][$headlineCount],
4440  $anchor,
4441  $headline,
4442  $editlink,
4443  $fallbackAnchor
4444  );
4445 
4446  $headlineCount++;
4447  }
4448 
4449  $this->setOutputType( $oldType );
4450 
4451  # Never ever show TOC if no headers (or suppressed)
4452  $suppressToc = $this->mOptions->getSuppressTOC();
4453  if ( !$haveTocEntries || $suppressToc ) {
4454  $enoughToc = false;
4455  }
4456 
4457  if ( $enoughToc ) {
4458  // Record the fact that the TOC should be shown. T294950
4459  // (We shouldn't be looking at ::getTOCHTML() for this because
4460  // eventually that will be replaced (T293513) and
4461  // $tocData will contain sections even if there aren't
4462  // $enoughToc to show.)
4463  $this->mOutput->setOutputFlag( ParserOutputFlags::SHOW_TOC );
4464  }
4465 
4466  if ( $isMain && !$suppressToc ) {
4467  // We generally output the section information via the API
4468  // even if there isn't "enough" of a ToC to merit showing
4469  // it -- but the "suppress TOC" parser option is set when
4470  // any sections that might be found aren't "really there"
4471  // (ie, JavaScript content that might have spurious === or
4472  // <h2>: T307691) so we will *not* set section information
4473  // in that case.
4474  $this->mOutput->setTOCData( $tocData );
4475  }
4476 
4477  # split up and insert constructed headlines
4478  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4479  $i = 0;
4480 
4481  // build an array of document sections
4482  $sections = [];
4483  foreach ( $blocks as $block ) {
4484  // $head is zero-based, sections aren't.
4485  if ( empty( $head[$i - 1] ) ) {
4486  $sections[$i] = $block;
4487  } else {
4488  $sections[$i] = $head[$i - 1] . $block;
4489  }
4490 
4491  $i++;
4492  }
4493 
4494  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4495  // append the TOC at the beginning
4496  // Top anchor now in skin
4497  // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset At least one element when enoughToc is true
4498  $sections[0] .= self::TOC_PLACEHOLDER . "\n";
4499  }
4500 
4501  $full .= implode( '', $sections );
4502 
4503  return $full;
4504  }
4505 
4518  public function preSaveTransform(
4519  $text,
4520  PageReference $page,
4521  UserIdentity $user,
4522  ParserOptions $options,
4523  $clearState = true
4524  ) {
4525  if ( $clearState ) {
4526  $magicScopeVariable = $this->lock();
4527  }
4528  $this->startParse( $page, $options, self::OT_WIKI, $clearState );
4529  $this->setUser( $user );
4530 
4531  // Strip U+0000 NULL (T159174)
4532  $text = str_replace( "\000", '', $text );
4533 
4534  // We still normalize line endings (including trimming trailing whitespace) for
4535  // backwards-compatibility with other code that just calls PST, but this should already
4536  // be handled in TextContent subclasses
4537  $text = TextContent::normalizeLineEndings( $text );
4538 
4539  if ( $options->getPreSaveTransform() ) {
4540  $text = $this->pstPass2( $text, $user );
4541  }
4542  $text = $this->mStripState->unstripBoth( $text );
4543 
4544  // Trim trailing whitespace again, because the previous steps can introduce it.
4545  $text = rtrim( $text );
4546 
4547  $this->hookRunner->onParserPreSaveTransformComplete( $this, $text );
4548 
4549  $this->setUser( null ); # Reset
4550 
4551  return $text;
4552  }
4553 
4562  private function pstPass2( $text, UserIdentity $user ) {
4563  # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
4564  # $this->contLang here in order to give everyone the same signature and use the default one
4565  # rather than the one selected in each user's preferences. (see also T14815)
4566  $ts = $this->mOptions->getTimestamp();
4567  $timestamp = MWTimestamp::getLocalInstance( $ts );
4568  $ts = $timestamp->format( 'YmdHis' );
4569  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4570 
4571  $d = $this->contLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4572 
4573  # Variable replacement
4574  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4575  $text = $this->replaceVariables( $text );
4576 
4577  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4578  # which may corrupt this parser instance via its wfMessage()->text() call-
4579 
4580  # Signatures
4581  if ( strpos( $text, '~~~' ) !== false ) {
4582  $sigText = $this->getUserSig( $user );
4583  $text = strtr( $text, [
4584  '~~~~~' => $d,
4585  '~~~~' => "$sigText $d",
4586  '~~~' => $sigText
4587  ] );
4588  # The main two signature forms used above are time-sensitive
4589  $this->setOutputFlag( ParserOutputFlags::USER_SIGNATURE, 'User signature detected' );
4590  }
4591 
4592  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4593  $tc = '[' . Title::legalChars() . ']';
4594  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4595 
4596  // [[ns:page (context)|]]
4597  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4598  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4599  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4600  // [[ns:page (context), context|]] (using single, double-width or Arabic comma)
4601  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,|، )$tc+|)\\|]]/";
4602  // [[|page]] (reverse pipe trick: add context from page title)
4603  $p2 = "/\[\[\\|($tc+)]]/";
4604 
4605  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4606  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4607  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4608  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4609 
4610  $t = $this->getTitle()->getText();
4611  $m = [];
4612  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4613  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4614  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4615  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4616  } else {
4617  # if there's no context, don't bother duplicating the title
4618  $text = preg_replace( $p2, '[[\\1]]', $text );
4619  }
4620 
4621  return $text;
4622  }
4623 
4639  public function getUserSig( UserIdentity $user, $nickname = false, $fancySig = null ) {
4640  $username = $user->getName();
4641 
4642  # If not given, retrieve from the user object.
4643  if ( $nickname === false ) {
4644  $nickname = $this->userOptionsLookup->getOption( $user, 'nickname' );
4645  }
4646 
4647  if ( $fancySig === null ) {
4648  $fancySig = $this->userOptionsLookup->getBoolOption( $user, 'fancysig' );
4649  }
4650 
4651  if ( $nickname === null || $nickname === '' ) {
4652  // Empty value results in the default signature (even when fancysig is enabled)
4653  $nickname = $username;
4654  } elseif ( mb_strlen( $nickname ) > $this->svcOptions->get( MainConfigNames::MaxSigChars ) ) {
4655  $nickname = $username;
4656  $this->logger->debug( __METHOD__ . ": $username has overlong signature." );
4657  } elseif ( $fancySig !== false ) {
4658  # Sig. might contain markup; validate this
4659  $isValid = $this->validateSig( $nickname ) !== false;
4660 
4661  # New validator
4662  $sigValidation = $this->svcOptions->get( MainConfigNames::SignatureValidation );
4663  if ( $isValid && $sigValidation === 'disallow' ) {
4664  $parserOpts = new ParserOptions(
4665  $this->mOptions->getUserIdentity(),
4666  $this->contLang
4667  );
4668  $validator = $this->signatureValidatorFactory
4669  ->newSignatureValidator( $user, null, $parserOpts );
4670  $isValid = !$validator->validateSignature( $nickname );
4671  }
4672 
4673  if ( $isValid ) {
4674  # Validated; clean up (if needed) and return it
4675  return $this->cleanSig( $nickname, true );
4676  } else {
4677  # Failed to validate; fall back to the default
4678  $nickname = $username;
4679  $this->logger->debug( __METHOD__ . ": $username has invalid signature." );
4680  }
4681  }
4682 
4683  # Make sure nickname doesnt get a sig in a sig
4684  $nickname = self::cleanSigInSig( $nickname );
4685 
4686  # If we're still here, make it a link to the user page
4687  $userText = wfEscapeWikiText( $username );
4688  $nickText = wfEscapeWikiText( $nickname );
4689  if ( $this->userNameUtils->isTemp( $username ) ) {
4690  $msgName = 'signature-temp';
4691  } elseif ( $user->isRegistered() ) {
4692  $msgName = 'signature';
4693  } else {
4694  $msgName = 'signature-anon';
4695  }
4696 
4697  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4698  ->page( $this->getPage() )->text();
4699  }
4700 
4708  public function validateSig( $text ) {
4709  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4710  }
4711 
4723  public function cleanSig( $text, $parsing = false ) {
4724  if ( !$parsing ) {
4725  global $wgTitle;
4726  $magicScopeVariable = $this->lock();
4727  $this->startParse(
4728  $wgTitle,
4731  true
4732  );
4733  }
4734 
4735  # Option to disable this feature
4736  if ( !$this->mOptions->getCleanSignatures() ) {
4737  return $text;
4738  }
4739 
4740  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4741  # => Move this logic to braceSubstitution()
4742  $substWord = $this->magicWordFactory->get( 'subst' );
4743  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4744  $substText = '{{' . $substWord->getSynonym( 0 );
4745 
4746  $text = preg_replace( $substRegex, $substText, $text );
4747  $text = self::cleanSigInSig( $text );
4748  $dom = $this->preprocessToDom( $text );
4749  $frame = $this->getPreprocessor()->newFrame();
4750  $text = $frame->expand( $dom );
4751 
4752  if ( !$parsing ) {
4753  $text = $this->mStripState->unstripBoth( $text );
4754  }
4755 
4756  return $text;
4757  }
4758 
4766  public static function cleanSigInSig( $text ) {
4767  $text = preg_replace( '/~{3,5}/', '', $text );
4768  return $text;
4769  }
4770 
4787  public static function replaceTableOfContentsMarker( $text, $toc ) {
4788  return preg_replace_callback(
4789  self::TOC_PLACEHOLDER_REGEX,
4790  static function ( array $matches ) use( $toc ) {
4791  return $toc; // Ensure $1 \1 etc are safe to use in $toc
4792  },
4793  // For backwards compatibility during transition period,
4794  // also replace "old" TOC_PLACEHOLDER value
4795  str_replace( '<mw:tocplace></mw:tocplace>', $toc, $text )
4796  );
4797  }
4798 
4810  public function startExternalParse( ?PageReference $page, ParserOptions $options,
4811  $outputType, $clearState = true, $revId = null
4812  ) {
4813  $this->startParse( $page, $options, $outputType, $clearState );
4814  if ( $revId !== null ) {
4815  $this->mRevisionId = $revId;
4816  }
4817  }
4818 
4825  private function startParse( ?PageReference $page, ParserOptions $options,
4826  $outputType, $clearState = true
4827  ) {
4828  $this->setPage( $page );
4829  $this->mOptions = $options;
4830  $this->setOutputType( $outputType );
4831  if ( $clearState ) {
4832  $this->clearState();
4833  }
4834  }
4835 
4845  public function transformMsg( $text, ParserOptions $options, ?PageReference $page = null ) {
4846  static $executing = false;
4847 
4848  # Guard against infinite recursion
4849  if ( $executing ) {
4850  return $text;
4851  }
4852  $executing = true;
4853 
4854  if ( !$page ) {
4855  global $wgTitle;
4856  $page = $wgTitle;
4857  }
4858 
4859  $text = $this->preprocess( $text, $page, $options );
4860 
4861  $executing = false;
4862  return $text;
4863  }
4864 
4885  public function setHook( $tag, callable $callback ) {
4886  $tag = strtolower( $tag );
4887  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4888  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4889  }
4890  $oldVal = $this->mTagHooks[$tag] ?? null;
4891  $this->mTagHooks[$tag] = $callback;
4892  if ( !in_array( $tag, $this->mStripList ) ) {
4893  $this->mStripList[] = $tag;
4894  }
4895 
4896  return $oldVal;
4897  }
4898 
4903  public function clearTagHooks() {
4904  $this->mTagHooks = [];
4905  $this->mStripList = [];
4906  }
4907 
4952  public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
4953  $oldVal = $this->mFunctionHooks[$id][0] ?? null;
4954  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4955 
4956  # Add to function cache
4957  $mw = $this->magicWordFactory->get( $id );
4958  if ( !$mw ) {
4959  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4960  }
4961 
4962  $synonyms = $mw->getSynonyms();
4963  $sensitive = intval( $mw->isCaseSensitive() );
4964 
4965  foreach ( $synonyms as $syn ) {
4966  # Case
4967  if ( !$sensitive ) {
4968  $syn = $this->contLang->lc( $syn );
4969  }
4970  # Add leading hash
4971  if ( !( $flags & self::SFH_NO_HASH ) ) {
4972  $syn = '#' . $syn;
4973  }
4974  # Remove trailing colon
4975  if ( substr( $syn, -1, 1 ) === ':' ) {
4976  $syn = substr( $syn, 0, -1 );
4977  }
4978  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4979  }
4980  return $oldVal;
4981  }
4982 
4989  public function getFunctionHooks() {
4990  return array_keys( $this->mFunctionHooks );
4991  }
4992 
5001  public function replaceLinkHolders( &$text, $options = 0 ) {
5002  $this->replaceLinkHoldersPrivate( $text, $options );
5003  }
5004 
5012  private function replaceLinkHoldersPrivate( &$text, $options = 0 ) {
5013  $this->mLinkHolders->replace( $text );
5014  }
5015 
5023  private function replaceLinkHoldersText( $text ) {
5024  return $this->mLinkHolders->replaceText( $text );
5025  }
5026 
5041  public function renderImageGallery( $text, array $params ) {
5042  $mode = false;
5043  if ( isset( $params['mode'] ) ) {
5044  $mode = $params['mode'];
5045  }
5046 
5047  try {
5048  $ig = ImageGalleryBase::factory( $mode );
5049  } catch ( ImageGalleryClassNotFoundException $e ) {
5050  // If invalid type set, fallback to default.
5051  $ig = ImageGalleryBase::factory( false );
5052  }
5053 
5054  $ig->setContextTitle( $this->getTitle() );
5055  $ig->setShowBytes( false );
5056  $ig->setShowDimensions( false );
5057  $ig->setShowFilename( false );
5058  $ig->setParser( $this );
5059  $ig->setHideBadImages();
5060  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
5061 
5062  if ( isset( $params['showfilename'] ) ) {
5063  $ig->setShowFilename( true );
5064  } else {
5065  $ig->setShowFilename( false );
5066  }
5067  if ( isset( $params['caption'] ) ) {
5068  // NOTE: We aren't passing a frame here or below. Frame info
5069  // is currently opaque to Parsoid, which acts on OT_PREPROCESS.
5070  // See T107332#4030581
5071  $caption = $this->recursiveTagParse( $params['caption'] );
5072  $ig->setCaptionHtml( $caption );
5073  }
5074  if ( isset( $params['perrow'] ) ) {
5075  $ig->setPerRow( $params['perrow'] );
5076  }
5077  if ( isset( $params['widths'] ) ) {
5078  $ig->setWidths( $params['widths'] );
5079  }
5080  if ( isset( $params['heights'] ) ) {
5081  $ig->setHeights( $params['heights'] );
5082  }
5083  $ig->setAdditionalOptions( $params );
5084 
5085  $lines = StringUtils::explode( "\n", $text );
5086  foreach ( $lines as $line ) {
5087  # match lines like these:
5088  # Image:someimage.jpg|This is some image
5089  $matches = [];
5090  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5091  # Skip empty lines
5092  if ( count( $matches ) == 0 ) {
5093  continue;
5094  }
5095 
5096  if ( strpos( $matches[0], '%' ) !== false ) {
5097  $matches[1] = rawurldecode( $matches[1] );
5098  }
5100  if ( $title === null ) {
5101  # Bogus title. Ignore these so we don't bomb out later.
5102  continue;
5103  }
5104 
5105  # We need to get what handler the file uses, to figure out parameters.
5106  # Note, a hook can override the file name, and chose an entirely different
5107  # file (which potentially could be of a different type and have different handler).
5108  $options = [];
5109  $descQuery = false;
5110  $this->hookRunner->onBeforeParserFetchFileAndTitle(
5111  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
5112  $this, $title, $options, $descQuery
5113  );
5114  # Don't register it now, as TraditionalImageGallery does that later.
5115  $file = $this->fetchFileNoRegister( $title, $options );
5116  $handler = $file ? $file->getHandler() : false;
5117 
5118  $paramMap = [
5119  'img_alt' => 'gallery-internal-alt',
5120  'img_link' => 'gallery-internal-link',
5121  ];
5122  if ( $handler ) {
5123  $paramMap += $handler->getParamMap();
5124  // We don't want people to specify per-image widths.
5125  // Additionally the width parameter would need special casing anyhow.
5126  unset( $paramMap['img_width'] );
5127  }
5128 
5129  $mwArray = $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5130 
5131  $label = '';
5132  $alt = '';
5133  $handlerOptions = [];
5134  $imageOptions = [];
5135  $hasAlt = false;
5136 
5137  if ( isset( $matches[3] ) ) {
5138  // look for an |alt= definition while trying not to break existing
5139  // captions with multiple pipes (|) in it, until a more sensible grammar
5140  // is defined for images in galleries
5141 
5142  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5143  // splitting on '|' is a bit odd, and different from makeImage.
5144  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5145  // Protect LanguageConverter markup
5146  $parameterMatches = StringUtils::delimiterExplode(
5147  '-{', '}-',
5148  '|',
5149  $matches[3],
5150  true /* nested */
5151  );
5152 
5153  foreach ( $parameterMatches as $parameterMatch ) {
5154  [ $magicName, $match ] = $mwArray->matchVariableStartToEnd( $parameterMatch );
5155  if ( !$magicName ) {
5156  // Last pipe wins.
5157  $label = $parameterMatch;
5158  continue;
5159  }
5160 
5161  $paramName = $paramMap[$magicName];
5162  switch ( $paramName ) {
5163  case 'gallery-internal-alt':
5164  $hasAlt = true;
5165  $alt = $this->stripAltText( $match, false );
5166  break;
5167  case 'gallery-internal-link':
5168  $linkValue = $this->stripAltText( $match, false );
5169  if ( preg_match( '/^-{R\|(.*)}-$/', $linkValue ) ) {
5170  // Result of LanguageConverter::markNoConversion
5171  // invoked on an external link.
5172  $linkValue = substr( $linkValue, 4, -2 );
5173  }
5174  [ $type, $target ] = $this->parseLinkParameter( $linkValue );
5175  if ( $type ) {
5176  if ( $type === 'no-link' ) {
5177  $target = true;
5178  }
5179  $imageOptions[$type] = $target;
5180  }
5181  break;
5182  default:
5183  // Must be a handler specific parameter.
5184  if ( $handler->validateParam( $paramName, $match ) ) {
5185  $handlerOptions[$paramName] = $match;
5186  } else {
5187  // Guess not, consider it as caption.
5188  $this->logger->debug(
5189  "$parameterMatch failed parameter validation" );
5190  $label = $parameterMatch;
5191  }
5192  }
5193  }
5194  }
5195 
5196  // Match makeImage when !$hasVisibleCaption
5197  if ( !$hasAlt ) {
5198  if ( $label !== '' ) {
5199  $alt = $this->stripAltText( $label, false );
5200  } else {
5201  $alt = $title->getText();
5202  }
5203  }
5204  $imageOptions['title'] = $this->stripAltText( $label, false );
5205 
5206  $ig->add(
5207  $title, $label, $alt, '', $handlerOptions,
5208  ImageGalleryBase::LOADING_DEFAULT, $imageOptions
5209  );
5210  }
5211  $html = $ig->toHTML();
5212  $this->hookRunner->onAfterParserFetchFileAndTitle( $this, $ig, $html );
5213  return $html;
5214  }
5215 
5220  private function getImageParams( $handler ) {
5221  if ( $handler ) {
5222  $handlerClass = get_class( $handler );
5223  } else {
5224  $handlerClass = '';
5225  }
5226  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5227  # Initialise static lists
5228  static $internalParamNames = [
5229  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5230  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5231  'bottom', 'text-bottom' ],
5232  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5233  'upright', 'border', 'link', 'alt', 'class' ],
5234  ];
5235  static $internalParamMap;
5236  if ( !$internalParamMap ) {
5237  $internalParamMap = [];
5238  foreach ( $internalParamNames as $type => $names ) {
5239  foreach ( $names as $name ) {
5240  // For grep: img_left, img_right, img_center, img_none,
5241  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5242  // img_bottom, img_text_bottom,
5243  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5244  // img_border, img_link, img_alt, img_class
5245  $magicName = str_replace( '-', '_', "img_$name" );
5246  $internalParamMap[$magicName] = [ $type, $name ];
5247  }
5248  }
5249  }
5250 
5251  # Add handler params
5252  $paramMap = $internalParamMap;
5253  if ( $handler ) {
5254  $handlerParamMap = $handler->getParamMap();
5255  foreach ( $handlerParamMap as $magic => $paramName ) {
5256  $paramMap[$magic] = [ 'handler', $paramName ];
5257  }
5258  } else {
5259  // Parse the size for non-existent files. See T273013
5260  $paramMap[ 'img_width' ] = [ 'handler', 'width' ];
5261  }
5262  $this->mImageParams[$handlerClass] = $paramMap;
5263  $this->mImageParamsMagicArray[$handlerClass] =
5264  $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5265  }
5266  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5267  }
5268 
5278  public function makeImage( LinkTarget $link, $options, $holders = false ) {
5279  # Check if the options text is of the form "options|alt text"
5280  # Options are:
5281  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5282  # * left no resizing, just left align. label is used for alt= only
5283  # * right same, but right aligned
5284  # * none same, but not aligned
5285  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5286  # * center center the image
5287  # * framed Keep original image size, no magnify-button.
5288  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5289  # * upright reduce width for upright images, rounded to full __0 px
5290  # * border draw a 1px border around the image
5291  # * alt Text for HTML alt attribute (defaults to empty)
5292  # * class Set a class for img node
5293  # * link Set the target of the image link. Can be external, interwiki, or local
5294  # vertical-align values (no % or length right now):
5295  # * baseline
5296  # * sub
5297  # * super
5298  # * top
5299  # * text-top
5300  # * middle
5301  # * bottom
5302  # * text-bottom
5303 
5304  # Protect LanguageConverter markup when splitting into parts
5306  '-{', '}-', '|', $options, true /* allow nesting */
5307  );
5308 
5309  # Give extensions a chance to select the file revision for us
5310  $options = [];
5311  $descQuery = false;
5312  $title = Title::castFromLinkTarget( $link ); // hook signature compat
5313  $this->hookRunner->onBeforeParserFetchFileAndTitle(
5314  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
5315  $this, $title, $options, $descQuery
5316  );
5317  # Fetch and register the file (file title may be different via hooks)
5318  [ $file, $link ] = $this->fetchFileAndTitle( $link, $options );
5319 
5320  # Get parameter map
5321  $handler = $file ? $file->getHandler() : false;
5322 
5323  [ $paramMap, $mwArray ] = $this->getImageParams( $handler );
5324 
5325  if ( !$file ) {
5326  $this->addTrackingCategory( 'broken-file-category' );
5327  }
5328 
5329  # Process the input parameters
5330  $caption = '';
5331  $params = [ 'frame' => [], 'handler' => [],
5332  'horizAlign' => [], 'vertAlign' => [] ];
5333  $seenformat = false;
5334  foreach ( $parts as $part ) {
5335  $part = trim( $part );
5336  [ $magicName, $value ] = $mwArray->matchVariableStartToEnd( $part );
5337  $validated = false;
5338  if ( isset( $paramMap[$magicName] ) ) {
5339  [ $type, $paramName ] = $paramMap[$magicName];
5340 
5341  # Special case; width and height come in one variable together
5342  if ( $type === 'handler' && $paramName === 'width' ) {
5343  $parsedWidthParam = self::parseWidthParam( $value );
5344  // Parsoid applies data-(width|height) attributes to broken
5345  // media spans, for client use. See T273013
5346  $validateFunc = static function ( $name, $value ) use ( $handler ) {
5347  return $handler
5348  ? $handler->validateParam( $name, $value )
5349  : $value > 0;
5350  };
5351  if ( isset( $parsedWidthParam['width'] ) ) {
5352  $width = $parsedWidthParam['width'];
5353  if ( $validateFunc( 'width', $width ) ) {
5354  $params[$type]['width'] = $width;
5355  $validated = true;
5356  }
5357  }
5358  if ( isset( $parsedWidthParam['height'] ) ) {
5359  $height = $parsedWidthParam['height'];
5360  if ( $validateFunc( 'height', $height ) ) {
5361  $params[$type]['height'] = $height;
5362  $validated = true;
5363  }
5364  }
5365  # else no validation -- T15436
5366  } else {
5367  if ( $type === 'handler' ) {
5368  # Validate handler parameter
5369  $validated = $handler->validateParam( $paramName, $value );
5370  } else {
5371  # Validate internal parameters
5372  switch ( $paramName ) {
5373  case 'alt':
5374  case 'class':
5375  $validated = true;
5376  $value = $this->stripAltText( $value, $holders );
5377  break;
5378  case 'link':
5379  [ $paramName, $value ] =
5380  $this->parseLinkParameter(
5381  $this->stripAltText( $value, $holders )
5382  );
5383  if ( $paramName ) {
5384  $validated = true;
5385  if ( $paramName === 'no-link' ) {
5386  $value = true;
5387  }
5388  }
5389  break;
5390  case 'manualthumb':
5391  # @todo FIXME: Possibly check validity here for
5392  # manualthumb? downstream behavior seems odd with
5393  # missing manual thumbs.
5394  $value = $this->stripAltText( $value, $holders );
5395  // fall through
5396  case 'frameless':
5397  case 'framed':
5398  case 'thumbnail':
5399  // use first appearing option, discard others.
5400  $validated = !$seenformat;
5401  $seenformat = true;
5402  break;
5403  default:
5404  # Most other things appear to be empty or numeric...
5405  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5406  }
5407  }
5408 
5409  if ( $validated ) {
5410  $params[$type][$paramName] = $value;
5411  }
5412  }
5413  }
5414  if ( !$validated ) {
5415  $caption = $part;
5416  }
5417  }
5418 
5419  # Process alignment parameters
5420  if ( $params['horizAlign'] !== [] ) {
5421  $params['frame']['align'] = array_key_first( $params['horizAlign'] );
5422  }
5423  if ( $params['vertAlign'] !== [] ) {
5424  $params['frame']['valign'] = array_key_first( $params['vertAlign'] );
5425  }
5426 
5427  $params['frame']['caption'] = $caption;
5428 
5429  # Will the image be presented in a frame, with the caption below?
5430  // @phan-suppress-next-line PhanImpossibleCondition
5431  $hasVisibleCaption = isset( $params['frame']['framed'] )
5432  // @phan-suppress-next-line PhanImpossibleCondition
5433  || isset( $params['frame']['thumbnail'] )
5434  // @phan-suppress-next-line PhanImpossibleCondition
5435  || isset( $params['frame']['manualthumb'] );
5436 
5437  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5438  # came to also set the caption, ordinary text after the image -- which
5439  # makes no sense, because that just repeats the text multiple times in
5440  # screen readers. It *also* came to set the title attribute.
5441  # Now that we have an alt attribute, we should not set the alt text to
5442  # equal the caption: that's worse than useless, it just repeats the
5443  # text. This is the framed/thumbnail case. If there's no caption, we
5444  # use the unnamed parameter for alt text as well, just for the time be-
5445  # ing, if the unnamed param is set and the alt param is not.
5446  # For the future, we need to figure out if we want to tweak this more,
5447  # e.g., introducing a title= parameter for the title; ignoring the un-
5448  # named parameter entirely for images without a caption; adding an ex-
5449  # plicit caption= parameter and preserving the old magic unnamed para-
5450  # meter for BC; ...
5451  if ( $hasVisibleCaption ) {
5452  // @phan-suppress-next-line PhanImpossibleCondition
5453  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5454  # No caption or alt text, add the filename as the alt text so
5455  # that screen readers at least get some description of the image
5456  $params['frame']['alt'] = $link->getText();
5457  }
5458  # Do not set $params['frame']['title'] because tooltips are unnecessary
5459  # for framed images, the caption is visible
5460  } else {
5461  // @phan-suppress-next-line PhanImpossibleCondition
5462  if ( !isset( $params['frame']['alt'] ) ) {
5463  # No alt text, use the "caption" for the alt text
5464  if ( $caption !== '' ) {
5465  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5466  } else {
5467  # No caption, fall back to using the filename for the
5468  # alt text
5469  $params['frame']['alt'] = $link->getText();
5470  }
5471  }
5472  # Use the "caption" for the tooltip text
5473  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5474  }
5475  $params['handler']['targetlang'] = $this->getTargetLanguage()->getCode();
5476 
5477  // hook signature compat again, $link may have changed
5478  $title = Title::castFromLinkTarget( $link );
5479  $this->hookRunner->onParserMakeImageParams( $title, $file, $params, $this );
5480 
5481  # Linker does the rest
5482  $time = $options['time'] ?? false;
5483  $ret = Linker::makeImageLink( $this, $link, $file, $params['frame'], $params['handler'],
5484  $time, $descQuery, $this->mOptions->getThumbSize() );
5485 
5486  # Give the handler a chance to modify the parser object
5487  if ( $handler ) {
5488  $handler->parserTransformHook( $this, $file );
5489  }
5490  if ( $file ) {
5491  $this->modifyImageHtml( $file, $params, $ret );
5492  }
5493 
5494  return $ret;
5495  }
5496 
5515  private function parseLinkParameter( $value ) {
5516  $chars = self::EXT_LINK_URL_CLASS;
5517  $addr = self::EXT_LINK_ADDR;
5518  $prots = $this->urlUtils->validProtocols();
5519  $type = null;
5520  $target = false;
5521  if ( $value === '' ) {
5522  $type = 'no-link';
5523  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5524  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5525  $this->mOutput->addExternalLink( $value );
5526  $type = 'link-url';
5527  $target = $value;
5528  }
5529  } else {
5530  // Percent-decode link arguments for consistency with wikilink
5531  // handling (T216003#7836261).
5532  //
5533  // There's slight concern here though. The |link= option supports
5534  // two formats, link=Test%22test vs link=[[Test%22test]], both of
5535  // which are about to be decoded.
5536  //
5537  // In the former case, the decoding here is straightforward and
5538  // desirable.
5539  //
5540  // In the latter case, there's a potential for double decoding,
5541  // because the wikilink syntax has a higher precedence and has
5542  // already been parsed as a link before we get here. $value
5543  // has had stripAltText() called on it, which in turn calls
5544  // replaceLinkHoldersText() on the link. So, the text we're
5545  // getting at this point has already been percent decoded.
5546  //
5547  // The problematic case is if %25 is in the title, since that
5548  // decodes to %, which could combine with trailing characters.
5549  // However, % is not a valid link title character, so it would
5550  // not parse as a link and the string we received here would
5551  // still contain the encoded %25.
5552  //
5553  // Hence, double decoded is not an issue. See the test,
5554  // "Should not double decode the link option"
5555  if ( strpos( $value, '%' ) !== false ) {
5556  $value = rawurldecode( $value );
5557  }
5558  $linkTitle = Title::newFromText( $value );
5559  if ( $linkTitle ) {
5560  $this->mOutput->addLink( $linkTitle );
5561  $type = 'link-title';
5562  $target = $linkTitle;
5563  }
5564  }
5565  return [ $type, $target ];
5566  }
5567 
5575  public function modifyImageHtml( File $file, array $params, string &$html ) {
5576  $this->hookRunner->onParserModifyImageHTML( $this, $file, $params, $html );
5577  }
5578 
5584  private function stripAltText( $caption, $holders ) {
5585  # Strip bad stuff out of the title (tooltip). We can't just use
5586  # replaceLinkHoldersText() here, because if this function is called
5587  # from handleInternalLinks2(), mLinkHolders won't be up-to-date.
5588  if ( $holders ) {
5589  $tooltip = $holders->replaceText( $caption );
5590  } else {
5591  $tooltip = $this->replaceLinkHoldersText( $caption );
5592  }
5593 
5594  # make sure there are no placeholders in thumbnail attributes
5595  # that are later expanded to html- so expand them now and
5596  # remove the tags
5597  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5598  # Compatibility hack! In HTML certain entity references not terminated
5599  # by a semicolon are decoded (but not if we're in an attribute; that's
5600  # how link URLs get away without properly escaping & in queries).
5601  # But wikitext has always required semicolon-termination of entities,
5602  # so encode & where needed to avoid decode of semicolon-less entities.
5603  # See T209236 and
5604  # https://www.w3.org/TR/html5/syntax.html#named-character-references
5605  # T210437 discusses moving this workaround to Sanitizer::stripAllTags.
5606  $tooltip = preg_replace( "/
5607  & # 1. entity prefix
5608  (?= # 2. followed by:
5609  (?: # a. one of the legacy semicolon-less named entities
5610  A(?:Elig|MP|acute|circ|grave|ring|tilde|uml)|
5611  C(?:OPY|cedil)|E(?:TH|acute|circ|grave|uml)|
5612  GT|I(?:acute|circ|grave|uml)|LT|Ntilde|
5613  O(?:acute|circ|grave|slash|tilde|uml)|QUOT|REG|THORN|
5614  U(?:acute|circ|grave|uml)|Yacute|
5615  a(?:acute|c(?:irc|ute)|elig|grave|mp|ring|tilde|uml)|brvbar|
5616  c(?:cedil|edil|urren)|cent(?!erdot;)|copy(?!sr;)|deg|
5617  divide(?!ontimes;)|e(?:acute|circ|grave|th|uml)|
5618  frac(?:1(?:2|4)|34)|
5619  gt(?!c(?:c|ir)|dot|lPar|quest|r(?:a(?:pprox|rr)|dot|eq(?:less|qless)|less|sim);)|
5620  i(?:acute|circ|excl|grave|quest|uml)|laquo|
5621  lt(?!c(?:c|ir)|dot|hree|imes|larr|quest|r(?:Par|i(?:e|f|));)|
5622  m(?:acr|i(?:cro|ddot))|n(?:bsp|tilde)|
5623  not(?!in(?:E|dot|v(?:a|b|c)|)|ni(?:v(?:a|b|c)|);)|
5624  o(?:acute|circ|grave|rd(?:f|m)|slash|tilde|uml)|
5625  p(?:lusmn|ound)|para(?!llel;)|quot|r(?:aquo|eg)|
5626  s(?:ect|hy|up(?:1|2|3)|zlig)|thorn|times(?!b(?:ar|)|d;)|
5627  u(?:acute|circ|grave|ml|uml)|y(?:acute|en|uml)
5628  )
5629  (?:[^;]|$)) # b. and not followed by a semicolon
5630  # S = study, for efficiency
5631  /Sx", '&amp;', $tooltip );
5632  $tooltip = Sanitizer::stripAllTags( $tooltip );
5633 
5634  return $tooltip;
5635  }
5636 
5646  public function attributeStripCallback( &$text, $frame = false ) {
5647  wfDeprecated( __METHOD__, '1.35' );
5648  $text = $this->replaceVariables( $text, $frame );
5649  $text = $this->mStripState->unstripBoth( $text );
5650  return $text;
5651  }
5652 
5659  public function getTags(): array {
5660  return array_keys( $this->mTagHooks );
5661  }
5662 
5667  public function getFunctionSynonyms() {
5668  return $this->mFunctionSynonyms;
5669  }
5670 
5675  public function getUrlProtocols() {
5676  return $this->urlUtils->validProtocols();
5677  }
5678 
5708  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5709  global $wgTitle; # not generally used but removes an ugly failure mode
5710 
5711  $magicScopeVariable = $this->lock();
5712  $this->startParse(
5713  $wgTitle,
5716  true
5717  );
5718  $outText = '';
5719  $frame = $this->getPreprocessor()->newFrame();
5720 
5721  # Process section extraction flags
5722  $flags = 0;
5723  $sectionParts = explode( '-', $sectionId );
5724  // The section ID may either be a magic string such as 'new' (which should be treated as 0),
5725  // or a numbered section ID in the format of "T-<section index>".
5726  // Explicitly coerce the section index into a number accordingly. (T323373)
5727  $sectionIndex = (int)array_pop( $sectionParts );
5728  foreach ( $sectionParts as $part ) {
5729  if ( $part === 'T' ) {
5731  }
5732  }
5733 
5734  # Check for empty input
5735  if ( strval( $text ) === '' ) {
5736  # Only sections 0 and T-0 exist in an empty document
5737  if ( $sectionIndex === 0 ) {
5738  if ( $mode === 'get' ) {
5739  return '';
5740  }
5741 
5742  return $newText;
5743  } else {
5744  if ( $mode === 'get' ) {
5745  return $newText;
5746  }
5747 
5748  return $text;
5749  }
5750  }
5751 
5752  # Preprocess the text
5753  $root = $this->preprocessToDom( $text, $flags );
5754 
5755  # <h> nodes indicate section breaks
5756  # They can only occur at the top level, so we can find them by iterating the root's children
5757  $node = $root->getFirstChild();
5758 
5759  # Find the target section
5760  if ( $sectionIndex === 0 ) {
5761  # Section zero doesn't nest, level=big
5762  $targetLevel = 1000;
5763  } else {
5764  while ( $node ) {
5765  if ( $node->getName() === 'h' ) {
5766  $bits = $node->splitHeading();
5767  if ( $bits['i'] == $sectionIndex ) {
5768  $targetLevel = $bits['level'];
5769  break;
5770  }
5771  }
5772  if ( $mode === 'replace' ) {
5773  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5774  }
5775  $node = $node->getNextSibling();
5776  }
5777  }
5778 
5779  if ( !$node ) {
5780  # Not found
5781  if ( $mode === 'get' ) {
5782  return $newText;
5783  } else {
5784  return $text;
5785  }
5786  }
5787 
5788  # Find the end of the section, including nested sections
5789  do {
5790  if ( $node->getName() === 'h' ) {
5791  $bits = $node->splitHeading();
5792  $curLevel = $bits['level'];
5793  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable False positive
5794  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5795  break;
5796  }
5797  }
5798  if ( $mode === 'get' ) {
5799  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5800  }
5801  $node = $node->getNextSibling();
5802  } while ( $node );
5803 
5804  # Write out the remainder (in replace mode only)
5805  if ( $mode === 'replace' ) {
5806  # Output the replacement text
5807  # Add two newlines on -- trailing whitespace in $newText is conventionally
5808  # stripped by the editor, so we need both newlines to restore the paragraph gap
5809  # Only add trailing whitespace if there is newText
5810  if ( $newText != "" ) {
5811  $outText .= $newText . "\n\n";
5812  }
5813 
5814  while ( $node ) {
5815  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5816  $node = $node->getNextSibling();
5817  }
5818  }
5819 
5820  # Re-insert stripped tags
5821  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5822 
5823  return $outText;
5824  }
5825 
5841  public function getSection( $text, $sectionId, $defaultText = '' ) {
5842  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5843  }
5844 
5858  public function replaceSection( $oldText, $sectionId, $newText ) {
5859  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5860  }
5861 
5891  public function getFlatSectionInfo( $text ) {
5892  $magicScopeVariable = $this->lock();
5893  $this->startParse(
5894  null,
5897  true
5898  );
5899  $frame = $this->getPreprocessor()->newFrame();
5900  $root = $this->preprocessToDom( $text, 0 );
5901  $node = $root->getFirstChild();
5902  $offset = 0;
5903  $currentSection = [
5904  'index' => 0,
5905  'level' => 0,
5906  'offset' => 0,
5907  'heading' => '',
5908  'text' => ''
5909  ];
5910  $sections = [];
5911 
5912  while ( $node ) {
5913  $nodeText = $frame->expand( $node, PPFrame::RECOVER_ORIG );
5914  if ( $node->getName() === 'h' ) {
5915  $bits = $node->splitHeading();
5916  $sections[] = $currentSection;
5917  $currentSection = [
5918  'index' => $bits['i'],
5919  'level' => $bits['level'],
5920  'offset' => $offset,
5921  'heading' => $nodeText,
5922  'text' => $nodeText
5923  ];
5924  } else {
5925  $currentSection['text'] .= $nodeText;
5926  }
5927  $offset += strlen( $nodeText );
5928  $node = $node->getNextSibling();
5929  }
5930  $sections[] = $currentSection;
5931  return $sections;
5932  }
5933 
5945  public function getRevisionId() {
5946  return $this->mRevisionId;
5947  }
5948 
5955  public function getRevisionRecordObject() {
5956  if ( $this->mRevisionRecordObject ) {
5957  return $this->mRevisionRecordObject;
5958  }
5959 
5960  // NOTE: try to get the RevisionRecord object even if mRevisionId is null.
5961  // This is useful when parsing a revision that has not yet been saved.
5962  // However, if we get back a saved revision even though we are in
5963  // preview mode, we'll have to ignore it, see below.
5964  // NOTE: This callback may be used to inject an OLD revision that was
5965  // already loaded, so "current" is a bit of a misnomer. We can't just
5966  // skip it if mRevisionId is set.
5967  $rev = call_user_func(
5968  $this->mOptions->getCurrentRevisionRecordCallback(),
5969  $this->getTitle(),
5970  $this
5971  );
5972 
5973  if ( $rev === false ) {
5974  // The revision record callback returns `false` (not null) to
5975  // indicate that the revision is missing. (See for example
5976  // Parser::statelessFetchRevisionRecord(), the default callback.)
5977  // This API expects `null` instead. (T251952)
5978  $rev = null;
5979  }
5980 
5981  if ( $this->mRevisionId === null && $rev && $rev->getId() ) {
5982  // We are in preview mode (mRevisionId is null), and the current revision callback
5983  // returned an existing revision. Ignore it and return null, it's probably the page's
5984  // current revision, which is not what we want here. Note that we do want to call the
5985  // callback to allow the unsaved revision to be injected here, e.g. for
5986  // self-transclusion previews.
5987  return null;
5988  }
5989 
5990  // If the parse is for a new revision, then the callback should have
5991  // already been set to force the object and should match mRevisionId.
5992  // If not, try to fetch by mRevisionId instead.
5993  if ( $this->mRevisionId && $rev && $rev->getId() != $this->mRevisionId ) {
5994  $rev = MediaWikiServices::getInstance()
5995  ->getRevisionLookup()
5996  ->getRevisionById( $this->mRevisionId );
5997  }
5998 
5999  $this->mRevisionRecordObject = $rev;
6000 
6001  return $this->mRevisionRecordObject;
6002  }
6003 
6010  public function getRevisionTimestamp() {
6011  if ( $this->mRevisionTimestamp !== null ) {
6012  return $this->mRevisionTimestamp;
6013  }
6014 
6015  # Use specified revision timestamp, falling back to the current timestamp
6016  $revObject = $this->getRevisionRecordObject();
6017  $timestamp = $revObject && $revObject->getTimestamp()
6018  ? $revObject->getTimestamp()
6019  : $this->mOptions->getTimestamp();
6020  $this->mOutput->setRevisionTimestampUsed( $timestamp ); // unadjusted time zone
6021 
6022  # The cryptic '' timezone parameter tells to use the site-default
6023  # timezone offset instead of the user settings.
6024  # Since this value will be saved into the parser cache, served
6025  # to other users, and potentially even used inside links and such,
6026  # it needs to be consistent for all visitors.
6027  $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp, '' );
6028 
6029  return $this->mRevisionTimestamp;
6030  }
6031 
6038  public function getRevisionUser(): ?string {
6039  if ( $this->mRevisionUser === null ) {
6040  $revObject = $this->getRevisionRecordObject();
6041 
6042  # if this template is subst: the revision id will be blank,
6043  # so just use the current user's name
6044  if ( $revObject && $revObject->getUser() ) {
6045  $this->mRevisionUser = $revObject->getUser()->getName();
6046  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
6047  $this->mRevisionUser = $this->getUserIdentity()->getName();
6048  } else {
6049  # Note that we fall through here with
6050  # $this->mRevisionUser still null
6051  }
6052  }
6053  return $this->mRevisionUser;
6054  }
6055 
6062  public function getRevisionSize() {
6063  if ( $this->mRevisionSize === null ) {
6064  $revObject = $this->getRevisionRecordObject();
6065 
6066  # if this variable is subst: the revision id will be blank,
6067  # so just use the parser input size, because the own substitution
6068  # will change the size.
6069  if ( $revObject ) {
6070  $this->mRevisionSize = $revObject->getSize();
6071  } else {
6072  $this->mRevisionSize = $this->mInputSize;
6073  }
6074  }
6075  return $this->mRevisionSize;
6076  }
6077 
6086  public function setDefaultSort( $sort ) {
6087  wfDeprecated( __METHOD__, '1.38' );
6088  $this->mOutput->setPageProperty( 'defaultsort', $sort );
6089  }
6090 
6104  public function getDefaultSort() {
6105  wfDeprecated( __METHOD__, '1.38' );
6106  return $this->mOutput->getPageProperty( 'defaultsort' ) ?? '';
6107  }
6108 
6118  public function getCustomDefaultSort() {
6119  wfDeprecated( __METHOD__, '1.38' );
6120  return $this->mOutput->getPageProperty( 'defaultsort' ) ?? false;
6121  }
6122 
6123  private static function getSectionNameFromStrippedText( $text ) {
6125  $text = Sanitizer::decodeCharReferences( $text );
6126  $text = self::normalizeSectionName( $text );
6127  return $text;
6128  }
6129 
6130  private static function makeAnchor( $sectionName ) {
6131  return '#' . Sanitizer::escapeIdForLink( $sectionName );
6132  }
6133 
6134  private function makeLegacyAnchor( $sectionName ) {
6135  $fragmentMode = $this->svcOptions->get( MainConfigNames::FragmentMode );
6136  if ( isset( $fragmentMode[1] ) && $fragmentMode[1] === 'legacy' ) {
6137  // ForAttribute() and ForLink() are the same for legacy encoding
6139  } else {
6140  $id = Sanitizer::escapeIdForLink( $sectionName );
6141  }
6142 
6143  return "#$id";
6144  }
6145 
6155  public function guessSectionNameFromWikiText( $text ) {
6156  # Strip out wikitext links(they break the anchor)
6157  $text = $this->stripSectionName( $text );
6158  $sectionName = self::getSectionNameFromStrippedText( $text );
6159  return self::makeAnchor( $sectionName );
6160  }
6161 
6172  public function guessLegacySectionNameFromWikiText( $text ) {
6173  # Strip out wikitext links(they break the anchor)
6174  $text = $this->stripSectionName( $text );
6175  $sectionName = self::getSectionNameFromStrippedText( $text );
6176  return $this->makeLegacyAnchor( $sectionName );
6177  }
6178 
6185  public static function guessSectionNameFromStrippedText( $text ) {
6186  $sectionName = self::getSectionNameFromStrippedText( $text );
6187  return self::makeAnchor( $sectionName );
6188  }
6189 
6196  private static function normalizeSectionName( $text ) {
6197  # T90902: ensure the same normalization is applied for IDs as to links
6199  $titleParser = MediaWikiServices::getInstance()->getTitleParser();
6200  '@phan-var MediaWikiTitleCodec $titleParser';
6201  try {
6202 
6203  $parts = $titleParser->splitTitleString( "#$text" );
6204  } catch ( MalformedTitleException $ex ) {
6205  return $text;
6206  }
6207  return $parts['fragment'];
6208  }
6209 
6225  public function stripSectionName( $text ) {
6226  # Strip internal link markup
6227  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
6228  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
6229 
6230  # Strip external link markup
6231  # @todo FIXME: Not tolerant to blank link text
6232  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
6233  # on how many empty links there are on the page - need to figure that out.
6234  $text = preg_replace(
6235  '/\[(?i:' . $this->urlUtils->validProtocols() . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
6236 
6237  # Parse wikitext quotes (italics & bold)
6238  $text = $this->doQuotes( $text );
6239 
6240  # Strip HTML tags
6241  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
6242  return $text;
6243  }
6244 
6258  private function fuzzTestSrvus( $text, PageReference $page, ParserOptions $options,
6259  $outputType = self::OT_HTML
6260  ) {
6261  $magicScopeVariable = $this->lock();
6262  $this->startParse( $page, $options, $outputType, true );
6263 
6264  $text = $this->replaceVariables( $text );
6265  $text = $this->mStripState->unstripBoth( $text );
6266  $text = Sanitizer::internalRemoveHtmlTags( $text );
6267  return $text;
6268  }
6269 
6281  private function fuzzTestPst( $text, PageReference $page, ParserOptions $options ) {
6282  return $this->preSaveTransform( $text, $page, $options->getUserIdentity(), $options );
6283  }
6284 
6296  private function fuzzTestPreprocess( $text, PageReference $page, ParserOptions $options ) {
6297  return $this->fuzzTestSrvus( $text, $page, $options, self::OT_PREPROCESS );
6298  }
6299 
6318  public function markerSkipCallback( $s, callable $callback ) {
6319  $i = 0;
6320  $out = '';
6321  while ( $i < strlen( $s ) ) {
6322  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
6323  if ( $markerStart === false ) {
6324  $out .= call_user_func( $callback, substr( $s, $i ) );
6325  break;
6326  } else {
6327  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
6328  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
6329  if ( $markerEnd === false ) {
6330  $out .= substr( $s, $markerStart );
6331  break;
6332  } else {
6333  $markerEnd += strlen( self::MARKER_SUFFIX );
6334  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
6335  $i = $markerEnd;
6336  }
6337  }
6338  }
6339  return $out;
6340  }
6341 
6349  public function killMarkers( $text ) {
6350  return $this->mStripState->killMarkers( $text );
6351  }
6352 
6363  public static function parseWidthParam( $value, $parseHeight = true ) {
6364  $parsedWidthParam = [];
6365  if ( $value === '' ) {
6366  return $parsedWidthParam;
6367  }
6368  $m = [];
6369  # (T15500) In both cases (width/height and width only),
6370  # permit trailing "px" for backward compatibility.
6371  if ( $parseHeight && preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6372  $width = intval( $m[1] );
6373  $height = intval( $m[2] );
6374  $parsedWidthParam['width'] = $width;
6375  $parsedWidthParam['height'] = $height;
6376  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6377  $width = intval( $value );
6378  $parsedWidthParam['width'] = $width;
6379  }
6380  return $parsedWidthParam;
6381  }
6382 
6392  protected function lock() {
6393  if ( $this->mInParse ) {
6394  throw new MWException( "Parser state cleared while parsing. "
6395  . "Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6396  }
6397 
6398  // Save the backtrace when locking, so that if some code tries locking again,
6399  // we can print the lock owner's backtrace for easier debugging
6400  $e = new Exception;
6401  $this->mInParse = $e->getTraceAsString();
6402 
6403  $recursiveCheck = new ScopedCallback( function () {
6404  $this->mInParse = false;
6405  } );
6406 
6407  return $recursiveCheck;
6408  }
6409 
6417  public function isLocked() {
6418  return (bool)$this->mInParse;
6419  }
6420 
6431  public static function stripOuterParagraph( $html ) {
6432  $m = [];
6433  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) && strpos( $m[1], '</p>' ) === false ) {
6434  $html = $m[1];
6435  }
6436 
6437  return $html;
6438  }
6439 
6450  public static function formatPageTitle( $nsText, $nsSeparator, $mainText ): string {
6451  $html = '';
6452  if ( $nsText !== '' ) {
6453  $html .= '<span class="mw-page-title-namespace">' . HtmlArmor::getHtml( $nsText ) . '</span>';
6454  $html .= '<span class="mw-page-title-separator">' . HtmlArmor::getHtml( $nsSeparator ) . '</span>';
6455  }
6456  $html .= '<span class="mw-page-title-main">' . HtmlArmor::getHtml( $mainText ) . '</span>';
6457  return $html;
6458  }
6459 
6471  public function getFreshParser() {
6472  if ( $this->mInParse ) {
6473  return $this->factory->create();
6474  } else {
6475  return $this;
6476  }
6477  }
6478 
6486  public function enableOOUI() {
6487  wfDeprecated( __METHOD__, '1.35' );
6489  $this->mOutput->setEnableOOUI( true );
6490  }
6491 
6498  private function setOutputFlag( string $flag, string $reason ): void {
6499  $this->mOutput->setOutputFlag( $flag );
6500  $name = $this->getTitle()->getPrefixedText();
6501  $this->logger->debug( __METHOD__ . ": set $flag flag on '$name'; $reason" );
6502  }
6503 }
getUser()
const OT_WIKI
Definition: Defines.php:159
const SFH_NO_HASH
Definition: Defines.php:171
const SFH_OBJECT_ARGS
Definition: Defines.php:172
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:162
const OT_PREPROCESS
Definition: Defines.php:160
const OT_HTML
Definition: Defines.php:158
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:508
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition: WebStart.php:82
static doBlockLevels( $text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
static expand(Parser $parser, string $id, ConvertibleTimestamp $ts, NamespaceInfo $nsInfo, 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:67
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:173
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:54
static factory( $mode=false, IContextSource $context=null)
Get a new image gallery.
Class for exceptions thrown by ImageGalleryBase::factory().
Base class for language-specific code.
Definition: Language.php:56
MediaWiki exception.
Definition: MWException.php:30
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:40
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:36
Helper class for mapping value objects representing basic entities to cache keys.
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:561
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 anchor link elements for pages.
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.
Class for handling an array of magic words.
A factory that stores information about MagicWords, and creates them on demand with caching.
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.
Stub object for the user language.
Base class for HTML cleanup utilities.
Creates User objects.
Definition: UserFactory.php:38
UserNameUtils service.
Provides access to user options.
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:340
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:143
static plaintextParam( $plaintext)
Definition: Message.php:1266
static numParam( $num)
Definition: Message.php:1145
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
static setupOOUI( $skinName='default', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
static int $inParserFactory
Track calls to Parser constructor to aid in deprecation of direct Parser invocation.
Set options of the Parser.
getUserIdentity()
Get the identity of the user for whom the parse is made.
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:104
addTrackingCategory( $msg)
Definition: Parser.php:4143
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:1281
getTargetLanguage()
Get the target language for the content being parsed.
Definition: Parser.php:1184
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
Definition: Parser.php:6010
setDefaultSort( $sort)
Mutator for the 'defaultsort' page property.
Definition: Parser.php:6086
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6431
static normalizeLinkUrl( $url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:2312
__clone()
Allow extensions to clean up when the parser is cloned.
Definition: Parser.php:578
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4766
getMagicWordFactory()
Get the MagicWordFactory that this Parser is using.
Definition: Parser.php:1238
$mHighestExpansionDepth
Definition: Parser.php:277
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:689
getCustomDefaultSort()
Accessor for the 'defaultsort' page property.
Definition: Parser.php:6118
ParserOptions null $mOptions
Definition: Parser.php:304
getOptions()
Definition: Parser.php:1124
cleanSig( $text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4723
getStripState()
Definition: Parser.php:1352
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:6038
isCurrentRevisionOfTitleCached(LinkTarget $link)
Definition: Parser.php:3558
replaceVariables( $text, $frame=false, $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
Definition: Parser.php:2932
getFunctionSynonyms()
Definition: Parser.php:5667
extensionSubstitution(array $params, PPFrame $frame, bool $processNowiki=false)
Return the text to be used for a given extension tag.
Definition: Parser.php:3971
interwikiTransclude(LinkTarget $link, $action)
Transclude an interwiki link.
Definition: Parser.php:3832
const OT_HTML
Definition: Parser.php:137
$mHeadings
Definition: Parser.php:280
getPreloadText( $text, PageReference $page, ParserOptions $options, $params=[])
Process the wikitext for the "?preload=" feature.
Definition: Parser.php:993
setTitle(Title $t=null)
Set the context title.
Definition: Parser.php:1026
static guessSectionNameFromStrippedText( $text)
Like guessSectionNameFromWikiText(), but takes already-stripped text as input.
Definition: Parser.php:6185
const OT_WIKI
Definition: Parser.php:138
getFlatSectionInfo( $text)
Get an array of preprocessor section information.
Definition: Parser.php:5891
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:2983
modifyImageHtml(File $file, array $params, string &$html)
Give hooks a chance to modify image thumbnail HTML.
Definition: Parser.php:5575
fetchTemplateAndTitle(LinkTarget $link)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3598
Title null $mTitle
Since 1.34, leaving mTitle uninitialized or setting mTitle to null is deprecated.
Definition: Parser.php:313
setUser(?UserIdentity $user)
Set the current user.
Definition: Parser.php:1015
getRevisionSize()
Get the size of the revision.
Definition: Parser.php:6062
stripSectionName( $text)
Strips a text string of wikitext for use in a section anchor.
Definition: Parser.php:6225
tagNeedsNowikiStrippedInTagPF(string $lowerTagName)
Definition: Parser.php:3945
getOutputType()
Accessor for the output type.
Definition: Parser.php:1080
fetchCurrentRevisionRecordOfTitle(LinkTarget $link)
Fetch the current revision of a given title as a RevisionRecord.
Definition: Parser.php:3528
fetchFileAndTitle(LinkTarget $link, array $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3782
recursivePreprocess( $text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:973
fetchFileNoRegister(LinkTarget $link, array $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3809
getUrlProtocols()
Definition: Parser.php:5675
setFunctionHook( $id, callable $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4952
getHookRunner()
Get a HookRunner for calling core hooks.
Definition: Parser.php:1693
getTitle()
Definition: Parser.php:1035
braceSubstitution(array $piece, PPFrame $frame)
Return the text of a template, after recursively replacing any variables or templates within the temp...
Definition: Parser.php:3008
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:1223
preprocess( $text, ?PageReference $page, ParserOptions $options, $revid=null, $frame=false)
Expand templates and variables in the text, producing valid, static wikitext.
Definition: Parser.php:946
getExternalLinkAttribs( $url)
Get an associative array of additional HTML attributes appropriate for a particular external link.
Definition: Parser.php:2281
setOutputType( $ot)
Mutator for the output type.
Definition: Parser.php:1089
getFunctionLang()
Get a language object for use in parser functions such as {{FORMATNUM:}}.
Definition: Parser.php:1171
callParserFunction(PPFrame $frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3393
makeLimitReport()
Set the limit report data in the current ParserOutput.
Definition: Parser.php:790
parseExtensionTagAsTopLevelDoc( $text)
Needed by Parsoid/PHP to ensure all the hooks for extensions are run in the right order.
Definition: Parser.php:927
transformMsg( $text, ParserOptions $options, ?PageReference $page=null)
Wrapper for preprocess()
Definition: Parser.php:4845
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:4810
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1344
setHook( $tag, callable $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4885
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition: Parser.php:1258
getTags()
Accessor.
Definition: Parser.php:5659
static statelessFetchRevisionRecord(LinkTarget $link, $parser=null)
Wrapper around RevisionLookup::getKnownCurrentRevision.
Definition: Parser.php:3574
recursiveTagParse( $text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:878
getTargetLanguageConverter()
Shorthand for getting a Language Converter for Target language.
Definition: Parser.php:1657
static replaceTableOfContentsMarker( $text, $toc)
Replace table of contents marker in parsed HTML.
Definition: Parser.php:4787
const OT_PLAIN
Definition: Parser.php:142
getSection( $text, $sectionId, $defaultText='')
This function returns the text of a section, specified by a number ($section).
Definition: Parser.php:5841
internalParse( $text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
Definition: Parser.php:1588
replaceSection( $oldText, $sectionId, $newText)
This function returns $oldtext after the content of the section specified by $section has been replac...
Definition: Parser.php:5858
setPage(?PageReference $t=null)
Set the page used as context for parsing, e.g.
Definition: Parser.php:1048
getUserIdentity()
Get a user either from the user set on Parser if it's set, or from the ParserOptions object otherwise...
Definition: Parser.php:1203
doBlockLevels( $text, $linestart)
Make lists from lines starting with ':', '*', '#', etc.
Definition: Parser.php:2826
$mPPNodeCount
Definition: Parser.php:275
getDefaultSort()
Accessor for the 'defaultsort' page property.
Definition: Parser.php:6104
const MARKER_PREFIX
Definition: Parser.php:162
isLocked()
Will entry points such as parse() throw an exception due to the parser already being active?
Definition: Parser.php:6417
getHookContainer()
Get a HookContainer capable of returning metadata about hooks or running extension hooks.
Definition: Parser.php:1681
getFunctionHooks()
Get all registered function hook identifiers.
Definition: Parser.php:4989
$mMarkerIndex
Definition: Parser.php:222
getPage()
Returns the page used as context for parsing, e.g.
Definition: Parser.php:1071
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:2256
$mExpensiveFunctionCount
Definition: Parser.php:286
getContentLanguage()
Get the content language that this Parser is using.
Definition: Parser.php:1248
getOutput()
Definition: Parser.php:1116
Options( $x=null)
Accessor/mutator for the ParserOptions object.
Definition: Parser.php:1144
recursiveTagParseFully( $text, $frame=false)
Fully parse wikitext to fully parsed HTML.
Definition: Parser.php:902
guessSectionNameFromWikiText( $text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
Definition: Parser.php:6155
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4903
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5945
setOptions(ParserOptions $options)
Mutator for the ParserOptions object.
Definition: Parser.php:1133
static parseWidthParam( $value, $parseHeight=true)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6363
validateSig( $text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4708
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:1213
guessLegacySectionNameFromWikiText( $text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead, if possible.
Definition: Parser.php:6172
const EXT_LINK_URL_CLASS
Definition: Parser.php:116
OutputType( $x=null)
Accessor/mutator for the output type.
Definition: Parser.php:1107
const SFH_OBJECT_ARGS
Definition: Parser.php:108
__destruct()
Reduce memory usage to reduce the impact of circular references.
Definition: Parser.php:564
resetOutput()
Reset the ParserOutput.
Definition: Parser.php:666
setLinkID( $id)
Definition: Parser.php:1161
doQuotes( $text)
Helper function for handleAllQuotes()
Definition: Parser.php:1991
getTemplateDom(LinkTarget $title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3483
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:6450
replaceLinkHolders(&$text, $options=0)
Replace "<!--LINK-->" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:5001
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:1365
const CONSTRUCTOR_OPTIONS
Definition: Parser.php:424
const OT_MSG
Definition: Parser.php:140
incrementExpensiveFunctionCount()
Definition: Parser.php:4077
makeImage(LinkTarget $link, $options, $holders=false)
Parse image options text and use it to make an image.
Definition: Parser.php:5278
const OT_PREPROCESS
Definition: Parser.php:139
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:4639
__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:475
firstCallInit()
Used to do various kinds of initialisation on the first call of the parser.
Definition: Parser.php:608
preprocessToDom( $text, $flags=0)
Get the document object model for the given wikitext.
Definition: Parser.php:2907
markerSkipCallback( $s, callable $callback)
Call a callback function on all regions of the given text that are not inside strip markers,...
Definition: Parser.php:6318
clearState()
Clear Parser state.
Definition: Parser.php:620
killMarkers( $text)
Remove any strip markers found in the given text.
Definition: Parser.php:6349
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6486
nextLinkID()
Definition: Parser.php:1153
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:4162
getRevisionRecordObject()
Get the revision record object for $this->mRevisionId.
Definition: Parser.php:5955
attributeStripCallback(&$text, $frame=false)
Callback from the Sanitizer for expanding items found in HTML attribute values, so they can be safely...
Definition: Parser.php:5646
argSubstitution(array $piece, PPFrame $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3904
const SFH_NO_HASH
Definition: Parser.php:107
renderImageGallery( $text, array $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:5041
lock()
Lock the current instance of the parser.
Definition: Parser.php:6392
static statelessFetchTemplate( $page, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3640
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:6471
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:4518
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.
static fixTagAttributes( $text, $element, $sorted=false)
Take a tag soup fragment listing an HTML element's attributes and normalize it to well-formed XML,...
Definition: Sanitizer.php:838
static cleanUrl( $url)
Definition: Sanitizer.php:1776
static escapeIdForLink( $id)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid URL...
Definition: Sanitizer.php:973
static validateTagAttributes( $attribs, $element)
Take an array of attribute names and values and normalize or discard illegal values for the given ele...
Definition: Sanitizer.php:525
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:392
static internalRemoveHtmlTags( $text, $processCallback=null, $args=[], $extratags=[], $removetags=[])
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments; BEWARE there may be...
Definition: Sanitizer.php:317
static normalizeSectionNameWhitespace( $section)
Normalizes whitespace in a section name, such as might be returned by Parser::stripSectionName(),...
Definition: Sanitizer.php:1239
const ID_FALLBACK
Tells escapeUrlForHtml() to encode the ID using the fallback encoding, or return false if no fallback...
Definition: Sanitizer.php:85
static decodeCharReferences( $text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string.
Definition: Sanitizer.php:1371
static escapeIdForAttribute( $id, $mode=self::ID_PRIMARY)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid HTM...
Definition: Sanitizer.php:946
static stripAllTags( $html)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
Definition: Sanitizer.php:1725
static decodeTagAttributes( $text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1139
const ID_PRIMARY
Tells escapeUrlForHtml() to encode the ID using the wiki's primary encoding.
Definition: Sanitizer.php:77
Arbitrary section name based PHP profiling.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
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.
Represents a title within MediaWiki.
Definition: Title.php:52
static castFromPageReference(?PageReference $pageReference)
Return a Title for a given Reference.
Definition: Title.php:335
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:285
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:737
static castFromLinkTarget( $linkTarget)
Same as newFromLinkTarget, but if passed null, returns null.
Definition: Title.php:309
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:373
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:641
This class performs some operations related to tracking categories, such as creating a list of all su...
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:70
static newFromName( $name, $validate='valid')
Definition: User.php:591
Multi-datacenter aware caching interface.
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:746
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 => '