MediaWiki  master
Parser.php
Go to the documentation of this file.
1 <?php
51 use Psr\Log\LoggerInterface;
52 use Wikimedia\IPUtils;
53 use Wikimedia\ScopedCallback;
54 
95 class Parser {
96 
97  # Flags for Parser::setFunctionHook
98  public const SFH_NO_HASH = 1;
99  public const SFH_OBJECT_ARGS = 2;
100 
101  # Constants needed for external link processing
102  # Everything except bracket, space, or control characters
103  # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
104  # as well as U+3000 is IDEOGRAPHIC SPACE for T21052
105  # \x{FFFD} is the Unicode replacement character, which the HTML5 spec
106  # uses to replace invalid HTML characters.
107  public const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]';
108  # Simplified expression to match an IPv4 or IPv6 address, or
109  # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
110  // phpcs:ignore Generic.Files.LineLength
111  private const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
112  # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
113  // phpcs:ignore Generic.Files.LineLength
114  private const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
115  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
116 
117  # Regular expression for a non-newline space
118  private const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
119 
124  public const PTD_FOR_INCLUSION = Preprocessor::DOM_FOR_INCLUSION;
125 
126  # Allowed values for $this->mOutputType
127  # Parameter to startExternalParse().
128  public const OT_HTML = 1; # like parse()
129  public const OT_WIKI = 2; # like preSaveTransform()
130  public const OT_PREPROCESS = 3; # like preprocess()
131  public const OT_MSG = 3;
132  # like extractSections() - portions of the original are returned unchanged.
133  public const OT_PLAIN = 4;
134 
152  public const MARKER_SUFFIX = "-QINU`\"'\x7f";
153  public const MARKER_PREFIX = "\x7f'\"`UNIQ-";
154 
167  public const TOC_START = '<mw:toc>';
168 
174  public const TOC_END = '</mw:toc>';
175 
193  public const TOC_PLACEHOLDER = '<meta property="mw:PageProp/toc" />';
194 
202  private const TOC_PLACEHOLDER_REGEX = '/<meta\\b[^>]*\\bproperty\\s*=\\s*"mw:PageProp\\/toc"[^>]*\\/>/';
203 
204  # Persistent:
205  private $mTagHooks = [];
206  private $mFunctionHooks = [];
207  private $mFunctionSynonyms = [ 0 => [], 1 => [] ];
208  private $mStripList = [];
209  private $mVarCache = [];
210  private $mImageParams = [];
211  private $mImageParamsMagicArray = [];
213  public $mMarkerIndex = 0;
214 
215  # Initialised by initializeVariables()
216 
220  private $mVariables;
221 
225  private $mSubstWords;
226 
227  # Initialised in constructor
228  private $mExtLinkBracketedRegex;
229 
235  private $urlUtils;
236 
237  # Initialized in constructor
241  private $mPreprocessor;
242 
243  # Cleared with clearState():
247  private $mOutput;
248  private $mAutonumber;
249 
253  private $mStripState;
254 
258  private $mLinkHolders;
259 
263  private $mLinkID;
264  private $mIncludeSizes;
269  private $mTplRedirCache;
271  public $mHeadings;
275  private $mDoubleUnderscores;
277  public $mExpensiveFunctionCount; # number of expensive parser function calls
278  private $mShowToc;
279  private $mForceTocPosition;
281  private $mTplDomCache;
282 
286  private $mUser;
287 
288  # Temporary
289  # These are variables reset at least once per parse regardless of $clearState
290 
295  public $mOptions;
296 
304  public $mTitle; # Title context, used for self-link rendering and similar things
305  private $mOutputType; # Output type, one of the OT_xxx constants
307  public $ot; # Shortcut alias, see setOutputType()
308  private $mRevisionId; # ID to display in {{REVISIONID}} tags
309  private $mRevisionTimestamp; # The timestamp of the specified revision ID
310  private $mRevisionUser; # User to display in {{REVISIONUSER}} tag
311  private $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
312  private $mInputSize = false; # For {{PAGESIZE}} on current page.
313 
315  private $mRevisionRecordObject;
316 
322  private $mLangLinkLanguages;
323 
330  private $currentRevisionCache;
331 
336  private $mInParse = false;
337 
339  private $mProfiler;
340 
344  private $mLinkRenderer;
345 
347  private $magicWordFactory;
348 
350  private $contLang;
351 
353  private $languageConverterFactory;
354 
356  private $factory;
357 
359  private $specialPageFactory;
360 
362  private $titleFormatter;
363 
371  private $svcOptions;
372 
374  private $linkRendererFactory;
375 
377  private $nsInfo;
378 
380  private $logger;
381 
383  private $badFileLookup;
384 
386  private $hookContainer;
387 
389  private $hookRunner;
390 
392  private $tidy;
393 
395  private $userOptionsLookup;
396 
398  private $userFactory;
399 
401  private $httpRequestFactory;
402 
404  private $trackingCategories;
405 
407  private $signatureValidatorFactory;
408 
410  private $userNameUtils;
411 
415  public const CONSTRUCTOR_OPTIONS = [
416  // See documentation for the corresponding config options
417  // Many of these are only used in (eg) CoreMagicVariables
418  MainConfigNames::AllowDisplayTitle,
419  MainConfigNames::AllowSlowParserFunctions,
420  MainConfigNames::ArticlePath,
421  MainConfigNames::EnableScaryTranscluding,
422  MainConfigNames::ExtraInterlanguageLinkPrefixes,
423  MainConfigNames::FragmentMode,
424  MainConfigNames::Localtimezone,
425  MainConfigNames::MaxSigChars,
426  MainConfigNames::MaxTocLevel,
427  MainConfigNames::MiserMode,
428  MainConfigNames::RawHtml,
429  MainConfigNames::ScriptPath,
430  MainConfigNames::Server,
431  MainConfigNames::ServerName,
432  MainConfigNames::ShowHostnames,
433  MainConfigNames::SignatureValidation,
434  MainConfigNames::Sitename,
435  MainConfigNames::StylePath,
436  MainConfigNames::TranscludeCacheExpiry,
437  MainConfigNames::PreprocessorCacheThreshold,
438  ];
439 
466  public function __construct(
467  ServiceOptions $svcOptions,
468  MagicWordFactory $magicWordFactory,
469  Language $contLang,
470  ParserFactory $factory,
471  UrlUtils $urlUtils,
472  SpecialPageFactory $spFactory,
473  LinkRendererFactory $linkRendererFactory,
474  NamespaceInfo $nsInfo,
475  LoggerInterface $logger,
476  BadFileLookup $badFileLookup,
477  LanguageConverterFactory $languageConverterFactory,
478  HookContainer $hookContainer,
479  TidyDriverBase $tidy,
480  WANObjectCache $wanCache,
481  UserOptionsLookup $userOptionsLookup,
482  UserFactory $userFactory,
483  TitleFormatter $titleFormatter,
484  HttpRequestFactory $httpRequestFactory,
485  TrackingCategories $trackingCategories,
486  SignatureValidatorFactory $signatureValidatorFactory,
487  UserNameUtils $userNameUtils
488  ) {
489  if ( ParserFactory::$inParserFactory === 0 ) {
490  // Direct construction of Parser was deprecated in 1.34 and
491  // removed in 1.36; use a ParserFactory instead.
492  throw new MWException( 'Direct construction of Parser not allowed' );
493  }
494  $svcOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
495  $this->svcOptions = $svcOptions;
496 
497  $this->urlUtils = $urlUtils;
498  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->urlUtils->validProtocols() . ')' .
499  self::EXT_LINK_ADDR .
500  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
501 
502  $this->magicWordFactory = $magicWordFactory;
503 
504  $this->contLang = $contLang;
505 
506  $this->factory = $factory;
507  $this->specialPageFactory = $spFactory;
508  $this->linkRendererFactory = $linkRendererFactory;
509  $this->nsInfo = $nsInfo;
510  $this->logger = $logger;
511  $this->badFileLookup = $badFileLookup;
512 
513  $this->languageConverterFactory = $languageConverterFactory;
514 
515  $this->hookContainer = $hookContainer;
516  $this->hookRunner = new HookRunner( $hookContainer );
517 
518  $this->tidy = $tidy;
519 
520  $this->mPreprocessor = new Preprocessor_Hash(
521  $this,
522  $wanCache,
523  [
524  'cacheThreshold' => $svcOptions->get( MainConfigNames::PreprocessorCacheThreshold ),
525  'disableLangConversion' => $languageConverterFactory->isConversionDisabled(),
526  ]
527  );
528 
529  $this->userOptionsLookup = $userOptionsLookup;
530  $this->userFactory = $userFactory;
531  $this->titleFormatter = $titleFormatter;
532  $this->httpRequestFactory = $httpRequestFactory;
533  $this->trackingCategories = $trackingCategories;
534  $this->signatureValidatorFactory = $signatureValidatorFactory;
535  $this->userNameUtils = $userNameUtils;
536 
537  // These steps used to be done in "::firstCallInit()"
538  // (if you're chasing a reference from some old code)
540  $this,
542  );
544  $this,
546  );
547  $this->initializeVariables();
548 
549  $this->hookRunner->onParserFirstCallInit( $this );
550  }
551 
555  public function __destruct() {
556  if ( isset( $this->mLinkHolders ) ) {
557  // @phan-suppress-next-line PhanTypeObjectUnsetDeclaredProperty
558  unset( $this->mLinkHolders );
559  }
560  // @phan-suppress-next-line PhanTypeSuspiciousNonTraversableForeach
561  foreach ( $this as $name => $value ) {
562  unset( $this->$name );
563  }
564  }
565 
569  public function __clone() {
570  $this->mInParse = false;
571 
572  // T58226: When you create a reference "to" an object field, that
573  // makes the object field itself be a reference too (until the other
574  // reference goes out of scope). When cloning, any field that's a
575  // reference is copied as a reference in the new object. Both of these
576  // are defined PHP5 behaviors, as inconvenient as it is for us when old
577  // hooks from PHP4 days are passing fields by reference.
578  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
579  // Make a non-reference copy of the field, then rebind the field to
580  // reference the new copy.
581  $tmp = $this->$k;
582  $this->$k =& $tmp;
583  unset( $tmp );
584  }
585 
586  $this->mPreprocessor = clone $this->mPreprocessor;
587  $this->mPreprocessor->resetParser( $this );
588 
589  $this->hookRunner->onParserCloned( $this );
590  }
591 
599  public function firstCallInit() {
600  /*
601  * This method should be hard-deprecated once remaining calls are
602  * removed; it no longer does anything.
603  */
604  }
605 
611  public function clearState() {
612  $this->resetOutput();
613  $this->mAutonumber = 0;
614  $this->mLinkHolders = new LinkHolderArray(
615  $this,
616  $this->getContentLanguageConverter(),
617  $this->getHookContainer()
618  );
619  $this->mLinkID = 0;
620  $this->mRevisionTimestamp = null;
621  $this->mRevisionId = null;
622  $this->mRevisionUser = null;
623  $this->mRevisionSize = null;
624  $this->mRevisionRecordObject = null;
625  $this->mVarCache = [];
626  $this->mUser = null;
627  $this->mLangLinkLanguages = [];
628  $this->currentRevisionCache = null;
629 
630  $this->mStripState = new StripState( $this );
631 
632  # Clear these on every parse, T6549
633  $this->mTplRedirCache = [];
634  $this->mTplDomCache = [];
635 
636  $this->mShowToc = true;
637  $this->mForceTocPosition = false;
638  $this->mIncludeSizes = [
639  'post-expand' => 0,
640  'arg' => 0,
641  ];
642  $this->mPPNodeCount = 0;
643  $this->mHighestExpansionDepth = 0;
644  $this->mHeadings = [];
645  $this->mDoubleUnderscores = [];
646  $this->mExpensiveFunctionCount = 0;
647 
648  $this->mProfiler = new SectionProfiler();
649 
650  $this->hookRunner->onParserClearState( $this );
651  }
652 
657  public function resetOutput() {
658  $this->mOutput = new ParserOutput;
659  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
660  }
661 
680  public function parse(
681  $text, PageReference $page, ParserOptions $options,
682  $linestart = true, $clearState = true, $revid = null
683  ) {
684  if ( $clearState ) {
685  // We use U+007F DELETE to construct strip markers, so we have to make
686  // sure that this character does not occur in the input text.
687  $text = strtr( $text, "\x7f", "?" );
688  $magicScopeVariable = $this->lock();
689  }
690  // Strip U+0000 NULL (T159174)
691  $text = str_replace( "\000", '', $text );
692 
693  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive
694  $this->startParse( $page, $options, self::OT_HTML, $clearState );
695 
696  $this->currentRevisionCache = null;
697  $this->mInputSize = strlen( $text );
698  $this->mOutput->resetParseStartTime();
699 
700  $oldRevisionId = $this->mRevisionId;
701  $oldRevisionRecordObject = $this->mRevisionRecordObject;
702  $oldRevisionTimestamp = $this->mRevisionTimestamp;
703  $oldRevisionUser = $this->mRevisionUser;
704  $oldRevisionSize = $this->mRevisionSize;
705  if ( $revid !== null ) {
706  $this->mRevisionId = $revid;
707  $this->mRevisionRecordObject = null;
708  $this->mRevisionTimestamp = null;
709  $this->mRevisionUser = null;
710  $this->mRevisionSize = null;
711  }
712 
713  $text = $this->internalParse( $text );
714  $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
715 
716  $text = $this->internalParseHalfParsed( $text, true, $linestart );
717 
725  if ( !$options->getDisableTitleConversion()
726  && !isset( $this->mDoubleUnderscores['nocontentconvert'] )
727  && !isset( $this->mDoubleUnderscores['notitleconvert'] )
728  && $this->mOutput->getDisplayTitle() === false
729  ) {
730  $titleText = $this->getTargetLanguageConverter()->getConvRuleTitle();
731  if ( $titleText !== false ) {
732  $titleText = Sanitizer::removeSomeTags( $titleText );
733  } else {
734  [ $nsText, $nsSeparator, $mainText ] = $this->getTargetLanguageConverter()->convertSplitTitle( $page );
735  // In the future, those three pieces could be stored separately rather than joined into $titleText,
736  // and OutputPage would format them and join them together, to resolve T314399.
737  $titleText = self::formatPageTitle( $nsText, $nsSeparator, $mainText );
738  }
739  $this->mOutput->setTitleText( $titleText );
740  }
741 
742  # Compute runtime adaptive expiry if set
743  $this->mOutput->finalizeAdaptiveCacheExpiry();
744 
745  # Warn if too many heavyweight parser functions were used
746  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
747  $this->limitationWarn( 'expensive-parserfunction',
748  $this->mExpensiveFunctionCount,
749  $this->mOptions->getExpensiveParserFunctionLimit()
750  );
751  }
752 
753  # Information on limits, for the benefit of users who try to skirt them
754  if ( MediaWikiServices::getInstance()->getMainConfig()->get(
755  MainConfigNames::EnableParserLimitReporting ) ) {
756  $this->makeLimitReport();
757  }
758 
759  # Wrap non-interface parser output in a <div> so it can be targeted
760  # with CSS (T37247)
761  $class = $this->mOptions->getWrapOutputClass();
762  if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
763  $this->mOutput->addWrapperDivClass( $class );
764  }
765 
766  $this->mOutput->setText( $text );
767 
768  $this->mRevisionId = $oldRevisionId;
769  $this->mRevisionRecordObject = $oldRevisionRecordObject;
770  $this->mRevisionTimestamp = $oldRevisionTimestamp;
771  $this->mRevisionUser = $oldRevisionUser;
772  $this->mRevisionSize = $oldRevisionSize;
773  $this->mInputSize = false;
774  $this->currentRevisionCache = null;
775 
776  return $this->mOutput;
777  }
778 
782  protected function makeLimitReport() {
783  $maxIncludeSize = $this->mOptions->getMaxIncludeSize();
784 
785  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
786  if ( $cpuTime !== null ) {
787  $this->mOutput->setLimitReportData( 'limitreport-cputime',
788  sprintf( "%.3f", $cpuTime )
789  );
790  }
791 
792  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
793  $this->mOutput->setLimitReportData( 'limitreport-walltime',
794  sprintf( "%.3f", $wallTime )
795  );
796 
797  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
798  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
799  );
800  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
801  [ $this->mIncludeSizes['post-expand'], $maxIncludeSize ]
802  );
803  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
804  [ $this->mIncludeSizes['arg'], $maxIncludeSize ]
805  );
806  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
807  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
808  );
809  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
810  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
811  );
812 
813  foreach ( $this->mStripState->getLimitReport() as list( $key, $value ) ) {
814  $this->mOutput->setLimitReportData( $key, $value );
815  }
816 
817  $this->hookRunner->onParserLimitReportPrepare( $this, $this->mOutput );
818 
819  // Add on template profiling data in human/machine readable way
820  $dataByFunc = $this->mProfiler->getFunctionStats();
821  uasort( $dataByFunc, static function ( $a, $b ) {
822  return $b['real'] <=> $a['real']; // descending order
823  } );
824  $profileReport = [];
825  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
826  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
827  $item['%real'], $item['real'], $item['calls'],
828  htmlspecialchars( $item['name'] ) );
829  }
830 
831  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
832 
833  // Add other cache related metadata
834  if ( $this->svcOptions->get( MainConfigNames::ShowHostnames ) ) {
835  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
836  }
837  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
838  $this->mOutput->getCacheTime() );
839  $this->mOutput->setLimitReportData( 'cachereport-ttl',
840  $this->mOutput->getCacheExpiry() );
841  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
842  $this->mOutput->hasReducedExpiry() );
843  }
844 
870  public function recursiveTagParse( $text, $frame = false ) {
871  $text = $this->internalParse( $text, false, $frame );
872  return $text;
873  }
874 
894  public function recursiveTagParseFully( $text, $frame = false ) {
895  $text = $this->recursiveTagParse( $text, $frame );
896  $text = $this->internalParseHalfParsed( $text, false );
897  return $text;
898  }
899 
919  public function parseExtensionTagAsTopLevelDoc( $text ) {
920  $text = $this->recursiveTagParse( $text );
921  $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
922  $text = $this->internalParseHalfParsed( $text, true );
923  return $text;
924  }
925 
938  public function preprocess(
939  $text,
940  ?PageReference $page,
941  ParserOptions $options,
942  $revid = null,
943  $frame = false
944  ) {
945  $magicScopeVariable = $this->lock();
946  $this->startParse( $page, $options, self::OT_PREPROCESS, true );
947  if ( $revid !== null ) {
948  $this->mRevisionId = $revid;
949  }
950  $this->hookRunner->onParserBeforePreprocess( $this, $text, $this->mStripState );
951  $text = $this->replaceVariables( $text, $frame );
952  $text = $this->mStripState->unstripBoth( $text );
953  return $text;
954  }
955 
965  public function recursivePreprocess( $text, $frame = false ) {
966  $text = $this->replaceVariables( $text, $frame );
967  $text = $this->mStripState->unstripBoth( $text );
968  return $text;
969  }
970 
985  public function getPreloadText( $text, PageReference $page, ParserOptions $options, $params = [] ) {
986  $msg = new RawMessage( $text );
987  $text = $msg->params( $params )->plain();
988 
989  # Parser (re)initialisation
990  $magicScopeVariable = $this->lock();
991  $this->startParse( $page, $options, self::OT_PLAIN, true );
992 
994  $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
995  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
996  $text = $this->mStripState->unstripBoth( $text );
997  return $text;
998  }
999 
1007  public function setUser( ?UserIdentity $user ) {
1008  $this->mUser = $user;
1009  }
1010 
1018  public function setTitle( Title $t = null ) {
1019  $this->setPage( $t );
1020  }
1021 
1027  public function getTitle(): Title {
1028  if ( !$this->mTitle ) {
1029  $this->mTitle = Title::makeTitle( NS_SPECIAL, 'Badtitle/Parser' );
1030  }
1031  return $this->mTitle;
1032  }
1033 
1040  public function setPage( ?PageReference $t = null ) {
1041  if ( !$t ) {
1042  $t = Title::makeTitle( NS_SPECIAL, 'Badtitle/Parser' );
1043  } else {
1044  // For now (early 1.37 alpha), always convert to Title, so we don't have to do it over
1045  // and over again in other methods. Eventually, we will no longer need to have a Title
1046  // instance internally.
1048  }
1049 
1050  if ( $t->hasFragment() ) {
1051  # Strip the fragment to avoid various odd effects
1052  $this->mTitle = $t->createFragmentTarget( '' );
1053  } else {
1054  $this->mTitle = $t;
1055  }
1056  }
1057 
1063  public function getPage(): ?PageReference {
1064  return $this->mTitle;
1065  }
1066 
1072  public function getOutputType(): int {
1073  return $this->mOutputType;
1074  }
1075 
1081  public function setOutputType( $ot ): void {
1082  $this->mOutputType = $ot;
1083  # Shortcut alias
1084  $this->ot = [
1085  'html' => $ot == self::OT_HTML,
1086  'wiki' => $ot == self::OT_WIKI,
1087  'pre' => $ot == self::OT_PREPROCESS,
1088  'plain' => $ot == self::OT_PLAIN,
1089  ];
1090  }
1091 
1099  public function OutputType( $x = null ) {
1100  wfDeprecated( __METHOD__, '1.35' );
1101  return wfSetVar( $this->mOutputType, $x );
1102  }
1103 
1108  public function getOutput() {
1109  return $this->mOutput;
1110  }
1111 
1116  public function getOptions() {
1117  return $this->mOptions;
1118  }
1119 
1125  public function setOptions( ParserOptions $options ): void {
1126  $this->mOptions = $options;
1127  }
1128 
1136  public function Options( $x = null ) {
1137  wfDeprecated( __METHOD__, '1.35' );
1138  return wfSetVar( $this->mOptions, $x );
1139  }
1140 
1145  public function nextLinkID() {
1146  return $this->mLinkID++;
1147  }
1148 
1153  public function setLinkID( $id ) {
1154  $this->mLinkID = $id;
1155  }
1156 
1162  public function getFunctionLang() {
1163  return $this->getTargetLanguage();
1164  }
1165 
1174  public function getTargetLanguage() {
1175  $target = $this->mOptions->getTargetLanguage();
1176 
1177  if ( $target !== null ) {
1178  return $target;
1179  } elseif ( $this->mOptions->getInterfaceMessage() ) {
1180  return $this->mOptions->getUserLangObj();
1181  }
1182 
1183  return $this->getTitle()->getPageLanguage();
1184  }
1185 
1193  public function getUserIdentity(): UserIdentity {
1194  return $this->mUser ?? $this->getOptions()->getUserIdentity();
1195  }
1196 
1203  public function getPreprocessor() {
1204  return $this->mPreprocessor;
1205  }
1206 
1213  public function getLinkRenderer() {
1214  // XXX We make the LinkRenderer with current options and then cache it forever
1215  if ( !$this->mLinkRenderer ) {
1216  $this->mLinkRenderer = $this->linkRendererFactory->create();
1217  }
1218 
1219  return $this->mLinkRenderer;
1220  }
1221 
1228  public function getMagicWordFactory() {
1229  return $this->magicWordFactory;
1230  }
1231 
1238  public function getContentLanguage() {
1239  return $this->contLang;
1240  }
1241 
1248  public function getBadFileLookup() {
1249  return $this->badFileLookup;
1250  }
1251 
1271  public static function extractTagsAndParams( array $elements, $text, &$matches ) {
1272  static $n = 1;
1273  $stripped = '';
1274  $matches = [];
1275 
1276  $taglist = implode( '|', $elements );
1277  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i";
1278 
1279  while ( $text != '' ) {
1280  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1281  $stripped .= $p[0];
1282  if ( count( $p ) < 5 ) {
1283  break;
1284  }
1285  if ( count( $p ) > 5 ) {
1286  # comment
1287  $element = $p[4];
1288  $attributes = '';
1289  $close = '';
1290  $inside = $p[5];
1291  } else {
1292  # tag
1293  list( , $element, $attributes, $close, $inside ) = $p;
1294  }
1295 
1296  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
1297  $stripped .= $marker;
1298 
1299  if ( $close === '/>' ) {
1300  # Empty element tag, <tag />
1301  $content = null;
1302  $text = $inside;
1303  $tail = null;
1304  } else {
1305  if ( $element === '!--' ) {
1306  $end = '/(-->)/';
1307  } else {
1308  $end = "/(<\\/$element\\s*>)/i";
1309  }
1310  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1311  $content = $q[0];
1312  if ( count( $q ) < 3 ) {
1313  # No end tag -- let it run out to the end of the text.
1314  $tail = '';
1315  $text = '';
1316  } else {
1317  list( , $tail, $text ) = $q;
1318  }
1319  }
1320 
1321  $matches[$marker] = [ $element,
1322  $content,
1323  Sanitizer::decodeTagAttributes( $attributes ),
1324  "<$element$attributes$close$content$tail" ];
1325  }
1326  return $stripped;
1327  }
1328 
1334  public function getStripList() {
1335  return $this->mStripList;
1336  }
1337 
1342  public function getStripState() {
1343  return $this->mStripState;
1344  }
1345 
1355  public function insertStripItem( $text ) {
1356  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1357  $this->mMarkerIndex++;
1358  $this->mStripState->addGeneral( $marker, $text );
1359  return $marker;
1360  }
1361 
1368  private function handleTables( $text ) {
1369  $lines = StringUtils::explode( "\n", $text );
1370  $out = '';
1371  $td_history = []; # Is currently a td tag open?
1372  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1373  $tr_history = []; # Is currently a tr tag open?
1374  $tr_attributes = []; # history of tr attributes
1375  $has_opened_tr = []; # Did this table open a <tr> element?
1376  $indent_level = 0; # indent level of the table
1377 
1378  foreach ( $lines as $outLine ) {
1379  $line = trim( $outLine );
1380 
1381  if ( $line === '' ) { # empty line, go to next line
1382  $out .= $outLine . "\n";
1383  continue;
1384  }
1385 
1386  $first_character = $line[0];
1387  $first_two = substr( $line, 0, 2 );
1388  $matches = [];
1389 
1390  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1391  # First check if we are starting a new table
1392  $indent_level = strlen( $matches[1] );
1393 
1394  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1395  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1396 
1397  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1398  array_push( $td_history, false );
1399  array_push( $last_tag_history, '' );
1400  array_push( $tr_history, false );
1401  array_push( $tr_attributes, '' );
1402  array_push( $has_opened_tr, false );
1403  } elseif ( count( $td_history ) == 0 ) {
1404  # Don't do any of the following
1405  $out .= $outLine . "\n";
1406  continue;
1407  } elseif ( $first_two === '|}' ) {
1408  # We are ending a table
1409  $line = '</table>' . substr( $line, 2 );
1410  $last_tag = array_pop( $last_tag_history );
1411 
1412  if ( !array_pop( $has_opened_tr ) ) {
1413  $line = "<tr><td></td></tr>{$line}";
1414  }
1415 
1416  if ( array_pop( $tr_history ) ) {
1417  $line = "</tr>{$line}";
1418  }
1419 
1420  if ( array_pop( $td_history ) ) {
1421  $line = "</{$last_tag}>{$line}";
1422  }
1423  array_pop( $tr_attributes );
1424  if ( $indent_level > 0 ) {
1425  $outLine = rtrim( $line ) . str_repeat( '</dd></dl>', $indent_level );
1426  } else {
1427  $outLine = $line;
1428  }
1429  } elseif ( $first_two === '|-' ) {
1430  # Now we have a table row
1431  $line = preg_replace( '#^\|-+#', '', $line );
1432 
1433  # Whats after the tag is now only attributes
1434  $attributes = $this->mStripState->unstripBoth( $line );
1435  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1436  array_pop( $tr_attributes );
1437  array_push( $tr_attributes, $attributes );
1438 
1439  $line = '';
1440  $last_tag = array_pop( $last_tag_history );
1441  array_pop( $has_opened_tr );
1442  array_push( $has_opened_tr, true );
1443 
1444  if ( array_pop( $tr_history ) ) {
1445  $line = '</tr>';
1446  }
1447 
1448  if ( array_pop( $td_history ) ) {
1449  $line = "</{$last_tag}>{$line}";
1450  }
1451 
1452  $outLine = $line;
1453  array_push( $tr_history, false );
1454  array_push( $td_history, false );
1455  array_push( $last_tag_history, '' );
1456  } elseif ( $first_character === '|'
1457  || $first_character === '!'
1458  || $first_two === '|+'
1459  ) {
1460  # This might be cell elements, td, th or captions
1461  if ( $first_two === '|+' ) {
1462  $first_character = '+';
1463  $line = substr( $line, 2 );
1464  } else {
1465  $line = substr( $line, 1 );
1466  }
1467 
1468  // Implies both are valid for table headings.
1469  if ( $first_character === '!' ) {
1470  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1471  }
1472 
1473  # Split up multiple cells on the same line.
1474  # FIXME : This can result in improper nesting of tags processed
1475  # by earlier parser steps.
1476  $cells = explode( '||', $line );
1477 
1478  $outLine = '';
1479 
1480  # Loop through each table cell
1481  foreach ( $cells as $cell ) {
1482  $previous = '';
1483  if ( $first_character !== '+' ) {
1484  $tr_after = array_pop( $tr_attributes );
1485  if ( !array_pop( $tr_history ) ) {
1486  $previous = "<tr{$tr_after}>\n";
1487  }
1488  array_push( $tr_history, true );
1489  array_push( $tr_attributes, '' );
1490  array_pop( $has_opened_tr );
1491  array_push( $has_opened_tr, true );
1492  }
1493 
1494  $last_tag = array_pop( $last_tag_history );
1495 
1496  if ( array_pop( $td_history ) ) {
1497  $previous = "</{$last_tag}>\n{$previous}";
1498  }
1499 
1500  if ( $first_character === '|' ) {
1501  $last_tag = 'td';
1502  } elseif ( $first_character === '!' ) {
1503  $last_tag = 'th';
1504  } elseif ( $first_character === '+' ) {
1505  $last_tag = 'caption';
1506  } else {
1507  $last_tag = '';
1508  }
1509 
1510  array_push( $last_tag_history, $last_tag );
1511 
1512  # A cell could contain both parameters and data
1513  $cell_data = explode( '|', $cell, 2 );
1514 
1515  # T2553: Note that a '|' inside an invalid link should not
1516  # be mistaken as delimiting cell parameters
1517  # Bug T153140: Neither should language converter markup.
1518  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1519  $cell = "{$previous}<{$last_tag}>" . trim( $cell );
1520  } elseif ( count( $cell_data ) == 1 ) {
1521  // Whitespace in cells is trimmed
1522  $cell = "{$previous}<{$last_tag}>" . trim( $cell_data[0] );
1523  } else {
1524  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1525  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1526  // Whitespace in cells is trimmed
1527  $cell = "{$previous}<{$last_tag}{$attributes}>" . trim( $cell_data[1] );
1528  }
1529 
1530  $outLine .= $cell;
1531  array_push( $td_history, true );
1532  }
1533  }
1534  $out .= $outLine . "\n";
1535  }
1536 
1537  # Closing open td, tr && table
1538  while ( count( $td_history ) > 0 ) {
1539  if ( array_pop( $td_history ) ) {
1540  $out .= "</td>\n";
1541  }
1542  if ( array_pop( $tr_history ) ) {
1543  $out .= "</tr>\n";
1544  }
1545  if ( !array_pop( $has_opened_tr ) ) {
1546  $out .= "<tr><td></td></tr>\n";
1547  }
1548 
1549  $out .= "</table>\n";
1550  }
1551 
1552  # Remove trailing line-ending (b/c)
1553  if ( substr( $out, -1 ) === "\n" ) {
1554  $out = substr( $out, 0, -1 );
1555  }
1556 
1557  # special case: don't return empty table
1558  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1559  $out = '';
1560  }
1561 
1562  return $out;
1563  }
1564 
1578  public function internalParse( $text, $isMain = true, $frame = false ) {
1579  $origText = $text;
1580 
1581  # Hook to suspend the parser in this state
1582  if ( !$this->hookRunner->onParserBeforeInternalParse( $this, $text, $this->mStripState ) ) {
1583  return $text;
1584  }
1585 
1586  # if $frame is provided, then use $frame for replacing any variables
1587  if ( $frame ) {
1588  # use frame depth to infer how include/noinclude tags should be handled
1589  # depth=0 means this is the top-level document; otherwise it's an included document
1590  if ( !$frame->depth ) {
1591  $flag = 0;
1592  } else {
1594  }
1595  $dom = $this->preprocessToDom( $text, $flag );
1596  $text = $frame->expand( $dom );
1597  } else {
1598  # if $frame is not provided, then use old-style replaceVariables
1599  $text = $this->replaceVariables( $text );
1600  }
1601 
1602  $this->hookRunner->onInternalParseBeforeSanitize( $this, $text, $this->mStripState );
1604  $text,
1605  // Callback from the Sanitizer for expanding items found in
1606  // HTML attribute values, so they can be safely tested and escaped.
1607  function ( &$text, $frame = false ) {
1608  $text = $this->replaceVariables( $text, $frame );
1609  $text = $this->mStripState->unstripBoth( $text );
1610  },
1611  false,
1612  [],
1613  []
1614  );
1615  $this->hookRunner->onInternalParseBeforeLinks( $this, $text, $this->mStripState );
1616 
1617  # Tables need to come after variable replacement for things to work
1618  # properly; putting them before other transformations should keep
1619  # exciting things like link expansions from showing up in surprising
1620  # places.
1621  $text = $this->handleTables( $text );
1622 
1623  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1624 
1625  $text = $this->handleDoubleUnderscore( $text );
1626 
1627  $text = $this->handleHeadings( $text );
1628  $text = $this->handleInternalLinks( $text );
1629  $text = $this->handleAllQuotes( $text );
1630  $text = $this->handleExternalLinks( $text );
1631 
1632  # handleInternalLinks may sometimes leave behind
1633  # absolute URLs, which have to be masked to hide them from handleExternalLinks
1634  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1635 
1636  $text = $this->handleMagicLinks( $text );
1637  $text = $this->finalizeHeadings( $text, $origText, $isMain );
1638 
1639  return $text;
1640  }
1641 
1649  return $this->languageConverterFactory->getLanguageConverter(
1650  $this->getTargetLanguage()
1651  );
1652  }
1653 
1659  private function getContentLanguageConverter(): ILanguageConverter {
1660  return $this->languageConverterFactory->getLanguageConverter(
1661  $this->getContentLanguage()
1662  );
1663  }
1664 
1672  protected function getHookContainer() {
1673  return $this->hookContainer;
1674  }
1675 
1684  protected function getHookRunner() {
1685  return $this->hookRunner;
1686  }
1687 
1697  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1698  $text = $this->mStripState->unstripGeneral( $text );
1699 
1700  $text = BlockLevelPass::doBlockLevels( $text, $linestart );
1701 
1702  $this->replaceLinkHoldersPrivate( $text );
1703 
1711  if ( !( $this->mOptions->getDisableContentConversion()
1712  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1713  && !$this->mOptions->getInterfaceMessage()
1714  ) {
1715  # The position of the convert() call should not be changed. it
1716  # assumes that the links are all replaced and the only thing left
1717  # is the <nowiki> mark.
1718  $text = $this->getTargetLanguageConverter()->convert( $text );
1719  // Record information necessary for language conversion of TOC.
1720  $this->mOutput->setExtensionData(
1721  // T303329: this should migrate out of extension data
1722  'core:target-lang',
1723  $this->getTargetLanguage()->getCode()
1724  );
1725  $this->mOutput->setExtensionData(
1726  // T303329: this should migrate out of extension data
1727  'core:target-lang-variant',
1728  $this->getTargetLanguageConverter()->getPreferredVariant()
1729  );
1730  } else {
1731  $this->mOutput->setOutputFlag( ParserOutputFlags::NO_TOC_CONVERSION );
1732  }
1733 
1734  $text = $this->mStripState->unstripNoWiki( $text );
1735 
1736  $text = $this->mStripState->unstripGeneral( $text );
1737 
1738  $text = $this->tidy->tidy( $text, [ Sanitizer::class, 'armorFrenchSpaces' ] );
1739 
1740  if ( $isMain ) {
1741  $this->hookRunner->onParserAfterTidy( $this, $text );
1742  }
1743 
1744  return $text;
1745  }
1746 
1757  private function handleMagicLinks( $text ) {
1758  $prots = $this->urlUtils->validAbsoluteProtocols();
1759  $urlChar = self::EXT_LINK_URL_CLASS;
1760  $addr = self::EXT_LINK_ADDR;
1761  $space = self::SPACE_NOT_NL; # non-newline space
1762  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1763  $spaces = "$space++"; # possessive match of 1 or more spaces
1764  $text = preg_replace_callback(
1765  '!(?: # Start cases
1766  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1767  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1768  (\b # m[3]: Free external links
1769  (?i:$prots)
1770  ($addr$urlChar*) # m[4]: Post-protocol path
1771  ) |
1772  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1773  ([0-9]+)\b |
1774  \bISBN $spaces ( # m[6]: ISBN, capture number
1775  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1776  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1777  [0-9Xx] # check digit
1778  )\b
1779  )!xu",
1780  [ $this, 'magicLinkCallback' ],
1781  $text
1782  );
1783  return $text;
1784  }
1785 
1791  private function magicLinkCallback( array $m ) {
1792  if ( isset( $m[1] ) && $m[1] !== '' ) {
1793  # Skip anchor
1794  return $m[0];
1795  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1796  # Skip HTML element
1797  return $m[0];
1798  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1799  # Free external link
1800  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1801  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1802  # RFC or PMID
1803  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1804  if ( !$this->mOptions->getMagicRFCLinks() ) {
1805  return $m[0];
1806  }
1807  $keyword = 'RFC';
1808  $urlmsg = 'rfcurl';
1809  $cssClass = 'mw-magiclink-rfc';
1810  $trackingCat = 'magiclink-tracking-rfc';
1811  $id = $m[5];
1812  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1813  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1814  return $m[0];
1815  }
1816  $keyword = 'PMID';
1817  $urlmsg = 'pubmedurl';
1818  $cssClass = 'mw-magiclink-pmid';
1819  $trackingCat = 'magiclink-tracking-pmid';
1820  $id = $m[5];
1821  } else {
1822  // Should never happen
1823  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1824  substr( $m[0], 0, 20 ) . '"' );
1825  }
1826  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1827  $this->addTrackingCategory( $trackingCat );
1828  return Linker::makeExternalLink(
1829  $url,
1830  "{$keyword} {$id}",
1831  true,
1832  $cssClass,
1833  [],
1834  $this->getTitle()
1835  );
1836  } elseif ( isset( $m[6] ) && $m[6] !== ''
1837  && $this->mOptions->getMagicISBNLinks()
1838  ) {
1839  # ISBN
1840  $isbn = $m[6];
1841  $space = self::SPACE_NOT_NL; # non-newline space
1842  $isbn = preg_replace( "/$space/", ' ', $isbn );
1843  $num = strtr( $isbn, [
1844  '-' => '',
1845  ' ' => '',
1846  'x' => 'X',
1847  ] );
1848  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1849  return $this->getLinkRenderer()->makeKnownLink(
1850  SpecialPage::getTitleFor( 'Booksources', $num ),
1851  "ISBN $isbn",
1852  [
1853  'class' => 'internal mw-magiclink-isbn',
1854  'title' => false // suppress title attribute
1855  ]
1856  );
1857  } else {
1858  return $m[0];
1859  }
1860  }
1861 
1871  private function makeFreeExternalLink( $url, $numPostProto ) {
1872  $trail = '';
1873 
1874  # The characters '<' and '>' (which were escaped by
1875  # internalRemoveHtmlTags()) should not be included in
1876  # URLs, per RFC 2396.
1877  # Make &nbsp; terminate a URL as well (bug T84937)
1878  $m2 = [];
1879  if ( preg_match(
1880  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1881  $url,
1882  $m2,
1883  PREG_OFFSET_CAPTURE
1884  ) ) {
1885  $trail = substr( $url, $m2[0][1] ) . $trail;
1886  $url = substr( $url, 0, $m2[0][1] );
1887  }
1888 
1889  # Move trailing punctuation to $trail
1890  $sep = ',;\.:!?';
1891  # If there is no left bracket, then consider right brackets fair game too
1892  if ( strpos( $url, '(' ) === false ) {
1893  $sep .= ')';
1894  }
1895 
1896  $urlRev = strrev( $url );
1897  $numSepChars = strspn( $urlRev, $sep );
1898  # Don't break a trailing HTML entity by moving the ; into $trail
1899  # This is in hot code, so use substr_compare to avoid having to
1900  # create a new string object for the comparison
1901  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1902  # more optimization: instead of running preg_match with a $
1903  # anchor, which can be slow, do the match on the reversed
1904  # string starting at the desired offset.
1905  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1906  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1907  $numSepChars--;
1908  }
1909  }
1910  if ( $numSepChars ) {
1911  $trail = substr( $url, -$numSepChars ) . $trail;
1912  $url = substr( $url, 0, -$numSepChars );
1913  }
1914 
1915  # Verify that we still have a real URL after trail removal, and
1916  # not just lone protocol
1917  if ( strlen( $trail ) >= $numPostProto ) {
1918  return $url . $trail;
1919  }
1920 
1921  $url = Sanitizer::cleanUrl( $url );
1922 
1923  # Is this an external image?
1924  $text = $this->maybeMakeExternalImage( $url );
1925  if ( $text === false ) {
1926  # Not an image, make a link
1927  $text = Linker::makeExternalLink(
1928  $url,
1929  $this->getTargetLanguageConverter()->markNoConversion( $url ),
1930  true,
1931  'free',
1932  $this->getExternalLinkAttribs( $url ),
1933  $this->getTitle()
1934  );
1935  # Register it in the output object...
1936  $this->mOutput->addExternalLink( $url );
1937  }
1938  return $text . $trail;
1939  }
1940 
1947  private function handleHeadings( $text ) {
1948  for ( $i = 6; $i >= 1; --$i ) {
1949  $h = str_repeat( '=', $i );
1950  // Trim non-newline whitespace from headings
1951  // Using \s* will break for: "==\n===\n" and parse as <h2>=</h2>
1952  $text = preg_replace( "/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m", "<h$i>\\1</h$i>", $text );
1953  }
1954  return $text;
1955  }
1956 
1964  private function handleAllQuotes( $text ) {
1965  $outtext = '';
1966  $lines = StringUtils::explode( "\n", $text );
1967  foreach ( $lines as $line ) {
1968  $outtext .= $this->doQuotes( $line ) . "\n";
1969  }
1970  $outtext = substr( $outtext, 0, -1 );
1971  return $outtext;
1972  }
1973 
1982  public function doQuotes( $text ) {
1983  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1984  $countarr = count( $arr );
1985  if ( $countarr == 1 ) {
1986  return $text;
1987  }
1988 
1989  // First, do some preliminary work. This may shift some apostrophes from
1990  // being mark-up to being text. It also counts the number of occurrences
1991  // of bold and italics mark-ups.
1992  $numbold = 0;
1993  $numitalics = 0;
1994  for ( $i = 1; $i < $countarr; $i += 2 ) {
1995  $thislen = strlen( $arr[$i] );
1996  // If there are ever four apostrophes, assume the first is supposed to
1997  // be text, and the remaining three constitute mark-up for bold text.
1998  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1999  if ( $thislen == 4 ) {
2000  $arr[$i - 1] .= "'";
2001  $arr[$i] = "'''";
2002  $thislen = 3;
2003  } elseif ( $thislen > 5 ) {
2004  // If there are more than 5 apostrophes in a row, assume they're all
2005  // text except for the last 5.
2006  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
2007  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
2008  $arr[$i] = "'''''";
2009  $thislen = 5;
2010  }
2011  // Count the number of occurrences of bold and italics mark-ups.
2012  if ( $thislen == 2 ) {
2013  $numitalics++;
2014  } elseif ( $thislen == 3 ) {
2015  $numbold++;
2016  } elseif ( $thislen == 5 ) {
2017  $numitalics++;
2018  $numbold++;
2019  }
2020  }
2021 
2022  // If there is an odd number of both bold and italics, it is likely
2023  // that one of the bold ones was meant to be an apostrophe followed
2024  // by italics. Which one we cannot know for certain, but it is more
2025  // likely to be one that has a single-letter word before it.
2026  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
2027  $firstsingleletterword = -1;
2028  $firstmultiletterword = -1;
2029  $firstspace = -1;
2030  for ( $i = 1; $i < $countarr; $i += 2 ) {
2031  if ( strlen( $arr[$i] ) == 3 ) {
2032  $x1 = substr( $arr[$i - 1], -1 );
2033  $x2 = substr( $arr[$i - 1], -2, 1 );
2034  if ( $x1 === ' ' ) {
2035  if ( $firstspace == -1 ) {
2036  $firstspace = $i;
2037  }
2038  } elseif ( $x2 === ' ' ) {
2039  $firstsingleletterword = $i;
2040  // if $firstsingleletterword is set, we don't
2041  // look at the other options, so we can bail early.
2042  break;
2043  } elseif ( $firstmultiletterword == -1 ) {
2044  $firstmultiletterword = $i;
2045  }
2046  }
2047  }
2048 
2049  // If there is a single-letter word, use it!
2050  if ( $firstsingleletterword > -1 ) {
2051  $arr[$firstsingleletterword] = "''";
2052  $arr[$firstsingleletterword - 1] .= "'";
2053  } elseif ( $firstmultiletterword > -1 ) {
2054  // If not, but there's a multi-letter word, use that one.
2055  $arr[$firstmultiletterword] = "''";
2056  $arr[$firstmultiletterword - 1] .= "'";
2057  } elseif ( $firstspace > -1 ) {
2058  // ... otherwise use the first one that has neither.
2059  // (notice that it is possible for all three to be -1 if, for example,
2060  // there is only one pentuple-apostrophe in the line)
2061  $arr[$firstspace] = "''";
2062  $arr[$firstspace - 1] .= "'";
2063  }
2064  }
2065 
2066  // Now let's actually convert our apostrophic mush to HTML!
2067  $output = '';
2068  $buffer = '';
2069  $state = '';
2070  $i = 0;
2071  foreach ( $arr as $r ) {
2072  if ( ( $i % 2 ) == 0 ) {
2073  if ( $state === 'both' ) {
2074  $buffer .= $r;
2075  } else {
2076  $output .= $r;
2077  }
2078  } else {
2079  $thislen = strlen( $r );
2080  if ( $thislen == 2 ) {
2081  // two quotes - open or close italics
2082  if ( $state === 'i' ) {
2083  $output .= '</i>';
2084  $state = '';
2085  } elseif ( $state === 'bi' ) {
2086  $output .= '</i>';
2087  $state = 'b';
2088  } elseif ( $state === 'ib' ) {
2089  $output .= '</b></i><b>';
2090  $state = 'b';
2091  } elseif ( $state === 'both' ) {
2092  $output .= '<b><i>' . $buffer . '</i>';
2093  $state = 'b';
2094  } else { // $state can be 'b' or ''
2095  $output .= '<i>';
2096  $state .= 'i';
2097  }
2098  } elseif ( $thislen == 3 ) {
2099  // three quotes - open or close bold
2100  if ( $state === 'b' ) {
2101  $output .= '</b>';
2102  $state = '';
2103  } elseif ( $state === 'bi' ) {
2104  $output .= '</i></b><i>';
2105  $state = 'i';
2106  } elseif ( $state === 'ib' ) {
2107  $output .= '</b>';
2108  $state = 'i';
2109  } elseif ( $state === 'both' ) {
2110  $output .= '<i><b>' . $buffer . '</b>';
2111  $state = 'i';
2112  } else { // $state can be 'i' or ''
2113  $output .= '<b>';
2114  $state .= 'b';
2115  }
2116  } elseif ( $thislen == 5 ) {
2117  // five quotes - open or close both separately
2118  if ( $state === 'b' ) {
2119  $output .= '</b><i>';
2120  $state = 'i';
2121  } elseif ( $state === 'i' ) {
2122  $output .= '</i><b>';
2123  $state = 'b';
2124  } elseif ( $state === 'bi' ) {
2125  $output .= '</i></b>';
2126  $state = '';
2127  } elseif ( $state === 'ib' ) {
2128  $output .= '</b></i>';
2129  $state = '';
2130  } elseif ( $state === 'both' ) {
2131  $output .= '<i><b>' . $buffer . '</b></i>';
2132  $state = '';
2133  } else { // ($state == '')
2134  $buffer = '';
2135  $state = 'both';
2136  }
2137  }
2138  }
2139  $i++;
2140  }
2141  // Now close all remaining tags. Notice that the order is important.
2142  if ( $state === 'b' || $state === 'ib' ) {
2143  $output .= '</b>';
2144  }
2145  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
2146  $output .= '</i>';
2147  }
2148  if ( $state === 'bi' ) {
2149  $output .= '</b>';
2150  }
2151  // There might be lonely ''''', so make sure we have a buffer
2152  if ( $state === 'both' && $buffer ) {
2153  $output .= '<b><i>' . $buffer . '</i></b>';
2154  }
2155  return $output;
2156  }
2157 
2168  private function handleExternalLinks( $text ) {
2169  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
2170  // @phan-suppress-next-line PhanTypeComparisonFromArray See phan issue #3161
2171  if ( $bits === false ) {
2172  throw new MWException( "PCRE needs to be compiled with "
2173  . "--enable-unicode-properties in order for MediaWiki to function" );
2174  }
2175  $s = array_shift( $bits );
2176 
2177  $i = 0;
2178  while ( $i < count( $bits ) ) {
2179  $url = $bits[$i++];
2180  $i++; // protocol
2181  $text = $bits[$i++];
2182  $trail = $bits[$i++];
2183 
2184  # The characters '<' and '>' (which were escaped by
2185  # internalRemoveHtmlTags()) should not be included in
2186  # URLs, per RFC 2396.
2187  $m2 = [];
2188  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
2189  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
2190  $url = substr( $url, 0, $m2[0][1] );
2191  }
2192 
2193  # If the link text is an image URL, replace it with an <img> tag
2194  # This happened by accident in the original parser, but some people used it extensively
2195  $img = $this->maybeMakeExternalImage( $text );
2196  if ( $img !== false ) {
2197  $text = $img;
2198  }
2199 
2200  $dtrail = '';
2201 
2202  # Set linktype for CSS
2203  $linktype = 'text';
2204 
2205  # No link text, e.g. [http://domain.tld/some.link]
2206  if ( $text == '' ) {
2207  # Autonumber
2208  $langObj = $this->getTargetLanguage();
2209  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
2210  $linktype = 'autonumber';
2211  } else {
2212  # Have link text, e.g. [http://domain.tld/some.link text]s
2213  # Check for trail
2214  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
2215  }
2216 
2217  // Excluding protocol-relative URLs may avoid many false positives.
2218  if ( preg_match( '/^(?:' . $this->urlUtils->validAbsoluteProtocols() . ')/', $text ) ) {
2219  $text = $this->getTargetLanguageConverter()->markNoConversion( $text );
2220  }
2221 
2222  $url = Sanitizer::cleanUrl( $url );
2223 
2224  # Use the encoded URL
2225  # This means that users can paste URLs directly into the text
2226  # Funny characters like ö aren't valid in URLs anyway
2227  # This was changed in August 2004
2228  // @phan-suppress-next-line SecurityCheck-XSS,SecurityCheck-DoubleEscaped using false for escape is valid
2229  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
2230  $this->getExternalLinkAttribs( $url ), $this->getTitle() ) . $dtrail . $trail;
2231 
2232  # Register link in the output object.
2233  $this->mOutput->addExternalLink( $url );
2234  }
2235 
2236  // @phan-suppress-next-line PhanTypeMismatchReturnNullable False positive from array_shift
2237  return $s;
2238  }
2239 
2250  public static function getExternalLinkRel( $url = false, LinkTarget $title = null ) {
2251  $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
2252  $noFollowLinks = $mainConfig->get( MainConfigNames::NoFollowLinks );
2253  $noFollowNsExceptions = $mainConfig->get( MainConfigNames::NoFollowNsExceptions );
2254  $noFollowDomainExceptions = $mainConfig->get( MainConfigNames::NoFollowDomainExceptions );
2255  $ns = $title ? $title->getNamespace() : false;
2256  if ( $noFollowLinks && !in_array( $ns, $noFollowNsExceptions )
2257  && !wfMatchesDomainList( $url, $noFollowDomainExceptions )
2258  ) {
2259  return 'nofollow';
2260  }
2261  return null;
2262  }
2263 
2275  public function getExternalLinkAttribs( $url ) {
2276  $attribs = [];
2277  $rel = self::getExternalLinkRel( $url, $this->getTitle() );
2278 
2279  $target = $this->mOptions->getExternalLinkTarget();
2280  if ( $target ) {
2281  $attribs['target'] = $target;
2282  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
2283  // T133507. New windows can navigate parent cross-origin.
2284  // Including noreferrer due to lacking browser
2285  // support of noopener. Eventually noreferrer should be removed.
2286  if ( $rel !== '' ) {
2287  $rel .= ' ';
2288  }
2289  $rel .= 'noreferrer noopener';
2290  }
2291  }
2292  $attribs['rel'] = $rel;
2293  return $attribs;
2294  }
2295 
2306  public static function normalizeLinkUrl( $url ) {
2307  # Test for RFC 3986 IPv6 syntax
2308  $scheme = '[a-z][a-z0-9+.-]*:';
2309  $userinfo = '(?:[a-z0-9\-._~!$&\'()*+,;=:]|%[0-9a-f]{2})*';
2310  $ipv6Host = '\\[((?:[0-9a-f:]|%3[0-A]|%[46][1-6])+)\\]';
2311  if ( preg_match( "<^(?:{$scheme})?//(?:{$userinfo}@)?{$ipv6Host}(?:[:/?#].*|)$>i", $url, $m ) &&
2312  IPUtils::isValid( rawurldecode( $m[1] ) )
2313  ) {
2314  $isIPv6 = rawurldecode( $m[1] );
2315  } else {
2316  $isIPv6 = false;
2317  }
2318 
2319  # Make sure unsafe characters are encoded
2320  $url = preg_replace_callback(
2321  '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
2322  static function ( $m ) {
2323  return rawurlencode( $m[0] );
2324  },
2325  $url
2326  );
2327 
2328  $ret = '';
2329  $end = strlen( $url );
2330 
2331  # Fragment part - 'fragment'
2332  $start = strpos( $url, '#' );
2333  if ( $start !== false && $start < $end ) {
2334  $ret = self::normalizeUrlComponent(
2335  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
2336  $end = $start;
2337  }
2338 
2339  # Query part - 'query' minus &=+;
2340  $start = strpos( $url, '?' );
2341  if ( $start !== false && $start < $end ) {
2342  $ret = self::normalizeUrlComponent(
2343  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
2344  $end = $start;
2345  }
2346 
2347  # Scheme and path part - 'pchar'
2348  # (we assume no userinfo or encoded colons in the host)
2349  $ret = self::normalizeUrlComponent(
2350  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
2351 
2352  # Fix IPv6 syntax
2353  if ( $isIPv6 !== false ) {
2354  $ipv6Host = "%5B({$isIPv6})%5D";
2355  $ret = preg_replace(
2356  "<^((?:{$scheme})?//(?:{$userinfo}@)?){$ipv6Host}(?=[:/?#]|$)>i",
2357  "$1[$2]",
2358  $ret
2359  );
2360  }
2361 
2362  return $ret;
2363  }
2364 
2365  private static function normalizeUrlComponent( $component, $unsafe ) {
2366  $callback = static function ( $matches ) use ( $unsafe ) {
2367  $char = urldecode( $matches[0] );
2368  $ord = ord( $char );
2369  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2370  # Unescape it
2371  return $char;
2372  } else {
2373  # Leave it escaped, but use uppercase for a-f
2374  return strtoupper( $matches[0] );
2375  }
2376  };
2377  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2378  }
2379 
2388  private function maybeMakeExternalImage( $url ) {
2389  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2390  $imagesexception = !empty( $imagesfrom );
2391  $text = false;
2392  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2393  if ( $imagesexception && is_array( $imagesfrom ) ) {
2394  $imagematch = false;
2395  foreach ( $imagesfrom as $match ) {
2396  if ( strpos( $url, $match ) === 0 ) {
2397  $imagematch = true;
2398  break;
2399  }
2400  }
2401  } elseif ( $imagesexception ) {
2402  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2403  } else {
2404  $imagematch = false;
2405  }
2406 
2407  if ( $this->mOptions->getAllowExternalImages()
2408  || ( $imagesexception && $imagematch )
2409  ) {
2410  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2411  # Image found
2412  $text = Linker::makeExternalImage( $url );
2413  }
2414  }
2415  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2416  && preg_match( self::EXT_IMAGE_REGEX, $url )
2417  ) {
2418  $whitelist = explode(
2419  "\n",
2420  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2421  );
2422 
2423  foreach ( $whitelist as $entry ) {
2424  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2425  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2426  continue;
2427  }
2428  // @phan-suppress-next-line SecurityCheck-ReDoS preg_quote is not wanted here
2429  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2430  # Image matches a whitelist entry
2431  $text = Linker::makeExternalImage( $url );
2432  break;
2433  }
2434  }
2435  }
2436  return $text;
2437  }
2438 
2446  private function handleInternalLinks( $text ) {
2447  $this->mLinkHolders->merge( $this->handleInternalLinks2( $text ) );
2448  return $text;
2449  }
2450 
2456  private function handleInternalLinks2( &$s ) {
2457  static $tc = false, $e1, $e1_img;
2458  # the % is needed to support urlencoded titles as well
2459  if ( !$tc ) {
2460  $tc = Title::legalChars() . '#%';
2461  # Match a link having the form [[namespace:link|alternate]]trail
2462  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2463  # Match cases where there is no "]]", which might still be images
2464  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2465  }
2466 
2467  $holders = new LinkHolderArray(
2468  $this,
2469  $this->getContentLanguageConverter(),
2470  $this->getHookContainer() );
2471 
2472  # split the entire text string on occurrences of [[
2473  $a = StringUtils::explode( '[[', ' ' . $s );
2474  # get the first element (all text up to first [[), and remove the space we added
2475  $s = $a->current();
2476  $a->next();
2477  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2478  $s = substr( $s, 1 );
2479 
2480  $nottalk = !$this->getTitle()->isTalkPage();
2481 
2482  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2483  $e2 = null;
2484  if ( $useLinkPrefixExtension ) {
2485  # Match the end of a line for a word that's not followed by whitespace,
2486  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2487  $charset = $this->contLang->linkPrefixCharset();
2488  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2489  $m = [];
2490  if ( preg_match( $e2, $s, $m ) ) {
2491  $first_prefix = $m[2];
2492  } else {
2493  $first_prefix = false;
2494  }
2495  $prefix = false;
2496  } else {
2497  $first_prefix = false;
2498  $prefix = '';
2499  }
2500 
2501  # Some namespaces don't allow subpages
2502  $useSubpages = $this->nsInfo->hasSubpages(
2503  $this->getTitle()->getNamespace()
2504  );
2505 
2506  # Loop for each link
2507  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2508  # Check for excessive memory usage
2509  if ( $holders->isBig() ) {
2510  # Too big
2511  # Do the existence check, replace the link holders and clear the array
2512  $holders->replace( $s );
2513  $holders->clear();
2514  }
2515 
2516  if ( $useLinkPrefixExtension ) {
2517  // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal $e2 is set under this condition
2518  if ( preg_match( $e2, $s, $m ) ) {
2519  list( , $s, $prefix ) = $m;
2520  } else {
2521  $prefix = '';
2522  }
2523  # first link
2524  if ( $first_prefix ) {
2525  $prefix = $first_prefix;
2526  $first_prefix = false;
2527  }
2528  }
2529 
2530  $might_be_img = false;
2531 
2532  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2533  $text = $m[2];
2534  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2535  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2536  # the real problem is with the $e1 regex
2537  # See T1500.
2538  # Still some problems for cases where the ] is meant to be outside punctuation,
2539  # and no image is in sight. See T4095.
2540  if ( $text !== ''
2541  && substr( $m[3], 0, 1 ) === ']'
2542  && strpos( $text, '[' ) !== false
2543  ) {
2544  $text .= ']'; # so that handleExternalLinks($text) works later
2545  $m[3] = substr( $m[3], 1 );
2546  }
2547  # fix up urlencoded title texts
2548  if ( strpos( $m[1], '%' ) !== false ) {
2549  # Should anchors '#' also be rejected?
2550  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2551  }
2552  $trail = $m[3];
2553  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2554  # Invalid, but might be an image with a link in its caption
2555  $might_be_img = true;
2556  $text = $m[2];
2557  if ( strpos( $m[1], '%' ) !== false ) {
2558  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2559  }
2560  $trail = "";
2561  } else { # Invalid form; output directly
2562  $s .= $prefix . '[[' . $line;
2563  continue;
2564  }
2565 
2566  // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset preg_match success when reached here
2567  $origLink = ltrim( $m[1], ' ' );
2568 
2569  # Don't allow internal links to pages containing
2570  # PROTO: where PROTO is a valid URL protocol; these
2571  # should be external links.
2572  if ( preg_match( '/^(?i:' . $this->urlUtils->validProtocols() . ')/', $origLink ) ) {
2573  $s .= $prefix . '[[' . $line;
2574  continue;
2575  }
2576 
2577  # Make subpage if necessary
2578  if ( $useSubpages ) {
2580  $this->getTitle(), $origLink, $text
2581  );
2582  } else {
2583  $link = $origLink;
2584  }
2585 
2586  // \x7f isn't a default legal title char, so most likely strip
2587  // markers will force us into the "invalid form" path above. But,
2588  // just in case, let's assert that xmlish tags aren't valid in
2589  // the title position.
2590  $unstrip = $this->mStripState->killMarkers( $link );
2591  $noMarkers = ( $unstrip === $link );
2592 
2593  $nt = $noMarkers ? Title::newFromText( $link ) : null;
2594  if ( $nt === null ) {
2595  $s .= $prefix . '[[' . $line;
2596  continue;
2597  }
2598 
2599  $ns = $nt->getNamespace();
2600  $iw = $nt->getInterwiki();
2601 
2602  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2603 
2604  if ( $might_be_img ) { # if this is actually an invalid link
2605  if ( $ns === NS_FILE && $noforce ) { # but might be an image
2606  $found = false;
2607  while ( true ) {
2608  # look at the next 'line' to see if we can close it there
2609  $a->next();
2610  $next_line = $a->current();
2611  if ( $next_line === false || $next_line === null ) {
2612  break;
2613  }
2614  $m = explode( ']]', $next_line, 3 );
2615  if ( count( $m ) == 3 ) {
2616  # the first ]] closes the inner link, the second the image
2617  $found = true;
2618  $text .= "[[{$m[0]}]]{$m[1]}";
2619  $trail = $m[2];
2620  break;
2621  } elseif ( count( $m ) == 2 ) {
2622  # if there's exactly one ]] that's fine, we'll keep looking
2623  $text .= "[[{$m[0]}]]{$m[1]}";
2624  } else {
2625  # if $next_line is invalid too, we need look no further
2626  $text .= '[[' . $next_line;
2627  break;
2628  }
2629  }
2630  if ( !$found ) {
2631  # we couldn't find the end of this imageLink, so output it raw
2632  # but don't ignore what might be perfectly normal links in the text we've examined
2633  $holders->merge( $this->handleInternalLinks2( $text ) );
2634  $s .= "{$prefix}[[$link|$text";
2635  # note: no $trail, because without an end, there *is* no trail
2636  continue;
2637  }
2638  } else { # it's not an image, so output it raw
2639  $s .= "{$prefix}[[$link|$text";
2640  # note: no $trail, because without an end, there *is* no trail
2641  continue;
2642  }
2643  }
2644 
2645  $wasblank = ( $text == '' );
2646  if ( $wasblank ) {
2647  $text = $link;
2648  if ( !$noforce ) {
2649  # Strip off leading ':'
2650  $text = substr( $text, 1 );
2651  }
2652  } else {
2653  # T6598 madness. Handle the quotes only if they come from the alternate part
2654  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2655  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2656  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2657  $text = $this->doQuotes( $text );
2658  }
2659 
2660  # Link not escaped by : , create the various objects
2661  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2662  # Interwikis
2663  if (
2664  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2665  MediaWikiServices::getInstance()->getLanguageNameUtils()
2666  ->getLanguageName(
2667  $iw,
2668  LanguageNameUtils::AUTONYMS,
2669  LanguageNameUtils::DEFINED
2670  )
2671  || in_array( $iw, $this->svcOptions->get( MainConfigNames::ExtraInterlanguageLinkPrefixes ) )
2672  )
2673  ) {
2674  # T26502: filter duplicates
2675  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2676  $this->mLangLinkLanguages[$iw] = true;
2677  $this->mOutput->addLanguageLink( $nt->getFullText() );
2678  }
2679 
2683  $s = rtrim( $s . $prefix ) . $trail; # T175416
2684  continue;
2685  }
2686 
2687  if ( $ns === NS_FILE ) {
2688  if ( !$this->badFileLookup->isBadFile( $nt->getDBkey(), $this->getTitle() ) ) {
2689  if ( $wasblank ) {
2690  # if no parameters were passed, $text
2691  # becomes something like "File:Foo.png",
2692  # which we don't want to pass on to the
2693  # image generator
2694  $text = '';
2695  } else {
2696  # recursively parse links inside the image caption
2697  # actually, this will parse them in any other parameters, too,
2698  # but it might be hard to fix that, and it doesn't matter ATM
2699  $text = $this->handleExternalLinks( $text );
2700  $holders->merge( $this->handleInternalLinks2( $text ) );
2701  }
2702  # cloak any absolute URLs inside the image markup, so handleExternalLinks() won't touch them
2703  $s .= $prefix . $this->armorLinks(
2704  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2705  continue;
2706  }
2707  } elseif ( $ns === NS_CATEGORY ) {
2711  $s = rtrim( $s . $prefix ) . $trail; # T2087, T87753
2712 
2713  if ( $wasblank ) {
2714  $sortkey = $this->mOutput->getPageProperty( 'defaultsort' ) ?? '';
2715  } else {
2716  $sortkey = $text;
2717  }
2718  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2719  $sortkey = str_replace( "\n", '', $sortkey );
2720  $sortkey = $this->getTargetLanguageConverter()->convertCategoryKey( $sortkey );
2721  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2722 
2723  continue;
2724  }
2725  }
2726 
2727  # Self-link checking. For some languages, variants of the title are checked in
2728  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2729  # for linking to a different variant.
2730  if ( $ns !== NS_SPECIAL && $nt->equals( $this->getTitle() ) && !$nt->hasFragment() ) {
2731  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2732  continue;
2733  }
2734 
2735  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2736  # @todo FIXME: Should do batch file existence checks, see comment below
2737  if ( $ns === NS_MEDIA ) {
2738  # Give extensions a chance to select the file revision for us
2739  $options = [];
2740  $descQuery = false;
2741  $this->hookRunner->onBeforeParserFetchFileAndTitle(
2742  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
2743  $this, $nt, $options, $descQuery
2744  );
2745  # Fetch and register the file (file title may be different via hooks)
2746  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2747  # Cloak with NOPARSE to avoid replacement in handleExternalLinks
2748  $s .= $prefix . $this->armorLinks(
2749  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2750  continue;
2751  }
2752 
2753  # Some titles, such as valid special pages or files in foreign repos, should
2754  # be shown as bluelinks even though they're not included in the page table
2755  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2756  # batch file existence checks for NS_FILE and NS_MEDIA
2757  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2758  $this->mOutput->addLink( $nt );
2759  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2760  } else {
2761  # Links will be added to the output link list after checking
2762  $s .= $holders->makeHolder( $nt, $text, $trail, $prefix );
2763  }
2764  }
2765  return $holders;
2766  }
2767 
2781  private function makeKnownLinkHolder( LinkTarget $nt, $text = '', $trail = '', $prefix = '' ) {
2782  list( $inside, $trail ) = Linker::splitTrail( $trail );
2783 
2784  if ( $text == '' ) {
2785  $text = htmlspecialchars( $this->titleFormatter->getPrefixedText( $nt ) );
2786  }
2787 
2788  $link = $this->getLinkRenderer()->makeKnownLink(
2789  $nt, new HtmlArmor( "$prefix$text$inside" )
2790  );
2791 
2792  return $this->armorLinks( $link ) . $trail;
2793  }
2794 
2805  private function armorLinks( $text ) {
2806  return preg_replace( '/\b((?i)' . $this->urlUtils->validProtocols() . ')/',
2807  self::MARKER_PREFIX . "NOPARSE$1", $text );
2808  }
2809 
2819  public function doBlockLevels( $text, $linestart ) {
2820  wfDeprecated( __METHOD__, '1.35' );
2821  return BlockLevelPass::doBlockLevels( $text, $linestart );
2822  }
2823 
2832  private function expandMagicVariable( $index, $frame = false ) {
2837  if ( isset( $this->mVarCache[$index] ) ) {
2838  return $this->mVarCache[$index];
2839  }
2840 
2841  $ts = new MWTimestamp( $this->mOptions->getTimestamp() /* TS_MW */ );
2842  if ( $this->hookContainer->isRegistered( 'ParserGetVariableValueTs' ) ) {
2843  $s = $ts->getTimestamp( TS_UNIX );
2844  $this->hookRunner->onParserGetVariableValueTs( $this, $s );
2845  $ts = new MWTimestamp( $s );
2846  }
2847 
2848  $value = CoreMagicVariables::expand(
2849  $this, $index, $ts, $this->nsInfo, $this->svcOptions, $this->logger
2850  );
2851 
2852  if ( $value === null ) {
2853  // Not a defined core magic word
2854  // Don't give this hook unrestricted access to mVarCache
2855  $fakeCache = [];
2856  $this->hookRunner->onParserGetVariableValueSwitch(
2857  // @phan-suppress-next-line PhanTypeMismatchArgument $value is passed as null but returned as string
2858  $this, $fakeCache, $index, $value, $frame
2859  );
2860  // Cache the value returned by the hook by falling through here.
2861  // Assert the the hook returned a non-null value for this MV
2862  '@phan-var string $value';
2863  }
2864 
2865  $this->mVarCache[$index] = $value;
2866 
2867  return $value;
2868  }
2869 
2874  private function initializeVariables() {
2875  $variableIDs = $this->magicWordFactory->getVariableIDs();
2876  $substIDs = $this->magicWordFactory->getSubstIDs();
2877 
2878  $this->mVariables = $this->magicWordFactory->newArray( $variableIDs );
2879  $this->mSubstWords = $this->magicWordFactory->newArray( $substIDs );
2880  }
2881 
2900  public function preprocessToDom( $text, $flags = 0 ) {
2901  return $this->getPreprocessor()->preprocessToObj( $text, $flags );
2902  }
2903 
2925  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2926  # Is there any text? Also, Prevent too big inclusions!
2927  $textSize = strlen( $text );
2928  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2929  return $text;
2930  }
2931 
2932  if ( $frame === false ) {
2933  $frame = $this->getPreprocessor()->newFrame();
2934  } elseif ( !( $frame instanceof PPFrame ) ) {
2935  $this->logger->debug(
2936  __METHOD__ . " called using plain parameters instead of " .
2937  "a PPFrame instance. Creating custom frame."
2938  );
2939  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2940  }
2941 
2942  $dom = $this->preprocessToDom( $text );
2943  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2944  $text = $frame->expand( $dom, $flags );
2945 
2946  return $text;
2947  }
2948 
2976  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2977  # does no harm if $current and $max are present but are unnecessary for the message
2978  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2979  # only during preview, and that would split the parser cache unnecessarily.
2980  $this->mOutput->addWarningMsg(
2981  "$limitationType-warning",
2982  Message::numParam( $current ),
2983  Message::numParam( $max )
2984  );
2985  $this->addTrackingCategory( "$limitationType-category" );
2986  }
2987 
3001  public function braceSubstitution( array $piece, PPFrame $frame ) {
3002  // Flags
3003 
3004  // $text has been filled
3005  $found = false;
3006  $text = '';
3007  // wiki markup in $text should be escaped
3008  $nowiki = false;
3009  // $text is HTML, armour it against wikitext transformation
3010  $isHTML = false;
3011  // Force interwiki transclusion to be done in raw mode not rendered
3012  $forceRawInterwiki = false;
3013  // $text is a DOM node needing expansion in a child frame
3014  $isChildObj = false;
3015  // $text is a DOM node needing expansion in the current frame
3016  $isLocalObj = false;
3017 
3018  # Title object, where $text came from
3019  $title = false;
3020 
3021  # $part1 is the bit before the first |, and must contain only title characters.
3022  # Various prefixes will be stripped from it later.
3023  $titleWithSpaces = $frame->expand( $piece['title'] );
3024  $part1 = trim( $titleWithSpaces );
3025  $titleText = false;
3026 
3027  # Original title text preserved for various purposes
3028  $originalTitle = $part1;
3029 
3030  # $args is a list of argument nodes, starting from index 0, not including $part1
3031  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3032  # below won't work b/c this $args isn't an object
3033  $args = ( $piece['parts'] == null ) ? [] : $piece['parts'];
3034 
3035  $profileSection = null; // profile templates
3036 
3037  $sawDeprecatedTemplateEquals = false; // T91154
3038 
3039  # SUBST
3040  // @phan-suppress-next-line PhanImpossibleCondition
3041  if ( !$found ) {
3042  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3043 
3044  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3045  # Decide whether to expand template or keep wikitext as-is.
3046  if ( $this->ot['wiki'] ) {
3047  if ( $substMatch === false ) {
3048  $literal = true; # literal when in PST with no prefix
3049  } else {
3050  $literal = false; # expand when in PST with subst: or safesubst:
3051  }
3052  } else {
3053  if ( $substMatch == 'subst' ) {
3054  $literal = true; # literal when not in PST with plain subst:
3055  } else {
3056  $literal = false; # expand when not in PST with safesubst: or no prefix
3057  }
3058  }
3059  if ( $literal ) {
3060  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3061  $isLocalObj = true;
3062  $found = true;
3063  }
3064  }
3065 
3066  # Variables
3067  if ( !$found && $args->getLength() == 0 ) {
3068  $id = $this->mVariables->matchStartToEnd( $part1 );
3069  if ( $id !== false ) {
3070  if ( strpos( $part1, ':' ) !== false ) {
3072  'Registering a magic variable with a name including a colon',
3073  '1.39', false, false
3074  );
3075  }
3076  $text = $this->expandMagicVariable( $id, $frame );
3077  if ( $this->magicWordFactory->getCacheTTL( $id ) > -1 ) {
3078  $this->mOutput->updateCacheExpiry(
3079  $this->magicWordFactory->getCacheTTL( $id ) );
3080  }
3081  $found = true;
3082  }
3083  }
3084 
3085  # MSG, MSGNW and RAW
3086  if ( !$found ) {
3087  # Check for MSGNW:
3088  $mwMsgnw = $this->magicWordFactory->get( 'msgnw' );
3089  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3090  $nowiki = true;
3091  } else {
3092  # Remove obsolete MSG:
3093  $mwMsg = $this->magicWordFactory->get( 'msg' );
3094  $mwMsg->matchStartAndRemove( $part1 );
3095  }
3096 
3097  # Check for RAW:
3098  $mwRaw = $this->magicWordFactory->get( 'raw' );
3099  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3100  $forceRawInterwiki = true;
3101  }
3102  }
3103 
3104  # Parser functions
3105  if ( !$found ) {
3106  $colonPos = strpos( $part1, ':' );
3107  if ( $colonPos !== false ) {
3108  $func = substr( $part1, 0, $colonPos );
3109  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3110  $argsLength = $args->getLength();
3111  for ( $i = 0; $i < $argsLength; $i++ ) {
3112  $funcArgs[] = $args->item( $i );
3113  }
3114 
3115  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3116 
3117  // Extract any forwarded flags
3118  if ( isset( $result['title'] ) ) {
3119  $title = $result['title'];
3120  }
3121  if ( isset( $result['found'] ) ) {
3122  $found = $result['found'];
3123  }
3124  if ( array_key_exists( 'text', $result ) ) {
3125  // a string or null
3126  $text = $result['text'];
3127  }
3128  if ( isset( $result['nowiki'] ) ) {
3129  $nowiki = $result['nowiki'];
3130  }
3131  if ( isset( $result['isHTML'] ) ) {
3132  $isHTML = $result['isHTML'];
3133  }
3134  if ( isset( $result['forceRawInterwiki'] ) ) {
3135  $forceRawInterwiki = $result['forceRawInterwiki'];
3136  }
3137  if ( isset( $result['isChildObj'] ) ) {
3138  $isChildObj = $result['isChildObj'];
3139  }
3140  if ( isset( $result['isLocalObj'] ) ) {
3141  $isLocalObj = $result['isLocalObj'];
3142  }
3143  }
3144  }
3145 
3146  # Finish mangling title and then check for loops.
3147  # Set $title to a Title object and $titleText to the PDBK
3148  if ( !$found ) {
3149  $ns = NS_TEMPLATE;
3150  # Split the title into page and subpage
3151  $subpage = '';
3152  $relative = Linker::normalizeSubpageLink(
3153  $this->getTitle(), $part1, $subpage
3154  );
3155  if ( $part1 !== $relative ) {
3156  $part1 = $relative;
3157  $ns = $this->getTitle()->getNamespace();
3158  }
3159  $title = Title::newFromText( $part1, $ns );
3160  if ( $title ) {
3161  $titleText = $title->getPrefixedText();
3162  # Check for language variants if the template is not found
3163  if ( $this->getTargetLanguageConverter()->hasVariants() && $title->getArticleID() == 0 ) {
3164  $this->getTargetLanguageConverter()->findVariantLink( $part1, $title, true );
3165  }
3166  # Do recursion depth check
3167  $limit = $this->mOptions->getMaxTemplateDepth();
3168  if ( $frame->depth >= $limit ) {
3169  $found = true;
3170  $text = '<span class="error">'
3171  . wfMessage( 'parser-template-recursion-depth-warning' )
3172  ->numParams( $limit )->inContentLanguage()->text()
3173  . '</span>';
3174  }
3175  }
3176  }
3177 
3178  # Load from database
3179  if ( !$found && $title ) {
3180  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3181  if ( !$title->isExternal() ) {
3182  if ( $title->isSpecialPage()
3183  && $this->mOptions->getAllowSpecialInclusion()
3184  && $this->ot['html']
3185  ) {
3186  $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() );
3187  // Pass the template arguments as URL parameters.
3188  // "uselang" will have no effect since the Language object
3189  // is forced to the one defined in ParserOptions.
3190  $pageArgs = [];
3191  $argsLength = $args->getLength();
3192  for ( $i = 0; $i < $argsLength; $i++ ) {
3193  $bits = $args->item( $i )->splitArg();
3194  if ( strval( $bits['index'] ) === '' ) {
3195  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3196  $value = trim( $frame->expand( $bits['value'] ) );
3197  $pageArgs[$name] = $value;
3198  }
3199  }
3200 
3201  // Create a new context to execute the special page
3202  $context = new RequestContext;
3203  $context->setTitle( $title );
3204  $context->setRequest( new FauxRequest( $pageArgs ) );
3205  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3206  $context->setUser( $this->userFactory->newFromUserIdentity( $this->getUserIdentity() ) );
3207  } else {
3208  // If this page is cached, then we better not be per user.
3209  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3210  }
3211  $context->setLanguage( $this->mOptions->getUserLangObj() );
3212  $ret = $this->specialPageFactory->capturePath( $title, $context, $this->getLinkRenderer() );
3213  if ( $ret ) {
3214  $text = $context->getOutput()->getHTML();
3215  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3216  $found = true;
3217  $isHTML = true;
3218  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3219  $this->mOutput->updateRuntimeAdaptiveExpiry(
3220  $specialPage->maxIncludeCacheTime()
3221  );
3222  }
3223  }
3224  } elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
3225  $found = false; # access denied
3226  $this->logger->debug(
3227  __METHOD__ .
3228  ": template inclusion denied for " . $title->getPrefixedDBkey()
3229  );
3230  } else {
3231  list( $text, $title ) = $this->getTemplateDom( $title );
3232  if ( $text !== false ) {
3233  $found = true;
3234  $isChildObj = true;
3235  if (
3236  $title->getNamespace() === NS_TEMPLATE &&
3237  $title->getDBkey() === '=' &&
3238  $originalTitle === '='
3239  ) {
3240  // Note that we won't get here if `=` is evaluated
3241  // (in the future) as a parser function, nor if
3242  // the Template namespace is given explicitly,
3243  // ie `{{Template:=}}`. Only `{{=}}` triggers.
3244  $sawDeprecatedTemplateEquals = true; // T91154
3245  }
3246  }
3247  }
3248 
3249  # If the title is valid but undisplayable, make a link to it
3250  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3251  $text = "[[:$titleText]]";
3252  $found = true;
3253  }
3254  } elseif ( $title->isTrans() ) {
3255  # Interwiki transclusion
3256  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3257  $text = $this->interwikiTransclude( $title, 'render' );
3258  $isHTML = true;
3259  } else {
3260  $text = $this->interwikiTransclude( $title, 'raw' );
3261  # Preprocess it like a template
3262  $text = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
3263  $isChildObj = true;
3264  }
3265  $found = true;
3266  }
3267 
3268  # Do infinite loop check
3269  # This has to be done after redirect resolution to avoid infinite loops via redirects
3270  if ( !$frame->loopCheck( $title ) ) {
3271  $found = true;
3272  $text = '<span class="error">'
3273  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3274  . '</span>';
3275  $this->addTrackingCategory( 'template-loop-category' );
3276  $this->mOutput->addWarningMsg(
3277  'template-loop-warning',
3278  Message::plaintextParam( $titleText )
3279  );
3280  $this->logger->debug( __METHOD__ . ": template loop broken at '$titleText'" );
3281  }
3282  }
3283 
3284  # If we haven't found text to substitute by now, we're done
3285  # Recover the source wikitext and return it
3286  if ( !$found ) {
3287  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3288  if ( $profileSection ) {
3289  $this->mProfiler->scopedProfileOut( $profileSection );
3290  }
3291  return [ 'object' => $text ];
3292  }
3293 
3294  # Expand DOM-style return values in a child frame
3295  if ( $isChildObj ) {
3296  # Clean up argument array
3297  $newFrame = $frame->newChild( $args, $title );
3298 
3299  if ( $nowiki ) {
3300  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3301  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3302  # Expansion is eligible for the empty-frame cache
3303  $text = $newFrame->cachedExpand( $titleText, $text );
3304  } else {
3305  # Uncached expansion
3306  $text = $newFrame->expand( $text );
3307  }
3308  }
3309  if ( $isLocalObj && $nowiki ) {
3310  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3311  $isLocalObj = false;
3312  }
3313 
3314  if ( $profileSection ) {
3315  $this->mProfiler->scopedProfileOut( $profileSection );
3316  }
3317  if (
3318  $sawDeprecatedTemplateEquals &&
3319  $this->mStripState->unstripBoth( $text ) !== '='
3320  ) {
3321  // T91154: {{=}} is deprecated when it doesn't expand to `=`;
3322  // use {{Template:=}} if you must.
3323  $this->addTrackingCategory( 'template-equals-category' );
3324  $this->mOutput->addWarningMsg( 'template-equals-warning' );
3325  }
3326 
3327  # Replace raw HTML by a placeholder
3328  if ( $isHTML ) {
3329  // @phan-suppress-next-line SecurityCheck-XSS Mixed mode, here html and safe
3330  $text = $this->insertStripItem( $text );
3331  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3332  # Escape nowiki-style return values
3333  // @phan-suppress-next-line SecurityCheck-DoubleEscaped Mixed mode, here html and safe
3334  $text = wfEscapeWikiText( $text );
3335  } elseif ( is_string( $text )
3336  && !$piece['lineStart']
3337  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3338  ) {
3339  # T2529: if the template begins with a table or block-level
3340  # element, it should be treated as beginning a new line.
3341  # This behavior is somewhat controversial.
3342  $text = "\n" . $text;
3343  }
3344 
3345  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3346  # Error, oversize inclusion
3347  if ( $titleText !== false ) {
3348  # Make a working, properly escaped link if possible (T25588)
3349  $text = "[[:$titleText]]";
3350  } else {
3351  # This will probably not be a working link, but at least it may
3352  # provide some hint of where the problem is
3353  $originalTitle = preg_replace( '/^:/', '', $originalTitle );
3354  $text = "[[:$originalTitle]]";
3355  }
3356  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3357  . 'post-expand include size too large -->' );
3358  $this->limitationWarn( 'post-expand-template-inclusion' );
3359  }
3360 
3361  if ( $isLocalObj ) {
3362  $ret = [ 'object' => $text ];
3363  } else {
3364  $ret = [ 'text' => $text ];
3365  }
3366 
3367  return $ret;
3368  }
3369 
3388  public function callParserFunction( PPFrame $frame, $function, array $args = [] ) {
3389  # Case sensitive functions
3390  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3391  $function = $this->mFunctionSynonyms[1][$function];
3392  } else {
3393  # Case insensitive functions
3394  $function = $this->contLang->lc( $function );
3395  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3396  $function = $this->mFunctionSynonyms[0][$function];
3397  } else {
3398  return [ 'found' => false ];
3399  }
3400  }
3401 
3402  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3403 
3404  $allArgs = [ $this ];
3405  if ( $flags & self::SFH_OBJECT_ARGS ) {
3406  # Convert arguments to PPNodes and collect for appending to $allArgs
3407  $funcArgs = [];
3408  foreach ( $args as $k => $v ) {
3409  if ( $v instanceof PPNode || $k === 0 ) {
3410  $funcArgs[] = $v;
3411  } else {
3412  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3413  }
3414  }
3415 
3416  # Add a frame parameter, and pass the arguments as an array
3417  $allArgs[] = $frame;
3418  $allArgs[] = $funcArgs;
3419  } else {
3420  # Convert arguments to plain text and append to $allArgs
3421  foreach ( $args as $k => $v ) {
3422  if ( $v instanceof PPNode ) {
3423  $allArgs[] = trim( $frame->expand( $v ) );
3424  } elseif ( is_int( $k ) && $k >= 0 ) {
3425  $allArgs[] = trim( $v );
3426  } else {
3427  $allArgs[] = trim( "$k=$v" );
3428  }
3429  }
3430  }
3431 
3432  $result = $callback( ...$allArgs );
3433 
3434  # The interface for function hooks allows them to return a wikitext
3435  # string or an array containing the string and any flags. This mungs
3436  # things around to match what this method should return.
3437  if ( !is_array( $result ) ) {
3438  $result = [
3439  'found' => true,
3440  'text' => $result,
3441  ];
3442  } else {
3443  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3444  $result['text'] = $result[0];
3445  }
3446  unset( $result[0] );
3447  $result += [
3448  'found' => true,
3449  ];
3450  }
3451 
3452  $noparse = true;
3453  $preprocessFlags = 0;
3454  if ( isset( $result['noparse'] ) ) {
3455  $noparse = $result['noparse'];
3456  }
3457  if ( isset( $result['preprocessFlags'] ) ) {
3458  $preprocessFlags = $result['preprocessFlags'];
3459  }
3460 
3461  if ( !$noparse ) {
3462  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3463  $result['isChildObj'] = true;
3464  }
3465 
3466  return $result;
3467  }
3468 
3478  public function getTemplateDom( LinkTarget $title ) {
3479  $cacheTitle = $title;
3480  $titleKey = CacheKeyHelper::getKeyForPage( $title );
3481 
3482  if ( isset( $this->mTplRedirCache[$titleKey] ) ) {
3483  list( $ns, $dbk ) = $this->mTplRedirCache[$titleKey];
3484  $title = Title::makeTitle( $ns, $dbk );
3485  $titleKey = CacheKeyHelper::getKeyForPage( $title );
3486  }
3487  if ( isset( $this->mTplDomCache[$titleKey] ) ) {
3488  return [ $this->mTplDomCache[$titleKey], $title ];
3489  }
3490 
3491  # Cache miss, go to the database
3492  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3493 
3494  if ( $text === false ) {
3495  $this->mTplDomCache[$titleKey] = false;
3496  return [ false, $title ];
3497  }
3498 
3499  $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
3500  $this->mTplDomCache[$titleKey] = $dom;
3501 
3502  if ( !$title->isSamePageAs( $cacheTitle ) ) {
3503  $this->mTplRedirCache[ CacheKeyHelper::getKeyForPage( $cacheTitle ) ] =
3504  [ $title->getNamespace(), $title->getDBkey() ];
3505  }
3506 
3507  return [ $dom, $title ];
3508  }
3509 
3524  $cacheKey = CacheKeyHelper::getKeyForPage( $link );
3525  if ( !$this->currentRevisionCache ) {
3526  $this->currentRevisionCache = new MapCacheLRU( 100 );
3527  }
3528  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3529  $title = Title::castFromLinkTarget( $link ); // hook signature compat
3530  $revisionRecord =
3531  // Defaults to Parser::statelessFetchRevisionRecord()
3532  call_user_func(
3533  $this->mOptions->getCurrentRevisionRecordCallback(),
3534  $title,
3535  $this
3536  );
3537  if ( !$revisionRecord ) {
3538  // Parser::statelessFetchRevisionRecord() can return false;
3539  // normalize it to null.
3540  $revisionRecord = null;
3541  }
3542  $this->currentRevisionCache->set( $cacheKey, $revisionRecord );
3543  }
3544  return $this->currentRevisionCache->get( $cacheKey );
3545  }
3546 
3553  public function isCurrentRevisionOfTitleCached( LinkTarget $link ) {
3554  $key = CacheKeyHelper::getKeyForPage( $link );
3555  return (
3556  $this->currentRevisionCache &&
3557  $this->currentRevisionCache->has( $key )
3558  );
3559  }
3560 
3569  public static function statelessFetchRevisionRecord( LinkTarget $link, $parser = null ) {
3570  if ( $link instanceof PageIdentity ) {
3571  // probably a Title, just use it.
3572  $page = $link;
3573  } else {
3574  // XXX: use RevisionStore::getPageForLink()!
3575  // ...but get the info for the current revision at the same time?
3576  // Should RevisionStore::getKnownCurrentRevision accept a LinkTarget?
3577  $page = Title::castFromLinkTarget( $link );
3578  }
3579 
3580  $revRecord = MediaWikiServices::getInstance()
3581  ->getRevisionLookup()
3582  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable castFrom does not return null here
3583  ->getKnownCurrentRevision( $page );
3584  return $revRecord;
3585  }
3586 
3593  public function fetchTemplateAndTitle( LinkTarget $link ) {
3594  // Use Title for compatibility with callbacks and return type
3595  $title = Title::castFromLinkTarget( $link );
3596 
3597  // Defaults to Parser::statelessFetchTemplate()
3598  $templateCb = $this->mOptions->getTemplateCallback();
3599  $stuff = $templateCb( $title, $this );
3600  $revRecord = $stuff['revision-record'] ?? null;
3601 
3602  $text = $stuff['text'];
3603  if ( is_string( $stuff['text'] ) ) {
3604  // We use U+007F DELETE to distinguish strip markers from regular text
3605  $text = strtr( $text, "\x7f", "?" );
3606  }
3607  $finalTitle = $stuff['finalTitle'] ?? $title;
3608  foreach ( ( $stuff['deps'] ?? [] ) as $dep ) {
3609  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3610  if ( $dep['title']->equals( $this->getTitle() ) && $revRecord instanceof RevisionRecord ) {
3611  // Self-transclusion; final result may change based on the new page version
3612  try {
3613  $sha1 = $revRecord->getSha1();
3614  } catch ( RevisionAccessException $e ) {
3615  $sha1 = null;
3616  }
3617  $this->setOutputFlag( ParserOutputFlags::VARY_REVISION_SHA1, 'Self transclusion' );
3618  $this->getOutput()->setRevisionUsedSha1Base36( $sha1 );
3619  }
3620  }
3621 
3622  return [ $text, $finalTitle ];
3623  }
3624 
3635  public static function statelessFetchTemplate( $page, $parser = false ) {
3636  $title = Title::castFromLinkTarget( $page ); // for compatibility with return type
3637  $text = $skip = false;
3638  $finalTitle = $title;
3639  $deps = [];
3640  $revRecord = null;
3641  $contextTitle = $parser ? $parser->getTitle() : null;
3642 
3643  # Loop to fetch the article, with up to 2 redirects
3644  $revLookup = MediaWikiServices::getInstance()->getRevisionLookup();
3645  for ( $i = 0; $i < 3 && is_object( $title ); $i++ ) {
3646  # Give extensions a chance to select the revision instead
3647  $revRecord = null; # Assume no hook
3648  $id = false; # Assume current
3649  $origTitle = $title;
3650  $titleChanged = false;
3651  Hooks::runner()->onBeforeParserFetchTemplateRevisionRecord(
3652  # The $title is a not a PageIdentity, as it may
3653  # contain fragments or even represent an attempt to transclude
3654  # a broken or otherwise-missing Title, which the hook may
3655  # fix up. Similarly, the $contextTitle may represent a special
3656  # page or other page which "exists" as a parsing context but
3657  # is not in the DB.
3658  $contextTitle, $title,
3659  $skip, $revRecord
3660  );
3661  if ( !$skip && !$revRecord ) {
3662  # Deprecated legacy hook
3663  Hooks::runner()->onBeforeParserFetchTemplateAndtitle(
3664  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
3665  $parser, $title, $skip, $id
3666  );
3667  }
3668 
3669  if ( $skip ) {
3670  $text = false;
3671  $deps[] = [
3672  'title' => $title,
3673  'page_id' => $title->getArticleID(),
3674  'rev_id' => null
3675  ];
3676  break;
3677  }
3678  # Get the revision
3679  if ( !$revRecord ) {
3680  if ( $id ) {
3681  # Handle $id returned by deprecated legacy hook
3682  $revRecord = $revLookup->getRevisionById( $id );
3683  } elseif ( $parser ) {
3684  $revRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
3685  } else {
3686  $revRecord = $revLookup->getRevisionByTitle( $title );
3687  }
3688  }
3689  if ( $revRecord ) {
3690  # Update title, as $revRecord may have been changed by hook
3692  $revRecord->getPageAsLinkTarget()
3693  );
3694  $deps[] = [
3695  'title' => $title,
3696  'page_id' => $revRecord->getPageId(),
3697  'rev_id' => $revRecord->getId(),
3698  ];
3699  } else {
3700  $deps[] = [
3701  'title' => $title,
3702  'page_id' => $title->getArticleID(),
3703  'rev_id' => null,
3704  ];
3705  }
3706  if ( !$title->equals( $origTitle ) ) {
3707  # If we fetched a rev from a different title, register
3708  # the original title too...
3709  $deps[] = [
3710  'title' => $origTitle,
3711  'page_id' => $origTitle->getArticleID(),
3712  'rev_id' => null,
3713  ];
3714  $titleChanged = true;
3715  }
3716  # If there is no current revision, there is no page
3717  if ( $revRecord === null || $revRecord->getId() === null ) {
3718  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3719  $linkCache->addBadLinkObj( $title );
3720  }
3721  if ( $revRecord ) {
3722  if ( $titleChanged && !$revRecord->hasSlot( SlotRecord::MAIN ) ) {
3723  // We've added this (missing) title to the dependencies;
3724  // give the hook another chance to redirect it to an
3725  // actual page.
3726  $text = false;
3727  $finalTitle = $title;
3728  continue;
3729  }
3730  if ( $revRecord->hasSlot( SlotRecord::MAIN ) ) { // T276476
3731  $content = $revRecord->getContent( SlotRecord::MAIN );
3732  $text = $content ? $content->getWikitextForTransclusion() : null;
3733  } else {
3734  $text = false;
3735  }
3736 
3737  if ( $text === false || $text === null ) {
3738  $text = false;
3739  break;
3740  }
3741  } elseif ( $title->getNamespace() === NS_MEDIAWIKI ) {
3742  $message = wfMessage( MediaWikiServices::getInstance()->getContentLanguage()->
3743  lcfirst( $title->getText() ) )->inContentLanguage();
3744  if ( !$message->exists() ) {
3745  $text = false;
3746  break;
3747  }
3748  $text = $message->plain();
3749  break;
3750  } else {
3751  break;
3752  }
3753  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable Only reached when content is set
3754  if ( !$content ) {
3755  break;
3756  }
3757  # Redirect?
3758  $finalTitle = $title;
3759  $title = $content->getRedirectTarget();
3760  }
3761 
3762  $retValues = [
3763  // previously, when this also returned a Revision object, we set
3764  // 'revision-record' to false instead of null if it was unavailable,
3765  // so that callers to use isset and then rely on the revision-record
3766  // key instead of the revision key, even if there was no corresponding
3767  // object - we continue to set to false here for backwards compatability
3768  'revision-record' => $revRecord ?: false,
3769  'text' => $text,
3770  'finalTitle' => $finalTitle,
3771  'deps' => $deps
3772  ];
3773  return $retValues;
3774  }
3775 
3784  public function fetchFileAndTitle( LinkTarget $link, array $options = [] ) {
3785  $file = $this->fetchFileNoRegister( $link, $options );
3786 
3787  $time = $file ? $file->getTimestamp() : false;
3788  $sha1 = $file ? $file->getSha1() : false;
3789  # Register the file as a dependency...
3790  $this->mOutput->addImage( $link->getDBkey(), $time, $sha1 );
3791  if ( $file && !$link->isSameLinkAs( $file->getTitle() ) ) {
3792  # Update fetched file title after resolving redirects, etc.
3793  $link = $file->getTitle();
3794  $this->mOutput->addImage( $link->getDBkey(), $time, $sha1 );
3795  }
3796 
3797  $title = Title::castFromLinkTarget( $link ); // for return type compat
3798  return [ $file, $title ];
3799  }
3800 
3811  protected function fetchFileNoRegister( LinkTarget $link, array $options = [] ) {
3812  if ( isset( $options['broken'] ) ) {
3813  $file = false; // broken thumbnail forced by hook
3814  } else {
3815  $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
3816  if ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3817  $file = $repoGroup->findFileFromKey( $options['sha1'], $options );
3818  } else { // get by (name,timestamp)
3819  $file = $repoGroup->findFile( $link, $options );
3820  }
3821  }
3822  return $file;
3823  }
3824 
3834  public function interwikiTransclude( LinkTarget $link, $action ) {
3835  if ( !$this->svcOptions->get( MainConfigNames::EnableScaryTranscluding ) ) {
3836  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3837  }
3838 
3839  // TODO: extract relevant functionality from Title
3840  $title = Title::castFromLinkTarget( $link );
3841 
3842  $url = $title->getFullURL( [ 'action' => $action ] );
3843  if ( strlen( $url ) > 1024 ) {
3844  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3845  }
3846 
3847  $wikiId = $title->getTransWikiID(); // remote wiki ID or false
3848 
3849  $fname = __METHOD__;
3850  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
3851 
3852  $data = $cache->getWithSetCallback(
3853  $cache->makeGlobalKey(
3854  'interwiki-transclude',
3855  ( $wikiId !== false ) ? $wikiId : 'external',
3856  sha1( $url )
3857  ),
3858  $this->svcOptions->get( MainConfigNames::TranscludeCacheExpiry ),
3859  function ( $oldValue, &$ttl ) use ( $url, $fname, $cache ) {
3860  $req = $this->httpRequestFactory->create( $url, [], $fname );
3861 
3862  $status = $req->execute(); // Status object
3863  if ( !$status->isOK() ) {
3864  $ttl = $cache::TTL_UNCACHEABLE;
3865  } elseif ( $req->getResponseHeader( 'X-Database-Lagged' ) !== null ) {
3866  $ttl = min( $cache::TTL_LAGGED, $ttl );
3867  }
3868 
3869  return [
3870  'text' => $status->isOK() ? $req->getContent() : null,
3871  'code' => $req->getStatus()
3872  ];
3873  },
3874  [
3875  'checkKeys' => ( $wikiId !== false )
3876  ? [ $cache->makeGlobalKey( 'interwiki-page', $wikiId, $title->getDBkey() ) ]
3877  : [],
3878  'pcGroup' => 'interwiki-transclude:5',
3879  'pcTTL' => $cache::TTL_PROC_LONG
3880  ]
3881  );
3882 
3883  if ( is_string( $data['text'] ) ) {
3884  $text = $data['text'];
3885  } elseif ( $data['code'] != 200 ) {
3886  // Though we failed to fetch the content, this status is useless.
3887  $text = wfMessage( 'scarytranscludefailed-httpstatus' )
3888  ->params( $url, $data['code'] )->inContentLanguage()->text();
3889  } else {
3890  $text = wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3891  }
3892 
3893  return $text;
3894  }
3895 
3906  public function argSubstitution( array $piece, PPFrame $frame ) {
3907  $error = false;
3908  $parts = $piece['parts'];
3909  $nameWithSpaces = $frame->expand( $piece['title'] );
3910  $argName = trim( $nameWithSpaces );
3911  $object = false;
3912  $text = $frame->getArgument( $argName );
3913  if ( $text === false && $parts->getLength() > 0
3914  && ( $this->ot['html']
3915  || $this->ot['pre']
3916  || ( $this->ot['wiki'] && $frame->isTemplate() )
3917  )
3918  ) {
3919  # No match in frame, use the supplied default
3920  $object = $parts->item( 0 )->getChildren();
3921  }
3922  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3923  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3924  $this->limitationWarn( 'post-expand-template-argument' );
3925  }
3926 
3927  if ( $text === false && $object === false ) {
3928  # No match anywhere
3929  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3930  }
3931  if ( $error !== false ) {
3932  $text .= $error;
3933  }
3934  if ( $object !== false ) {
3935  $ret = [ 'object' => $object ];
3936  } else {
3937  $ret = [ 'text' => $text ];
3938  }
3939 
3940  return $ret;
3941  }
3942 
3947  public function tagNeedsNowikiStrippedInTagPF( string $lowerTagName ): bool {
3948  $parsoidSiteConfig = MediaWikiServices::getInstance()->getParsoidSiteConfig();
3949  return $parsoidSiteConfig->tagNeedsNowikiStrippedInTagPF( $lowerTagName );
3950  }
3951 
3973  public function extensionSubstitution( array $params, PPFrame $frame, bool $processNowiki = false ) {
3974  static $errorStr = '<span class="error">';
3975  static $errorLen = 20;
3976 
3977  $name = $frame->expand( $params['name'] );
3978  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3979  // Probably expansion depth or node count exceeded. Just punt the
3980  // error up.
3981  return $name;
3982  }
3983 
3984  $attrText = !isset( $params['attr'] ) ? '' : $frame->expand( $params['attr'] );
3985  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3986  // See above
3987  return $attrText;
3988  }
3989 
3990  // We can't safely check if the expansion for $content resulted in an
3991  // error, because the content could happen to be the error string
3992  // (T149622).
3993  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3994 
3995  $marker = self::MARKER_PREFIX . "-$name-"
3996  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3997 
3998  $normalizedName = strtolower( $name );
3999  $isNowiki = $normalizedName === 'nowiki';
4000  $markerType = $isNowiki ? 'nowiki' : 'general';
4001  if ( $this->ot['html'] || ( $processNowiki && $isNowiki ) ) {
4002  $name = $normalizedName;
4003  $attributes = Sanitizer::decodeTagAttributes( $attrText );
4004  if ( isset( $params['attributes'] ) ) {
4005  $attributes += $params['attributes'];
4006  }
4007 
4008  if ( isset( $this->mTagHooks[$name] ) ) {
4009  // Note that $content may be null here, for example if the
4010  // tag is self-closed.
4011  $output = call_user_func_array( $this->mTagHooks[$name],
4012  [ $content, $attributes, $this, $frame ] );
4013  } else {
4014  $output = '<span class="error">Invalid tag extension name: ' .
4015  htmlspecialchars( $name ) . '</span>';
4016  }
4017 
4018  if ( is_array( $output ) ) {
4019  // Extract flags
4020  $flags = $output;
4021  $output = $flags[0];
4022  if ( isset( $flags['markerType'] ) ) {
4023  $markerType = $flags['markerType'];
4024  }
4025  }
4026  } else {
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 ( substr( $close, 0, $errorLen ) === $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  // @phan-suppress-next-line PhanTypeMismatchReturnNullable False positive
4135  return $text;
4136  }
4137 
4144  public function addTrackingCategory( $msg ) {
4145  return $this->trackingCategories->addTrackingCategory(
4146  $this->mOutput, $msg, $this->getPage()
4147  );
4148  }
4149 
4165  private function finalizeHeadings( $text, $origText, $isMain = true ) {
4166  # Inhibit editsection links if requested in the page
4167  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4168  $maybeShowEditLink = false;
4169  } else {
4170  $maybeShowEditLink = true; /* Actual presence will depend on post-cache transforms */
4171  }
4172 
4173  # Get all headlines for numbering them and adding funky stuff like [edit]
4174  # links - this is for later, but we need the number of headlines right now
4175  # NOTE: white space in headings have been trimmed in handleHeadings. They shouldn't
4176  # be trimmed here since whitespace in HTML headings is significant.
4177  $matches = [];
4178  $numMatches = preg_match_all(
4179  '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4180  $text,
4181  $matches
4182  );
4183 
4184  # if there are fewer than 4 headlines in the article, do not show TOC
4185  # unless it's been explicitly enabled.
4186  $enoughToc = $this->mShowToc &&
4187  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4188 
4189  # Allow user to stipulate that a page should have a "new section"
4190  # link added via __NEWSECTIONLINK__
4191  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4192  $this->mOutput->setNewSection( true );
4193  }
4194 
4195  # Allow user to remove the "new section"
4196  # link via __NONEWSECTIONLINK__
4197  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4198  $this->mOutput->setHideNewSection( true );
4199  }
4200 
4201  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4202  # override above conditions and always show TOC above first header
4203  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4204  $this->mShowToc = true;
4205  $enoughToc = true;
4206  }
4207 
4208  # headline counter
4209  $headlineCount = 0;
4210  $numVisible = 0;
4211 
4212  # Ugh .. the TOC should have neat indentation levels which can be
4213  # passed to the skin functions. These are determined here
4214  $toc = '';
4215  $full = '';
4216  $head = [];
4217  $sublevelCount = [];
4218  $levelCount = [];
4219  $level = 0;
4220  $prevlevel = 0;
4221  $toclevel = 0;
4222  $prevtoclevel = 0;
4223  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4224  $baseTitleText = $this->getTitle()->getPrefixedDBkey();
4225  $oldType = $this->mOutputType;
4226  $this->setOutputType( self::OT_WIKI );
4227  $frame = $this->getPreprocessor()->newFrame();
4228  $root = $this->preprocessToDom( $origText );
4229  $node = $root->getFirstChild();
4230  $byteOffset = 0;
4231  $tocraw = [];
4232  $refers = [];
4233 
4234  $headlines = $numMatches !== false ? $matches[3] : [];
4235 
4236  $maxTocLevel = $this->svcOptions->get( MainConfigNames::MaxTocLevel );
4237  foreach ( $headlines as $headline ) {
4238  $isTemplate = false;
4239  $titleText = false;
4240  $sectionIndex = false;
4241  $numbering = '';
4242  $markerMatches = [];
4243  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4244  $serial = (int)$markerMatches[1];
4245  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4246  $isTemplate = ( $titleText != $baseTitleText );
4247  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4248  }
4249 
4250  if ( $toclevel ) {
4251  $prevlevel = $level;
4252  }
4253  $level = (int)$matches[1][$headlineCount];
4254 
4255  if ( $level > $prevlevel ) {
4256  # Increase TOC level
4257  $toclevel++;
4258  $sublevelCount[$toclevel] = 0;
4259  if ( $toclevel < $maxTocLevel ) {
4260  $prevtoclevel = $toclevel;
4261  $toc .= Linker::tocIndent();
4262  $numVisible++;
4263  }
4264  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4265  # Decrease TOC level, find level to jump to
4266 
4267  for ( $i = $toclevel; $i > 0; $i-- ) {
4268  // @phan-suppress-next-line PhanTypeInvalidDimOffset
4269  if ( $levelCount[$i] == $level ) {
4270  # Found last matching level
4271  $toclevel = $i;
4272  break;
4273  } elseif ( $levelCount[$i] < $level ) {
4274  // @phan-suppress-previous-line PhanTypeInvalidDimOffset
4275  # Found first matching level below current level
4276  $toclevel = $i + 1;
4277  break;
4278  }
4279  }
4280  if ( $i == 0 ) {
4281  $toclevel = 1;
4282  }
4283  if ( $toclevel < $maxTocLevel ) {
4284  if ( $prevtoclevel < $maxTocLevel ) {
4285  # Unindent only if the previous toc level was shown :p
4286  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4287  $prevtoclevel = $toclevel;
4288  } else {
4289  $toc .= Linker::tocLineEnd();
4290  }
4291  }
4292  } else {
4293  # No change in level, end TOC line
4294  if ( $toclevel < $maxTocLevel ) {
4295  $toc .= Linker::tocLineEnd();
4296  }
4297  }
4298 
4299  $levelCount[$toclevel] = $level;
4300 
4301  # count number of headlines for each level
4302  $sublevelCount[$toclevel]++;
4303  $dot = 0;
4304  for ( $i = 1; $i <= $toclevel; $i++ ) {
4305  if ( !empty( $sublevelCount[$i] ) ) {
4306  if ( $dot ) {
4307  $numbering .= '.';
4308  }
4309  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4310  $dot = 1;
4311  }
4312  }
4313 
4314  # The safe header is a version of the header text safe to use for links
4315 
4316  # Remove link placeholders by the link text.
4317  # <!--LINK number-->
4318  # turns into
4319  # link text with suffix
4320  # Do this before unstrip since link text can contain strip markers
4321  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4322 
4323  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4324  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4325 
4326  # Remove any <style> or <script> tags (T198618)
4327  $safeHeadline = preg_replace(
4328  '#<(style|script)(?: [^>]*[^>/])?>.*?</\1>#is',
4329  '',
4330  $safeHeadline
4331  );
4332 
4333  # Strip out HTML (first regex removes any tag not allowed)
4334  # Allowed tags are:
4335  # * <sup> and <sub> (T10393)
4336  # * <i> (T28375)
4337  # * <b> (r105284)
4338  # * <bdi> (T74884)
4339  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4340  # * <s> and <strike> (T35715)
4341  # * <q> (T251672)
4342  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4343  # to allow setting directionality in toc items.
4344  $tocline = preg_replace(
4345  [
4346  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike|q)(?: [^>]*)?>).*?>#',
4347  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4348  ],
4349  [ '', '<$1>' ],
4350  $safeHeadline
4351  );
4352 
4353  # Strip '<span></span>', which is the result from the above if
4354  # <span id="foo"></span> is used to produce an additional anchor
4355  # for a section.
4356  $tocline = str_replace( '<span></span>', '', $tocline );
4357 
4358  $tocline = trim( $tocline );
4359 
4360  # For the anchor, strip out HTML-y stuff period
4361  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4362  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4363 
4364  # Save headline for section edit hint before it's escaped
4365  $headlineHint = $safeHeadline;
4366 
4367  # Decode HTML entities
4368  $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4369 
4370  $safeHeadline = self::normalizeSectionName( $safeHeadline );
4371 
4372  $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4373  $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4374  $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4375  if ( $fallbackHeadline === $safeHeadline ) {
4376  # No reason to have both (in fact, we can't)
4377  $fallbackHeadline = false;
4378  }
4379 
4380  # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4381  $arrayKey = strtolower( $safeHeadline );
4382  if ( $fallbackHeadline === false ) {
4383  $fallbackArrayKey = false;
4384  } else {
4385  $fallbackArrayKey = strtolower( $fallbackHeadline );
4386  }
4387 
4388  # Create the anchor for linking from the TOC to the section
4389  $anchor = $safeHeadline;
4390  $fallbackAnchor = $fallbackHeadline;
4391  if ( isset( $refers[$arrayKey] ) ) {
4392  for ( $i = 2; isset( $refers["{$arrayKey}_$i"] ); ++$i );
4393  $anchor .= "_$i";
4394  $linkAnchor .= "_$i";
4395  $refers["{$arrayKey}_$i"] = true;
4396  } else {
4397  $refers[$arrayKey] = true;
4398  }
4399  if ( $fallbackHeadline !== false && isset( $refers[$fallbackArrayKey] ) ) {
4400  for ( $i = 2; isset( $refers["{$fallbackArrayKey}_$i"] ); ++$i );
4401  $fallbackAnchor .= "_$i";
4402  $refers["{$fallbackArrayKey}_$i"] = true;
4403  } else {
4404  $refers[$fallbackArrayKey] = true;
4405  }
4406 
4407  if ( $enoughToc && ( !isset( $maxTocLevel ) || $toclevel < $maxTocLevel ) ) {
4408  $toc .= Linker::tocLine(
4409  $linkAnchor,
4410  $tocline,
4411  $numbering,
4412  $toclevel,
4413  ( $isTemplate ? false : $sectionIndex )
4414  );
4415  }
4416 
4417  # Add the section to the section tree
4418  # Find the DOM node for this header
4419  $noOffset = ( $isTemplate || $sectionIndex === false );
4420  while ( $node && !$noOffset ) {
4421  if ( $node->getName() === 'h' ) {
4422  $bits = $node->splitHeading();
4423  if ( $bits['i'] == $sectionIndex ) {
4424  break;
4425  }
4426  }
4427  $byteOffset += mb_strlen(
4428  $this->mStripState->unstripBoth(
4429  $frame->expand( $node, PPFrame::RECOVER_ORIG )
4430  )
4431  );
4432  $node = $node->getNextSibling();
4433  }
4434  $tocraw[] = [
4435  'toclevel' => $toclevel,
4436  // cast $level to string in order to keep b/c for the parse api
4437  'level' => (string)$level,
4438  'line' => $tocline,
4439  'number' => $numbering,
4440  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4441  'fromtitle' => $titleText,
4442  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4443  'anchor' => $anchor,
4444  'linkAnchor' => $linkAnchor,
4445  ];
4446 
4447  # give headline the correct <h#> tag
4448  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4449  // Output edit section links as markers with styles that can be customized by skins
4450  if ( $isTemplate ) {
4451  # Put a T flag in the section identifier, to indicate to extractSections()
4452  # that sections inside <includeonly> should be counted.
4453  $editsectionPage = $titleText;
4454  $editsectionSection = "T-$sectionIndex";
4455  } else {
4456  $editsectionPage = $this->getTitle()->getPrefixedText();
4457  $editsectionSection = $sectionIndex;
4458  }
4459  $editsectionContent = $headlineHint;
4460  // We use a bit of pesudo-xml for editsection markers. The
4461  // language converter is run later on. Using a UNIQ style marker
4462  // leads to the converter screwing up the tokens when it
4463  // converts stuff. And trying to insert strip tags fails too. At
4464  // this point all real inputted tags have already been escaped,
4465  // so we don't have to worry about a user trying to input one of
4466  // these markers directly. We use a page and section attribute
4467  // to stop the language converter from converting these
4468  // important bits of data, but put the headline hint inside a
4469  // content block because the language converter is supposed to
4470  // be able to convert that piece of data.
4471  // Gets replaced with html in ParserOutput::getText
4472  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage, ENT_COMPAT );
4473  // @phan-suppress-next-line SecurityCheck-DoubleEscaped
4474  $editlink .= '" section="' . htmlspecialchars( $editsectionSection, ENT_COMPAT ) . '"';
4475  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4476  } else {
4477  $editlink = '';
4478  }
4479  $head[$headlineCount] = Linker::makeHeadline(
4480  $level,
4481  $matches['attrib'][$headlineCount],
4482  $anchor,
4483  $headline,
4484  $editlink,
4485  $fallbackAnchor
4486  );
4487 
4488  $headlineCount++;
4489  }
4490 
4491  $this->setOutputType( $oldType );
4492 
4493  # Never ever show TOC if no headers (or suppressed)
4494  $suppressToc = $this->mOptions->getSuppressTOC();
4495  if ( $numVisible < 1 || $suppressToc ) {
4496  $enoughToc = false;
4497  }
4498 
4499  if ( $enoughToc ) {
4500  if ( $prevtoclevel > 0 && $prevtoclevel < $maxTocLevel ) {
4501  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4502  }
4503  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4504  $this->mOutput->setTOCHTML( $toc );
4505  // Record the fact that the TOC should be shown. T294950
4506  // (We shouldn't be looking at ::getTOCHTML() for this because
4507  // eventually that will be replaced (T293513) and
4508  // ::getSections() will contain sections even if there aren't
4509  // $enoughToc to show.)
4510  $this->mOutput->setOutputFlag( ParserOutputFlags::SHOW_TOC );
4511  }
4512 
4513  if ( $isMain && !$suppressToc ) {
4514  // We generally output the section information via the API
4515  // even if there isn't "enough" of a ToC to merit showing
4516  // it -- but the "suppress TOC" parser option is set when
4517  // any sections that might be found aren't "really there"
4518  // (ie, JavaScript content that might have spurious === or
4519  // <h2>: T307691) so we will *not* set section information
4520  // in that case.
4521  $this->mOutput->setSections( $tocraw );
4522  }
4523 
4524  # split up and insert constructed headlines
4525  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4526  $i = 0;
4527 
4528  // build an array of document sections
4529  $sections = [];
4530  foreach ( $blocks as $block ) {
4531  // $head is zero-based, sections aren't.
4532  if ( empty( $head[$i - 1] ) ) {
4533  $sections[$i] = $block;
4534  } else {
4535  $sections[$i] = $head[$i - 1] . $block;
4536  }
4537 
4548  $this->hookRunner->onParserSectionCreate( $this, $i, $sections[$i], $maybeShowEditLink );
4549 
4550  $i++;
4551  }
4552 
4553  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4554  // append the TOC at the beginning
4555  // Top anchor now in skin
4556  // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset At least one element when enoughToc is true
4557  $sections[0] .= self::TOC_PLACEHOLDER . "\n";
4558  }
4559 
4560  $full .= implode( '', $sections );
4561 
4562  return $full;
4563  }
4564 
4577  public function preSaveTransform(
4578  $text,
4579  PageReference $page,
4580  UserIdentity $user,
4581  ParserOptions $options,
4582  $clearState = true
4583  ) {
4584  if ( $clearState ) {
4585  $magicScopeVariable = $this->lock();
4586  }
4587  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive
4588  $this->startParse( $page, $options, self::OT_WIKI, $clearState );
4589  $this->setUser( $user );
4590 
4591  // Strip U+0000 NULL (T159174)
4592  $text = str_replace( "\000", '', $text );
4593 
4594  // We still normalize line endings (including trimming trailing whitespace) for
4595  // backwards-compatibility with other code that just calls PST, but this should already
4596  // be handled in TextContent subclasses
4597  $text = TextContent::normalizeLineEndings( $text );
4598 
4599  if ( $options->getPreSaveTransform() ) {
4600  $text = $this->pstPass2( $text, $user );
4601  }
4602  $text = $this->mStripState->unstripBoth( $text );
4603 
4604  // Trim trailing whitespace again, because the previous steps can introduce it.
4605  $text = rtrim( $text );
4606 
4607  $this->hookRunner->onParserPreSaveTransformComplete( $this, $text );
4608 
4609  $this->setUser( null ); # Reset
4610 
4611  return $text;
4612  }
4613 
4622  private function pstPass2( $text, UserIdentity $user ) {
4623  # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
4624  # $this->contLang here in order to give everyone the same signature and use the default one
4625  # rather than the one selected in each user's preferences. (see also T14815)
4626  $ts = $this->mOptions->getTimestamp();
4627  $timestamp = MWTimestamp::getLocalInstance( $ts );
4628  $ts = $timestamp->format( 'YmdHis' );
4629  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4630 
4631  $d = $this->contLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4632 
4633  # Variable replacement
4634  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4635  $text = $this->replaceVariables( $text );
4636 
4637  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4638  # which may corrupt this parser instance via its wfMessage()->text() call-
4639 
4640  # Signatures
4641  if ( strpos( $text, '~~~' ) !== false ) {
4642  $sigText = $this->getUserSig( $user );
4643  $text = strtr( $text, [
4644  '~~~~~' => $d,
4645  '~~~~' => "$sigText $d",
4646  '~~~' => $sigText
4647  ] );
4648  # The main two signature forms used above are time-sensitive
4649  $this->setOutputFlag( ParserOutputFlags::USER_SIGNATURE, 'User signature detected' );
4650  }
4651 
4652  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4653  $tc = '[' . Title::legalChars() . ']';
4654  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4655 
4656  // [[ns:page (context)|]]
4657  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4658  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4659  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4660  // [[ns:page (context), context|]] (using single, double-width or Arabic comma)
4661  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,|، )$tc+|)\\|]]/";
4662  // [[|page]] (reverse pipe trick: add context from page title)
4663  $p2 = "/\[\[\\|($tc+)]]/";
4664 
4665  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4666  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4667  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4668  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4669 
4670  $t = $this->getTitle()->getText();
4671  $m = [];
4672  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4673  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4674  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4675  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4676  } else {
4677  # if there's no context, don't bother duplicating the title
4678  $text = preg_replace( $p2, '[[\\1]]', $text );
4679  }
4680 
4681  return $text;
4682  }
4683 
4699  public function getUserSig( UserIdentity $user, $nickname = false, $fancySig = null ) {
4700  $username = $user->getName();
4701 
4702  # If not given, retrieve from the user object.
4703  if ( $nickname === false ) {
4704  $nickname = $this->userOptionsLookup->getOption( $user, 'nickname' );
4705  }
4706 
4707  if ( $fancySig === null ) {
4708  $fancySig = $this->userOptionsLookup->getBoolOption( $user, 'fancysig' );
4709  }
4710 
4711  if ( $nickname === null || $nickname === '' ) {
4712  // Empty value results in the default signature (even when fancysig is enabled)
4713  $nickname = $username;
4714  } elseif ( mb_strlen( $nickname ) > $this->svcOptions->get( MainConfigNames::MaxSigChars ) ) {
4715  $nickname = $username;
4716  $this->logger->debug( __METHOD__ . ": $username has overlong signature." );
4717  } elseif ( $fancySig !== false ) {
4718  # Sig. might contain markup; validate this
4719  $isValid = $this->validateSig( $nickname ) !== false;
4720 
4721  # New validator
4722  $sigValidation = $this->svcOptions->get( MainConfigNames::SignatureValidation );
4723  if ( $isValid && $sigValidation === 'disallow' ) {
4724  $parserOpts = new ParserOptions(
4725  $this->mOptions->getUserIdentity(),
4726  $this->contLang
4727  );
4728  $validator = $this->signatureValidatorFactory
4729  ->newSignatureValidator( $user, null, $parserOpts );
4730  $isValid = !$validator->validateSignature( $nickname );
4731  }
4732 
4733  if ( $isValid ) {
4734  # Validated; clean up (if needed) and return it
4735  return $this->cleanSig( $nickname, true );
4736  } else {
4737  # Failed to validate; fall back to the default
4738  $nickname = $username;
4739  $this->logger->debug( __METHOD__ . ": $username has invalid signature." );
4740  }
4741  }
4742 
4743  # Make sure nickname doesnt get a sig in a sig
4744  $nickname = self::cleanSigInSig( $nickname );
4745 
4746  # If we're still here, make it a link to the user page
4747  $userText = wfEscapeWikiText( $username );
4748  $nickText = wfEscapeWikiText( $nickname );
4749  if ( $this->userNameUtils->isTemp( $username ) ) {
4750  $msgName = 'signature-temp';
4751  } elseif ( $user->isRegistered() ) {
4752  $msgName = 'signature';
4753  } else {
4754  $msgName = 'signature-anon';
4755  }
4756 
4757  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4758  ->page( $this->getPage() )->text();
4759  }
4760 
4768  public function validateSig( $text ) {
4769  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4770  }
4771 
4783  public function cleanSig( $text, $parsing = false ) {
4784  if ( !$parsing ) {
4785  global $wgTitle;
4786  $magicScopeVariable = $this->lock();
4787  $this->startParse(
4788  $wgTitle,
4791  true
4792  );
4793  }
4794 
4795  # Option to disable this feature
4796  if ( !$this->mOptions->getCleanSignatures() ) {
4797  return $text;
4798  }
4799 
4800  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4801  # => Move this logic to braceSubstitution()
4802  $substWord = $this->magicWordFactory->get( 'subst' );
4803  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4804  $substText = '{{' . $substWord->getSynonym( 0 );
4805 
4806  $text = preg_replace( $substRegex, $substText, $text );
4807  $text = self::cleanSigInSig( $text );
4808  $dom = $this->preprocessToDom( $text );
4809  $frame = $this->getPreprocessor()->newFrame();
4810  $text = $frame->expand( $dom );
4811 
4812  if ( !$parsing ) {
4813  $text = $this->mStripState->unstripBoth( $text );
4814  }
4815 
4816  return $text;
4817  }
4818 
4826  public static function cleanSigInSig( $text ) {
4827  $text = preg_replace( '/~{3,5}/', '', $text );
4828  return $text;
4829  }
4830 
4847  public static function replaceTableOfContentsMarker( $text, $toc ) {
4848  return preg_replace_callback(
4849  self::TOC_PLACEHOLDER_REGEX,
4850  static function ( array $matches ) use( $toc ) {
4851  return $toc; // Ensure $1 \1 etc are safe to use in $toc
4852  },
4853  // For backwards compatibility during transition period,
4854  // also replace "old" TOC_PLACEHOLDER value
4855  str_replace( '<mw:tocplace></mw:tocplace>', $toc, $text )
4856  );
4857  }
4858 
4870  public function startExternalParse( ?PageReference $page, ParserOptions $options,
4871  $outputType, $clearState = true, $revId = null
4872  ) {
4873  $this->startParse( $page, $options, $outputType, $clearState );
4874  if ( $revId !== null ) {
4875  $this->mRevisionId = $revId;
4876  }
4877  }
4878 
4885  private function startParse( ?PageReference $page, ParserOptions $options,
4886  $outputType, $clearState = true
4887  ) {
4888  $this->setPage( $page );
4889  $this->mOptions = $options;
4890  $this->setOutputType( $outputType );
4891  if ( $clearState ) {
4892  $this->clearState();
4893  }
4894  }
4895 
4905  public function transformMsg( $text, ParserOptions $options, ?PageReference $page = null ) {
4906  static $executing = false;
4907 
4908  # Guard against infinite recursion
4909  if ( $executing ) {
4910  return $text;
4911  }
4912  $executing = true;
4913 
4914  if ( !$page ) {
4915  global $wgTitle;
4916  $page = $wgTitle;
4917  }
4918 
4919  $text = $this->preprocess( $text, $page, $options );
4920 
4921  $executing = false;
4922  return $text;
4923  }
4924 
4948  public function setHook( $tag, callable $callback ) {
4949  $tag = strtolower( $tag );
4950  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4951  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4952  }
4953  $oldVal = $this->mTagHooks[$tag] ?? null;
4954  $this->mTagHooks[$tag] = $callback;
4955  if ( !in_array( $tag, $this->mStripList ) ) {
4956  $this->mStripList[] = $tag;
4957  }
4958 
4959  return $oldVal;
4960  }
4961 
4966  public function clearTagHooks() {
4967  $this->mTagHooks = [];
4968  $this->mStripList = [];
4969  }
4970 
5015  public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
5016  $oldVal = $this->mFunctionHooks[$id][0] ?? null;
5017  $this->mFunctionHooks[$id] = [ $callback, $flags ];
5018 
5019  # Add to function cache
5020  $mw = $this->magicWordFactory->get( $id );
5021  if ( !$mw ) {
5022  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
5023  }
5024 
5025  $synonyms = $mw->getSynonyms();
5026  $sensitive = intval( $mw->isCaseSensitive() );
5027 
5028  foreach ( $synonyms as $syn ) {
5029  # Case
5030  if ( !$sensitive ) {
5031  $syn = $this->contLang->lc( $syn );
5032  }
5033  # Add leading hash
5034  if ( !( $flags & self::SFH_NO_HASH ) ) {
5035  $syn = '#' . $syn;
5036  }
5037  # Remove trailing colon
5038  if ( substr( $syn, -1, 1 ) === ':' ) {
5039  $syn = substr( $syn, 0, -1 );
5040  }
5041  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
5042  }
5043  return $oldVal;
5044  }
5045 
5052  public function getFunctionHooks() {
5053  return array_keys( $this->mFunctionHooks );
5054  }
5055 
5064  public function replaceLinkHolders( &$text, $options = 0 ) {
5065  $this->replaceLinkHoldersPrivate( $text, $options );
5066  }
5067 
5075  private function replaceLinkHoldersPrivate( &$text, $options = 0 ) {
5076  $this->mLinkHolders->replace( $text );
5077  }
5078 
5086  private function replaceLinkHoldersText( $text ) {
5087  return $this->mLinkHolders->replaceText( $text );
5088  }
5089 
5104  public function renderImageGallery( $text, array $params ) {
5105  $mode = false;
5106  if ( isset( $params['mode'] ) ) {
5107  $mode = $params['mode'];
5108  }
5109 
5110  try {
5111  $ig = ImageGalleryBase::factory( $mode );
5112  } catch ( ImageGalleryClassNotFoundException $e ) {
5113  // If invalid type set, fallback to default.
5114  $ig = ImageGalleryBase::factory( false );
5115  }
5116 
5117  $ig->setContextTitle( $this->getTitle() );
5118  $ig->setShowBytes( false );
5119  $ig->setShowDimensions( false );
5120  $ig->setShowFilename( false );
5121  $ig->setParser( $this );
5122  $ig->setHideBadImages();
5123  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
5124 
5125  if ( isset( $params['showfilename'] ) ) {
5126  $ig->setShowFilename( true );
5127  } else {
5128  $ig->setShowFilename( false );
5129  }
5130  if ( isset( $params['caption'] ) ) {
5131  // NOTE: We aren't passing a frame here or below. Frame info
5132  // is currently opaque to Parsoid, which acts on OT_PREPROCESS.
5133  // See T107332#4030581
5134  $caption = $this->recursiveTagParse( $params['caption'] );
5135  $ig->setCaptionHtml( $caption );
5136  }
5137  if ( isset( $params['perrow'] ) ) {
5138  $ig->setPerRow( $params['perrow'] );
5139  }
5140  if ( isset( $params['widths'] ) ) {
5141  $ig->setWidths( $params['widths'] );
5142  }
5143  if ( isset( $params['heights'] ) ) {
5144  $ig->setHeights( $params['heights'] );
5145  }
5146  $ig->setAdditionalOptions( $params );
5147 
5148  $this->hookRunner->onBeforeParserrenderImageGallery( $this, $ig );
5149 
5150  $lines = StringUtils::explode( "\n", $text );
5151  foreach ( $lines as $line ) {
5152  # match lines like these:
5153  # Image:someimage.jpg|This is some image
5154  $matches = [];
5155  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5156  # Skip empty lines
5157  if ( count( $matches ) == 0 ) {
5158  continue;
5159  }
5160 
5161  if ( strpos( $matches[0], '%' ) !== false ) {
5162  $matches[1] = rawurldecode( $matches[1] );
5163  }
5165  if ( $title === null ) {
5166  # Bogus title. Ignore these so we don't bomb out later.
5167  continue;
5168  }
5169 
5170  # We need to get what handler the file uses, to figure out parameters.
5171  # Note, a hook can override the file name, and chose an entirely different
5172  # file (which potentially could be of a different type and have different handler).
5173  $options = [];
5174  $descQuery = false;
5175  $this->hookRunner->onBeforeParserFetchFileAndTitle(
5176  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
5177  $this, $title, $options, $descQuery
5178  );
5179  # Don't register it now, as TraditionalImageGallery does that later.
5180  $file = $this->fetchFileNoRegister( $title, $options );
5181  $handler = $file ? $file->getHandler() : false;
5182 
5183  $paramMap = [
5184  'img_alt' => 'gallery-internal-alt',
5185  'img_link' => 'gallery-internal-link',
5186  ];
5187  if ( $handler ) {
5188  $paramMap += $handler->getParamMap();
5189  // We don't want people to specify per-image widths.
5190  // Additionally the width parameter would need special casing anyhow.
5191  unset( $paramMap['img_width'] );
5192  }
5193 
5194  $mwArray = $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5195 
5196  $label = '';
5197  $alt = '';
5198  $handlerOptions = [];
5199  $imageOptions = [];
5200  $hasAlt = false;
5201 
5202  if ( isset( $matches[3] ) ) {
5203  // look for an |alt= definition while trying not to break existing
5204  // captions with multiple pipes (|) in it, until a more sensible grammar
5205  // is defined for images in galleries
5206 
5207  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5208  // splitting on '|' is a bit odd, and different from makeImage.
5209  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5210  // Protect LanguageConverter markup
5211  $parameterMatches = StringUtils::delimiterExplode(
5212  '-{', '}-',
5213  '|',
5214  $matches[3],
5215  true /* nested */
5216  );
5217 
5218  foreach ( $parameterMatches as $parameterMatch ) {
5219  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5220  if ( !$magicName ) {
5221  // Last pipe wins.
5222  $label = $parameterMatch;
5223  continue;
5224  }
5225 
5226  $paramName = $paramMap[$magicName];
5227  switch ( $paramName ) {
5228  case 'gallery-internal-alt':
5229  $hasAlt = true;
5230  $alt = $this->stripAltText( $match, false );
5231  break;
5232  case 'gallery-internal-link':
5233  $linkValue = $this->stripAltText( $match, false );
5234  if ( preg_match( '/^-{R\|(.*)}-$/', $linkValue ) ) {
5235  // Result of LanguageConverter::markNoConversion
5236  // invoked on an external link.
5237  $linkValue = substr( $linkValue, 4, -2 );
5238  }
5239  list( $type, $target ) = $this->parseLinkParameter( $linkValue );
5240  if ( $type ) {
5241  if ( $type === 'no-link' ) {
5242  $target = true;
5243  }
5244  $imageOptions[$type] = $target;
5245  }
5246  break;
5247  default:
5248  // Must be a handler specific parameter.
5249  if ( $handler->validateParam( $paramName, $match ) ) {
5250  $handlerOptions[$paramName] = $match;
5251  } else {
5252  // Guess not, consider it as caption.
5253  $this->logger->debug(
5254  "$parameterMatch failed parameter validation" );
5255  $label = $parameterMatch;
5256  }
5257  }
5258  }
5259  }
5260 
5261  // Match makeImage when !$hasVisibleCaption
5262  if ( !$hasAlt ) {
5263  if ( $label !== '' ) {
5264  $alt = $this->stripAltText( $label, false );
5265  } else {
5266  $alt = $title->getText();
5267  }
5268  }
5269  $imageOptions['title'] = $this->stripAltText( $label, false );
5270 
5271  $ig->add(
5272  $title, $label, $alt, '', $handlerOptions,
5273  ImageGalleryBase::LOADING_DEFAULT, $imageOptions
5274  );
5275  }
5276  $html = $ig->toHTML();
5277  $this->hookRunner->onAfterParserFetchFileAndTitle( $this, $ig, $html );
5278  return $html;
5279  }
5280 
5285  private function getImageParams( $handler ) {
5286  if ( $handler ) {
5287  $handlerClass = get_class( $handler );
5288  } else {
5289  $handlerClass = '';
5290  }
5291  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5292  # Initialise static lists
5293  static $internalParamNames = [
5294  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5295  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5296  'bottom', 'text-bottom' ],
5297  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5298  'upright', 'border', 'link', 'alt', 'class' ],
5299  ];
5300  static $internalParamMap;
5301  if ( !$internalParamMap ) {
5302  $internalParamMap = [];
5303  foreach ( $internalParamNames as $type => $names ) {
5304  foreach ( $names as $name ) {
5305  // For grep: img_left, img_right, img_center, img_none,
5306  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5307  // img_bottom, img_text_bottom,
5308  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5309  // img_border, img_link, img_alt, img_class
5310  $magicName = str_replace( '-', '_', "img_$name" );
5311  $internalParamMap[$magicName] = [ $type, $name ];
5312  }
5313  }
5314  }
5315 
5316  # Add handler params
5317  $paramMap = $internalParamMap;
5318  if ( $handler ) {
5319  $handlerParamMap = $handler->getParamMap();
5320  foreach ( $handlerParamMap as $magic => $paramName ) {
5321  $paramMap[$magic] = [ 'handler', $paramName ];
5322  }
5323  } else {
5324  // Parse the size for non-existent files. See T273013
5325  $paramMap[ 'img_width' ] = [ 'handler', 'width' ];
5326  }
5327  $this->mImageParams[$handlerClass] = $paramMap;
5328  $this->mImageParamsMagicArray[$handlerClass] =
5329  $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5330  }
5331  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5332  }
5333 
5343  public function makeImage( LinkTarget $link, $options, $holders = false ) {
5344  # Check if the options text is of the form "options|alt text"
5345  # Options are:
5346  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5347  # * left no resizing, just left align. label is used for alt= only
5348  # * right same, but right aligned
5349  # * none same, but not aligned
5350  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5351  # * center center the image
5352  # * framed Keep original image size, no magnify-button.
5353  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5354  # * upright reduce width for upright images, rounded to full __0 px
5355  # * border draw a 1px border around the image
5356  # * alt Text for HTML alt attribute (defaults to empty)
5357  # * class Set a class for img node
5358  # * link Set the target of the image link. Can be external, interwiki, or local
5359  # vertical-align values (no % or length right now):
5360  # * baseline
5361  # * sub
5362  # * super
5363  # * top
5364  # * text-top
5365  # * middle
5366  # * bottom
5367  # * text-bottom
5368 
5369  # Protect LanguageConverter markup when splitting into parts
5371  '-{', '}-', '|', $options, true /* allow nesting */
5372  );
5373 
5374  # Give extensions a chance to select the file revision for us
5375  $options = [];
5376  $descQuery = false;
5377  $title = Title::castFromLinkTarget( $link ); // hook signature compat
5378  $this->hookRunner->onBeforeParserFetchFileAndTitle(
5379  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
5380  $this, $title, $options, $descQuery
5381  );
5382  # Fetch and register the file (file title may be different via hooks)
5383  list( $file, $link ) = $this->fetchFileAndTitle( $link, $options );
5384 
5385  # Get parameter map
5386  $handler = $file ? $file->getHandler() : false;
5387 
5388  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5389 
5390  if ( !$file ) {
5391  $this->addTrackingCategory( 'broken-file-category' );
5392  }
5393 
5394  # Process the input parameters
5395  $caption = '';
5396  $params = [ 'frame' => [], 'handler' => [],
5397  'horizAlign' => [], 'vertAlign' => [] ];
5398  $seenformat = false;
5399  foreach ( $parts as $part ) {
5400  $part = trim( $part );
5401  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5402  $validated = false;
5403  if ( isset( $paramMap[$magicName] ) ) {
5404  list( $type, $paramName ) = $paramMap[$magicName];
5405 
5406  # Special case; width and height come in one variable together
5407  if ( $type === 'handler' && $paramName === 'width' ) {
5408  $parsedWidthParam = self::parseWidthParam( $value );
5409  // Parsoid applies data-(width|height) attributes to broken
5410  // media spans, for client use. See T273013
5411  $validateFunc = static function ( $name, $value ) use ( $handler ) {
5412  return $handler
5413  ? $handler->validateParam( $name, $value )
5414  : $value > 0;
5415  };
5416  if ( isset( $parsedWidthParam['width'] ) ) {
5417  $width = $parsedWidthParam['width'];
5418  if ( $validateFunc( 'width', $width ) ) {
5419  $params[$type]['width'] = $width;
5420  $validated = true;
5421  }
5422  }
5423  if ( isset( $parsedWidthParam['height'] ) ) {
5424  $height = $parsedWidthParam['height'];
5425  if ( $validateFunc( 'height', $height ) ) {
5426  $params[$type]['height'] = $height;
5427  $validated = true;
5428  }
5429  }
5430  # else no validation -- T15436
5431  } else {
5432  if ( $type === 'handler' ) {
5433  # Validate handler parameter
5434  $validated = $handler->validateParam( $paramName, $value );
5435  } else {
5436  # Validate internal parameters
5437  switch ( $paramName ) {
5438  case 'alt':
5439  case 'class':
5440  $validated = true;
5441  $value = $this->stripAltText( $value, $holders );
5442  break;
5443  case 'link':
5444  list( $paramName, $value ) =
5445  $this->parseLinkParameter(
5446  $this->stripAltText( $value, $holders )
5447  );
5448  if ( $paramName ) {
5449  $validated = true;
5450  if ( $paramName === 'no-link' ) {
5451  $value = true;
5452  }
5453  }
5454  break;
5455  case 'manualthumb':
5456  # @todo FIXME: Possibly check validity here for
5457  # manualthumb? downstream behavior seems odd with
5458  # missing manual thumbs.
5459  $value = $this->stripAltText( $value, $holders );
5460  // fall through
5461  case 'frameless':
5462  case 'framed':
5463  case 'thumbnail':
5464  // use first appearing option, discard others.
5465  $validated = !$seenformat;
5466  $seenformat = true;
5467  break;
5468  default:
5469  # Most other things appear to be empty or numeric...
5470  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5471  }
5472  }
5473 
5474  if ( $validated ) {
5475  $params[$type][$paramName] = $value;
5476  }
5477  }
5478  }
5479  if ( !$validated ) {
5480  $caption = $part;
5481  }
5482  }
5483 
5484  # Process alignment parameters
5485  if ( $params['horizAlign'] !== [] ) {
5486  $params['frame']['align'] = key( $params['horizAlign'] );
5487  }
5488  if ( $params['vertAlign'] !== [] ) {
5489  $params['frame']['valign'] = key( $params['vertAlign'] );
5490  }
5491 
5492  $params['frame']['caption'] = $caption;
5493 
5494  # Will the image be presented in a frame, with the caption below?
5495  // @phan-suppress-next-line PhanImpossibleCondition
5496  $hasVisibleCaption = isset( $params['frame']['framed'] )
5497  // @phan-suppress-next-line PhanImpossibleCondition
5498  || isset( $params['frame']['thumbnail'] )
5499  // @phan-suppress-next-line PhanImpossibleCondition
5500  || isset( $params['frame']['manualthumb'] );
5501 
5502  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5503  # came to also set the caption, ordinary text after the image -- which
5504  # makes no sense, because that just repeats the text multiple times in
5505  # screen readers. It *also* came to set the title attribute.
5506  # Now that we have an alt attribute, we should not set the alt text to
5507  # equal the caption: that's worse than useless, it just repeats the
5508  # text. This is the framed/thumbnail case. If there's no caption, we
5509  # use the unnamed parameter for alt text as well, just for the time be-
5510  # ing, if the unnamed param is set and the alt param is not.
5511  # For the future, we need to figure out if we want to tweak this more,
5512  # e.g., introducing a title= parameter for the title; ignoring the un-
5513  # named parameter entirely for images without a caption; adding an ex-
5514  # plicit caption= parameter and preserving the old magic unnamed para-
5515  # meter for BC; ...
5516  if ( $hasVisibleCaption ) {
5517  // @phan-suppress-next-line PhanImpossibleCondition
5518  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5519  # No caption or alt text, add the filename as the alt text so
5520  # that screen readers at least get some description of the image
5521  $params['frame']['alt'] = $link->getText();
5522  }
5523  # Do not set $params['frame']['title'] because tooltips are unnecessary
5524  # for framed images, the caption is visible
5525  } else {
5526  // @phan-suppress-next-line PhanImpossibleCondition
5527  if ( !isset( $params['frame']['alt'] ) ) {
5528  # No alt text, use the "caption" for the alt text
5529  if ( $caption !== '' ) {
5530  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5531  } else {
5532  # No caption, fall back to using the filename for the
5533  # alt text
5534  $params['frame']['alt'] = $link->getText();
5535  }
5536  }
5537  # Use the "caption" for the tooltip text
5538  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5539  }
5540  $params['handler']['targetlang'] = $this->getTargetLanguage()->getCode();
5541 
5542  // hook signature compat again, $link may have changed
5543  $title = Title::castFromLinkTarget( $link );
5544  $this->hookRunner->onParserMakeImageParams( $title, $file, $params, $this );
5545 
5546  # Linker does the rest
5547  $time = $options['time'] ?? false;
5548  $ret = Linker::makeImageLink( $this, $link, $file, $params['frame'], $params['handler'],
5549  $time, $descQuery, $this->mOptions->getThumbSize() );
5550 
5551  # Give the handler a chance to modify the parser object
5552  if ( $handler ) {
5553  $handler->parserTransformHook( $this, $file );
5554  }
5555  if ( $file ) {
5556  $this->modifyImageHtml( $file, $params, $ret );
5557  }
5558 
5559  return $ret;
5560  }
5561 
5580  private function parseLinkParameter( $value ) {
5581  $chars = self::EXT_LINK_URL_CLASS;
5582  $addr = self::EXT_LINK_ADDR;
5583  $prots = $this->urlUtils->validProtocols();
5584  $type = null;
5585  $target = false;
5586  if ( $value === '' ) {
5587  $type = 'no-link';
5588  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5589  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5590  $this->mOutput->addExternalLink( $value );
5591  $type = 'link-url';
5592  $target = $value;
5593  }
5594  } else {
5595  // Percent-decode link arguments for consistency with wikilink
5596  // handling (T216003#7836261).
5597  //
5598  // There's slight concern here though. The |link= option supports
5599  // two formats, link=Test%22test vs link=[[Test%22test]], both of
5600  // which are about to be decoded.
5601  //
5602  // In the former case, the decoding here is straightforward and
5603  // desirable.
5604  //
5605  // In the latter case, there's a potential for double decoding,
5606  // because the wikilink syntax has a higher precedence and has
5607  // already been parsed as a link before we get here. $value
5608  // has had stripAltText() called on it, which in turn calls
5609  // replaceLinkHoldersText() on the link. So, the text we're
5610  // getting at this point has already been percent decoded.
5611  //
5612  // The problematic case is if %25 is in the title, since that
5613  // decodes to %, which could combine with trailing characters.
5614  // However, % is not a valid link title character, so it would
5615  // not parse as a link and the string we received here would
5616  // still contain the encoded %25.
5617  //
5618  // Hence, double decoded is not an issue. See the test,
5619  // "Should not double decode the link option"
5620  if ( strpos( $value, '%' ) !== false ) {
5621  $value = rawurldecode( $value );
5622  }
5623  $linkTitle = Title::newFromText( $value );
5624  if ( $linkTitle ) {
5625  $this->mOutput->addLink( $linkTitle );
5626  $type = 'link-title';
5627  $target = $linkTitle;
5628  }
5629  }
5630  return [ $type, $target ];
5631  }
5632 
5640  public function modifyImageHtml( File $file, array $params, string &$html ) {
5641  $this->hookRunner->onParserModifyImageHTML( $this, $file, $params, $html );
5642  }
5643 
5649  private function stripAltText( $caption, $holders ) {
5650  # Strip bad stuff out of the title (tooltip). We can't just use
5651  # replaceLinkHoldersText() here, because if this function is called
5652  # from handleInternalLinks2(), mLinkHolders won't be up-to-date.
5653  if ( $holders ) {
5654  $tooltip = $holders->replaceText( $caption );
5655  } else {
5656  $tooltip = $this->replaceLinkHoldersText( $caption );
5657  }
5658 
5659  # make sure there are no placeholders in thumbnail attributes
5660  # that are later expanded to html- so expand them now and
5661  # remove the tags
5662  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5663  # Compatibility hack! In HTML certain entity references not terminated
5664  # by a semicolon are decoded (but not if we're in an attribute; that's
5665  # how link URLs get away without properly escaping & in queries).
5666  # But wikitext has always required semicolon-termination of entities,
5667  # so encode & where needed to avoid decode of semicolon-less entities.
5668  # See T209236 and
5669  # https://www.w3.org/TR/html5/syntax.html#named-character-references
5670  # T210437 discusses moving this workaround to Sanitizer::stripAllTags.
5671  $tooltip = preg_replace( "/
5672  & # 1. entity prefix
5673  (?= # 2. followed by:
5674  (?: # a. one of the legacy semicolon-less named entities
5675  A(?:Elig|MP|acute|circ|grave|ring|tilde|uml)|
5676  C(?:OPY|cedil)|E(?:TH|acute|circ|grave|uml)|
5677  GT|I(?:acute|circ|grave|uml)|LT|Ntilde|
5678  O(?:acute|circ|grave|slash|tilde|uml)|QUOT|REG|THORN|
5679  U(?:acute|circ|grave|uml)|Yacute|
5680  a(?:acute|c(?:irc|ute)|elig|grave|mp|ring|tilde|uml)|brvbar|
5681  c(?:cedil|edil|urren)|cent(?!erdot;)|copy(?!sr;)|deg|
5682  divide(?!ontimes;)|e(?:acute|circ|grave|th|uml)|
5683  frac(?:1(?:2|4)|34)|
5684  gt(?!c(?:c|ir)|dot|lPar|quest|r(?:a(?:pprox|rr)|dot|eq(?:less|qless)|less|sim);)|
5685  i(?:acute|circ|excl|grave|quest|uml)|laquo|
5686  lt(?!c(?:c|ir)|dot|hree|imes|larr|quest|r(?:Par|i(?:e|f|));)|
5687  m(?:acr|i(?:cro|ddot))|n(?:bsp|tilde)|
5688  not(?!in(?:E|dot|v(?:a|b|c)|)|ni(?:v(?:a|b|c)|);)|
5689  o(?:acute|circ|grave|rd(?:f|m)|slash|tilde|uml)|
5690  p(?:lusmn|ound)|para(?!llel;)|quot|r(?:aquo|eg)|
5691  s(?:ect|hy|up(?:1|2|3)|zlig)|thorn|times(?!b(?:ar|)|d;)|
5692  u(?:acute|circ|grave|ml|uml)|y(?:acute|en|uml)
5693  )
5694  (?:[^;]|$)) # b. and not followed by a semicolon
5695  # S = study, for efficiency
5696  /Sx", '&amp;', $tooltip );
5697  $tooltip = Sanitizer::stripAllTags( $tooltip );
5698 
5699  return $tooltip;
5700  }
5701 
5711  public function attributeStripCallback( &$text, $frame = false ) {
5712  wfDeprecated( __METHOD__, '1.35' );
5713  $text = $this->replaceVariables( $text, $frame );
5714  $text = $this->mStripState->unstripBoth( $text );
5715  return $text;
5716  }
5717 
5724  public function getTags() {
5725  return array_keys( $this->mTagHooks );
5726  }
5727 
5732  public function getFunctionSynonyms() {
5733  return $this->mFunctionSynonyms;
5734  }
5735 
5740  public function getUrlProtocols() {
5741  return $this->urlUtils->validProtocols();
5742  }
5743 
5773  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5774  global $wgTitle; # not generally used but removes an ugly failure mode
5775 
5776  $magicScopeVariable = $this->lock();
5777  $this->startParse(
5778  $wgTitle,
5781  true
5782  );
5783  $outText = '';
5784  $frame = $this->getPreprocessor()->newFrame();
5785 
5786  # Process section extraction flags
5787  $flags = 0;
5788  $sectionParts = explode( '-', $sectionId );
5789  $sectionIndex = array_pop( $sectionParts );
5790  foreach ( $sectionParts as $part ) {
5791  if ( $part === 'T' ) {
5793  }
5794  }
5795 
5796  # Check for empty input
5797  if ( strval( $text ) === '' ) {
5798  # Only sections 0 and T-0 exist in an empty document
5799  if ( $sectionIndex == 0 ) {
5800  if ( $mode === 'get' ) {
5801  return '';
5802  }
5803 
5804  return $newText;
5805  } else {
5806  if ( $mode === 'get' ) {
5807  return $newText;
5808  }
5809 
5810  return $text;
5811  }
5812  }
5813 
5814  # Preprocess the text
5815  $root = $this->preprocessToDom( $text, $flags );
5816 
5817  # <h> nodes indicate section breaks
5818  # They can only occur at the top level, so we can find them by iterating the root's children
5819  $node = $root->getFirstChild();
5820 
5821  # Find the target section
5822  if ( $sectionIndex == 0 ) {
5823  # Section zero doesn't nest, level=big
5824  $targetLevel = 1000;
5825  } else {
5826  while ( $node ) {
5827  if ( $node->getName() === 'h' ) {
5828  $bits = $node->splitHeading();
5829  if ( $bits['i'] == $sectionIndex ) {
5830  $targetLevel = $bits['level'];
5831  break;
5832  }
5833  }
5834  if ( $mode === 'replace' ) {
5835  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5836  }
5837  $node = $node->getNextSibling();
5838  }
5839  }
5840 
5841  if ( !$node ) {
5842  # Not found
5843  if ( $mode === 'get' ) {
5844  return $newText;
5845  } else {
5846  return $text;
5847  }
5848  }
5849 
5850  # Find the end of the section, including nested sections
5851  do {
5852  if ( $node->getName() === 'h' ) {
5853  $bits = $node->splitHeading();
5854  $curLevel = $bits['level'];
5855  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable False positive
5856  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5857  break;
5858  }
5859  }
5860  if ( $mode === 'get' ) {
5861  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5862  }
5863  $node = $node->getNextSibling();
5864  } while ( $node );
5865 
5866  # Write out the remainder (in replace mode only)
5867  if ( $mode === 'replace' ) {
5868  # Output the replacement text
5869  # Add two newlines on -- trailing whitespace in $newText is conventionally
5870  # stripped by the editor, so we need both newlines to restore the paragraph gap
5871  # Only add trailing whitespace if there is newText
5872  if ( $newText != "" ) {
5873  $outText .= $newText . "\n\n";
5874  }
5875 
5876  while ( $node ) {
5877  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5878  $node = $node->getNextSibling();
5879  }
5880  }
5881 
5882  # Re-insert stripped tags
5883  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5884 
5885  return $outText;
5886  }
5887 
5903  public function getSection( $text, $sectionId, $defaultText = '' ) {
5904  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5905  }
5906 
5920  public function replaceSection( $oldText, $sectionId, $newText ) {
5921  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5922  }
5923 
5953  public function getFlatSectionInfo( $text ) {
5954  $magicScopeVariable = $this->lock();
5955  $this->startParse(
5956  null,
5959  true
5960  );
5961  $frame = $this->getPreprocessor()->newFrame();
5962  $root = $this->preprocessToDom( $text, 0 );
5963  $node = $root->getFirstChild();
5964  $offset = 0;
5965  $currentSection = [
5966  'index' => 0,
5967  'level' => 0,
5968  'offset' => 0,
5969  'heading' => '',
5970  'text' => ''
5971  ];
5972  $sections = [];
5973 
5974  while ( $node ) {
5975  $nodeText = $frame->expand( $node, PPFrame::RECOVER_ORIG );
5976  if ( $node->getName() === 'h' ) {
5977  $bits = $node->splitHeading();
5978  $sections[] = $currentSection;
5979  $currentSection = [
5980  'index' => $bits['i'],
5981  'level' => $bits['level'],
5982  'offset' => $offset,
5983  'heading' => $nodeText,
5984  'text' => $nodeText
5985  ];
5986  } else {
5987  $currentSection['text'] .= $nodeText;
5988  }
5989  $offset += strlen( $nodeText );
5990  $node = $node->getNextSibling();
5991  }
5992  $sections[] = $currentSection;
5993  return $sections;
5994  }
5995 
6007  public function getRevisionId() {
6008  return $this->mRevisionId;
6009  }
6010 
6017  public function getRevisionRecordObject() {
6018  if ( $this->mRevisionRecordObject ) {
6019  return $this->mRevisionRecordObject;
6020  }
6021 
6022  // NOTE: try to get the RevisionRecord object even if mRevisionId is null.
6023  // This is useful when parsing a revision that has not yet been saved.
6024  // However, if we get back a saved revision even though we are in
6025  // preview mode, we'll have to ignore it, see below.
6026  // NOTE: This callback may be used to inject an OLD revision that was
6027  // already loaded, so "current" is a bit of a misnomer. We can't just
6028  // skip it if mRevisionId is set.
6029  $rev = call_user_func(
6030  $this->mOptions->getCurrentRevisionRecordCallback(),
6031  $this->getTitle(),
6032  $this
6033  );
6034 
6035  if ( $rev === false ) {
6036  // The revision record callback returns `false` (not null) to
6037  // indicate that the revision is missing. (See for example
6038  // Parser::statelessFetchRevisionRecord(), the default callback.)
6039  // This API expects `null` instead. (T251952)
6040  $rev = null;
6041  }
6042 
6043  if ( $this->mRevisionId === null && $rev && $rev->getId() ) {
6044  // We are in preview mode (mRevisionId is null), and the current revision callback
6045  // returned an existing revision. Ignore it and return null, it's probably the page's
6046  // current revision, which is not what we want here. Note that we do want to call the
6047  // callback to allow the unsaved revision to be injected here, e.g. for
6048  // self-transclusion previews.
6049  return null;
6050  }
6051 
6052  // If the parse is for a new revision, then the callback should have
6053  // already been set to force the object and should match mRevisionId.
6054  // If not, try to fetch by mRevisionId instead.
6055  if ( $this->mRevisionId && $rev && $rev->getId() != $this->mRevisionId ) {
6056  $rev = MediaWikiServices::getInstance()
6057  ->getRevisionLookup()
6058  ->getRevisionById( $this->mRevisionId );
6059  }
6060 
6061  $this->mRevisionRecordObject = $rev;
6062 
6063  return $this->mRevisionRecordObject;
6064  }
6065 
6072  public function getRevisionTimestamp() {
6073  if ( $this->mRevisionTimestamp !== null ) {
6074  return $this->mRevisionTimestamp;
6075  }
6076 
6077  # Use specified revision timestamp, falling back to the current timestamp
6078  $revObject = $this->getRevisionRecordObject();
6079  $timestamp = $revObject && $revObject->getTimestamp()
6080  ? $revObject->getTimestamp()
6081  : $this->mOptions->getTimestamp();
6082  $this->mOutput->setRevisionTimestampUsed( $timestamp ); // unadjusted time zone
6083 
6084  # The cryptic '' timezone parameter tells to use the site-default
6085  # timezone offset instead of the user settings.
6086  # Since this value will be saved into the parser cache, served
6087  # to other users, and potentially even used inside links and such,
6088  # it needs to be consistent for all visitors.
6089  $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp, '' );
6090 
6091  return $this->mRevisionTimestamp;
6092  }
6093 
6100  public function getRevisionUser(): ?string {
6101  if ( $this->mRevisionUser === null ) {
6102  $revObject = $this->getRevisionRecordObject();
6103 
6104  # if this template is subst: the revision id will be blank,
6105  # so just use the current user's name
6106  if ( $revObject && $revObject->getUser() ) {
6107  $this->mRevisionUser = $revObject->getUser()->getName();
6108  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
6109  $this->mRevisionUser = $this->getUserIdentity()->getName();
6110  } else {
6111  # Note that we fall through here with
6112  # $this->mRevisionUser still null
6113  }
6114  }
6115  return $this->mRevisionUser;
6116  }
6117 
6124  public function getRevisionSize() {
6125  if ( $this->mRevisionSize === null ) {
6126  $revObject = $this->getRevisionRecordObject();
6127 
6128  # if this variable is subst: the revision id will be blank,
6129  # so just use the parser input size, because the own substitution
6130  # will change the size.
6131  if ( $revObject ) {
6132  $this->mRevisionSize = $revObject->getSize();
6133  } else {
6134  $this->mRevisionSize = $this->mInputSize;
6135  }
6136  }
6137  return $this->mRevisionSize;
6138  }
6139 
6148  public function setDefaultSort( $sort ) {
6149  wfDeprecated( __METHOD__, '1.38' );
6150  $this->mOutput->setPageProperty( 'defaultsort', $sort );
6151  }
6152 
6166  public function getDefaultSort() {
6167  wfDeprecated( __METHOD__, '1.38' );
6168  return $this->mOutput->getPageProperty( 'defaultsort' ) ?? '';
6169  }
6170 
6180  public function getCustomDefaultSort() {
6181  wfDeprecated( __METHOD__, '1.38' );
6182  return $this->mOutput->getPageProperty( 'defaultsort' ) ?? false;
6183  }
6184 
6185  private static function getSectionNameFromStrippedText( $text ) {
6187  $text = Sanitizer::decodeCharReferences( $text );
6188  $text = self::normalizeSectionName( $text );
6189  return $text;
6190  }
6191 
6192  private static function makeAnchor( $sectionName ) {
6193  return '#' . Sanitizer::escapeIdForLink( $sectionName );
6194  }
6195 
6196  private function makeLegacyAnchor( $sectionName ) {
6197  $fragmentMode = $this->svcOptions->get( MainConfigNames::FragmentMode );
6198  if ( isset( $fragmentMode[1] ) && $fragmentMode[1] === 'legacy' ) {
6199  // ForAttribute() and ForLink() are the same for legacy encoding
6201  } else {
6202  $id = Sanitizer::escapeIdForLink( $sectionName );
6203  }
6204 
6205  return "#$id";
6206  }
6207 
6217  public function guessSectionNameFromWikiText( $text ) {
6218  # Strip out wikitext links(they break the anchor)
6219  $text = $this->stripSectionName( $text );
6220  $sectionName = self::getSectionNameFromStrippedText( $text );
6221  return self::makeAnchor( $sectionName );
6222  }
6223 
6234  public function guessLegacySectionNameFromWikiText( $text ) {
6235  # Strip out wikitext links(they break the anchor)
6236  $text = $this->stripSectionName( $text );
6237  $sectionName = self::getSectionNameFromStrippedText( $text );
6238  return $this->makeLegacyAnchor( $sectionName );
6239  }
6240 
6247  public static function guessSectionNameFromStrippedText( $text ) {
6248  $sectionName = self::getSectionNameFromStrippedText( $text );
6249  return self::makeAnchor( $sectionName );
6250  }
6251 
6258  private static function normalizeSectionName( $text ) {
6259  # T90902: ensure the same normalization is applied for IDs as to links
6261  $titleParser = MediaWikiServices::getInstance()->getTitleParser();
6262  '@phan-var MediaWikiTitleCodec $titleParser';
6263  try {
6264 
6265  $parts = $titleParser->splitTitleString( "#$text" );
6266  } catch ( MalformedTitleException $ex ) {
6267  return $text;
6268  }
6269  return $parts['fragment'];
6270  }
6271 
6287  public function stripSectionName( $text ) {
6288  # Strip internal link markup
6289  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
6290  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
6291 
6292  # Strip external link markup
6293  # @todo FIXME: Not tolerant to blank link text
6294  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
6295  # on how many empty links there are on the page - need to figure that out.
6296  $text = preg_replace(
6297  '/\[(?i:' . $this->urlUtils->validProtocols() . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
6298 
6299  # Parse wikitext quotes (italics & bold)
6300  $text = $this->doQuotes( $text );
6301 
6302  # Strip HTML tags
6303  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
6304  return $text;
6305  }
6306 
6320  private function fuzzTestSrvus( $text, PageReference $page, ParserOptions $options,
6321  $outputType = self::OT_HTML
6322  ) {
6323  $magicScopeVariable = $this->lock();
6324  $this->startParse( $page, $options, $outputType, true );
6325 
6326  $text = $this->replaceVariables( $text );
6327  $text = $this->mStripState->unstripBoth( $text );
6328  $text = Sanitizer::internalRemoveHtmlTags( $text );
6329  return $text;
6330  }
6331 
6343  private function fuzzTestPst( $text, PageReference $page, ParserOptions $options ) {
6344  return $this->preSaveTransform( $text, $page, $options->getUserIdentity(), $options );
6345  }
6346 
6358  private function fuzzTestPreprocess( $text, PageReference $page, ParserOptions $options ) {
6359  return $this->fuzzTestSrvus( $text, $page, $options, self::OT_PREPROCESS );
6360  }
6361 
6380  public function markerSkipCallback( $s, callable $callback ) {
6381  $i = 0;
6382  $out = '';
6383  while ( $i < strlen( $s ) ) {
6384  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
6385  if ( $markerStart === false ) {
6386  $out .= call_user_func( $callback, substr( $s, $i ) );
6387  break;
6388  } else {
6389  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
6390  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
6391  if ( $markerEnd === false ) {
6392  $out .= substr( $s, $markerStart );
6393  break;
6394  } else {
6395  $markerEnd += strlen( self::MARKER_SUFFIX );
6396  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
6397  $i = $markerEnd;
6398  }
6399  }
6400  }
6401  return $out;
6402  }
6403 
6411  public function killMarkers( $text ) {
6412  return $this->mStripState->killMarkers( $text );
6413  }
6414 
6425  public static function parseWidthParam( $value, $parseHeight = true ) {
6426  $parsedWidthParam = [];
6427  if ( $value === '' ) {
6428  return $parsedWidthParam;
6429  }
6430  $m = [];
6431  # (T15500) In both cases (width/height and width only),
6432  # permit trailing "px" for backward compatibility.
6433  if ( $parseHeight && preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6434  $width = intval( $m[1] );
6435  $height = intval( $m[2] );
6436  $parsedWidthParam['width'] = $width;
6437  $parsedWidthParam['height'] = $height;
6438  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6439  $width = intval( $value );
6440  $parsedWidthParam['width'] = $width;
6441  }
6442  return $parsedWidthParam;
6443  }
6444 
6454  protected function lock() {
6455  if ( $this->mInParse ) {
6456  throw new MWException( "Parser state cleared while parsing. "
6457  . "Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6458  }
6459 
6460  // Save the backtrace when locking, so that if some code tries locking again,
6461  // we can print the lock owner's backtrace for easier debugging
6462  $e = new Exception;
6463  $this->mInParse = $e->getTraceAsString();
6464 
6465  $recursiveCheck = new ScopedCallback( function () {
6466  $this->mInParse = false;
6467  } );
6468 
6469  return $recursiveCheck;
6470  }
6471 
6479  public function isLocked() {
6480  return (bool)$this->mInParse;
6481  }
6482 
6493  public static function stripOuterParagraph( $html ) {
6494  $m = [];
6495  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) && strpos( $m[1], '</p>' ) === false ) {
6496  $html = $m[1];
6497  }
6498 
6499  return $html;
6500  }
6501 
6512  public static function formatPageTitle( $nsText, $nsSeparator, $mainText ): string {
6513  $html = '';
6514  if ( $nsText !== '' ) {
6515  $html .= '<span class="mw-page-title-namespace">' . HtmlArmor::getHtml( $nsText ) . '</span>';
6516  $html .= '<span class="mw-page-title-separator">' . HtmlArmor::getHtml( $nsSeparator ) . '</span>';
6517  }
6518  $html .= '<span class="mw-page-title-main">' . HtmlArmor::getHtml( $mainText ) . '</span>';
6519  return $html;
6520  }
6521 
6533  public function getFreshParser() {
6534  if ( $this->mInParse ) {
6535  return $this->factory->create();
6536  } else {
6537  return $this;
6538  }
6539  }
6540 
6548  public function enableOOUI() {
6549  wfDeprecated( __METHOD__, '1.35' );
6551  $this->mOutput->setEnableOOUI( true );
6552  }
6553 
6560  private function setOutputFlag( string $flag, string $reason ): void {
6561  $this->mOutput->setOutputFlag( $flag );
6562  $name = $this->getTitle()->getPrefixedText();
6563  $this->logger->debug( __METHOD__ . ": set $flag flag on '$name'; $reason" );
6564  }
6565 }
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:497
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
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:37
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:68
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:53
static makeMediaLinkFile(LinkTarget $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition: Linker.php:950
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition: Linker.php:243
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition: Linker.php:1444
static tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1620
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='')
Make appropriate markup for a link to the current article.
Definition: Linker.php:164
static makeImageLink(Parser $parser, LinkTarget $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query='', $widthOption=null)
Given parameters derived from [[Image:Foo|options...]], generate the HTML that that syntax inserts in...
Definition: Linker.php:300
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1594
static splitTrail( $trail)
Split a link trail, return the "inside" portion and the remainder of the trail as a two-element array...
Definition: Linker.php:1752
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:1018
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition: Linker.php:1731
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1605
static tocList( $toc, Language $lang=null)
Wraps the TOC in a div with ARIA navigation role and provides the hide/collapse JavaScript.
Definition: Linker.php:1656
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1644
MediaWiki exception.
Definition: MWException.php:29
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:39
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
A factory that stores information about MagicWords, and creates them on demand with caching.
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:564
Factory creating MWHttpRequest objects.
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.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
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.
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:345
static plaintextParam( $plaintext)
Definition: Message.php:1287
static numParam( $num)
Definition: Message.php:1166
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:95
addTrackingCategory( $msg)
Definition: Parser.php:4144
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:1271
getTargetLanguage()
Get the target language for the content being parsed.
Definition: Parser.php:1174
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
Definition: Parser.php:6072
setDefaultSort( $sort)
Mutator for the 'defaultsort' page property.
Definition: Parser.php:6148
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6493
static normalizeLinkUrl( $url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:2306
__clone()
Allow extensions to clean up when the parser is cloned.
Definition: Parser.php:569
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4826
getMagicWordFactory()
Get the MagicWordFactory that this Parser is using.
Definition: Parser.php:1228
$mHighestExpansionDepth
Definition: Parser.php:268
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:680
getCustomDefaultSort()
Accessor for the 'defaultsort' page property.
Definition: Parser.php:6180
ParserOptions null $mOptions
Definition: Parser.php:295
getOptions()
Definition: Parser.php:1116
cleanSig( $text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4783
getStripState()
Definition: Parser.php:1342
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:6100
isCurrentRevisionOfTitleCached(LinkTarget $link)
Definition: Parser.php:3553
replaceVariables( $text, $frame=false, $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
Definition: Parser.php:2925
getFunctionSynonyms()
Definition: Parser.php:5732
extensionSubstitution(array $params, PPFrame $frame, bool $processNowiki=false)
Return the text to be used for a given extension tag.
Definition: Parser.php:3973
interwikiTransclude(LinkTarget $link, $action)
Transclude an interwiki link.
Definition: Parser.php:3834
const OT_HTML
Definition: Parser.php:128
$mHeadings
Definition: Parser.php:271
getPreloadText( $text, PageReference $page, ParserOptions $options, $params=[])
Process the wikitext for the "?preload=" feature.
Definition: Parser.php:985
setTitle(Title $t=null)
Set the context title.
Definition: Parser.php:1018
static guessSectionNameFromStrippedText( $text)
Like guessSectionNameFromWikiText(), but takes already-stripped text as input.
Definition: Parser.php:6247
const OT_WIKI
Definition: Parser.php:129
getFlatSectionInfo( $text)
Get an array of preprocessor section information.
Definition: Parser.php:5953
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:2976
modifyImageHtml(File $file, array $params, string &$html)
Give hooks a chance to modify image thumbnail HTML.
Definition: Parser.php:5640
fetchTemplateAndTitle(LinkTarget $link)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3593
Title null $mTitle
Since 1.34, leaving mTitle uninitialized or setting mTitle to null is deprecated.
Definition: Parser.php:304
setUser(?UserIdentity $user)
Set the current user.
Definition: Parser.php:1007
getRevisionSize()
Get the size of the revision.
Definition: Parser.php:6124
stripSectionName( $text)
Strips a text string of wikitext for use in a section anchor.
Definition: Parser.php:6287
tagNeedsNowikiStrippedInTagPF(string $lowerTagName)
Definition: Parser.php:3947
getOutputType()
Accessor for the output type.
Definition: Parser.php:1072
fetchCurrentRevisionRecordOfTitle(LinkTarget $link)
Fetch the current revision of a given title as a RevisionRecord.
Definition: Parser.php:3523
fetchFileAndTitle(LinkTarget $link, array $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3784
recursivePreprocess( $text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:965
fetchFileNoRegister(LinkTarget $link, array $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3811
getUrlProtocols()
Definition: Parser.php:5740
setFunctionHook( $id, callable $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:5015
getHookRunner()
Get a HookRunner for calling core hooks.
Definition: Parser.php:1684
getTitle()
Definition: Parser.php:1027
braceSubstitution(array $piece, PPFrame $frame)
Return the text of a template, after recursively replacing any variables or templates within the temp...
Definition: Parser.php:3001
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:1213
preprocess( $text, ?PageReference $page, ParserOptions $options, $revid=null, $frame=false)
Expand templates and variables in the text, producing valid, static wikitext.
Definition: Parser.php:938
getExternalLinkAttribs( $url)
Get an associative array of additional HTML attributes appropriate for a particular external link.
Definition: Parser.php:2275
setOutputType( $ot)
Mutator for the output type.
Definition: Parser.php:1081
getFunctionLang()
Get a language object for use in parser functions such as {{FORMATNUM:}}.
Definition: Parser.php:1162
callParserFunction(PPFrame $frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3388
makeLimitReport()
Set the limit report data in the current ParserOutput.
Definition: Parser.php:782
parseExtensionTagAsTopLevelDoc( $text)
Needed by Parsoid/PHP to ensure all the hooks for extensions are run in the right order.
Definition: Parser.php:919
transformMsg( $text, ParserOptions $options, ?PageReference $page=null)
Wrapper for preprocess()
Definition: Parser.php:4905
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:4870
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1334
setHook( $tag, callable $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4948
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition: Parser.php:1248
getTags()
Accessor.
Definition: Parser.php:5724
static statelessFetchRevisionRecord(LinkTarget $link, $parser=null)
Wrapper around RevisionLookup::getKnownCurrentRevision.
Definition: Parser.php:3569
recursiveTagParse( $text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:870
getTargetLanguageConverter()
Shorthand for getting a Language Converter for Target language.
Definition: Parser.php:1648
static replaceTableOfContentsMarker( $text, $toc)
Replace table of contents marker in parsed HTML.
Definition: Parser.php:4847
const OT_PLAIN
Definition: Parser.php:133
getSection( $text, $sectionId, $defaultText='')
This function returns the text of a section, specified by a number ($section).
Definition: Parser.php:5903
internalParse( $text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
Definition: Parser.php:1578
replaceSection( $oldText, $sectionId, $newText)
This function returns $oldtext after the content of the section specified by $section has been replac...
Definition: Parser.php:5920
setPage(?PageReference $t=null)
Set the page used as context for parsing, e.g.
Definition: Parser.php:1040
getUserIdentity()
Get a user either from the user set on Parser if it's set, or from the ParserOptions object otherwise...
Definition: Parser.php:1193
doBlockLevels( $text, $linestart)
Make lists from lines starting with ':', '*', '#', etc.
Definition: Parser.php:2819
$mPPNodeCount
Definition: Parser.php:266
getDefaultSort()
Accessor for the 'defaultsort' page property.
Definition: Parser.php:6166
const MARKER_PREFIX
Definition: Parser.php:153
isLocked()
Will entry points such as parse() throw an exception due to the parser already being active?
Definition: Parser.php:6479
getHookContainer()
Get a HookContainer capable of returning metadata about hooks or running extension hooks.
Definition: Parser.php:1672
getFunctionHooks()
Get all registered function hook identifiers.
Definition: Parser.php:5052
$mMarkerIndex
Definition: Parser.php:213
getPage()
Returns the page used as context for parsing, e.g.
Definition: Parser.php:1063
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:2250
$mExpensiveFunctionCount
Definition: Parser.php:277
getContentLanguage()
Get the content language that this Parser is using.
Definition: Parser.php:1238
getOutput()
Definition: Parser.php:1108
Options( $x=null)
Accessor/mutator for the ParserOptions object.
Definition: Parser.php:1136
recursiveTagParseFully( $text, $frame=false)
Fully parse wikitext to fully parsed HTML.
Definition: Parser.php:894
guessSectionNameFromWikiText( $text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
Definition: Parser.php:6217
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4966
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:6007
setOptions(ParserOptions $options)
Mutator for the ParserOptions object.
Definition: Parser.php:1125
static parseWidthParam( $value, $parseHeight=true)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6425
validateSig( $text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4768
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:1203
guessLegacySectionNameFromWikiText( $text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead, if possible.
Definition: Parser.php:6234
const EXT_LINK_URL_CLASS
Definition: Parser.php:107
OutputType( $x=null)
Accessor/mutator for the output type.
Definition: Parser.php:1099
const SFH_OBJECT_ARGS
Definition: Parser.php:99
__destruct()
Reduce memory usage to reduce the impact of circular references.
Definition: Parser.php:555
resetOutput()
Reset the ParserOutput.
Definition: Parser.php:657
setLinkID( $id)
Definition: Parser.php:1153
doQuotes( $text)
Helper function for handleAllQuotes()
Definition: Parser.php:1982
getTemplateDom(LinkTarget $title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3478
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:6512
replaceLinkHolders(&$text, $options=0)
Replace "<!--LINK-->" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:5064
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:1355
const CONSTRUCTOR_OPTIONS
Definition: Parser.php:415
const OT_MSG
Definition: Parser.php:131
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:5343
const OT_PREPROCESS
Definition: Parser.php:130
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:4699
__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:466
firstCallInit()
Used to do various kinds of initialisation on the first call of the parser.
Definition: Parser.php:599
preprocessToDom( $text, $flags=0)
Get the document object model for the given wikitext.
Definition: Parser.php:2900
markerSkipCallback( $s, callable $callback)
Call a callback function on all regions of the given text that are not inside strip markers,...
Definition: Parser.php:6380
clearState()
Clear Parser state.
Definition: Parser.php:611
killMarkers( $text)
Remove any strip markers found in the given text.
Definition: Parser.php:6411
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6548
nextLinkID()
Definition: Parser.php:1145
getRevisionRecordObject()
Get the revision record object for $this->mRevisionId.
Definition: Parser.php:6017
attributeStripCallback(&$text, $frame=false)
Callback from the Sanitizer for expanding items found in HTML attribute values, so they can be safely...
Definition: Parser.php:5711
argSubstitution(array $piece, PPFrame $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3906
const SFH_NO_HASH
Definition: Parser.php:98
renderImageGallery( $text, array $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:5104
lock()
Lock the current instance of the parser.
Definition: Parser.php:6454
static statelessFetchTemplate( $page, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3635
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:6533
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:4577
Differences from DOM schema:
const DOM_FOR_INCLUSION
Transclusion mode flag for Preprocessor::preprocessToObj()
Variant of the Message class.
Definition: RawMessage.php:35
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:836
static cleanUrl( $url)
Definition: Sanitizer.php:1760
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:971
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:1237
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:1361
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:944
static stripAllTags( $html)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
Definition: Sanitizer.php:1709
static decodeTagAttributes( $text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1137
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:49
static castFromPageReference(?PageReference $pageReference)
Return a Title for a given Reference.
Definition: Title.php:332
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:282
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:734
static castFromLinkTarget( $linkTarget)
Same as newFromLinkTarget, but if passed null, returns null.
Definition: Title.php:306
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:370
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:638
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:598
Multi-datacenter aware caching interface.
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:749
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 =&g