MediaWiki  master
Parser.php
Go to the documentation of this file.
1 <?php
59 use Psr\Log\LoggerInterface;
60 use Wikimedia\Bcp47Code\Bcp47CodeValue;
61 use Wikimedia\IPUtils;
62 use Wikimedia\Parsoid\Core\SectionMetadata;
63 use Wikimedia\Parsoid\Core\TOCData;
64 use Wikimedia\ScopedCallback;
65 
106 #[AllowDynamicProperties]
107 class Parser {
108 
109  # Flags for Parser::setFunctionHook
110  public const SFH_NO_HASH = 1;
111  public const SFH_OBJECT_ARGS = 2;
112 
113  # Constants needed for external link processing
114  # Everything except bracket, space, or control characters
115  # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
116  # as well as U+3000 is IDEOGRAPHIC SPACE for T21052
117  # \x{FFFD} is the Unicode replacement character, which the HTML5 spec
118  # uses to replace invalid HTML characters.
119  public const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]';
120  # Simplified expression to match an IPv4 or IPv6 address, or
121  # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
122  // phpcs:ignore Generic.Files.LineLength
123  private const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
124  # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
125  // phpcs:ignore Generic.Files.LineLength
126  private const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
127  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
128 
129  # Regular expression for a non-newline space
130  private const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
131 
136  public const PTD_FOR_INCLUSION = Preprocessor::DOM_FOR_INCLUSION;
137 
138  # Allowed values for $this->mOutputType
139  # Parameter to startExternalParse().
140  public const OT_HTML = 1; # like parse()
141  public const OT_WIKI = 2; # like preSaveTransform()
142  public const OT_PREPROCESS = 3; # like preprocess()
143  public const OT_MSG = 3;
144  # like extractSections() - portions of the original are returned unchanged.
145  public const OT_PLAIN = 4;
146 
164  public const MARKER_SUFFIX = "-QINU`\"'\x7f";
165  public const MARKER_PREFIX = "\x7f'\"`UNIQ-";
166 
181  public const TOC_PLACEHOLDER = '<meta property="mw:PageProp/toc" />';
182 
190  private const TOC_PLACEHOLDER_REGEX = '/<meta\\b[^>]*\\bproperty\\s*=\\s*"mw:PageProp\\/toc"[^>]*\\/>/';
191 
192  # Persistent:
193  private $mTagHooks = [];
194  private $mFunctionHooks = [];
195  private $mFunctionSynonyms = [ 0 => [], 1 => [] ];
196  private $mStripList = [];
197  private $mVarCache = [];
198  private $mImageParams = [];
199  private $mImageParamsMagicArray = [];
201  public $mMarkerIndex = 0;
202 
203  # Initialised by initializeVariables()
204 
208  private $mVariables;
209 
213  private $mSubstWords;
214 
215  # Initialised in constructor
216  private $mExtLinkBracketedRegex;
217 
223  private $urlUtils;
224 
225  # Initialized in constructor
229  private $mPreprocessor;
230 
231  # Cleared with clearState():
235  private $mOutput;
236  private $mAutonumber;
237 
241  private $mStripState;
242 
246  private $mLinkHolders;
247 
251  private $mLinkID;
252  private $mIncludeSizes;
257  private $mTplRedirCache;
259  public $mHeadings;
263  private $mDoubleUnderscores;
265  public $mExpensiveFunctionCount; # number of expensive parser function calls
266  private $mShowToc;
267  private $mForceTocPosition;
269  private $mTplDomCache;
270 
274  private $mUser;
275 
276  # Temporary
277  # These are variables reset at least once per parse regardless of $clearState
278 
283  public $mOptions;
284 
292  public $mTitle; # Title context, used for self-link rendering and similar things
293  private $mOutputType; # Output type, one of the OT_xxx constants
295  public $ot; # Shortcut alias, see setOutputType()
296  private $mRevisionId; # ID to display in {{REVISIONID}} tags
297  private $mRevisionTimestamp; # The timestamp of the specified revision ID
298  private $mRevisionUser; # User to display in {{REVISIONUSER}} tag
299  private $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
300  private $mInputSize = false; # For {{PAGESIZE}} on current page.
301 
303  private $mRevisionRecordObject;
304 
310  private $mLangLinkLanguages;
311 
318  private $currentRevisionCache;
319 
324  private $mInParse = false;
325 
327  private $mProfiler;
328 
332  private $mLinkRenderer;
333 
335  private $magicWordFactory;
336 
338  private $contLang;
339 
341  private $languageConverterFactory;
342 
344  private $factory;
345 
347  private $specialPageFactory;
348 
350  private $titleFormatter;
351 
359  private $svcOptions;
360 
362  private $linkRendererFactory;
363 
365  private $nsInfo;
366 
368  private $logger;
369 
371  private $badFileLookup;
372 
374  private $hookContainer;
375 
377  private $hookRunner;
378 
380  private $tidy;
381 
383  private $userOptionsLookup;
384 
386  private $userFactory;
387 
389  private $httpRequestFactory;
390 
392  private $trackingCategories;
393 
395  private $signatureValidatorFactory;
396 
398  private $userNameUtils;
399 
403  public const CONSTRUCTOR_OPTIONS = [
404  // See documentation for the corresponding config options
405  // Many of these are only used in (eg) CoreMagicVariables
406  MainConfigNames::AllowDisplayTitle,
407  MainConfigNames::AllowSlowParserFunctions,
408  MainConfigNames::ArticlePath,
409  MainConfigNames::EnableScaryTranscluding,
410  MainConfigNames::ExtraInterlanguageLinkPrefixes,
411  MainConfigNames::FragmentMode,
412  MainConfigNames::Localtimezone,
413  MainConfigNames::MaxSigChars,
414  MainConfigNames::MaxTocLevel,
415  MainConfigNames::MiserMode,
416  MainConfigNames::RawHtml,
417  MainConfigNames::ScriptPath,
418  MainConfigNames::Server,
419  MainConfigNames::ServerName,
420  MainConfigNames::ShowHostnames,
421  MainConfigNames::SignatureValidation,
422  MainConfigNames::Sitename,
423  MainConfigNames::StylePath,
424  MainConfigNames::TranscludeCacheExpiry,
425  MainConfigNames::PreprocessorCacheThreshold,
426  ];
427 
454  public function __construct(
455  ServiceOptions $svcOptions,
456  MagicWordFactory $magicWordFactory,
457  Language $contLang,
458  ParserFactory $factory,
459  UrlUtils $urlUtils,
460  SpecialPageFactory $spFactory,
461  LinkRendererFactory $linkRendererFactory,
462  NamespaceInfo $nsInfo,
463  LoggerInterface $logger,
464  BadFileLookup $badFileLookup,
465  LanguageConverterFactory $languageConverterFactory,
466  HookContainer $hookContainer,
467  TidyDriverBase $tidy,
468  WANObjectCache $wanCache,
469  UserOptionsLookup $userOptionsLookup,
470  UserFactory $userFactory,
471  TitleFormatter $titleFormatter,
472  HttpRequestFactory $httpRequestFactory,
473  TrackingCategories $trackingCategories,
474  SignatureValidatorFactory $signatureValidatorFactory,
475  UserNameUtils $userNameUtils
476  ) {
477  if ( ParserFactory::$inParserFactory === 0 ) {
478  // Direct construction of Parser was deprecated in 1.34 and
479  // removed in 1.36; use a ParserFactory instead.
480  throw new MWException( 'Direct construction of Parser not allowed' );
481  }
482  $svcOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
483  $this->svcOptions = $svcOptions;
484 
485  $this->urlUtils = $urlUtils;
486  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->urlUtils->validProtocols() . ')' .
487  self::EXT_LINK_ADDR .
488  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*)\]/Su';
489 
490  $this->magicWordFactory = $magicWordFactory;
491 
492  $this->contLang = $contLang;
493 
494  $this->factory = $factory;
495  $this->specialPageFactory = $spFactory;
496  $this->linkRendererFactory = $linkRendererFactory;
497  $this->nsInfo = $nsInfo;
498  $this->logger = $logger;
499  $this->badFileLookup = $badFileLookup;
500 
501  $this->languageConverterFactory = $languageConverterFactory;
502 
503  $this->hookContainer = $hookContainer;
504  $this->hookRunner = new HookRunner( $hookContainer );
505 
506  $this->tidy = $tidy;
507 
508  $this->mPreprocessor = new Preprocessor_Hash(
509  $this,
510  $wanCache,
511  [
512  'cacheThreshold' => $svcOptions->get( MainConfigNames::PreprocessorCacheThreshold ),
513  'disableLangConversion' => $languageConverterFactory->isConversionDisabled(),
514  ]
515  );
516 
517  $this->userOptionsLookup = $userOptionsLookup;
518  $this->userFactory = $userFactory;
519  $this->titleFormatter = $titleFormatter;
520  $this->httpRequestFactory = $httpRequestFactory;
521  $this->trackingCategories = $trackingCategories;
522  $this->signatureValidatorFactory = $signatureValidatorFactory;
523  $this->userNameUtils = $userNameUtils;
524 
525  // These steps used to be done in "::firstCallInit()"
526  // (if you're chasing a reference from some old code)
528  $this,
530  );
532  $this,
534  );
535  $this->initializeVariables();
536 
537  $this->hookRunner->onParserFirstCallInit( $this );
538  }
539 
543  public function __destruct() {
544  if ( isset( $this->mLinkHolders ) ) {
545  // @phan-suppress-next-line PhanTypeObjectUnsetDeclaredProperty
546  unset( $this->mLinkHolders );
547  }
548  // @phan-suppress-next-line PhanTypeSuspiciousNonTraversableForeach
549  foreach ( $this as $name => $value ) {
550  unset( $this->$name );
551  }
552  }
553 
557  public function __clone() {
558  $this->mInParse = false;
559 
560  // T58226: When you create a reference "to" an object field, that
561  // makes the object field itself be a reference too (until the other
562  // reference goes out of scope). When cloning, any field that's a
563  // reference is copied as a reference in the new object. Both of these
564  // are defined PHP5 behaviors, as inconvenient as it is for us when old
565  // hooks from PHP4 days are passing fields by reference.
566  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
567  // Make a non-reference copy of the field, then rebind the field to
568  // reference the new copy.
569  $tmp = $this->$k;
570  $this->$k =& $tmp;
571  unset( $tmp );
572  }
573 
574  $this->mPreprocessor = clone $this->mPreprocessor;
575  $this->mPreprocessor->resetParser( $this );
576 
577  $this->hookRunner->onParserCloned( $this );
578  }
579 
587  public function firstCallInit() {
588  /*
589  * This method should be hard-deprecated once remaining calls are
590  * removed; it no longer does anything.
591  */
592  }
593 
599  public function clearState() {
600  $this->resetOutput();
601  $this->mAutonumber = 0;
602  $this->mLinkHolders = new LinkHolderArray(
603  $this,
604  $this->getContentLanguageConverter(),
605  $this->getHookContainer()
606  );
607  $this->mLinkID = 0;
608  $this->mRevisionTimestamp = null;
609  $this->mRevisionId = null;
610  $this->mRevisionUser = null;
611  $this->mRevisionSize = null;
612  $this->mRevisionRecordObject = null;
613  $this->mVarCache = [];
614  $this->mUser = null;
615  $this->mLangLinkLanguages = [];
616  $this->currentRevisionCache = null;
617 
618  $this->mStripState = new StripState( $this );
619 
620  # Clear these on every parse, T6549
621  $this->mTplRedirCache = [];
622  $this->mTplDomCache = [];
623 
624  $this->mShowToc = true;
625  $this->mForceTocPosition = false;
626  $this->mIncludeSizes = [
627  'post-expand' => 0,
628  'arg' => 0,
629  ];
630  $this->mPPNodeCount = 0;
631  $this->mHighestExpansionDepth = 0;
632  $this->mHeadings = [];
633  $this->mDoubleUnderscores = [];
634  $this->mExpensiveFunctionCount = 0;
635 
636  $this->mProfiler = new SectionProfiler();
637 
638  $this->hookRunner->onParserClearState( $this );
639  }
640 
645  public function resetOutput() {
646  $this->mOutput = new ParserOutput;
647  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
648  }
649 
668  public function parse(
669  $text, PageReference $page, ParserOptions $options,
670  $linestart = true, $clearState = true, $revid = null
671  ) {
672  if ( $clearState ) {
673  // We use U+007F DELETE to construct strip markers, so we have to make
674  // sure that this character does not occur in the input text.
675  $text = strtr( $text, "\x7f", "?" );
676  $magicScopeVariable = $this->lock();
677  }
678  // Strip U+0000 NULL (T159174)
679  $text = str_replace( "\000", '', $text );
680 
681  $this->startParse( $page, $options, self::OT_HTML, $clearState );
682 
683  $this->currentRevisionCache = null;
684  $this->mInputSize = strlen( $text );
685  $this->mOutput->resetParseStartTime();
686 
687  $oldRevisionId = $this->mRevisionId;
688  $oldRevisionRecordObject = $this->mRevisionRecordObject;
689  $oldRevisionTimestamp = $this->mRevisionTimestamp;
690  $oldRevisionUser = $this->mRevisionUser;
691  $oldRevisionSize = $this->mRevisionSize;
692  if ( $revid !== null ) {
693  $this->mRevisionId = $revid;
694  $this->mRevisionRecordObject = null;
695  $this->mRevisionTimestamp = null;
696  $this->mRevisionUser = null;
697  $this->mRevisionSize = null;
698  }
699 
700  $text = $this->internalParse( $text );
701  $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
702 
703  $text = $this->internalParseHalfParsed( $text, true, $linestart );
704 
712  if ( !$options->getDisableTitleConversion()
713  && !isset( $this->mDoubleUnderscores['nocontentconvert'] )
714  && !isset( $this->mDoubleUnderscores['notitleconvert'] )
715  && $this->mOutput->getDisplayTitle() === false
716  ) {
717  $titleText = $this->getTargetLanguageConverter()->getConvRuleTitle();
718  if ( $titleText !== false ) {
719  $titleText = Sanitizer::removeSomeTags( $titleText );
720  } else {
721  [ $nsText, $nsSeparator, $mainText ] = $this->getTargetLanguageConverter()->convertSplitTitle( $page );
722  // In the future, those three pieces could be stored separately rather than joined into $titleText,
723  // and OutputPage would format them and join them together, to resolve T314399.
724  $titleText = self::formatPageTitle( $nsText, $nsSeparator, $mainText );
725  }
726  $this->mOutput->setTitleText( $titleText );
727  }
728 
729  # Compute runtime adaptive expiry if set
730  $this->mOutput->finalizeAdaptiveCacheExpiry();
731 
732  # Warn if too many heavyweight parser functions were used
733  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
734  $this->limitationWarn( 'expensive-parserfunction',
735  $this->mExpensiveFunctionCount,
736  $this->mOptions->getExpensiveParserFunctionLimit()
737  );
738  }
739 
740  # Information on limits, for the benefit of users who try to skirt them
741  if ( MediaWikiServices::getInstance()->getMainConfig()->get(
742  MainConfigNames::EnableParserLimitReporting ) ) {
743  $this->makeLimitReport();
744  }
745 
746  # Wrap non-interface parser output in a <div> so it can be targeted
747  # with CSS (T37247)
748  $class = $this->mOptions->getWrapOutputClass();
749  if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
750  $this->mOutput->addWrapperDivClass( $class );
751  }
752 
753  $this->mOutput->setText( $text );
754 
755  $this->mRevisionId = $oldRevisionId;
756  $this->mRevisionRecordObject = $oldRevisionRecordObject;
757  $this->mRevisionTimestamp = $oldRevisionTimestamp;
758  $this->mRevisionUser = $oldRevisionUser;
759  $this->mRevisionSize = $oldRevisionSize;
760  $this->mInputSize = false;
761  $this->currentRevisionCache = null;
762 
763  return $this->mOutput;
764  }
765 
769  protected function makeLimitReport() {
770  $maxIncludeSize = $this->mOptions->getMaxIncludeSize();
771 
772  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
773  if ( $cpuTime !== null ) {
774  $this->mOutput->setLimitReportData( 'limitreport-cputime',
775  sprintf( "%.3f", $cpuTime )
776  );
777  }
778 
779  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
780  $this->mOutput->setLimitReportData( 'limitreport-walltime',
781  sprintf( "%.3f", $wallTime )
782  );
783 
784  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
785  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
786  );
787  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
788  [ $this->mIncludeSizes['post-expand'], $maxIncludeSize ]
789  );
790  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
791  [ $this->mIncludeSizes['arg'], $maxIncludeSize ]
792  );
793  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
794  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
795  );
796  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
797  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
798  );
799 
800  foreach ( $this->mStripState->getLimitReport() as [ $key, $value ] ) {
801  $this->mOutput->setLimitReportData( $key, $value );
802  }
803 
804  $this->hookRunner->onParserLimitReportPrepare( $this, $this->mOutput );
805 
806  // Add on template profiling data in human/machine readable way
807  $dataByFunc = $this->mProfiler->getFunctionStats();
808  uasort( $dataByFunc, static function ( $a, $b ) {
809  return $b['real'] <=> $a['real']; // descending order
810  } );
811  $profileReport = [];
812  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
813  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
814  $item['%real'], $item['real'], $item['calls'],
815  htmlspecialchars( $item['name'] ) );
816  }
817 
818  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
819 
820  // Add other cache related metadata
821  if ( $this->svcOptions->get( MainConfigNames::ShowHostnames ) ) {
822  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
823  }
824  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
825  $this->mOutput->getCacheTime() );
826  $this->mOutput->setLimitReportData( 'cachereport-ttl',
827  $this->mOutput->getCacheExpiry() );
828  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
829  $this->mOutput->hasReducedExpiry() );
830  }
831 
857  public function recursiveTagParse( $text, $frame = false ) {
858  $text = $this->internalParse( $text, false, $frame );
859  return $text;
860  }
861 
881  public function recursiveTagParseFully( $text, $frame = false ) {
882  $text = $this->recursiveTagParse( $text, $frame );
883  $text = $this->internalParseHalfParsed( $text, false );
884  return $text;
885  }
886 
906  public function parseExtensionTagAsTopLevelDoc( $text ) {
907  $text = $this->recursiveTagParse( $text );
908  $this->hookRunner->onParserAfterParse( $this, $text, $this->mStripState );
909  $text = $this->internalParseHalfParsed( $text, true );
910  return $text;
911  }
912 
925  public function preprocess(
926  $text,
927  ?PageReference $page,
928  ParserOptions $options,
929  $revid = null,
930  $frame = false
931  ) {
932  $magicScopeVariable = $this->lock();
933  $this->startParse( $page, $options, self::OT_PREPROCESS, true );
934  if ( $revid !== null ) {
935  $this->mRevisionId = $revid;
936  }
937  $this->hookRunner->onParserBeforePreprocess( $this, $text, $this->mStripState );
938  $text = $this->replaceVariables( $text, $frame );
939  $text = $this->mStripState->unstripBoth( $text );
940  return $text;
941  }
942 
952  public function recursivePreprocess( $text, $frame = false ) {
953  $text = $this->replaceVariables( $text, $frame );
954  $text = $this->mStripState->unstripBoth( $text );
955  return $text;
956  }
957 
972  public function getPreloadText( $text, PageReference $page, ParserOptions $options, $params = [] ) {
973  $msg = new RawMessage( $text );
974  $text = $msg->params( $params )->plain();
975 
976  # Parser (re)initialisation
977  $magicScopeVariable = $this->lock();
978  $this->startParse( $page, $options, self::OT_PLAIN, true );
979 
981  $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
982  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
983  $text = $this->mStripState->unstripBoth( $text );
984  return $text;
985  }
986 
994  public function setUser( ?UserIdentity $user ) {
995  $this->mUser = $user;
996  }
997 
1005  public function setTitle( Title $t = null ) {
1006  $this->setPage( $t );
1007  }
1008 
1014  public function getTitle(): Title {
1015  if ( !$this->mTitle ) {
1016  $this->mTitle = Title::makeTitle( NS_SPECIAL, 'Badtitle/Parser' );
1017  }
1018  return $this->mTitle;
1019  }
1020 
1027  public function setPage( ?PageReference $t = null ) {
1028  if ( !$t ) {
1029  $t = Title::makeTitle( NS_SPECIAL, 'Badtitle/Parser' );
1030  } else {
1031  // For now (early 1.37 alpha), always convert to Title, so we don't have to do it over
1032  // and over again in other methods. Eventually, we will no longer need to have a Title
1033  // instance internally.
1034  $t = Title::castFromPageReference( $t );
1035  }
1036 
1037  if ( $t->hasFragment() ) {
1038  # Strip the fragment to avoid various odd effects
1039  $this->mTitle = $t->createFragmentTarget( '' );
1040  } else {
1041  $this->mTitle = $t;
1042  }
1043  }
1044 
1050  public function getPage(): ?PageReference {
1051  return $this->mTitle;
1052  }
1053 
1059  public function getOutputType(): int {
1060  return $this->mOutputType;
1061  }
1062 
1068  public function setOutputType( $ot ): void {
1069  $this->mOutputType = $ot;
1070  # Shortcut alias
1071  $this->ot = [
1072  'html' => $ot == self::OT_HTML,
1073  'wiki' => $ot == self::OT_WIKI,
1074  'pre' => $ot == self::OT_PREPROCESS,
1075  'plain' => $ot == self::OT_PLAIN,
1076  ];
1077  }
1078 
1086  public function OutputType( $x = null ) {
1087  wfDeprecated( __METHOD__, '1.35' );
1088  return wfSetVar( $this->mOutputType, $x );
1089  }
1090 
1095  public function getOutput() {
1096  return $this->mOutput;
1097  }
1098 
1103  public function getOptions() {
1104  return $this->mOptions;
1105  }
1106 
1112  public function setOptions( ParserOptions $options ): void {
1113  $this->mOptions = $options;
1114  }
1115 
1123  public function Options( $x = null ) {
1124  wfDeprecated( __METHOD__, '1.35' );
1125  return wfSetVar( $this->mOptions, $x );
1126  }
1127 
1132  public function nextLinkID() {
1133  return $this->mLinkID++;
1134  }
1135 
1140  public function setLinkID( $id ) {
1141  $this->mLinkID = $id;
1142  }
1143 
1150  public function getFunctionLang() {
1151  wfDeprecated( __METHOD__, '1.40' );
1152  return $this->getTargetLanguage();
1153  }
1154 
1163  public function getTargetLanguage() {
1164  $target = $this->mOptions->getTargetLanguage();
1165 
1166  if ( $target !== null ) {
1167  return $target;
1168  } elseif ( $this->mOptions->getInterfaceMessage() ) {
1169  return $this->mOptions->getUserLangObj();
1170  }
1171 
1172  return $this->getTitle()->getPageLanguage();
1173  }
1174 
1182  public function getUserIdentity(): UserIdentity {
1183  return $this->mUser ?? $this->getOptions()->getUserIdentity();
1184  }
1185 
1192  public function getPreprocessor() {
1193  return $this->mPreprocessor;
1194  }
1195 
1202  public function getLinkRenderer() {
1203  // XXX We make the LinkRenderer with current options and then cache it forever
1204  if ( !$this->mLinkRenderer ) {
1205  $this->mLinkRenderer = $this->linkRendererFactory->create();
1206  }
1207 
1208  return $this->mLinkRenderer;
1209  }
1210 
1217  public function getMagicWordFactory() {
1218  return $this->magicWordFactory;
1219  }
1220 
1227  public function getContentLanguage() {
1228  return $this->contLang;
1229  }
1230 
1237  public function getBadFileLookup() {
1238  return $this->badFileLookup;
1239  }
1240 
1260  public static function extractTagsAndParams( array $elements, $text, &$matches ) {
1261  static $n = 1;
1262  $stripped = '';
1263  $matches = [];
1264 
1265  $taglist = implode( '|', $elements );
1266  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i";
1267 
1268  while ( $text != '' ) {
1269  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1270  $stripped .= $p[0];
1271  if ( count( $p ) < 5 ) {
1272  break;
1273  }
1274  if ( count( $p ) > 5 ) {
1275  # comment
1276  $element = $p[4];
1277  $attributes = '';
1278  $close = '';
1279  $inside = $p[5];
1280  } else {
1281  # tag
1282  [ , $element, $attributes, $close, $inside ] = $p;
1283  }
1284 
1285  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
1286  $stripped .= $marker;
1287 
1288  if ( $close === '/>' ) {
1289  # Empty element tag, <tag />
1290  $content = null;
1291  $text = $inside;
1292  $tail = null;
1293  } else {
1294  if ( $element === '!--' ) {
1295  $end = '/(-->)/';
1296  } else {
1297  $end = "/(<\\/$element\\s*>)/i";
1298  }
1299  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1300  $content = $q[0];
1301  if ( count( $q ) < 3 ) {
1302  # No end tag -- let it run out to the end of the text.
1303  $tail = '';
1304  $text = '';
1305  } else {
1306  [ , $tail, $text ] = $q;
1307  }
1308  }
1309 
1310  $matches[$marker] = [ $element,
1311  $content,
1312  Sanitizer::decodeTagAttributes( $attributes ),
1313  "<$element$attributes$close$content$tail" ];
1314  }
1315  return $stripped;
1316  }
1317 
1323  public function getStripList() {
1324  return $this->mStripList;
1325  }
1326 
1331  public function getStripState() {
1332  return $this->mStripState;
1333  }
1334 
1344  public function insertStripItem( $text ) {
1345  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1346  $this->mMarkerIndex++;
1347  $this->mStripState->addGeneral( $marker, $text );
1348  return $marker;
1349  }
1350 
1357  private function handleTables( $text ) {
1358  $lines = StringUtils::explode( "\n", $text );
1359  $out = '';
1360  $td_history = []; # Is currently a td tag open?
1361  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1362  $tr_history = []; # Is currently a tr tag open?
1363  $tr_attributes = []; # history of tr attributes
1364  $has_opened_tr = []; # Did this table open a <tr> element?
1365  $indent_level = 0; # indent level of the table
1366 
1367  foreach ( $lines as $outLine ) {
1368  $line = trim( $outLine );
1369 
1370  if ( $line === '' ) { # empty line, go to next line
1371  $out .= $outLine . "\n";
1372  continue;
1373  }
1374 
1375  $first_character = $line[0];
1376  $first_two = substr( $line, 0, 2 );
1377  $matches = [];
1378 
1379  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1380  # First check if we are starting a new table
1381  $indent_level = strlen( $matches[1] );
1382 
1383  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1384  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1385 
1386  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1387  $td_history[] = false;
1388  $last_tag_history[] = '';
1389  $tr_history[] = false;
1390  $tr_attributes[] = '';
1391  $has_opened_tr[] = false;
1392  } elseif ( count( $td_history ) == 0 ) {
1393  # Don't do any of the following
1394  $out .= $outLine . "\n";
1395  continue;
1396  } elseif ( $first_two === '|}' ) {
1397  # We are ending a table
1398  $line = '</table>' . substr( $line, 2 );
1399  $last_tag = array_pop( $last_tag_history );
1400 
1401  if ( !array_pop( $has_opened_tr ) ) {
1402  $line = "<tr><td></td></tr>{$line}";
1403  }
1404 
1405  if ( array_pop( $tr_history ) ) {
1406  $line = "</tr>{$line}";
1407  }
1408 
1409  if ( array_pop( $td_history ) ) {
1410  $line = "</{$last_tag}>{$line}";
1411  }
1412  array_pop( $tr_attributes );
1413  if ( $indent_level > 0 ) {
1414  $outLine = rtrim( $line ) . str_repeat( '</dd></dl>', $indent_level );
1415  } else {
1416  $outLine = $line;
1417  }
1418  } elseif ( $first_two === '|-' ) {
1419  # Now we have a table row
1420  $line = preg_replace( '#^\|-+#', '', $line );
1421 
1422  # Whats after the tag is now only attributes
1423  $attributes = $this->mStripState->unstripBoth( $line );
1424  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1425  array_pop( $tr_attributes );
1426  $tr_attributes[] = $attributes;
1427 
1428  $line = '';
1429  $last_tag = array_pop( $last_tag_history );
1430  array_pop( $has_opened_tr );
1431  $has_opened_tr[] = true;
1432 
1433  if ( array_pop( $tr_history ) ) {
1434  $line = '</tr>';
1435  }
1436 
1437  if ( array_pop( $td_history ) ) {
1438  $line = "</{$last_tag}>{$line}";
1439  }
1440 
1441  $outLine = $line;
1442  $tr_history[] = false;
1443  $td_history[] = false;
1444  $last_tag_history[] = '';
1445  } elseif ( $first_character === '|'
1446  || $first_character === '!'
1447  || $first_two === '|+'
1448  ) {
1449  # This might be cell elements, td, th or captions
1450  if ( $first_two === '|+' ) {
1451  $first_character = '+';
1452  $line = substr( $line, 2 );
1453  } else {
1454  $line = substr( $line, 1 );
1455  }
1456 
1457  // Implies both are valid for table headings.
1458  if ( $first_character === '!' ) {
1459  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1460  }
1461 
1462  # Split up multiple cells on the same line.
1463  # FIXME : This can result in improper nesting of tags processed
1464  # by earlier parser steps.
1465  $cells = explode( '||', $line );
1466 
1467  $outLine = '';
1468 
1469  # Loop through each table cell
1470  foreach ( $cells as $cell ) {
1471  $previous = '';
1472  if ( $first_character !== '+' ) {
1473  $tr_after = array_pop( $tr_attributes );
1474  if ( !array_pop( $tr_history ) ) {
1475  $previous = "<tr{$tr_after}>\n";
1476  }
1477  $tr_history[] = true;
1478  $tr_attributes[] = '';
1479  array_pop( $has_opened_tr );
1480  $has_opened_tr[] = true;
1481  }
1482 
1483  $last_tag = array_pop( $last_tag_history );
1484 
1485  if ( array_pop( $td_history ) ) {
1486  $previous = "</{$last_tag}>\n{$previous}";
1487  }
1488 
1489  if ( $first_character === '|' ) {
1490  $last_tag = 'td';
1491  } elseif ( $first_character === '!' ) {
1492  $last_tag = 'th';
1493  } elseif ( $first_character === '+' ) {
1494  $last_tag = 'caption';
1495  } else {
1496  $last_tag = '';
1497  }
1498 
1499  $last_tag_history[] = $last_tag;
1500 
1501  # A cell could contain both parameters and data
1502  $cell_data = explode( '|', $cell, 2 );
1503 
1504  # T2553: Note that a '|' inside an invalid link should not
1505  # be mistaken as delimiting cell parameters
1506  # Bug T153140: Neither should language converter markup.
1507  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1508  $cell = "{$previous}<{$last_tag}>" . trim( $cell );
1509  } elseif ( count( $cell_data ) == 1 ) {
1510  // Whitespace in cells is trimmed
1511  $cell = "{$previous}<{$last_tag}>" . trim( $cell_data[0] );
1512  } else {
1513  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1514  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1515  // Whitespace in cells is trimmed
1516  $cell = "{$previous}<{$last_tag}{$attributes}>" . trim( $cell_data[1] );
1517  }
1518 
1519  $outLine .= $cell;
1520  $td_history[] = true;
1521  }
1522  }
1523  $out .= $outLine . "\n";
1524  }
1525 
1526  # Closing open td, tr && table
1527  while ( count( $td_history ) > 0 ) {
1528  if ( array_pop( $td_history ) ) {
1529  $out .= "</td>\n";
1530  }
1531  if ( array_pop( $tr_history ) ) {
1532  $out .= "</tr>\n";
1533  }
1534  if ( !array_pop( $has_opened_tr ) ) {
1535  $out .= "<tr><td></td></tr>\n";
1536  }
1537 
1538  $out .= "</table>\n";
1539  }
1540 
1541  # Remove trailing line-ending (b/c)
1542  if ( substr( $out, -1 ) === "\n" ) {
1543  $out = substr( $out, 0, -1 );
1544  }
1545 
1546  # special case: don't return empty table
1547  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1548  $out = '';
1549  }
1550 
1551  return $out;
1552  }
1553 
1567  public function internalParse( $text, $isMain = true, $frame = false ) {
1568  $origText = $text;
1569 
1570  # Hook to suspend the parser in this state
1571  if ( !$this->hookRunner->onParserBeforeInternalParse( $this, $text, $this->mStripState ) ) {
1572  return $text;
1573  }
1574 
1575  # if $frame is provided, then use $frame for replacing any variables
1576  if ( $frame ) {
1577  # use frame depth to infer how include/noinclude tags should be handled
1578  # depth=0 means this is the top-level document; otherwise it's an included document
1579  if ( !$frame->depth ) {
1580  $flag = 0;
1581  } else {
1583  }
1584  $dom = $this->preprocessToDom( $text, $flag );
1585  $text = $frame->expand( $dom );
1586  } else {
1587  # if $frame is not provided, then use old-style replaceVariables
1588  $text = $this->replaceVariables( $text );
1589  }
1590 
1592  $text,
1593  // Callback from the Sanitizer for expanding items found in
1594  // HTML attribute values, so they can be safely tested and escaped.
1595  function ( &$text, $frame = false ) {
1596  $text = $this->replaceVariables( $text, $frame );
1597  $text = $this->mStripState->unstripBoth( $text );
1598  },
1599  false,
1600  [],
1601  []
1602  );
1603  $this->hookRunner->onInternalParseBeforeLinks( $this, $text, $this->mStripState );
1604 
1605  # Tables need to come after variable replacement for things to work
1606  # properly; putting them before other transformations should keep
1607  # exciting things like link expansions from showing up in surprising
1608  # places.
1609  $text = $this->handleTables( $text );
1610 
1611  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1612 
1613  $text = $this->handleDoubleUnderscore( $text );
1614 
1615  $text = $this->handleHeadings( $text );
1616  $text = $this->handleInternalLinks( $text );
1617  $text = $this->handleAllQuotes( $text );
1618  $text = $this->handleExternalLinks( $text );
1619 
1620  # handleInternalLinks may sometimes leave behind
1621  # absolute URLs, which have to be masked to hide them from handleExternalLinks
1622  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1623 
1624  $text = $this->handleMagicLinks( $text );
1625  $text = $this->finalizeHeadings( $text, $origText, $isMain );
1626 
1627  return $text;
1628  }
1629 
1637  return $this->languageConverterFactory->getLanguageConverter(
1638  $this->getTargetLanguage()
1639  );
1640  }
1641 
1647  private function getContentLanguageConverter(): ILanguageConverter {
1648  return $this->languageConverterFactory->getLanguageConverter(
1649  $this->getContentLanguage()
1650  );
1651  }
1652 
1660  protected function getHookContainer() {
1661  return $this->hookContainer;
1662  }
1663 
1672  protected function getHookRunner() {
1673  return $this->hookRunner;
1674  }
1675 
1685  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1686  $text = $this->mStripState->unstripGeneral( $text );
1687 
1688  $text = BlockLevelPass::doBlockLevels( $text, $linestart );
1689 
1690  $this->replaceLinkHoldersPrivate( $text );
1691 
1699  $converter = null;
1700  if ( !( $this->mOptions->getDisableContentConversion()
1701  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1702  && !$this->mOptions->getInterfaceMessage()
1703  ) {
1704  # The position of the convert() call should not be changed. it
1705  # assumes that the links are all replaced and the only thing left
1706  # is the <nowiki> mark.
1707  $converter = $this->getTargetLanguageConverter();
1708  $text = $converter->convert( $text );
1709  // TOC will be converted below.
1710  }
1711  // Convert the TOC. This is done *after* the main text
1712  // so that all the editor-defined conversion rules (by convention
1713  // defined at the start of the article) are applied to the TOC
1714  self::localizeTOC(
1715  $this->mOutput->getTOCData(),
1716  $this->getTargetLanguage(),
1717  $converter // null if conversion is to be suppressed.
1718  );
1719  if ( $converter ) {
1720  $this->mOutput->setLanguage( new Bcp47CodeValue(
1721  LanguageCode::bcp47( $converter->getPreferredVariant() )
1722  ) );
1723  } else {
1724  $this->mOutput->setLanguage( $this->getTargetLanguage() );
1725  }
1726 
1727  $text = $this->mStripState->unstripNoWiki( $text );
1728 
1729  $text = $this->mStripState->unstripGeneral( $text );
1730 
1731  $text = $this->tidy->tidy( $text, [ Sanitizer::class, 'armorFrenchSpaces' ] );
1732 
1733  if ( $isMain ) {
1734  $this->hookRunner->onParserAfterTidy( $this, $text );
1735  }
1736 
1737  return $text;
1738  }
1739 
1750  private function handleMagicLinks( $text ) {
1751  $prots = $this->urlUtils->validAbsoluteProtocols();
1752  $urlChar = self::EXT_LINK_URL_CLASS;
1753  $addr = self::EXT_LINK_ADDR;
1754  $space = self::SPACE_NOT_NL; # non-newline space
1755  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1756  $spaces = "$space++"; # possessive match of 1 or more spaces
1757  $text = preg_replace_callback(
1758  '!(?: # Start cases
1759  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1760  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1761  (\b # m[3]: Free external links
1762  (?i:$prots)
1763  ($addr$urlChar*) # m[4]: Post-protocol path
1764  ) |
1765  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1766  ([0-9]+)\b |
1767  \bISBN $spaces ( # m[6]: ISBN, capture number
1768  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1769  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1770  [0-9Xx] # check digit
1771  )\b
1772  )!xu",
1773  [ $this, 'magicLinkCallback' ],
1774  $text
1775  );
1776  return $text;
1777  }
1778 
1784  private function magicLinkCallback( array $m ) {
1785  if ( isset( $m[1] ) && $m[1] !== '' ) {
1786  # Skip anchor
1787  return $m[0];
1788  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1789  # Skip HTML element
1790  return $m[0];
1791  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1792  # Free external link
1793  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1794  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1795  # RFC or PMID
1796  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1797  if ( !$this->mOptions->getMagicRFCLinks() ) {
1798  return $m[0];
1799  }
1800  $keyword = 'RFC';
1801  $urlmsg = 'rfcurl';
1802  $cssClass = 'mw-magiclink-rfc';
1803  $trackingCat = 'magiclink-tracking-rfc';
1804  $id = $m[5];
1805  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1806  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1807  return $m[0];
1808  }
1809  $keyword = 'PMID';
1810  $urlmsg = 'pubmedurl';
1811  $cssClass = 'mw-magiclink-pmid';
1812  $trackingCat = 'magiclink-tracking-pmid';
1813  $id = $m[5];
1814  } else {
1815  // Should never happen
1816  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1817  substr( $m[0], 0, 20 ) . '"' );
1818  }
1819  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1820  $this->addTrackingCategory( $trackingCat );
1821  return Linker::makeExternalLink(
1822  $url,
1823  "{$keyword} {$id}",
1824  true,
1825  $cssClass,
1826  [],
1827  $this->getTitle()
1828  );
1829  } elseif ( isset( $m[6] ) && $m[6] !== ''
1830  && $this->mOptions->getMagicISBNLinks()
1831  ) {
1832  # ISBN
1833  $isbn = $m[6];
1834  $space = self::SPACE_NOT_NL; # non-newline space
1835  $isbn = preg_replace( "/$space/", ' ', $isbn );
1836  $num = strtr( $isbn, [
1837  '-' => '',
1838  ' ' => '',
1839  'x' => 'X',
1840  ] );
1841  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1842  return $this->getLinkRenderer()->makeKnownLink(
1843  SpecialPage::getTitleFor( 'Booksources', $num ),
1844  "ISBN $isbn",
1845  [
1846  'class' => 'internal mw-magiclink-isbn',
1847  'title' => false // suppress title attribute
1848  ]
1849  );
1850  } else {
1851  return $m[0];
1852  }
1853  }
1854 
1864  private function makeFreeExternalLink( $url, $numPostProto ) {
1865  $trail = '';
1866 
1867  # The characters '<' and '>' (which were escaped by
1868  # internalRemoveHtmlTags()) should not be included in
1869  # URLs, per RFC 2396.
1870  # Make &nbsp; terminate a URL as well (bug T84937)
1871  $m2 = [];
1872  if ( preg_match(
1873  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1874  $url,
1875  $m2,
1876  PREG_OFFSET_CAPTURE
1877  ) ) {
1878  $trail = substr( $url, $m2[0][1] ) . $trail;
1879  $url = substr( $url, 0, $m2[0][1] );
1880  }
1881 
1882  # Move trailing punctuation to $trail
1883  $sep = ',;\.:!?';
1884  # If there is no left bracket, then consider right brackets fair game too
1885  if ( strpos( $url, '(' ) === false ) {
1886  $sep .= ')';
1887  }
1888 
1889  $urlRev = strrev( $url );
1890  $numSepChars = strspn( $urlRev, $sep );
1891  # Don't break a trailing HTML entity by moving the ; into $trail
1892  # This is in hot code, so use substr_compare to avoid having to
1893  # create a new string object for the comparison
1894  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1895  # more optimization: instead of running preg_match with a $
1896  # anchor, which can be slow, do the match on the reversed
1897  # string starting at the desired offset.
1898  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1899  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1900  $numSepChars--;
1901  }
1902  }
1903  if ( $numSepChars ) {
1904  $trail = substr( $url, -$numSepChars ) . $trail;
1905  $url = substr( $url, 0, -$numSepChars );
1906  }
1907 
1908  # Verify that we still have a real URL after trail removal, and
1909  # not just lone protocol
1910  if ( strlen( $trail ) >= $numPostProto ) {
1911  return $url . $trail;
1912  }
1913 
1914  $url = Sanitizer::cleanUrl( $url );
1915 
1916  # Is this an external image?
1917  $text = $this->maybeMakeExternalImage( $url );
1918  if ( $text === false ) {
1919  # Not an image, make a link
1920  $text = Linker::makeExternalLink(
1921  $url,
1922  $this->getTargetLanguageConverter()->markNoConversion( $url ),
1923  true,
1924  'free',
1925  $this->getExternalLinkAttribs( $url ),
1926  $this->getTitle()
1927  );
1928  # Register it in the output object...
1929  $this->mOutput->addExternalLink( $url );
1930  }
1931  return $text . $trail;
1932  }
1933 
1940  private function handleHeadings( $text ) {
1941  for ( $i = 6; $i >= 1; --$i ) {
1942  $h = str_repeat( '=', $i );
1943  // Trim non-newline whitespace from headings
1944  // Using \s* will break for: "==\n===\n" and parse as <h2>=</h2>
1945  $text = preg_replace( "/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m", "<h$i>\\1</h$i>", $text );
1946  }
1947  return $text;
1948  }
1949 
1957  private function handleAllQuotes( $text ) {
1958  $outtext = '';
1959  $lines = StringUtils::explode( "\n", $text );
1960  foreach ( $lines as $line ) {
1961  $outtext .= $this->doQuotes( $line ) . "\n";
1962  }
1963  $outtext = substr( $outtext, 0, -1 );
1964  return $outtext;
1965  }
1966 
1975  public function doQuotes( $text ) {
1976  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1977  $countarr = count( $arr );
1978  if ( $countarr == 1 ) {
1979  return $text;
1980  }
1981 
1982  // First, do some preliminary work. This may shift some apostrophes from
1983  // being mark-up to being text. It also counts the number of occurrences
1984  // of bold and italics mark-ups.
1985  $numbold = 0;
1986  $numitalics = 0;
1987  for ( $i = 1; $i < $countarr; $i += 2 ) {
1988  $thislen = strlen( $arr[$i] );
1989  // If there are ever four apostrophes, assume the first is supposed to
1990  // be text, and the remaining three constitute mark-up for bold text.
1991  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1992  if ( $thislen == 4 ) {
1993  $arr[$i - 1] .= "'";
1994  $arr[$i] = "'''";
1995  $thislen = 3;
1996  } elseif ( $thislen > 5 ) {
1997  // If there are more than 5 apostrophes in a row, assume they're all
1998  // text except for the last 5.
1999  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
2000  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
2001  $arr[$i] = "'''''";
2002  $thislen = 5;
2003  }
2004  // Count the number of occurrences of bold and italics mark-ups.
2005  if ( $thislen == 2 ) {
2006  $numitalics++;
2007  } elseif ( $thislen == 3 ) {
2008  $numbold++;
2009  } elseif ( $thislen == 5 ) {
2010  $numitalics++;
2011  $numbold++;
2012  }
2013  }
2014 
2015  // If there is an odd number of both bold and italics, it is likely
2016  // that one of the bold ones was meant to be an apostrophe followed
2017  // by italics. Which one we cannot know for certain, but it is more
2018  // likely to be one that has a single-letter word before it.
2019  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
2020  $firstsingleletterword = -1;
2021  $firstmultiletterword = -1;
2022  $firstspace = -1;
2023  for ( $i = 1; $i < $countarr; $i += 2 ) {
2024  if ( strlen( $arr[$i] ) == 3 ) {
2025  $x1 = substr( $arr[$i - 1], -1 );
2026  $x2 = substr( $arr[$i - 1], -2, 1 );
2027  if ( $x1 === ' ' ) {
2028  if ( $firstspace == -1 ) {
2029  $firstspace = $i;
2030  }
2031  } elseif ( $x2 === ' ' ) {
2032  $firstsingleletterword = $i;
2033  // if $firstsingleletterword is set, we don't
2034  // look at the other options, so we can bail early.
2035  break;
2036  } elseif ( $firstmultiletterword == -1 ) {
2037  $firstmultiletterword = $i;
2038  }
2039  }
2040  }
2041 
2042  // If there is a single-letter word, use it!
2043  if ( $firstsingleletterword > -1 ) {
2044  $arr[$firstsingleletterword] = "''";
2045  $arr[$firstsingleletterword - 1] .= "'";
2046  } elseif ( $firstmultiletterword > -1 ) {
2047  // If not, but there's a multi-letter word, use that one.
2048  $arr[$firstmultiletterword] = "''";
2049  $arr[$firstmultiletterword - 1] .= "'";
2050  } elseif ( $firstspace > -1 ) {
2051  // ... otherwise use the first one that has neither.
2052  // (notice that it is possible for all three to be -1 if, for example,
2053  // there is only one pentuple-apostrophe in the line)
2054  $arr[$firstspace] = "''";
2055  $arr[$firstspace - 1] .= "'";
2056  }
2057  }
2058 
2059  // Now let's actually convert our apostrophic mush to HTML!
2060  $output = '';
2061  $buffer = '';
2062  $state = '';
2063  $i = 0;
2064  foreach ( $arr as $r ) {
2065  if ( ( $i % 2 ) == 0 ) {
2066  if ( $state === 'both' ) {
2067  $buffer .= $r;
2068  } else {
2069  $output .= $r;
2070  }
2071  } else {
2072  $thislen = strlen( $r );
2073  if ( $thislen == 2 ) {
2074  // two quotes - open or close italics
2075  if ( $state === 'i' ) {
2076  $output .= '</i>';
2077  $state = '';
2078  } elseif ( $state === 'bi' ) {
2079  $output .= '</i>';
2080  $state = 'b';
2081  } elseif ( $state === 'ib' ) {
2082  $output .= '</b></i><b>';
2083  $state = 'b';
2084  } elseif ( $state === 'both' ) {
2085  $output .= '<b><i>' . $buffer . '</i>';
2086  $state = 'b';
2087  } else { // $state can be 'b' or ''
2088  $output .= '<i>';
2089  $state .= 'i';
2090  }
2091  } elseif ( $thislen == 3 ) {
2092  // three quotes - open or close bold
2093  if ( $state === 'b' ) {
2094  $output .= '</b>';
2095  $state = '';
2096  } elseif ( $state === 'bi' ) {
2097  $output .= '</i></b><i>';
2098  $state = 'i';
2099  } elseif ( $state === 'ib' ) {
2100  $output .= '</b>';
2101  $state = 'i';
2102  } elseif ( $state === 'both' ) {
2103  $output .= '<i><b>' . $buffer . '</b>';
2104  $state = 'i';
2105  } else { // $state can be 'i' or ''
2106  $output .= '<b>';
2107  $state .= 'b';
2108  }
2109  } elseif ( $thislen == 5 ) {
2110  // five quotes - open or close both separately
2111  if ( $state === 'b' ) {
2112  $output .= '</b><i>';
2113  $state = 'i';
2114  } elseif ( $state === 'i' ) {
2115  $output .= '</i><b>';
2116  $state = 'b';
2117  } elseif ( $state === 'bi' ) {
2118  $output .= '</i></b>';
2119  $state = '';
2120  } elseif ( $state === 'ib' ) {
2121  $output .= '</b></i>';
2122  $state = '';
2123  } elseif ( $state === 'both' ) {
2124  $output .= '<i><b>' . $buffer . '</b></i>';
2125  $state = '';
2126  } else { // ($state == '')
2127  $buffer = '';
2128  $state = 'both';
2129  }
2130  }
2131  }
2132  $i++;
2133  }
2134  // Now close all remaining tags. Notice that the order is important.
2135  if ( $state === 'b' || $state === 'ib' ) {
2136  $output .= '</b>';
2137  }
2138  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
2139  $output .= '</i>';
2140  }
2141  if ( $state === 'bi' ) {
2142  $output .= '</b>';
2143  }
2144  // There might be lonely ''''', so make sure we have a buffer
2145  if ( $state === 'both' && $buffer ) {
2146  $output .= '<b><i>' . $buffer . '</i></b>';
2147  }
2148  return $output;
2149  }
2150 
2160  private function handleExternalLinks( $text ) {
2161  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
2162  // @phan-suppress-next-line PhanTypeComparisonFromArray See phan issue #3161
2163  if ( $bits === false ) {
2164  throw new RuntimeException( "PCRE failure" );
2165  }
2166  $s = array_shift( $bits );
2167 
2168  $i = 0;
2169  while ( $i < count( $bits ) ) {
2170  $url = $bits[$i++];
2171  $i++; // protocol
2172  $text = $bits[$i++];
2173  $trail = $bits[$i++];
2174 
2175  # The characters '<' and '>' (which were escaped by
2176  # internalRemoveHtmlTags()) should not be included in
2177  # URLs, per RFC 2396.
2178  $m2 = [];
2179  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
2180  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
2181  $url = substr( $url, 0, $m2[0][1] );
2182  }
2183 
2184  # If the link text is an image URL, replace it with an <img> tag
2185  # This happened by accident in the original parser, but some people used it extensively
2186  $img = $this->maybeMakeExternalImage( $text );
2187  if ( $img !== false ) {
2188  $text = $img;
2189  }
2190 
2191  $dtrail = '';
2192 
2193  # Set linktype for CSS
2194  $linktype = 'text';
2195 
2196  # No link text, e.g. [http://domain.tld/some.link]
2197  if ( $text == '' ) {
2198  # Autonumber
2199  $langObj = $this->getTargetLanguage();
2200  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
2201  $linktype = 'autonumber';
2202  } else {
2203  # Have link text, e.g. [http://domain.tld/some.link text]s
2204  # Check for trail
2205  [ $dtrail, $trail ] = Linker::splitTrail( $trail );
2206  }
2207 
2208  // Excluding protocol-relative URLs may avoid many false positives.
2209  if ( preg_match( '/^(?:' . $this->urlUtils->validAbsoluteProtocols() . ')/', $text ) ) {
2210  $text = $this->getTargetLanguageConverter()->markNoConversion( $text );
2211  }
2212 
2213  $url = Sanitizer::cleanUrl( $url );
2214 
2215  # Use the encoded URL
2216  # This means that users can paste URLs directly into the text
2217  # Funny characters like ö aren't valid in URLs anyway
2218  # This was changed in August 2004
2219  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
2220  $this->getExternalLinkAttribs( $url ), $this->getTitle() ) . $dtrail . $trail;
2221 
2222  # Register link in the output object.
2223  $this->mOutput->addExternalLink( $url );
2224  }
2225 
2226  // @phan-suppress-next-line PhanTypeMismatchReturnNullable False positive from array_shift
2227  return $s;
2228  }
2229 
2240  public static function getExternalLinkRel( $url = false, LinkTarget $title = null ) {
2241  $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
2242  $noFollowLinks = $mainConfig->get( MainConfigNames::NoFollowLinks );
2243  $noFollowNsExceptions = $mainConfig->get( MainConfigNames::NoFollowNsExceptions );
2244  $noFollowDomainExceptions = $mainConfig->get( MainConfigNames::NoFollowDomainExceptions );
2245  $ns = $title ? $title->getNamespace() : false;
2246  if ( $noFollowLinks && !in_array( $ns, $noFollowNsExceptions )
2247  && !wfMatchesDomainList( $url, $noFollowDomainExceptions )
2248  ) {
2249  return 'nofollow';
2250  }
2251  return null;
2252  }
2253 
2265  public function getExternalLinkAttribs( $url ) {
2266  $attribs = [];
2267  $rel = self::getExternalLinkRel( $url, $this->getTitle() );
2268 
2269  $target = $this->mOptions->getExternalLinkTarget();
2270  if ( $target ) {
2271  $attribs['target'] = $target;
2272  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
2273  // T133507. New windows can navigate parent cross-origin.
2274  // Including noreferrer due to lacking browser
2275  // support of noopener. Eventually noreferrer should be removed.
2276  if ( $rel !== '' ) {
2277  $rel .= ' ';
2278  }
2279  $rel .= 'noreferrer noopener';
2280  }
2281  }
2282  $attribs['rel'] = $rel;
2283  return $attribs;
2284  }
2285 
2296  public static function normalizeLinkUrl( $url ) {
2297  # Test for RFC 3986 IPv6 syntax
2298  $scheme = '[a-z][a-z0-9+.-]*:';
2299  $userinfo = '(?:[a-z0-9\-._~!$&\'()*+,;=:]|%[0-9a-f]{2})*';
2300  $ipv6Host = '\\[((?:[0-9a-f:]|%3[0-A]|%[46][1-6])+)\\]';
2301  if ( preg_match( "<^(?:{$scheme})?//(?:{$userinfo}@)?{$ipv6Host}(?:[:/?#].*|)$>i", $url, $m ) &&
2302  IPUtils::isValid( rawurldecode( $m[1] ) )
2303  ) {
2304  $isIPv6 = rawurldecode( $m[1] );
2305  } else {
2306  $isIPv6 = false;
2307  }
2308 
2309  # Make sure unsafe characters are encoded
2310  $url = preg_replace_callback(
2311  '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
2312  static function ( $m ) {
2313  return rawurlencode( $m[0] );
2314  },
2315  $url
2316  );
2317 
2318  $ret = '';
2319  $end = strlen( $url );
2320 
2321  # Fragment part - 'fragment'
2322  $start = strpos( $url, '#' );
2323  if ( $start !== false && $start < $end ) {
2324  $ret = self::normalizeUrlComponent(
2325  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
2326  $end = $start;
2327  }
2328 
2329  # Query part - 'query' minus &=+;
2330  $start = strpos( $url, '?' );
2331  if ( $start !== false && $start < $end ) {
2332  $ret = self::normalizeUrlComponent(
2333  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
2334  $end = $start;
2335  }
2336 
2337  # Scheme and path part - 'pchar'
2338  # (we assume no userinfo or encoded colons in the host)
2339  $ret = self::normalizeUrlComponent(
2340  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
2341 
2342  # Fix IPv6 syntax
2343  if ( $isIPv6 !== false ) {
2344  $ipv6Host = "%5B({$isIPv6})%5D";
2345  $ret = preg_replace(
2346  "<^((?:{$scheme})?//(?:{$userinfo}@)?){$ipv6Host}(?=[:/?#]|$)>i",
2347  "$1[$2]",
2348  $ret
2349  );
2350  }
2351 
2352  return $ret;
2353  }
2354 
2355  private static function normalizeUrlComponent( $component, $unsafe ) {
2356  $callback = static function ( $matches ) use ( $unsafe ) {
2357  $char = urldecode( $matches[0] );
2358  $ord = ord( $char );
2359  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2360  # Unescape it
2361  return $char;
2362  } else {
2363  # Leave it escaped, but use uppercase for a-f
2364  return strtoupper( $matches[0] );
2365  }
2366  };
2367  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2368  }
2369 
2378  private function maybeMakeExternalImage( $url ) {
2379  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2380  $imagesexception = !empty( $imagesfrom );
2381  $text = false;
2382  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2383  if ( $imagesexception && is_array( $imagesfrom ) ) {
2384  $imagematch = false;
2385  foreach ( $imagesfrom as $match ) {
2386  if ( strpos( $url, $match ) === 0 ) {
2387  $imagematch = true;
2388  break;
2389  }
2390  }
2391  } elseif ( $imagesexception ) {
2392  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2393  } else {
2394  $imagematch = false;
2395  }
2396 
2397  if ( $this->mOptions->getAllowExternalImages()
2398  || ( $imagesexception && $imagematch )
2399  ) {
2400  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2401  # Image found
2402  $text = Linker::makeExternalImage( $url );
2403  }
2404  }
2405  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2406  && preg_match( self::EXT_IMAGE_REGEX, $url )
2407  ) {
2408  $whitelist = explode(
2409  "\n",
2410  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2411  );
2412 
2413  foreach ( $whitelist as $entry ) {
2414  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2415  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2416  continue;
2417  }
2418  // @phan-suppress-next-line SecurityCheck-ReDoS preg_quote is not wanted here
2419  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2420  # Image matches a whitelist entry
2421  $text = Linker::makeExternalImage( $url );
2422  break;
2423  }
2424  }
2425  }
2426  return $text;
2427  }
2428 
2436  private function handleInternalLinks( $text ) {
2437  $this->mLinkHolders->merge( $this->handleInternalLinks2( $text ) );
2438  return $text;
2439  }
2440 
2446  private function handleInternalLinks2( &$s ) {
2447  static $tc = false, $e1, $e1_img;
2448  # the % is needed to support urlencoded titles as well
2449  if ( !$tc ) {
2450  $tc = Title::legalChars() . '#%';
2451  # Match a link having the form [[namespace:link|alternate]]trail
2452  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2453  # Match cases where there is no "]]", which might still be images
2454  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2455  }
2456 
2457  $holders = new LinkHolderArray(
2458  $this,
2459  $this->getContentLanguageConverter(),
2460  $this->getHookContainer() );
2461 
2462  # split the entire text string on occurrences of [[
2463  $a = StringUtils::explode( '[[', ' ' . $s );
2464  # get the first element (all text up to first [[), and remove the space we added
2465  $s = $a->current();
2466  $a->next();
2467  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2468  $s = substr( $s, 1 );
2469 
2470  $nottalk = !$this->getTitle()->isTalkPage();
2471 
2472  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2473  $e2 = null;
2474  if ( $useLinkPrefixExtension ) {
2475  # Match the end of a line for a word that's not followed by whitespace,
2476  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2477  $charset = $this->contLang->linkPrefixCharset();
2478  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2479  $m = [];
2480  if ( preg_match( $e2, $s, $m ) ) {
2481  $first_prefix = $m[2];
2482  } else {
2483  $first_prefix = false;
2484  }
2485  $prefix = false;
2486  } else {
2487  $first_prefix = false;
2488  $prefix = '';
2489  }
2490 
2491  # Some namespaces don't allow subpages
2492  $useSubpages = $this->nsInfo->hasSubpages(
2493  $this->getTitle()->getNamespace()
2494  );
2495 
2496  # Loop for each link
2497  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2498  # Check for excessive memory usage
2499  if ( $holders->isBig() ) {
2500  # Too big
2501  # Do the existence check, replace the link holders and clear the array
2502  $holders->replace( $s );
2503  $holders->clear();
2504  }
2505 
2506  if ( $useLinkPrefixExtension ) {
2507  // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal $e2 is set under this condition
2508  if ( preg_match( $e2, $s, $m ) ) {
2509  [ , $s, $prefix ] = $m;
2510  } else {
2511  $prefix = '';
2512  }
2513  # first link
2514  if ( $first_prefix ) {
2515  $prefix = $first_prefix;
2516  $first_prefix = false;
2517  }
2518  }
2519 
2520  $might_be_img = false;
2521 
2522  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2523  $text = $m[2];
2524  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2525  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2526  # the real problem is with the $e1 regex
2527  # See T1500.
2528  # Still some problems for cases where the ] is meant to be outside punctuation,
2529  # and no image is in sight. See T4095.
2530  if ( $text !== ''
2531  && substr( $m[3], 0, 1 ) === ']'
2532  && strpos( $text, '[' ) !== false
2533  ) {
2534  $text .= ']'; # so that handleExternalLinks($text) works later
2535  $m[3] = substr( $m[3], 1 );
2536  }
2537  # fix up urlencoded title texts
2538  if ( strpos( $m[1], '%' ) !== false ) {
2539  # Should anchors '#' also be rejected?
2540  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2541  }
2542  $trail = $m[3];
2543  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2544  # Invalid, but might be an image with a link in its caption
2545  $might_be_img = true;
2546  $text = $m[2];
2547  if ( strpos( $m[1], '%' ) !== false ) {
2548  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2549  }
2550  $trail = "";
2551  } else { # Invalid form; output directly
2552  $s .= $prefix . '[[' . $line;
2553  continue;
2554  }
2555 
2556  // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset preg_match success when reached here
2557  $origLink = ltrim( $m[1], ' ' );
2558 
2559  # Don't allow internal links to pages containing
2560  # PROTO: where PROTO is a valid URL protocol; these
2561  # should be external links.
2562  if ( preg_match( '/^(?i:' . $this->urlUtils->validProtocols() . ')/', $origLink ) ) {
2563  $s .= $prefix . '[[' . $line;
2564  continue;
2565  }
2566 
2567  # Make subpage if necessary
2568  if ( $useSubpages ) {
2569  $link = Linker::normalizeSubpageLink(
2570  $this->getTitle(), $origLink, $text
2571  );
2572  } else {
2573  $link = $origLink;
2574  }
2575 
2576  // \x7f isn't a default legal title char, so most likely strip
2577  // markers will force us into the "invalid form" path above. But,
2578  // just in case, let's assert that xmlish tags aren't valid in
2579  // the title position.
2580  $unstrip = $this->mStripState->killMarkers( $link );
2581  $noMarkers = ( $unstrip === $link );
2582 
2583  $nt = $noMarkers ? Title::newFromText( $link ) : null;
2584  if ( $nt === null ) {
2585  $s .= $prefix . '[[' . $line;
2586  continue;
2587  }
2588 
2589  $ns = $nt->getNamespace();
2590  $iw = $nt->getInterwiki();
2591 
2592  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2593 
2594  if ( $might_be_img ) { # if this is actually an invalid link
2595  if ( $ns === NS_FILE && $noforce ) { # but might be an image
2596  $found = false;
2597  while ( true ) {
2598  # look at the next 'line' to see if we can close it there
2599  $a->next();
2600  $next_line = $a->current();
2601  if ( $next_line === false || $next_line === null ) {
2602  break;
2603  }
2604  $m = explode( ']]', $next_line, 3 );
2605  if ( count( $m ) == 3 ) {
2606  # the first ]] closes the inner link, the second the image
2607  $found = true;
2608  $text .= "[[{$m[0]}]]{$m[1]}";
2609  $trail = $m[2];
2610  break;
2611  } elseif ( count( $m ) == 2 ) {
2612  # if there's exactly one ]] that's fine, we'll keep looking
2613  $text .= "[[{$m[0]}]]{$m[1]}";
2614  } else {
2615  # if $next_line is invalid too, we need look no further
2616  $text .= '[[' . $next_line;
2617  break;
2618  }
2619  }
2620  if ( !$found ) {
2621  # we couldn't find the end of this imageLink, so output it raw
2622  # but don't ignore what might be perfectly normal links in the text we've examined
2623  $holders->merge( $this->handleInternalLinks2( $text ) );
2624  $s .= "{$prefix}[[$link|$text";
2625  # note: no $trail, because without an end, there *is* no trail
2626  continue;
2627  }
2628  } else { # it's not an image, so output it raw
2629  $s .= "{$prefix}[[$link|$text";
2630  # note: no $trail, because without an end, there *is* no trail
2631  continue;
2632  }
2633  }
2634 
2635  $wasblank = ( $text == '' );
2636  if ( $wasblank ) {
2637  $text = $link;
2638  if ( !$noforce ) {
2639  # Strip off leading ':'
2640  $text = substr( $text, 1 );
2641  }
2642  } else {
2643  # T6598 madness. Handle the quotes only if they come from the alternate part
2644  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2645  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2646  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2647  $text = $this->doQuotes( $text );
2648  }
2649 
2650  # Link not escaped by : , create the various objects
2651  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2652  # Interwikis
2653  if (
2654  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2655  MediaWikiServices::getInstance()->getLanguageNameUtils()
2656  ->getLanguageName(
2657  $iw,
2658  LanguageNameUtils::AUTONYMS,
2659  LanguageNameUtils::DEFINED
2660  )
2661  || in_array( $iw, $this->svcOptions->get( MainConfigNames::ExtraInterlanguageLinkPrefixes ) )
2662  )
2663  ) {
2664  # T26502: filter duplicates
2665  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2666  $this->mLangLinkLanguages[$iw] = true;
2667  $this->mOutput->addLanguageLink( $nt->getFullText() );
2668  }
2669 
2673  $s = rtrim( $s . $prefix ) . $trail; # T175416
2674  continue;
2675  }
2676 
2677  if ( $ns === NS_FILE ) {
2678  if ( !$this->badFileLookup->isBadFile( $nt->getDBkey(), $this->getTitle() ) ) {
2679  if ( $wasblank ) {
2680  # if no parameters were passed, $text
2681  # becomes something like "File:Foo.png",
2682  # which we don't want to pass on to the
2683  # image generator
2684  $text = '';
2685  } else {
2686  # recursively parse links inside the image caption
2687  # actually, this will parse them in any other parameters, too,
2688  # but it might be hard to fix that, and it doesn't matter ATM
2689  $text = $this->handleExternalLinks( $text );
2690  $holders->merge( $this->handleInternalLinks2( $text ) );
2691  }
2692  # cloak any absolute URLs inside the image markup, so handleExternalLinks() won't touch them
2693  $s .= $prefix . $this->armorLinks(
2694  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2695  continue;
2696  }
2697  } elseif ( $ns === NS_CATEGORY ) {
2701  $s = rtrim( $s . $prefix ) . $trail; # T2087, T87753
2702 
2703  if ( $wasblank ) {
2704  $sortkey = $this->mOutput->getPageProperty( 'defaultsort' ) ?? '';
2705  } else {
2706  $sortkey = $text;
2707  }
2708  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2709  $sortkey = str_replace( "\n", '', $sortkey );
2710  $sortkey = $this->getTargetLanguageConverter()->convertCategoryKey( $sortkey );
2711  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2712 
2713  continue;
2714  }
2715  }
2716 
2717  # Self-link checking. For some languages, variants of the title are checked in
2718  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2719  # for linking to a different variant.
2720  if ( $ns !== NS_SPECIAL && $nt->equals( $this->getTitle() ) ) {
2721  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail, '',
2723  continue;
2724  }
2725 
2726  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2727  # @todo FIXME: Should do batch file existence checks, see comment below
2728  if ( $ns === NS_MEDIA ) {
2729  # Give extensions a chance to select the file revision for us
2730  $options = [];
2731  $descQuery = false;
2732  $this->hookRunner->onBeforeParserFetchFileAndTitle(
2733  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
2734  $this, $nt, $options, $descQuery
2735  );
2736  # Fetch and register the file (file title may be different via hooks)
2737  [ $file, $nt ] = $this->fetchFileAndTitle( $nt, $options );
2738  # Cloak with NOPARSE to avoid replacement in handleExternalLinks
2739  $s .= $prefix . $this->armorLinks(
2740  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2741  continue;
2742  }
2743 
2744  # Some titles, such as valid special pages or files in foreign repos, should
2745  # be shown as bluelinks even though they're not included in the page table
2746  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2747  # batch file existence checks for NS_FILE and NS_MEDIA
2748  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2749  $this->mOutput->addLink( $nt );
2750  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2751  } else {
2752  # Links will be added to the output link list after checking
2753  $s .= $holders->makeHolder( $nt, $text, $trail, $prefix );
2754  }
2755  }
2756  return $holders;
2757  }
2758 
2772  private function makeKnownLinkHolder( LinkTarget $nt, $text = '', $trail = '', $prefix = '' ) {
2773  [ $inside, $trail ] = Linker::splitTrail( $trail );
2774 
2775  if ( $text == '' ) {
2776  $text = htmlspecialchars( $this->titleFormatter->getPrefixedText( $nt ) );
2777  }
2778 
2779  $link = $this->getLinkRenderer()->makeKnownLink(
2780  $nt, new HtmlArmor( "$prefix$text$inside" )
2781  );
2782 
2783  return $this->armorLinks( $link ) . $trail;
2784  }
2785 
2796  private function armorLinks( $text ) {
2797  return preg_replace( '/\b((?i)' . $this->urlUtils->validProtocols() . ')/',
2798  self::MARKER_PREFIX . "NOPARSE$1", $text );
2799  }
2800 
2810  public function doBlockLevels( $text, $linestart ) {
2811  wfDeprecated( __METHOD__, '1.35' );
2812  return BlockLevelPass::doBlockLevels( $text, $linestart );
2813  }
2814 
2823  private function expandMagicVariable( $index, $frame = false ) {
2828  if ( isset( $this->mVarCache[$index] ) ) {
2829  return $this->mVarCache[$index];
2830  }
2831 
2832  $ts = new MWTimestamp( $this->mOptions->getTimestamp() /* TS_MW */ );
2833  if ( $this->hookContainer->isRegistered( 'ParserGetVariableValueTs' ) ) {
2834  $s = $ts->getTimestamp( TS_UNIX );
2835  $this->hookRunner->onParserGetVariableValueTs( $this, $s );
2836  $ts = new MWTimestamp( $s );
2837  }
2838 
2839  $value = CoreMagicVariables::expand(
2840  $this, $index, $ts, $this->svcOptions, $this->logger
2841  );
2842 
2843  if ( $value === null ) {
2844  // Not a defined core magic word
2845  // Don't give this hook unrestricted access to mVarCache
2846  $fakeCache = [];
2847  $this->hookRunner->onParserGetVariableValueSwitch(
2848  // @phan-suppress-next-line PhanTypeMismatchArgument $value is passed as null but returned as string
2849  $this, $fakeCache, $index, $value, $frame
2850  );
2851  // Cache the value returned by the hook by falling through here.
2852  // Assert the the hook returned a non-null value for this MV
2853  '@phan-var string $value';
2854  }
2855 
2856  $this->mVarCache[$index] = $value;
2857 
2858  return $value;
2859  }
2860 
2865  private function initializeVariables() {
2866  $variableIDs = $this->magicWordFactory->getVariableIDs();
2867  $substIDs = $this->magicWordFactory->getSubstIDs();
2868 
2869  $this->mVariables = $this->magicWordFactory->newArray( $variableIDs );
2870  $this->mSubstWords = $this->magicWordFactory->newArray( $substIDs );
2871  }
2872 
2891  public function preprocessToDom( $text, $flags = 0 ) {
2892  return $this->getPreprocessor()->preprocessToObj( $text, $flags );
2893  }
2894 
2916  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2917  # Is there any text? Also, Prevent too big inclusions!
2918  $textSize = strlen( $text );
2919  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2920  return $text;
2921  }
2922 
2923  if ( $frame === false ) {
2924  $frame = $this->getPreprocessor()->newFrame();
2925  } elseif ( !( $frame instanceof PPFrame ) ) {
2926  $this->logger->debug(
2927  __METHOD__ . " called using plain parameters instead of " .
2928  "a PPFrame instance. Creating custom frame."
2929  );
2930  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2931  }
2932 
2933  $dom = $this->preprocessToDom( $text );
2934  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2935  $text = $frame->expand( $dom, $flags );
2936 
2937  return $text;
2938  }
2939 
2967  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2968  # does no harm if $current and $max are present but are unnecessary for the message
2969  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2970  # only during preview, and that would split the parser cache unnecessarily.
2971  $this->mOutput->addWarningMsg(
2972  "$limitationType-warning",
2973  Message::numParam( $current ),
2974  Message::numParam( $max )
2975  );
2976  $this->addTrackingCategory( "$limitationType-category" );
2977  }
2978 
2992  public function braceSubstitution( array $piece, PPFrame $frame ) {
2993  // Flags
2994 
2995  // $text has been filled
2996  $found = false;
2997  $text = '';
2998  // wiki markup in $text should be escaped
2999  $nowiki = false;
3000  // $text is HTML, armour it against wikitext transformation
3001  $isHTML = false;
3002  // Force interwiki transclusion to be done in raw mode not rendered
3003  $forceRawInterwiki = false;
3004  // $text is a DOM node needing expansion in a child frame
3005  $isChildObj = false;
3006  // $text is a DOM node needing expansion in the current frame
3007  $isLocalObj = false;
3008 
3009  # Title object, where $text came from
3010  $title = false;
3011 
3012  # $part1 is the bit before the first |, and must contain only title characters.
3013  # Various prefixes will be stripped from it later.
3014  $titleWithSpaces = $frame->expand( $piece['title'] );
3015  $part1 = trim( $titleWithSpaces );
3016  $titleText = false;
3017 
3018  # Original title text preserved for various purposes
3019  $originalTitle = $part1;
3020 
3021  # $args is a list of argument nodes, starting from index 0, not including $part1
3022  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3023  # below won't work b/c this $args isn't an object
3024  $args = ( $piece['parts'] == null ) ? [] : $piece['parts'];
3025 
3026  $profileSection = null; // profile templates
3027 
3028  $sawDeprecatedTemplateEquals = false; // T91154
3029 
3030  # SUBST
3031  // @phan-suppress-next-line PhanImpossibleCondition
3032  if ( !$found ) {
3033  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3034 
3035  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3036  # Decide whether to expand template or keep wikitext as-is.
3037  if ( $this->ot['wiki'] ) {
3038  if ( $substMatch === false ) {
3039  $literal = true; # literal when in PST with no prefix
3040  } else {
3041  $literal = false; # expand when in PST with subst: or safesubst:
3042  }
3043  } else {
3044  if ( $substMatch == 'subst' ) {
3045  $literal = true; # literal when not in PST with plain subst:
3046  } else {
3047  $literal = false; # expand when not in PST with safesubst: or no prefix
3048  }
3049  }
3050  if ( $literal ) {
3051  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3052  $isLocalObj = true;
3053  $found = true;
3054  }
3055  }
3056 
3057  # Variables
3058  if ( !$found && $args->getLength() == 0 ) {
3059  $id = $this->mVariables->matchStartToEnd( $part1 );
3060  if ( $id !== false ) {
3061  if ( strpos( $part1, ':' ) !== false ) {
3063  'Registering a magic variable with a name including a colon',
3064  '1.39', false, false
3065  );
3066  }
3067  $text = $this->expandMagicVariable( $id, $frame );
3068  $found = true;
3069  }
3070  }
3071 
3072  # MSG, MSGNW and RAW
3073  if ( !$found ) {
3074  # Check for MSGNW:
3075  $mwMsgnw = $this->magicWordFactory->get( 'msgnw' );
3076  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3077  $nowiki = true;
3078  } else {
3079  # Remove obsolete MSG:
3080  $mwMsg = $this->magicWordFactory->get( 'msg' );
3081  $mwMsg->matchStartAndRemove( $part1 );
3082  }
3083 
3084  # Check for RAW:
3085  $mwRaw = $this->magicWordFactory->get( 'raw' );
3086  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3087  $forceRawInterwiki = true;
3088  }
3089  }
3090 
3091  # Parser functions
3092  if ( !$found ) {
3093  $colonPos = strpos( $part1, ':' );
3094  if ( $colonPos !== false ) {
3095  $func = substr( $part1, 0, $colonPos );
3096  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3097  $argsLength = $args->getLength();
3098  for ( $i = 0; $i < $argsLength; $i++ ) {
3099  $funcArgs[] = $args->item( $i );
3100  }
3101 
3102  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3103 
3104  // Extract any forwarded flags
3105  if ( isset( $result['title'] ) ) {
3106  $title = $result['title'];
3107  }
3108  if ( isset( $result['found'] ) ) {
3109  $found = $result['found'];
3110  }
3111  if ( array_key_exists( 'text', $result ) ) {
3112  // a string or null
3113  $text = $result['text'];
3114  }
3115  if ( isset( $result['nowiki'] ) ) {
3116  $nowiki = $result['nowiki'];
3117  }
3118  if ( isset( $result['isHTML'] ) ) {
3119  $isHTML = $result['isHTML'];
3120  }
3121  if ( isset( $result['forceRawInterwiki'] ) ) {
3122  $forceRawInterwiki = $result['forceRawInterwiki'];
3123  }
3124  if ( isset( $result['isChildObj'] ) ) {
3125  $isChildObj = $result['isChildObj'];
3126  }
3127  if ( isset( $result['isLocalObj'] ) ) {
3128  $isLocalObj = $result['isLocalObj'];
3129  }
3130  }
3131  }
3132 
3133  # Finish mangling title and then check for loops.
3134  # Set $title to a Title object and $titleText to the PDBK
3135  if ( !$found ) {
3136  $ns = NS_TEMPLATE;
3137  # Split the title into page and subpage
3138  $subpage = '';
3139  $relative = Linker::normalizeSubpageLink(
3140  $this->getTitle(), $part1, $subpage
3141  );
3142  if ( $part1 !== $relative ) {
3143  $part1 = $relative;
3144  $ns = $this->getTitle()->getNamespace();
3145  }
3146  $title = Title::newFromText( $part1, $ns );
3147  if ( $title ) {
3148  $titleText = $title->getPrefixedText();
3149  # Check for language variants if the template is not found
3150  if ( $this->getTargetLanguageConverter()->hasVariants() && $title->getArticleID() == 0 ) {
3151  $this->getTargetLanguageConverter()->findVariantLink( $part1, $title, true );
3152  }
3153  # Do recursion depth check
3154  $limit = $this->mOptions->getMaxTemplateDepth();
3155  if ( $frame->depth >= $limit ) {
3156  $found = true;
3157  $text = '<span class="error">'
3158  . wfMessage( 'parser-template-recursion-depth-warning' )
3159  ->numParams( $limit )->inContentLanguage()->text()
3160  . '</span>';
3161  }
3162  }
3163  }
3164 
3165  # Load from database
3166  if ( !$found && $title ) {
3167  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3168  if ( !$title->isExternal() ) {
3169  if ( $title->isSpecialPage()
3170  && $this->mOptions->getAllowSpecialInclusion()
3171  && $this->ot['html']
3172  ) {
3173  $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() );
3174  // Pass the template arguments as URL parameters.
3175  // "uselang" will have no effect since the Language object
3176  // is forced to the one defined in ParserOptions.
3177  $pageArgs = [];
3178  $argsLength = $args->getLength();
3179  for ( $i = 0; $i < $argsLength; $i++ ) {
3180  $bits = $args->item( $i )->splitArg();
3181  if ( strval( $bits['index'] ) === '' ) {
3182  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3183  $value = trim( $frame->expand( $bits['value'] ) );
3184  $pageArgs[$name] = $value;
3185  }
3186  }
3187 
3188  // Create a new context to execute the special page
3189  $context = new RequestContext;
3190  $context->setTitle( $title );
3191  $context->setRequest( new FauxRequest( $pageArgs ) );
3192  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3193  $context->setUser( $this->userFactory->newFromUserIdentity( $this->getUserIdentity() ) );
3194  } else {
3195  // If this page is cached, then we better not be per user.
3196  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3197  }
3198  $context->setLanguage( $this->mOptions->getUserLangObj() );
3199  $ret = $this->specialPageFactory->capturePath( $title, $context, $this->getLinkRenderer() );
3200  if ( $ret ) {
3201  $text = $context->getOutput()->getHTML();
3202  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3203  $found = true;
3204  $isHTML = true;
3205  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3206  $this->mOutput->updateRuntimeAdaptiveExpiry(
3207  $specialPage->maxIncludeCacheTime()
3208  );
3209  }
3210  }
3211  } elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
3212  $found = false; # access denied
3213  $this->logger->debug(
3214  __METHOD__ .
3215  ": template inclusion denied for " . $title->getPrefixedDBkey()
3216  );
3217  } else {
3218  [ $text, $title ] = $this->getTemplateDom( $title );
3219  if ( $text !== false ) {
3220  $found = true;
3221  $isChildObj = true;
3222  if (
3223  $title->getNamespace() === NS_TEMPLATE &&
3224  $title->getDBkey() === '=' &&
3225  $originalTitle === '='
3226  ) {
3227  // Note that we won't get here if `=` is evaluated
3228  // (in the future) as a parser function, nor if
3229  // the Template namespace is given explicitly,
3230  // ie `{{Template:=}}`. Only `{{=}}` triggers.
3231  $sawDeprecatedTemplateEquals = true; // T91154
3232  }
3233  }
3234  }
3235 
3236  # If the title is valid but undisplayable, make a link to it
3237  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3238  $text = "[[:$titleText]]";
3239  $found = true;
3240  }
3241  } elseif ( $title->isTrans() ) {
3242  # Interwiki transclusion
3243  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3244  $text = $this->interwikiTransclude( $title, 'render' );
3245  $isHTML = true;
3246  } else {
3247  $text = $this->interwikiTransclude( $title, 'raw' );
3248  # Preprocess it like a template
3249  $text = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
3250  $isChildObj = true;
3251  }
3252  $found = true;
3253  }
3254 
3255  # Do infinite loop check
3256  # This has to be done after redirect resolution to avoid infinite loops via redirects
3257  if ( !$frame->loopCheck( $title ) ) {
3258  $found = true;
3259  $text = '<span class="error">'
3260  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3261  . '</span>';
3262  $this->addTrackingCategory( 'template-loop-category' );
3263  $this->mOutput->addWarningMsg(
3264  'template-loop-warning',
3265  Message::plaintextParam( $titleText )
3266  );
3267  $this->logger->debug( __METHOD__ . ": template loop broken at '$titleText'" );
3268  }
3269  }
3270 
3271  # If we haven't found text to substitute by now, we're done
3272  # Recover the source wikitext and return it
3273  if ( !$found ) {
3274  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3275  if ( $profileSection ) {
3276  $this->mProfiler->scopedProfileOut( $profileSection );
3277  }
3278  return [ 'object' => $text ];
3279  }
3280 
3281  # Expand DOM-style return values in a child frame
3282  if ( $isChildObj ) {
3283  # Clean up argument array
3284  $newFrame = $frame->newChild( $args, $title );
3285 
3286  if ( $nowiki ) {
3287  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3288  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3289  # Expansion is eligible for the empty-frame cache
3290  $text = $newFrame->cachedExpand( $titleText, $text );
3291  } else {
3292  # Uncached expansion
3293  $text = $newFrame->expand( $text );
3294  }
3295  }
3296  if ( $isLocalObj && $nowiki ) {
3297  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3298  $isLocalObj = false;
3299  }
3300 
3301  if ( $profileSection ) {
3302  $this->mProfiler->scopedProfileOut( $profileSection );
3303  }
3304  if (
3305  $sawDeprecatedTemplateEquals &&
3306  $this->mStripState->unstripBoth( $text ) !== '='
3307  ) {
3308  // T91154: {{=}} is deprecated when it doesn't expand to `=`;
3309  // use {{Template:=}} if you must.
3310  $this->addTrackingCategory( 'template-equals-category' );
3311  $this->mOutput->addWarningMsg( 'template-equals-warning' );
3312  }
3313 
3314  # Replace raw HTML by a placeholder
3315  if ( $isHTML ) {
3316  $text = $this->insertStripItem( $text );
3317  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3318  # Escape nowiki-style return values
3319  $text = wfEscapeWikiText( $text );
3320  } elseif ( is_string( $text )
3321  && !$piece['lineStart']
3322  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3323  ) {
3324  # T2529: if the template begins with a table or block-level
3325  # element, it should be treated as beginning a new line.
3326  # This behavior is somewhat controversial.
3327  $text = "\n" . $text;
3328  }
3329 
3330  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3331  # Error, oversize inclusion
3332  if ( $titleText !== false ) {
3333  # Make a working, properly escaped link if possible (T25588)
3334  $text = "[[:$titleText]]";
3335  } else {
3336  # This will probably not be a working link, but at least it may
3337  # provide some hint of where the problem is
3338  $originalTitle = preg_replace( '/^:/', '', $originalTitle );
3339  $text = "[[:$originalTitle]]";
3340  }
3341  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3342  . 'post-expand include size too large -->' );
3343  $this->limitationWarn( 'post-expand-template-inclusion' );
3344  }
3345 
3346  if ( $isLocalObj ) {
3347  $ret = [ 'object' => $text ];
3348  } else {
3349  $ret = [ 'text' => $text ];
3350  }
3351 
3352  return $ret;
3353  }
3354 
3373  public function callParserFunction( PPFrame $frame, $function, array $args = [] ) {
3374  # Case sensitive functions
3375  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3376  $function = $this->mFunctionSynonyms[1][$function];
3377  } else {
3378  # Case insensitive functions
3379  $function = $this->contLang->lc( $function );
3380  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3381  $function = $this->mFunctionSynonyms[0][$function];
3382  } else {
3383  return [ 'found' => false ];
3384  }
3385  }
3386 
3387  [ $callback, $flags ] = $this->mFunctionHooks[$function];
3388 
3389  $allArgs = [ $this ];
3390  if ( $flags & self::SFH_OBJECT_ARGS ) {
3391  # Convert arguments to PPNodes and collect for appending to $allArgs
3392  $funcArgs = [];
3393  foreach ( $args as $k => $v ) {
3394  if ( $v instanceof PPNode || $k === 0 ) {
3395  $funcArgs[] = $v;
3396  } else {
3397  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3398  }
3399  }
3400 
3401  # Add a frame parameter, and pass the arguments as an array
3402  $allArgs[] = $frame;
3403  $allArgs[] = $funcArgs;
3404  } else {
3405  # Convert arguments to plain text and append to $allArgs
3406  foreach ( $args as $k => $v ) {
3407  if ( $v instanceof PPNode ) {
3408  $allArgs[] = trim( $frame->expand( $v ) );
3409  } elseif ( is_int( $k ) && $k >= 0 ) {
3410  $allArgs[] = trim( $v );
3411  } else {
3412  $allArgs[] = trim( "$k=$v" );
3413  }
3414  }
3415  }
3416 
3417  $result = $callback( ...$allArgs );
3418 
3419  # The interface for function hooks allows them to return a wikitext
3420  # string or an array containing the string and any flags. This mungs
3421  # things around to match what this method should return.
3422  if ( !is_array( $result ) ) {
3423  $result = [
3424  'found' => true,
3425  'text' => $result,
3426  ];
3427  } else {
3428  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3429  $result['text'] = $result[0];
3430  }
3431  unset( $result[0] );
3432  $result += [
3433  'found' => true,
3434  ];
3435  }
3436 
3437  $noparse = true;
3438  $preprocessFlags = 0;
3439  if ( isset( $result['noparse'] ) ) {
3440  $noparse = $result['noparse'];
3441  }
3442  if ( isset( $result['preprocessFlags'] ) ) {
3443  $preprocessFlags = $result['preprocessFlags'];
3444  }
3445 
3446  if ( !$noparse ) {
3447  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3448  $result['isChildObj'] = true;
3449  }
3450 
3451  return $result;
3452  }
3453 
3463  public function getTemplateDom( LinkTarget $title ) {
3464  $cacheTitle = $title;
3465  $titleKey = CacheKeyHelper::getKeyForPage( $title );
3466 
3467  if ( isset( $this->mTplRedirCache[$titleKey] ) ) {
3468  [ $ns, $dbk ] = $this->mTplRedirCache[$titleKey];
3469  $title = Title::makeTitle( $ns, $dbk );
3470  $titleKey = CacheKeyHelper::getKeyForPage( $title );
3471  }
3472  if ( isset( $this->mTplDomCache[$titleKey] ) ) {
3473  return [ $this->mTplDomCache[$titleKey], $title ];
3474  }
3475 
3476  # Cache miss, go to the database
3477  [ $text, $title ] = $this->fetchTemplateAndTitle( $title );
3478 
3479  if ( $text === false ) {
3480  $this->mTplDomCache[$titleKey] = false;
3481  return [ false, $title ];
3482  }
3483 
3484  $dom = $this->preprocessToDom( $text, Preprocessor::DOM_FOR_INCLUSION );
3485  $this->mTplDomCache[$titleKey] = $dom;
3486 
3487  if ( !$title->isSamePageAs( $cacheTitle ) ) {
3488  $this->mTplRedirCache[ CacheKeyHelper::getKeyForPage( $cacheTitle ) ] =
3489  [ $title->getNamespace(), $title->getDBkey() ];
3490  }
3491 
3492  return [ $dom, $title ];
3493  }
3494 
3509  $cacheKey = CacheKeyHelper::getKeyForPage( $link );
3510  if ( !$this->currentRevisionCache ) {
3511  $this->currentRevisionCache = new MapCacheLRU( 100 );
3512  }
3513  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3514  $title = Title::newFromLinkTarget( $link ); // hook signature compat
3515  $revisionRecord =
3516  // Defaults to Parser::statelessFetchRevisionRecord()
3517  call_user_func(
3518  $this->mOptions->getCurrentRevisionRecordCallback(),
3519  $title,
3520  $this
3521  );
3522  if ( $revisionRecord === false ) {
3523  // Parser::statelessFetchRevisionRecord() can return false;
3524  // normalize it to null.
3525  $revisionRecord = null;
3526  }
3527  $this->currentRevisionCache->set( $cacheKey, $revisionRecord );
3528  }
3529  return $this->currentRevisionCache->get( $cacheKey );
3530  }
3531 
3538  public function isCurrentRevisionOfTitleCached( LinkTarget $link ) {
3539  $key = CacheKeyHelper::getKeyForPage( $link );
3540  return (
3541  $this->currentRevisionCache &&
3542  $this->currentRevisionCache->has( $key )
3543  );
3544  }
3545 
3554  public static function statelessFetchRevisionRecord( LinkTarget $link, $parser = null ) {
3555  if ( $link instanceof PageIdentity ) {
3556  // probably a Title, just use it.
3557  $page = $link;
3558  } else {
3559  // XXX: use RevisionStore::getPageForLink()!
3560  // ...but get the info for the current revision at the same time?
3561  // Should RevisionStore::getKnownCurrentRevision accept a LinkTarget?
3562  $page = Title::newFromLinkTarget( $link );
3563  }
3564 
3565  $revRecord = MediaWikiServices::getInstance()
3566  ->getRevisionLookup()
3567  ->getKnownCurrentRevision( $page );
3568  return $revRecord;
3569  }
3570 
3577  public function fetchTemplateAndTitle( LinkTarget $link ) {
3578  // Use Title for compatibility with callbacks and return type
3579  $title = Title::newFromLinkTarget( $link );
3580 
3581  // Defaults to Parser::statelessFetchTemplate()
3582  $templateCb = $this->mOptions->getTemplateCallback();
3583  $stuff = $templateCb( $title, $this );
3584  $revRecord = $stuff['revision-record'] ?? null;
3585 
3586  $text = $stuff['text'];
3587  if ( is_string( $stuff['text'] ) ) {
3588  // We use U+007F DELETE to distinguish strip markers from regular text
3589  $text = strtr( $text, "\x7f", "?" );
3590  }
3591  $finalTitle = $stuff['finalTitle'] ?? $title;
3592  foreach ( ( $stuff['deps'] ?? [] ) as $dep ) {
3593  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3594  if ( $dep['title']->equals( $this->getTitle() ) && $revRecord instanceof RevisionRecord ) {
3595  // Self-transclusion; final result may change based on the new page version
3596  try {
3597  $sha1 = $revRecord->getSha1();
3598  } catch ( RevisionAccessException $e ) {
3599  $sha1 = null;
3600  }
3601  $this->setOutputFlag( ParserOutputFlags::VARY_REVISION_SHA1, 'Self transclusion' );
3602  $this->getOutput()->setRevisionUsedSha1Base36( $sha1 );
3603  }
3604  }
3605 
3606  return [ $text, $finalTitle ];
3607  }
3608 
3619  public static function statelessFetchTemplate( $page, $parser = false ) {
3620  $title = Title::castFromLinkTarget( $page ); // for compatibility with return type
3621  $text = $skip = false;
3622  $finalTitle = $title;
3623  $deps = [];
3624  $revRecord = null;
3625  $contextTitle = $parser ? $parser->getTitle() : null;
3626 
3627  $services = MediaWikiServices::getInstance();
3628  # Loop to fetch the article, with up to 2 redirects
3629  $revLookup = $services->getRevisionLookup();
3630  $hookRunner = new HookRunner( $services->getHookContainer() );
3631  for ( $i = 0; $i < 3 && is_object( $title ); $i++ ) {
3632  # Give extensions a chance to select the revision instead
3633  $revRecord = null; # Assume no hook
3634  $id = false; # Assume current
3635  $origTitle = $title;
3636  $titleChanged = false;
3637  $hookRunner->onBeforeParserFetchTemplateRevisionRecord(
3638  # The $title is a not a PageIdentity, as it may
3639  # contain fragments or even represent an attempt to transclude
3640  # a broken or otherwise-missing Title, which the hook may
3641  # fix up. Similarly, the $contextTitle may represent a special
3642  # page or other page which "exists" as a parsing context but
3643  # is not in the DB.
3644  $contextTitle, $title,
3645  $skip, $revRecord
3646  );
3647 
3648  if ( $skip ) {
3649  $text = false;
3650  $deps[] = [
3651  'title' => $title,
3652  'page_id' => $title->getArticleID(),
3653  'rev_id' => null
3654  ];
3655  break;
3656  }
3657  # Get the revision
3658  if ( !$revRecord ) {
3659  if ( $id ) {
3660  # Handle $id returned by deprecated legacy hook
3661  $revRecord = $revLookup->getRevisionById( $id );
3662  } elseif ( $parser ) {
3663  $revRecord = $parser->fetchCurrentRevisionRecordOfTitle( $title );
3664  } else {
3665  $revRecord = $revLookup->getRevisionByTitle( $title );
3666  }
3667  }
3668  if ( $revRecord ) {
3669  # Update title, as $revRecord may have been changed by hook
3670  $title = Title::newFromLinkTarget(
3671  $revRecord->getPageAsLinkTarget()
3672  );
3673  $deps[] = [
3674  'title' => $title,
3675  'page_id' => $revRecord->getPageId(),
3676  'rev_id' => $revRecord->getId(),
3677  ];
3678  } else {
3679  $deps[] = [
3680  'title' => $title,
3681  'page_id' => $title->getArticleID(),
3682  'rev_id' => null,
3683  ];
3684  }
3685  if ( !$title->equals( $origTitle ) ) {
3686  # If we fetched a rev from a different title, register
3687  # the original title too...
3688  $deps[] = [
3689  'title' => $origTitle,
3690  'page_id' => $origTitle->getArticleID(),
3691  'rev_id' => null,
3692  ];
3693  $titleChanged = true;
3694  }
3695  # If there is no current revision, there is no page
3696  if ( $revRecord === null || $revRecord->getId() === null ) {
3697  $linkCache = $services->getLinkCache();
3698  $linkCache->addBadLinkObj( $title );
3699  }
3700  if ( $revRecord ) {
3701  if ( $titleChanged && !$revRecord->hasSlot( SlotRecord::MAIN ) ) {
3702  // We've added this (missing) title to the dependencies;
3703  // give the hook another chance to redirect it to an
3704  // actual page.
3705  $text = false;
3706  $finalTitle = $title;
3707  continue;
3708  }
3709  if ( $revRecord->hasSlot( SlotRecord::MAIN ) ) { // T276476
3710  $content = $revRecord->getContent( SlotRecord::MAIN );
3711  $text = $content ? $content->getWikitextForTransclusion() : null;
3712  } else {
3713  $text = false;
3714  }
3715 
3716  if ( $text === false || $text === null ) {
3717  $text = false;
3718  break;
3719  }
3720  } elseif ( $title->getNamespace() === NS_MEDIAWIKI ) {
3721  $message = wfMessage( $services->getContentLanguage()->
3722  lcfirst( $title->getText() ) )->inContentLanguage();
3723  if ( !$message->exists() ) {
3724  $text = false;
3725  break;
3726  }
3727  $text = $message->plain();
3728  break;
3729  } else {
3730  break;
3731  }
3732  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable Only reached when content is set
3733  if ( !$content ) {
3734  break;
3735  }
3736  # Redirect?
3737  $finalTitle = $title;
3738  $title = $content->getRedirectTarget();
3739  }
3740 
3741  $retValues = [
3742  // previously, when this also returned a Revision object, we set
3743  // 'revision-record' to false instead of null if it was unavailable,
3744  // so that callers to use isset and then rely on the revision-record
3745  // key instead of the revision key, even if there was no corresponding
3746  // object - we continue to set to false here for backwards compatability
3747  'revision-record' => $revRecord ?: false,
3748  'text' => $text,
3749  'finalTitle' => $finalTitle,
3750  'deps' => $deps
3751  ];
3752  return $retValues;
3753  }
3754 
3763  public function fetchFileAndTitle( LinkTarget $link, array $options = [] ) {
3764  $file = $this->fetchFileNoRegister( $link, $options );
3765 
3766  $time = $file ? $file->getTimestamp() : false;
3767  $sha1 = $file ? $file->getSha1() : false;
3768  # Register the file as a dependency...
3769  $this->mOutput->addImage( $link->getDBkey(), $time, $sha1 );
3770  if ( $file && !$link->isSameLinkAs( $file->getTitle() ) ) {
3771  # Update fetched file title after resolving redirects, etc.
3772  $link = $file->getTitle();
3773  $this->mOutput->addImage( $link->getDBkey(), $time, $sha1 );
3774  }
3775 
3776  $title = Title::newFromLinkTarget( $link ); // for return type compat
3777  return [ $file, $title ];
3778  }
3779 
3790  protected function fetchFileNoRegister( LinkTarget $link, array $options = [] ) {
3791  if ( isset( $options['broken'] ) ) {
3792  $file = false; // broken thumbnail forced by hook
3793  } else {
3794  $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
3795  if ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3796  $file = $repoGroup->findFileFromKey( $options['sha1'], $options );
3797  } else { // get by (name,timestamp)
3798  $file = $repoGroup->findFile( $link, $options );
3799  }
3800  }
3801  return $file;
3802  }
3803 
3813  public function interwikiTransclude( LinkTarget $link, $action ) {
3814  if ( !$this->svcOptions->get( MainConfigNames::EnableScaryTranscluding ) ) {
3815  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3816  }
3817 
3818  // TODO: extract relevant functionality from Title
3819  $title = Title::newFromLinkTarget( $link );
3820 
3821  $url = $title->getFullURL( [ 'action' => $action ] );
3822  if ( strlen( $url ) > 1024 ) {
3823  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3824  }
3825 
3826  $wikiId = $title->getTransWikiID(); // remote wiki ID or false
3827 
3828  $fname = __METHOD__;
3829  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
3830 
3831  $data = $cache->getWithSetCallback(
3832  $cache->makeGlobalKey(
3833  'interwiki-transclude',
3834  ( $wikiId !== false ) ? $wikiId : 'external',
3835  sha1( $url )
3836  ),
3837  $this->svcOptions->get( MainConfigNames::TranscludeCacheExpiry ),
3838  function ( $oldValue, &$ttl ) use ( $url, $fname, $cache ) {
3839  $req = $this->httpRequestFactory->create( $url, [], $fname );
3840 
3841  $status = $req->execute(); // Status object
3842  if ( !$status->isOK() ) {
3843  $ttl = $cache::TTL_UNCACHEABLE;
3844  } elseif ( $req->getResponseHeader( 'X-Database-Lagged' ) !== null ) {
3845  $ttl = min( $cache::TTL_LAGGED, $ttl );
3846  }
3847 
3848  return [
3849  'text' => $status->isOK() ? $req->getContent() : null,
3850  'code' => $req->getStatus()
3851  ];
3852  },
3853  [
3854  'checkKeys' => ( $wikiId !== false )
3855  ? [ $cache->makeGlobalKey( 'interwiki-page', $wikiId, $title->getDBkey() ) ]
3856  : [],
3857  'pcGroup' => 'interwiki-transclude:5',
3858  'pcTTL' => $cache::TTL_PROC_LONG
3859  ]
3860  );
3861 
3862  if ( is_string( $data['text'] ) ) {
3863  $text = $data['text'];
3864  } elseif ( $data['code'] != 200 ) {
3865  // Though we failed to fetch the content, this status is useless.
3866  $text = wfMessage( 'scarytranscludefailed-httpstatus' )
3867  ->params( $url, $data['code'] )->inContentLanguage()->text();
3868  } else {
3869  $text = wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3870  }
3871 
3872  return $text;
3873  }
3874 
3885  public function argSubstitution( array $piece, PPFrame $frame ) {
3886  $error = false;
3887  $parts = $piece['parts'];
3888  $nameWithSpaces = $frame->expand( $piece['title'] );
3889  $argName = trim( $nameWithSpaces );
3890  $object = false;
3891  $text = $frame->getArgument( $argName );
3892  if ( $text === false && $parts->getLength() > 0
3893  && ( $this->ot['html']
3894  || $this->ot['pre']
3895  || ( $this->ot['wiki'] && $frame->isTemplate() )
3896  )
3897  ) {
3898  # No match in frame, use the supplied default
3899  $object = $parts->item( 0 )->getChildren();
3900  }
3901  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3902  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3903  $this->limitationWarn( 'post-expand-template-argument' );
3904  }
3905 
3906  if ( $text === false && $object === false ) {
3907  # No match anywhere
3908  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3909  }
3910  if ( $error !== false ) {
3911  $text .= $error;
3912  }
3913  if ( $object !== false ) {
3914  $ret = [ 'object' => $object ];
3915  } else {
3916  $ret = [ 'text' => $text ];
3917  }
3918 
3919  return $ret;
3920  }
3921 
3926  public function tagNeedsNowikiStrippedInTagPF( string $lowerTagName ): bool {
3927  $parsoidSiteConfig = MediaWikiServices::getInstance()->getParsoidSiteConfig();
3928  return $parsoidSiteConfig->tagNeedsNowikiStrippedInTagPF( $lowerTagName );
3929  }
3930 
3952  public function extensionSubstitution( array $params, PPFrame $frame, bool $processNowiki = false ) {
3953  static $errorStr = '<span class="error">';
3954 
3955  $name = $frame->expand( $params['name'] );
3956  if ( str_starts_with( $name, $errorStr ) ) {
3957  // Probably expansion depth or node count exceeded. Just punt the
3958  // error up.
3959  return $name;
3960  }
3961 
3962  // Parse attributes from XML-like wikitext syntax
3963  $attrText = !isset( $params['attr'] ) ? '' : $frame->expand( $params['attr'] );
3964  if ( str_starts_with( $attrText, $errorStr ) ) {
3965  // See above
3966  return $attrText;
3967  }
3968 
3969  // We can't safely check if the expansion for $content resulted in an
3970  // error, because the content could happen to be the error string
3971  // (T149622).
3972  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3973 
3974  $marker = self::MARKER_PREFIX . "-$name-"
3975  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3976 
3977  $normalizedName = strtolower( $name );
3978  $isNowiki = $normalizedName === 'nowiki';
3979  $markerType = $isNowiki ? 'nowiki' : 'general';
3980  if ( $this->ot['html'] || ( $processNowiki && $isNowiki ) ) {
3981  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3982  // Merge in attributes passed via {{#tag:}} parser function
3983  if ( isset( $params['attributes'] ) ) {
3984  $attributes += $params['attributes'];
3985  }
3986 
3987  if ( isset( $this->mTagHooks[$normalizedName] ) ) {
3988  // Note that $content may be null here, for example if the
3989  // tag is self-closed.
3990  $output = call_user_func_array( $this->mTagHooks[$normalizedName],
3991  [ $content, $attributes, $this, $frame ] );
3992  } else {
3993  $output = '<span class="error">Invalid tag extension name: ' .
3994  htmlspecialchars( $normalizedName ) . '</span>';
3995  }
3996 
3997  if ( is_array( $output ) ) {
3998  // Extract flags
3999  $flags = $output;
4000  $output = $flags[0];
4001  if ( isset( $flags['markerType'] ) ) {
4002  $markerType = $flags['markerType'];
4003  }
4004  }
4005  } else {
4006  // We're substituting a {{subst:#tag:}} parser function.
4007  // Convert the attributes it passed into the XML-like string.
4008  if ( isset( $params['attributes'] ) ) {
4009  foreach ( $params['attributes'] as $attrName => $attrValue ) {
4010  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
4011  htmlspecialchars( $attrValue, ENT_COMPAT ) . '"';
4012  }
4013  }
4014  if ( $content === null ) {
4015  $output = "<$name$attrText/>";
4016  } else {
4017  $close = $params['close'] === null ? '' : $frame->expand( $params['close'] );
4018  if ( str_starts_with( $close, $errorStr ) ) {
4019  // See above
4020  return $close;
4021  }
4022  $output = "<$name$attrText>$content$close";
4023  }
4024  }
4025 
4026  if ( $markerType === 'none' ) {
4027  return $output;
4028  } elseif ( $markerType === 'nowiki' ) {
4029  $this->mStripState->addNoWiki( $marker, $output );
4030  } elseif ( $markerType === 'general' ) {
4031  $this->mStripState->addGeneral( $marker, $output );
4032  } else {
4033  throw new MWException( __METHOD__ . ': invalid marker type' );
4034  }
4035  return $marker;
4036  }
4037 
4045  private function incrementIncludeSize( $type, $size ) {
4046  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
4047  return false;
4048  } else {
4049  $this->mIncludeSizes[$type] += $size;
4050  return true;
4051  }
4052  }
4053 
4059  $this->mExpensiveFunctionCount++;
4060  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
4061  }
4062 
4070  private function handleDoubleUnderscore( $text ) {
4071  # The position of __TOC__ needs to be recorded
4072  $mw = $this->magicWordFactory->get( 'toc' );
4073  if ( $mw->match( $text ) ) {
4074  $this->mShowToc = true;
4075  $this->mForceTocPosition = true;
4076 
4077  # Set a placeholder. At the end we'll fill it in with the TOC.
4078  $text = $mw->replace( self::TOC_PLACEHOLDER, $text, 1 );
4079 
4080  # Only keep the first one.
4081  $text = $mw->replace( '', $text );
4082  # For consistency with all other double-underscores
4083  # (see below)
4084  $this->mOutput->setPageProperty( 'toc', '' );
4085  }
4086 
4087  # Now match and remove the rest of them
4088  $mwa = $this->magicWordFactory->getDoubleUnderscoreArray();
4089  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
4090 
4091  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
4092  $this->mOutput->setNoGallery( true );
4093  }
4094  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
4095  $this->mShowToc = false;
4096  }
4097  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
4098  && $this->getTitle()->getNamespace() === NS_CATEGORY
4099  ) {
4100  $this->addTrackingCategory( 'hidden-category-category' );
4101  }
4102  # (T10068) Allow control over whether robots index a page.
4103  # __INDEX__ always overrides __NOINDEX__, see T16899
4104  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->getTitle()->canUseNoindex() ) {
4105  $this->mOutput->setIndexPolicy( 'noindex' );
4106  $this->addTrackingCategory( 'noindex-category' );
4107  }
4108  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->getTitle()->canUseNoindex() ) {
4109  $this->mOutput->setIndexPolicy( 'index' );
4110  $this->addTrackingCategory( 'index-category' );
4111  }
4112 
4113  # Cache all double underscores in the database
4114  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4115  $this->mOutput->setPageProperty( $key, '' );
4116  }
4117 
4118  return $text;
4119  }
4120 
4127  public function addTrackingCategory( $msg ) {
4128  return $this->trackingCategories->addTrackingCategory(
4129  $this->mOutput, $msg, $this->getPage()
4130  );
4131  }
4132 
4146  public function msg( string $msg, ...$args ): Message {
4147  return wfMessage( $msg, ...$args )
4148  ->inLanguage( $this->getTargetLanguage() )
4149  ->page( $this->getPage() );
4150  }
4151 
4167  private function finalizeHeadings( $text, $origText, $isMain = true ) {
4168  # Inhibit editsection links if requested in the page
4169  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4170  $maybeShowEditLink = false;
4171  } else {
4172  $maybeShowEditLink = true; /* Actual presence will depend on post-cache transforms */
4173  }
4174 
4175  # Get all headlines for numbering them and adding funky stuff like [edit]
4176  # links - this is for later, but we need the number of headlines right now
4177  # NOTE: white space in headings have been trimmed in handleHeadings. They shouldn't
4178  # be trimmed here since whitespace in HTML headings is significant.
4179  $matches = [];
4180  $numMatches = preg_match_all(
4181  '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4182  $text,
4183  $matches
4184  );
4185 
4186  # if there are fewer than 4 headlines in the article, do not show TOC
4187  # unless it's been explicitly enabled.
4188  $enoughToc = $this->mShowToc &&
4189  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4190 
4191  # Allow user to stipulate that a page should have a "new section"
4192  # link added via __NEWSECTIONLINK__
4193  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4194  $this->mOutput->setNewSection( true );
4195  }
4196 
4197  # Allow user to remove the "new section"
4198  # link via __NONEWSECTIONLINK__
4199  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4200  $this->mOutput->setHideNewSection( true );
4201  }
4202 
4203  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4204  # override above conditions and always show TOC above first header
4205  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4206  $this->mShowToc = true;
4207  $enoughToc = true;
4208  }
4209 
4210  # headline counter
4211  $headlineCount = 0;
4212  $haveTocEntries = false;
4213 
4214  # Ugh .. the TOC should have neat indentation levels which can be
4215  # passed to the skin functions. These are determined here
4216  $full = '';
4217  $head = [];
4218  $level = 0;
4219  $tocData = new TOCData();
4220  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4221  $baseTitleText = $this->getTitle()->getPrefixedDBkey();
4222  $oldType = $this->mOutputType;
4223  $this->setOutputType( self::OT_WIKI );
4224  $frame = $this->getPreprocessor()->newFrame();
4225  $root = $this->preprocessToDom( $origText );
4226  $node = $root->getFirstChild();
4227  $cpOffset = 0;
4228  $refers = [];
4229 
4230  $headlines = $numMatches !== false ? $matches[3] : [];
4231 
4232  $maxTocLevel = $this->svcOptions->get( MainConfigNames::MaxTocLevel );
4233  foreach ( $headlines as $headline ) {
4234  $isTemplate = false;
4235  $titleText = false;
4236  $sectionIndex = false;
4237  $markerMatches = [];
4238  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4239  $serial = (int)$markerMatches[1];
4240  [ $titleText, $sectionIndex ] = $this->mHeadings[$serial];
4241  $isTemplate = ( $titleText != $baseTitleText );
4242  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4243  }
4244 
4245  $sectionMetadata = SectionMetadata::fromLegacy( [
4246  "fromtitle" => $titleText ?: null,
4247  "index" => $sectionIndex === false
4248  ? '' : ( ( $isTemplate ? 'T-' : '' ) . $sectionIndex )
4249  ] );
4250  $tocData->addSection( $sectionMetadata );
4251 
4252  $oldLevel = $level;
4253  $level = (int)$matches[1][$headlineCount];
4254  $tocData->processHeading( $oldLevel, $level, $sectionMetadata );
4255 
4256  if ( $tocData->getCurrentTOCLevel() < $maxTocLevel ) {
4257  $haveTocEntries = true;
4258  }
4259 
4260  # The safe header is a version of the header text safe to use for links
4261 
4262  # Remove link placeholders by the link text.
4263  # <!--LINK number-->
4264  # turns into
4265  # link text with suffix
4266  # Do this before unstrip since link text can contain strip markers
4267  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4268 
4269  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4270  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4271 
4272  # Remove any <style> or <script> tags (T198618)
4273  $safeHeadline = preg_replace(
4274  '#<(style|script)(?: [^>]*[^>/])?>.*?</\1>#is',
4275  '',
4276  $safeHeadline
4277  );
4278 
4279  # Strip out HTML (first regex removes any tag not allowed)
4280  # Allowed tags are:
4281  # * <sup> and <sub> (T10393)
4282  # * <i> (T28375)
4283  # * <b> (r105284)
4284  # * <bdi> (T74884)
4285  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4286  # * <s> and <strike> (T35715)
4287  # * <q> (T251672)
4288  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4289  # to allow setting directionality in toc items.
4290  $tocline = preg_replace(
4291  [
4292  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike|q)(?: [^>]*)?>).*?>#',
4293  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4294  ],
4295  [ '', '<$1>' ],
4296  $safeHeadline
4297  );
4298 
4299  # Strip '<span></span>', which is the result from the above if
4300  # <span id="foo"></span> is used to produce an additional anchor
4301  # for a section.
4302  $tocline = str_replace( '<span></span>', '', $tocline );
4303 
4304  $tocline = trim( $tocline );
4305 
4306  # For the anchor, strip out HTML-y stuff period
4307  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4308  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4309 
4310  # Save headline for section edit hint before it's escaped
4311  $headlineHint = $safeHeadline;
4312 
4313  # Decode HTML entities
4314  $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4315 
4316  $safeHeadline = self::normalizeSectionName( $safeHeadline );
4317 
4318  $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4319  $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4320  $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4321  if ( $fallbackHeadline === $safeHeadline ) {
4322  # No reason to have both (in fact, we can't)
4323  $fallbackHeadline = false;
4324  }
4325 
4326  # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4327  $arrayKey = strtolower( $safeHeadline );
4328  if ( $fallbackHeadline === false ) {
4329  $fallbackArrayKey = false;
4330  } else {
4331  $fallbackArrayKey = strtolower( $fallbackHeadline );
4332  }
4333 
4334  # Create the anchor for linking from the TOC to the section
4335  $anchor = $safeHeadline;
4336  $fallbackAnchor = $fallbackHeadline;
4337  if ( isset( $refers[$arrayKey] ) ) {
4338  for ( $i = 2; isset( $refers["{$arrayKey}_$i"] ); ++$i );
4339  $anchor .= "_$i";
4340  $linkAnchor .= "_$i";
4341  $refers["{$arrayKey}_$i"] = true;
4342  } else {
4343  $refers[$arrayKey] = true;
4344  }
4345  if ( $fallbackHeadline !== false && isset( $refers[$fallbackArrayKey] ) ) {
4346  for ( $i = 2; isset( $refers["{$fallbackArrayKey}_$i"] ); ++$i );
4347  $fallbackAnchor .= "_$i";
4348  $refers["{$fallbackArrayKey}_$i"] = true;
4349  } else {
4350  $refers[$fallbackArrayKey] = true;
4351  }
4352 
4353  # Add the section to the section tree
4354  # Find the DOM node for this header
4355  $noOffset = ( $isTemplate || $sectionIndex === false );
4356  while ( $node && !$noOffset ) {
4357  if ( $node->getName() === 'h' ) {
4358  $bits = $node->splitHeading();
4359  if ( $bits['i'] == $sectionIndex ) {
4360  break;
4361  }
4362  }
4363  $cpOffset += mb_strlen(
4364  $this->mStripState->unstripBoth(
4365  $frame->expand( $node, PPFrame::RECOVER_ORIG )
4366  )
4367  );
4368  $node = $node->getNextSibling();
4369  }
4370  $sectionMetadata->line = $tocline;
4371  $sectionMetadata->codepointOffset = ( $noOffset ? null : $cpOffset );
4372  $sectionMetadata->anchor = $anchor;
4373  $sectionMetadata->linkAnchor = $linkAnchor;
4374 
4375  # give headline the correct <h#> tag
4376  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4377  // Output edit section links as markers with styles that can be customized by skins
4378  if ( $isTemplate ) {
4379  # Put a T flag in the section identifier, to indicate to extractSections()
4380  # that sections inside <includeonly> should be counted.
4381  $editsectionPage = $titleText;
4382  $editsectionSection = "T-$sectionIndex";
4383  } else {
4384  $editsectionPage = $this->getTitle()->getPrefixedText();
4385  $editsectionSection = $sectionIndex;
4386  }
4387  $editsectionContent = $headlineHint;
4388  // We use a bit of pesudo-xml for editsection markers. The
4389  // language converter is run later on. Using a UNIQ style marker
4390  // leads to the converter screwing up the tokens when it
4391  // converts stuff. And trying to insert strip tags fails too. At
4392  // this point all real inputted tags have already been escaped,
4393  // so we don't have to worry about a user trying to input one of
4394  // these markers directly. We use a page and section attribute
4395  // to stop the language converter from converting these
4396  // important bits of data, but put the headline hint inside a
4397  // content block because the language converter is supposed to
4398  // be able to convert that piece of data.
4399  // Gets replaced with html in ParserOutput::getText
4400  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage, ENT_COMPAT );
4401  $editlink .= '" section="' . htmlspecialchars( $editsectionSection, ENT_COMPAT ) . '"';
4402  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4403  } else {
4404  $editlink = '';
4405  }
4406  $head[$headlineCount] = Linker::makeHeadline(
4407  $level,
4408  $matches['attrib'][$headlineCount],
4409  $anchor,
4410  $headline,
4411  $editlink,
4412  $fallbackAnchor
4413  );
4414 
4415  $headlineCount++;
4416  }
4417 
4418  $this->setOutputType( $oldType );
4419 
4420  # Never ever show TOC if no headers (or suppressed)
4421  $suppressToc = $this->mOptions->getSuppressTOC();
4422  if ( !$haveTocEntries ) {
4423  $enoughToc = false;
4424  }
4425  $addTOCPlaceholder = false;
4426 
4427  if ( $isMain && !$suppressToc ) {
4428  // We generally output the section information via the API
4429  // even if there isn't "enough" of a ToC to merit showing
4430  // it -- but the "suppress TOC" parser option is set when
4431  // any sections that might be found aren't "really there"
4432  // (ie, JavaScript content that might have spurious === or
4433  // <h2>: T307691) so we will *not* set section information
4434  // in that case.
4435  $this->mOutput->setTOCData( $tocData );
4436 
4437  // T294950: Record a suggestion that the TOC should be shown.
4438  // We shouldn't be looking at ::getTOCHTML() for this because
4439  // that was replaced (T293513); and $tocData will contain sections
4440  // even if there aren't $enoughToc to show (T332243).
4441  // Skins are free to ignore this suggestion and implement their
4442  // own criteria for showing/suppressing TOC (T318186).
4443  if ( $enoughToc ) {
4444  $this->mOutput->setOutputFlag( ParserOutputFlags::SHOW_TOC );
4445  if ( !$this->mForceTocPosition ) {
4446  $addTOCPlaceholder = true;
4447  }
4448  }
4449 
4450  // If __NOTOC__ is used on the page (and not overridden by
4451  // __TOC__ or __FORCETOC__) set the NO_TOC flag to tell
4452  // the skin that although the section information is
4453  // valid, it should perhaps not be presented as a Table Of
4454  // Contents.
4455  if ( !$this->mShowToc ) {
4456  $this->mOutput->setOutputFlag( ParserOutputFlags::NO_TOC );
4457  }
4458  }
4459 
4460  # split up and insert constructed headlines
4461  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4462  $i = 0;
4463 
4464  // build an array of document sections
4465  $sections = [];
4466  foreach ( $blocks as $block ) {
4467  // $head is zero-based, sections aren't.
4468  if ( empty( $head[$i - 1] ) ) {
4469  $sections[$i] = $block;
4470  } else {
4471  $sections[$i] = $head[$i - 1] . $block;
4472  }
4473 
4474  $i++;
4475  }
4476 
4477  if ( $addTOCPlaceholder ) {
4478  // append the TOC at the beginning
4479  // Top anchor now in skin
4480  // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset At least one element when enoughToc is true
4481  $sections[0] .= self::TOC_PLACEHOLDER . "\n";
4482  }
4483 
4484  $full .= implode( '', $sections );
4485 
4486  return $full;
4487  }
4488 
4498  private static function localizeTOC(
4499  ?TOCData $tocData, Language $lang, ?ILanguageConverter $converter
4500  ) {
4501  if ( $tocData === null ) {
4502  return; // Nothing to do
4503  }
4504  foreach ( $tocData->getSections() as $s ) {
4505  // Localize heading
4506  if ( $converter ) {
4507  // T331316: don't use 'convert' or 'convertTo' as these reset
4508  // the language converter state.
4509  $s->line = $converter->convertTo(
4510  $s->line, $converter->getPreferredVariant(), false
4511  );
4512  }
4513  // Localize numbering
4514  $dot = '.';
4515  $pieces = explode( $dot, $s->number );
4516  $numbering = '';
4517  foreach ( $pieces as $i => $p ) {
4518  if ( $i > 0 ) {
4519  $numbering .= $dot;
4520  }
4521  $numbering .= $lang->formatNum( $p );
4522  }
4523  $s->number = $numbering;
4524  }
4525  }
4526 
4539  public function preSaveTransform(
4540  $text,
4541  PageReference $page,
4542  UserIdentity $user,
4543  ParserOptions $options,
4544  $clearState = true
4545  ) {
4546  if ( $clearState ) {
4547  $magicScopeVariable = $this->lock();
4548  }
4549  $this->startParse( $page, $options, self::OT_WIKI, $clearState );
4550  $this->setUser( $user );
4551 
4552  // Strip U+0000 NULL (T159174)
4553  $text = str_replace( "\000", '', $text );
4554 
4555  // We still normalize line endings (including trimming trailing whitespace) for
4556  // backwards-compatibility with other code that just calls PST, but this should already
4557  // be handled in TextContent subclasses
4558  $text = TextContent::normalizeLineEndings( $text );
4559 
4560  if ( $options->getPreSaveTransform() ) {
4561  $text = $this->pstPass2( $text, $user );
4562  }
4563  $text = $this->mStripState->unstripBoth( $text );
4564 
4565  // Trim trailing whitespace again, because the previous steps can introduce it.
4566  $text = rtrim( $text );
4567 
4568  $this->hookRunner->onParserPreSaveTransformComplete( $this, $text );
4569 
4570  $this->setUser( null ); # Reset
4571 
4572  return $text;
4573  }
4574 
4583  private function pstPass2( $text, UserIdentity $user ) {
4584  # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
4585  # $this->contLang here in order to give everyone the same signature and use the default one
4586  # rather than the one selected in each user's preferences. (see also T14815)
4587  $ts = $this->mOptions->getTimestamp();
4588  $timestamp = MWTimestamp::getLocalInstance( $ts );
4589  $ts = $timestamp->format( 'YmdHis' );
4590  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4591 
4592  $d = $this->contLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4593 
4594  # Variable replacement
4595  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4596  $text = $this->replaceVariables( $text );
4597 
4598  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4599  # which may corrupt this parser instance via its wfMessage()->text() call-
4600 
4601  # Signatures
4602  if ( strpos( $text, '~~~' ) !== false ) {
4603  $sigText = $this->getUserSig( $user );
4604  $text = strtr( $text, [
4605  '~~~~~' => $d,
4606  '~~~~' => "$sigText $d",
4607  '~~~' => $sigText
4608  ] );
4609  # The main two signature forms used above are time-sensitive
4610  $this->setOutputFlag( ParserOutputFlags::USER_SIGNATURE, 'User signature detected' );
4611  }
4612 
4613  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4614  $tc = '[' . Title::legalChars() . ']';
4615  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4616 
4617  // [[ns:page (context)|]]
4618  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4619  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4620  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4621  // [[ns:page (context), context|]] (using single, double-width or Arabic comma)
4622  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,|، )$tc+|)\\|]]/";
4623  // [[|page]] (reverse pipe trick: add context from page title)
4624  $p2 = "/\[\[\\|($tc+)]]/";
4625 
4626  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4627  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4628  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4629  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4630 
4631  $t = $this->getTitle()->getText();
4632  $m = [];
4633  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4634  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4635  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4636  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4637  } else {
4638  # if there's no context, don't bother duplicating the title
4639  $text = preg_replace( $p2, '[[\\1]]', $text );
4640  }
4641 
4642  return $text;
4643  }
4644 
4660  public function getUserSig( UserIdentity $user, $nickname = false, $fancySig = null ) {
4661  $username = $user->getName();
4662 
4663  # If not given, retrieve from the user object.
4664  if ( $nickname === false ) {
4665  $nickname = $this->userOptionsLookup->getOption( $user, 'nickname' );
4666  }
4667 
4668  if ( $fancySig === null ) {
4669  $fancySig = $this->userOptionsLookup->getBoolOption( $user, 'fancysig' );
4670  }
4671 
4672  if ( $nickname === null || $nickname === '' ) {
4673  // Empty value results in the default signature (even when fancysig is enabled)
4674  $nickname = $username;
4675  } elseif ( mb_strlen( $nickname ) > $this->svcOptions->get( MainConfigNames::MaxSigChars ) ) {
4676  $nickname = $username;
4677  $this->logger->debug( __METHOD__ . ": $username has overlong signature." );
4678  } elseif ( $fancySig !== false ) {
4679  # Sig. might contain markup; validate this
4680  $isValid = $this->validateSig( $nickname ) !== false;
4681 
4682  # New validator
4683  $sigValidation = $this->svcOptions->get( MainConfigNames::SignatureValidation );
4684  if ( $isValid && $sigValidation === 'disallow' ) {
4685  $parserOpts = new ParserOptions(
4686  $this->mOptions->getUserIdentity(),
4687  $this->contLang
4688  );
4689  $validator = $this->signatureValidatorFactory
4690  ->newSignatureValidator( $user, null, $parserOpts );
4691  $isValid = !$validator->validateSignature( $nickname );
4692  }
4693 
4694  if ( $isValid ) {
4695  # Validated; clean up (if needed) and return it
4696  return $this->cleanSig( $nickname, true );
4697  } else {
4698  # Failed to validate; fall back to the default
4699  $nickname = $username;
4700  $this->logger->debug( __METHOD__ . ": $username has invalid signature." );
4701  }
4702  }
4703 
4704  # Make sure nickname doesnt get a sig in a sig
4705  $nickname = self::cleanSigInSig( $nickname );
4706 
4707  # If we're still here, make it a link to the user page
4708  $userText = wfEscapeWikiText( $username );
4709  $nickText = wfEscapeWikiText( $nickname );
4710  if ( $this->userNameUtils->isTemp( $username ) ) {
4711  $msgName = 'signature-temp';
4712  } elseif ( $user->isRegistered() ) {
4713  $msgName = 'signature';
4714  } else {
4715  $msgName = 'signature-anon';
4716  }
4717 
4718  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4719  ->page( $this->getPage() )->text();
4720  }
4721 
4729  public function validateSig( $text ) {
4730  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4731  }
4732 
4744  public function cleanSig( $text, $parsing = false ) {
4745  if ( !$parsing ) {
4746  global $wgTitle;
4747  $magicScopeVariable = $this->lock();
4748  $this->startParse(
4749  $wgTitle,
4752  true
4753  );
4754  }
4755 
4756  # Option to disable this feature
4757  if ( !$this->mOptions->getCleanSignatures() ) {
4758  return $text;
4759  }
4760 
4761  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4762  # => Move this logic to braceSubstitution()
4763  $substWord = $this->magicWordFactory->get( 'subst' );
4764  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4765  $substText = '{{' . $substWord->getSynonym( 0 );
4766 
4767  $text = preg_replace( $substRegex, $substText, $text );
4768  $text = self::cleanSigInSig( $text );
4769  $dom = $this->preprocessToDom( $text );
4770  $frame = $this->getPreprocessor()->newFrame();
4771  $text = $frame->expand( $dom );
4772 
4773  if ( !$parsing ) {
4774  $text = $this->mStripState->unstripBoth( $text );
4775  }
4776 
4777  return $text;
4778  }
4779 
4787  public static function cleanSigInSig( $text ) {
4788  $text = preg_replace( '/~{3,5}/', '', $text );
4789  return $text;
4790  }
4791 
4808  public static function replaceTableOfContentsMarker( $text, $toc ) {
4809  return preg_replace_callback(
4810  self::TOC_PLACEHOLDER_REGEX,
4811  static function ( array $matches ) use( $toc ) {
4812  return $toc; // Ensure $1 \1 etc are safe to use in $toc
4813  },
4814  $text
4815  );
4816  }
4817 
4829  public function startExternalParse( ?PageReference $page, ParserOptions $options,
4830  $outputType, $clearState = true, $revId = null
4831  ) {
4832  $this->startParse( $page, $options, $outputType, $clearState );
4833  if ( $revId !== null ) {
4834  $this->mRevisionId = $revId;
4835  }
4836  }
4837 
4844  private function startParse( ?PageReference $page, ParserOptions $options,
4845  $outputType, $clearState = true
4846  ) {
4847  $this->setPage( $page );
4848  $this->mOptions = $options;
4849  $this->setOutputType( $outputType );
4850  if ( $clearState ) {
4851  $this->clearState();
4852  }
4853  }
4854 
4864  public function transformMsg( $text, ParserOptions $options, ?PageReference $page = null ) {
4865  static $executing = false;
4866 
4867  # Guard against infinite recursion
4868  if ( $executing ) {
4869  return $text;
4870  }
4871  $executing = true;
4872 
4873  if ( !$page ) {
4874  global $wgTitle;
4875  $page = $wgTitle;
4876  }
4877 
4878  $text = $this->preprocess( $text, $page, $options );
4879 
4880  $executing = false;
4881  return $text;
4882  }
4883 
4904  public function setHook( $tag, callable $callback ) {
4905  $tag = strtolower( $tag );
4906  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4907  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4908  }
4909  $oldVal = $this->mTagHooks[$tag] ?? null;
4910  $this->mTagHooks[$tag] = $callback;
4911  if ( !in_array( $tag, $this->mStripList ) ) {
4912  $this->mStripList[] = $tag;
4913  }
4914 
4915  return $oldVal;
4916  }
4917 
4922  public function clearTagHooks() {
4923  $this->mTagHooks = [];
4924  $this->mStripList = [];
4925  }
4926 
4971  public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
4972  $oldVal = $this->mFunctionHooks[$id][0] ?? null;
4973  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4974 
4975  # Add to function cache
4976  $mw = $this->magicWordFactory->get( $id );
4977  if ( !$mw ) {
4978  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4979  }
4980 
4981  $synonyms = $mw->getSynonyms();
4982  $sensitive = intval( $mw->isCaseSensitive() );
4983 
4984  foreach ( $synonyms as $syn ) {
4985  # Case
4986  if ( !$sensitive ) {
4987  $syn = $this->contLang->lc( $syn );
4988  }
4989  # Add leading hash
4990  if ( !( $flags & self::SFH_NO_HASH ) ) {
4991  $syn = '#' . $syn;
4992  }
4993  # Remove trailing colon
4994  if ( substr( $syn, -1, 1 ) === ':' ) {
4995  $syn = substr( $syn, 0, -1 );
4996  }
4997  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4998  }
4999  return $oldVal;
5000  }
5001 
5008  public function getFunctionHooks() {
5009  return array_keys( $this->mFunctionHooks );
5010  }
5011 
5019  public function replaceLinkHolders( &$text ) {
5020  $this->replaceLinkHoldersPrivate( $text );
5021  }
5022 
5029  private function replaceLinkHoldersPrivate( &$text ) {
5030  $this->mLinkHolders->replace( $text );
5031  }
5032 
5040  private function replaceLinkHoldersText( $text ) {
5041  return $this->mLinkHolders->replaceText( $text );
5042  }
5043 
5058  public function renderImageGallery( $text, array $params ) {
5059  $mode = false;
5060  if ( isset( $params['mode'] ) ) {
5061  $mode = $params['mode'];
5062  }
5063 
5064  try {
5065  $ig = ImageGalleryBase::factory( $mode );
5066  } catch ( ImageGalleryClassNotFoundException $e ) {
5067  // If invalid type set, fallback to default.
5068  $ig = ImageGalleryBase::factory( false );
5069  }
5070 
5071  $ig->setContextTitle( $this->getTitle() );
5072  $ig->setShowBytes( false );
5073  $ig->setShowDimensions( false );
5074  $ig->setShowFilename( false );
5075  $ig->setParser( $this );
5076  $ig->setHideBadImages();
5077  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
5078 
5079  if ( isset( $params['showfilename'] ) ) {
5080  $ig->setShowFilename( true );
5081  } else {
5082  $ig->setShowFilename( false );
5083  }
5084  if ( isset( $params['caption'] ) ) {
5085  // NOTE: We aren't passing a frame here or below. Frame info
5086  // is currently opaque to Parsoid, which acts on OT_PREPROCESS.
5087  // See T107332#4030581
5088  $caption = $this->recursiveTagParse( $params['caption'] );
5089  $ig->setCaptionHtml( $caption );
5090  }
5091  if ( isset( $params['perrow'] ) ) {
5092  $ig->setPerRow( $params['perrow'] );
5093  }
5094  if ( isset( $params['widths'] ) ) {
5095  $ig->setWidths( $params['widths'] );
5096  }
5097  if ( isset( $params['heights'] ) ) {
5098  $ig->setHeights( $params['heights'] );
5099  }
5100  $ig->setAdditionalOptions( $params );
5101 
5102  $enableLegacyMediaDOM = MediaWikiServices::getInstance()->getMainConfig()->get(
5103  MainConfigNames::ParserEnableLegacyMediaDOM
5104  );
5105 
5106  $lines = StringUtils::explode( "\n", $text );
5107  foreach ( $lines as $line ) {
5108  # match lines like these:
5109  # Image:someimage.jpg|This is some image
5110  $matches = [];
5111  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5112  # Skip empty lines
5113  if ( count( $matches ) == 0 ) {
5114  continue;
5115  }
5116 
5117  if ( strpos( $matches[0], '%' ) !== false ) {
5118  $matches[1] = rawurldecode( $matches[1] );
5119  }
5120  $title = Title::newFromText( $matches[1], NS_FILE );
5121  if ( $title === null ) {
5122  # Bogus title. Ignore these so we don't bomb out later.
5123  continue;
5124  }
5125 
5126  # We need to get what handler the file uses, to figure out parameters.
5127  # Note, a hook can override the file name, and chose an entirely different
5128  # file (which potentially could be of a different type and have different handler).
5129  $options = [];
5130  $descQuery = false;
5131  $this->hookRunner->onBeforeParserFetchFileAndTitle(
5132  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
5133  $this, $title, $options, $descQuery
5134  );
5135  # Don't register it now, as TraditionalImageGallery does that later.
5136  $file = $this->fetchFileNoRegister( $title, $options );
5137  $handler = $file ? $file->getHandler() : false;
5138 
5139  $paramMap = [
5140  'img_alt' => 'gallery-internal-alt',
5141  'img_link' => 'gallery-internal-link',
5142  ];
5143  if ( $handler ) {
5144  $paramMap += $handler->getParamMap();
5145  // We don't want people to specify per-image widths.
5146  // Additionally the width parameter would need special casing anyhow.
5147  unset( $paramMap['img_width'] );
5148  }
5149 
5150  $mwArray = $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5151 
5152  $label = '';
5153  $alt = null;
5154  $handlerOptions = [];
5155  $imageOptions = [];
5156  $hasAlt = false;
5157 
5158  if ( isset( $matches[3] ) ) {
5159  // look for an |alt= definition while trying not to break existing
5160  // captions with multiple pipes (|) in it, until a more sensible grammar
5161  // is defined for images in galleries
5162 
5163  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5164  // splitting on '|' is a bit odd, and different from makeImage.
5165  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5166  // Protect LanguageConverter markup
5167  $parameterMatches = StringUtils::delimiterExplode(
5168  '-{', '}-',
5169  '|',
5170  $matches[3],
5171  true /* nested */
5172  );
5173 
5174  foreach ( $parameterMatches as $parameterMatch ) {
5175  [ $magicName, $match ] = $mwArray->matchVariableStartToEnd( $parameterMatch );
5176  if ( !$magicName ) {
5177  // Last pipe wins.
5178  $label = $parameterMatch;
5179  continue;
5180  }
5181 
5182  $paramName = $paramMap[$magicName];
5183  switch ( $paramName ) {
5184  case 'gallery-internal-alt':
5185  $hasAlt = true;
5186  $alt = $this->stripAltText( $match, false );
5187  break;
5188  case 'gallery-internal-link':
5189  $linkValue = $this->stripAltText( $match, false );
5190  if ( preg_match( '/^-{R\|(.*)}-$/', $linkValue ) ) {
5191  // Result of LanguageConverter::markNoConversion
5192  // invoked on an external link.
5193  $linkValue = substr( $linkValue, 4, -2 );
5194  }
5195  [ $type, $target ] = $this->parseLinkParameter( $linkValue );
5196  if ( $type ) {
5197  if ( $type === 'no-link' ) {
5198  $target = true;
5199  }
5200  $imageOptions[$type] = $target;
5201  }
5202  break;
5203  default:
5204  // Must be a handler specific parameter.
5205  if ( $handler->validateParam( $paramName, $match ) ) {
5206  $handlerOptions[$paramName] = $match;
5207  } else {
5208  // Guess not, consider it as caption.
5209  $this->logger->debug(
5210  "$parameterMatch failed parameter validation" );
5211  $label = $parameterMatch;
5212  }
5213  }
5214  }
5215  }
5216 
5217  // Match makeImage when !$hasVisibleCaption
5218  if ( !$hasAlt ) {
5219  if ( $label !== '' ) {
5220  $alt = $this->stripAltText( $label, false );
5221  } else {
5222  if ( $enableLegacyMediaDOM ) {
5223  $alt = $title->getText();
5224  }
5225  }
5226  }
5227  $imageOptions['title'] = $this->stripAltText( $label, false );
5228 
5229  // Match makeImage which sets this unconditionally
5230  $handlerOptions['targetlang'] = $this->getTargetLanguage()->getCode();
5231 
5232  $ig->add(
5233  $title, $label, $alt, '', $handlerOptions,
5234  ImageGalleryBase::LOADING_DEFAULT, $imageOptions
5235  );
5236  }
5237  $html = $ig->toHTML();
5238  $this->hookRunner->onAfterParserFetchFileAndTitle( $this, $ig, $html );
5239  return $html;
5240  }
5241 
5246  private function getImageParams( $handler ) {
5247  if ( $handler ) {
5248  $handlerClass = get_class( $handler );
5249  } else {
5250  $handlerClass = '';
5251  }
5252  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5253  # Initialise static lists
5254  static $internalParamNames = [
5255  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5256  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5257  'bottom', 'text-bottom' ],
5258  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5259  'upright', 'border', 'link', 'alt', 'class' ],
5260  ];
5261  static $internalParamMap;
5262  if ( !$internalParamMap ) {
5263  $internalParamMap = [];
5264  foreach ( $internalParamNames as $type => $names ) {
5265  foreach ( $names as $name ) {
5266  // For grep: img_left, img_right, img_center, img_none,
5267  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5268  // img_bottom, img_text_bottom,
5269  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5270  // img_border, img_link, img_alt, img_class
5271  $magicName = str_replace( '-', '_', "img_$name" );
5272  $internalParamMap[$magicName] = [ $type, $name ];
5273  }
5274  }
5275  }
5276 
5277  # Add handler params
5278  $paramMap = $internalParamMap;
5279  if ( $handler ) {
5280  $handlerParamMap = $handler->getParamMap();
5281  foreach ( $handlerParamMap as $magic => $paramName ) {
5282  $paramMap[$magic] = [ 'handler', $paramName ];
5283  }
5284  } else {
5285  // Parse the size for non-existent files. See T273013
5286  $paramMap[ 'img_width' ] = [ 'handler', 'width' ];
5287  }
5288  $this->mImageParams[$handlerClass] = $paramMap;
5289  $this->mImageParamsMagicArray[$handlerClass] =
5290  $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5291  }
5292  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5293  }
5294 
5304  public function makeImage( LinkTarget $link, $options, $holders = false ) {
5305  # Check if the options text is of the form "options|alt text"
5306  # Options are:
5307  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5308  # * left no resizing, just left align. label is used for alt= only
5309  # * right same, but right aligned
5310  # * none same, but not aligned
5311  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5312  # * center center the image
5313  # * framed Keep original image size, no magnify-button.
5314  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5315  # * upright reduce width for upright images, rounded to full __0 px
5316  # * border draw a 1px border around the image
5317  # * alt Text for HTML alt attribute (defaults to empty)
5318  # * class Set a class for img node
5319  # * link Set the target of the image link. Can be external, interwiki, or local
5320  # vertical-align values (no % or length right now):
5321  # * baseline
5322  # * sub
5323  # * super
5324  # * top
5325  # * text-top
5326  # * middle
5327  # * bottom
5328  # * text-bottom
5329 
5330  # Protect LanguageConverter markup when splitting into parts
5332  '-{', '}-', '|', $options, true /* allow nesting */
5333  );
5334 
5335  # Give extensions a chance to select the file revision for us
5336  $options = [];
5337  $descQuery = false;
5338  $title = Title::castFromLinkTarget( $link ); // hook signature compat
5339  $this->hookRunner->onBeforeParserFetchFileAndTitle(
5340  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
5341  $this, $title, $options, $descQuery
5342  );
5343  # Fetch and register the file (file title may be different via hooks)
5344  [ $file, $link ] = $this->fetchFileAndTitle( $link, $options );
5345 
5346  # Get parameter map
5347  $handler = $file ? $file->getHandler() : false;
5348 
5349  [ $paramMap, $mwArray ] = $this->getImageParams( $handler );
5350 
5351  if ( !$file ) {
5352  $this->addTrackingCategory( 'broken-file-category' );
5353  }
5354 
5355  # Process the input parameters
5356  $caption = '';
5357  $params = [ 'frame' => [], 'handler' => [],
5358  'horizAlign' => [], 'vertAlign' => [] ];
5359  $seenformat = false;
5360  foreach ( $parts as $part ) {
5361  $part = trim( $part );
5362  [ $magicName, $value ] = $mwArray->matchVariableStartToEnd( $part );
5363  $validated = false;
5364  if ( isset( $paramMap[$magicName] ) ) {
5365  [ $type, $paramName ] = $paramMap[$magicName];
5366 
5367  # Special case; width and height come in one variable together
5368  if ( $type === 'handler' && $paramName === 'width' ) {
5369  $parsedWidthParam = self::parseWidthParam( $value );
5370  // Parsoid applies data-(width|height) attributes to broken
5371  // media spans, for client use. See T273013
5372  $validateFunc = static function ( $name, $value ) use ( $handler ) {
5373  return $handler
5374  ? $handler->validateParam( $name, $value )
5375  : $value > 0;
5376  };
5377  if ( isset( $parsedWidthParam['width'] ) ) {
5378  $width = $parsedWidthParam['width'];
5379  if ( $validateFunc( 'width', $width ) ) {
5380  $params[$type]['width'] = $width;
5381  $validated = true;
5382  }
5383  }
5384  if ( isset( $parsedWidthParam['height'] ) ) {
5385  $height = $parsedWidthParam['height'];
5386  if ( $validateFunc( 'height', $height ) ) {
5387  $params[$type]['height'] = $height;
5388  $validated = true;
5389  }
5390  }
5391  # else no validation -- T15436
5392  } else {
5393  if ( $type === 'handler' ) {
5394  # Validate handler parameter
5395  $validated = $handler->validateParam( $paramName, $value );
5396  } else {
5397  # Validate internal parameters
5398  switch ( $paramName ) {
5399  case 'alt':
5400  case 'class':
5401  $validated = true;
5402  $value = $this->stripAltText( $value, $holders );
5403  break;
5404  case 'link':
5405  [ $paramName, $value ] =
5406  $this->parseLinkParameter(
5407  $this->stripAltText( $value, $holders )
5408  );
5409  if ( $paramName ) {
5410  $validated = true;
5411  if ( $paramName === 'no-link' ) {
5412  $value = true;
5413  }
5414  }
5415  break;
5416  case 'manualthumb':
5417  # @todo FIXME: Possibly check validity here for
5418  # manualthumb? downstream behavior seems odd with
5419  # missing manual thumbs.
5420  $value = $this->stripAltText( $value, $holders );
5421  // fall through
5422  case 'frameless':
5423  case 'framed':
5424  case 'thumbnail':
5425  // use first appearing option, discard others.
5426  $validated = !$seenformat;
5427  $seenformat = true;
5428  break;
5429  default:
5430  # Most other things appear to be empty or numeric...
5431  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5432  }
5433  }
5434 
5435  if ( $validated ) {
5436  $params[$type][$paramName] = $value;
5437  }
5438  }
5439  }
5440  if ( !$validated ) {
5441  $caption = $part;
5442  }
5443  }
5444 
5445  # Process alignment parameters
5446  if ( $params['horizAlign'] !== [] ) {
5447  $params['frame']['align'] = array_key_first( $params['horizAlign'] );
5448  }
5449  if ( $params['vertAlign'] !== [] ) {
5450  $params['frame']['valign'] = array_key_first( $params['vertAlign'] );
5451  }
5452 
5453  $params['frame']['caption'] = $caption;
5454 
5455  $enableLegacyMediaDOM = MediaWikiServices::getInstance()->getMainConfig()->get(
5456  MainConfigNames::ParserEnableLegacyMediaDOM
5457  );
5458 
5459  # Will the image be presented in a frame, with the caption below?
5460  // @phan-suppress-next-line PhanImpossibleCondition
5461  $hasVisibleCaption = isset( $params['frame']['framed'] )
5462  // @phan-suppress-next-line PhanImpossibleCondition
5463  || isset( $params['frame']['thumbnail'] )
5464  // @phan-suppress-next-line PhanImpossibleCondition
5465  || isset( $params['frame']['manualthumb'] );
5466 
5467  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5468  # came to also set the caption, ordinary text after the image -- which
5469  # makes no sense, because that just repeats the text multiple times in
5470  # screen readers. It *also* came to set the title attribute.
5471  # Now that we have an alt attribute, we should not set the alt text to
5472  # equal the caption: that's worse than useless, it just repeats the
5473  # text. This is the framed/thumbnail case. If there's no caption, we
5474  # use the unnamed parameter for alt text as well, just for the time be-
5475  # ing, if the unnamed param is set and the alt param is not.
5476  # For the future, we need to figure out if we want to tweak this more,
5477  # e.g., introducing a title= parameter for the title; ignoring the un-
5478  # named parameter entirely for images without a caption; adding an ex-
5479  # plicit caption= parameter and preserving the old magic unnamed para-
5480  # meter for BC; ...
5481  if ( $hasVisibleCaption ) {
5482  if (
5483  // @phan-suppress-next-line PhanImpossibleCondition
5484  $caption === '' && !isset( $params['frame']['alt'] ) &&
5485  $enableLegacyMediaDOM
5486  ) {
5487  # No caption or alt text, add the filename as the alt text so
5488  # that screen readers at least get some description of the image
5489  $params['frame']['alt'] = $link->getText();
5490  }
5491  # Do not set $params['frame']['title'] because tooltips are unnecessary
5492  # for framed images, the caption is visible
5493  } else {
5494  // @phan-suppress-next-line PhanImpossibleCondition
5495  if ( !isset( $params['frame']['alt'] ) ) {
5496  # No alt text, use the "caption" for the alt text
5497  if ( $caption !== '' ) {
5498  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5499  } elseif ( $enableLegacyMediaDOM ) {
5500  # No caption, fall back to using the filename for the
5501  # alt text
5502  $params['frame']['alt'] = $link->getText();
5503  }
5504  }
5505  # Use the "caption" for the tooltip text
5506  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5507  }
5508  $params['handler']['targetlang'] = $this->getTargetLanguage()->getCode();
5509 
5510  // hook signature compat again, $link may have changed
5511  $title = Title::castFromLinkTarget( $link );
5512  $this->hookRunner->onParserMakeImageParams( $title, $file, $params, $this );
5513 
5514  # Linker does the rest
5515  $time = $options['time'] ?? false;
5516  // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
5517  $ret = Linker::makeImageLink( $this, $link, $file, $params['frame'], $params['handler'],
5518  $time, $descQuery, $this->mOptions->getThumbSize() );
5519 
5520  # Give the handler a chance to modify the parser object
5521  if ( $handler ) {
5522  $handler->parserTransformHook( $this, $file );
5523  }
5524  if ( $file ) {
5525  $this->modifyImageHtml( $file, $params, $ret );
5526  }
5527 
5528  return $ret;
5529  }
5530 
5549  private function parseLinkParameter( $value ) {
5550  $chars = self::EXT_LINK_URL_CLASS;
5551  $addr = self::EXT_LINK_ADDR;
5552  $prots = $this->urlUtils->validProtocols();
5553  $type = null;
5554  $target = false;
5555  if ( $value === '' ) {
5556  $type = 'no-link';
5557  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5558  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value ) ) {
5559  $this->mOutput->addExternalLink( $value );
5560  $type = 'link-url';
5561  $target = $value;
5562  }
5563  } else {
5564  // Percent-decode link arguments for consistency with wikilink
5565  // handling (T216003#7836261).
5566  //
5567  // There's slight concern here though. The |link= option supports
5568  // two formats, link=Test%22test vs link=[[Test%22test]], both of
5569  // which are about to be decoded.
5570  //
5571  // In the former case, the decoding here is straightforward and
5572  // desirable.
5573  //
5574  // In the latter case, there's a potential for double decoding,
5575  // because the wikilink syntax has a higher precedence and has
5576  // already been parsed as a link before we get here. $value
5577  // has had stripAltText() called on it, which in turn calls
5578  // replaceLinkHoldersText() on the link. So, the text we're
5579  // getting at this point has already been percent decoded.
5580  //
5581  // The problematic case is if %25 is in the title, since that
5582  // decodes to %, which could combine with trailing characters.
5583  // However, % is not a valid link title character, so it would
5584  // not parse as a link and the string we received here would
5585  // still contain the encoded %25.
5586  //
5587  // Hence, double decoded is not an issue. See the test,
5588  // "Should not double decode the link option"
5589  if ( strpos( $value, '%' ) !== false ) {
5590  $value = rawurldecode( $value );
5591  }
5592  $linkTitle = Title::newFromText( $value );
5593  if ( $linkTitle ) {
5594  $this->mOutput->addLink( $linkTitle );
5595  $type = 'link-title';
5596  $target = $linkTitle;
5597  }
5598  }
5599  return [ $type, $target ];
5600  }
5601 
5609  public function modifyImageHtml( File $file, array $params, string &$html ) {
5610  $this->hookRunner->onParserModifyImageHTML( $this, $file, $params, $html );
5611  }
5612 
5618  private function stripAltText( $caption, $holders ) {
5619  # Strip bad stuff out of the title (tooltip). We can't just use
5620  # replaceLinkHoldersText() here, because if this function is called
5621  # from handleInternalLinks2(), mLinkHolders won't be up-to-date.
5622  if ( $holders ) {
5623  $tooltip = $holders->replaceText( $caption );
5624  } else {
5625  $tooltip = $this->replaceLinkHoldersText( $caption );
5626  }
5627 
5628  # make sure there are no placeholders in thumbnail attributes
5629  # that are later expanded to html- so expand them now and
5630  # remove the tags
5631  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5632  # Compatibility hack! In HTML certain entity references not terminated
5633  # by a semicolon are decoded (but not if we're in an attribute; that's
5634  # how link URLs get away without properly escaping & in queries).
5635  # But wikitext has always required semicolon-termination of entities,
5636  # so encode & where needed to avoid decode of semicolon-less entities.
5637  # See T209236 and
5638  # https://www.w3.org/TR/html5/syntax.html#named-character-references
5639  # T210437 discusses moving this workaround to Sanitizer::stripAllTags.
5640  $tooltip = preg_replace( "/
5641  & # 1. entity prefix
5642  (?= # 2. followed by:
5643  (?: # a. one of the legacy semicolon-less named entities
5644  A(?:Elig|MP|acute|circ|grave|ring|tilde|uml)|
5645  C(?:OPY|cedil)|E(?:TH|acute|circ|grave|uml)|
5646  GT|I(?:acute|circ|grave|uml)|LT|Ntilde|
5647  O(?:acute|circ|grave|slash|tilde|uml)|QUOT|REG|THORN|
5648  U(?:acute|circ|grave|uml)|Yacute|
5649  a(?:acute|c(?:irc|ute)|elig|grave|mp|ring|tilde|uml)|brvbar|
5650  c(?:cedil|edil|urren)|cent(?!erdot;)|copy(?!sr;)|deg|
5651  divide(?!ontimes;)|e(?:acute|circ|grave|th|uml)|
5652  frac(?:1(?:2|4)|34)|
5653  gt(?!c(?:c|ir)|dot|lPar|quest|r(?:a(?:pprox|rr)|dot|eq(?:less|qless)|less|sim);)|
5654  i(?:acute|circ|excl|grave|quest|uml)|laquo|
5655  lt(?!c(?:c|ir)|dot|hree|imes|larr|quest|r(?:Par|i(?:e|f|));)|
5656  m(?:acr|i(?:cro|ddot))|n(?:bsp|tilde)|
5657  not(?!in(?:E|dot|v(?:a|b|c)|)|ni(?:v(?:a|b|c)|);)|
5658  o(?:acute|circ|grave|rd(?:f|m)|slash|tilde|uml)|
5659  p(?:lusmn|ound)|para(?!llel;)|quot|r(?:aquo|eg)|
5660  s(?:ect|hy|up(?:1|2|3)|zlig)|thorn|times(?!b(?:ar|)|d;)|
5661  u(?:acute|circ|grave|ml|uml)|y(?:acute|en|uml)
5662  )
5663  (?:[^;]|$)) # b. and not followed by a semicolon
5664  # S = study, for efficiency
5665  /Sx", '&amp;', $tooltip );
5666  $tooltip = Sanitizer::stripAllTags( $tooltip );
5667 
5668  return $tooltip;
5669  }
5670 
5680  public function attributeStripCallback( &$text, $frame = false ) {
5681  wfDeprecated( __METHOD__, '1.35' );
5682  $text = $this->replaceVariables( $text, $frame );
5683  $text = $this->mStripState->unstripBoth( $text );
5684  return $text;
5685  }
5686 
5693  public function getTags(): array {
5694  return array_keys( $this->mTagHooks );
5695  }
5696 
5701  public function getFunctionSynonyms() {
5702  return $this->mFunctionSynonyms;
5703  }
5704 
5709  public function getUrlProtocols() {
5710  return $this->urlUtils->validProtocols();
5711  }
5712 
5742  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5743  global $wgTitle; # not generally used but removes an ugly failure mode
5744 
5745  $magicScopeVariable = $this->lock();
5746  $this->startParse(
5747  $wgTitle,
5750  true
5751  );
5752  $outText = '';
5753  $frame = $this->getPreprocessor()->newFrame();
5754 
5755  # Process section extraction flags
5756  $flags = 0;
5757  $sectionParts = explode( '-', $sectionId );
5758  // The section ID may either be a magic string such as 'new' (which should be treated as 0),
5759  // or a numbered section ID in the format of "T-<section index>".
5760  // Explicitly coerce the section index into a number accordingly. (T323373)
5761  $sectionIndex = (int)array_pop( $sectionParts );
5762  foreach ( $sectionParts as $part ) {
5763  if ( $part === 'T' ) {
5765  }
5766  }
5767 
5768  # Check for empty input
5769  if ( strval( $text ) === '' ) {
5770  # Only sections 0 and T-0 exist in an empty document
5771  if ( $sectionIndex === 0 ) {
5772  if ( $mode === 'get' ) {
5773  return '';
5774  }
5775 
5776  return $newText;
5777  } else {
5778  if ( $mode === 'get' ) {
5779  return $newText;
5780  }
5781 
5782  return $text;
5783  }
5784  }
5785 
5786  # Preprocess the text
5787  $root = $this->preprocessToDom( $text, $flags );
5788 
5789  # <h> nodes indicate section breaks
5790  # They can only occur at the top level, so we can find them by iterating the root's children
5791  $node = $root->getFirstChild();
5792 
5793  # Find the target section
5794  if ( $sectionIndex === 0 ) {
5795  # Section zero doesn't nest, level=big
5796  $targetLevel = 1000;
5797  } else {
5798  while ( $node ) {
5799  if ( $node->getName() === 'h' ) {
5800  $bits = $node->splitHeading();
5801  if ( $bits['i'] == $sectionIndex ) {
5802  $targetLevel = $bits['level'];
5803  break;
5804  }
5805  }
5806  if ( $mode === 'replace' ) {
5807  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5808  }
5809  $node = $node->getNextSibling();
5810  }
5811  }
5812 
5813  if ( !$node ) {
5814  # Not found
5815  if ( $mode === 'get' ) {
5816  return $newText;
5817  } else {
5818  return $text;
5819  }
5820  }
5821 
5822  # Find the end of the section, including nested sections
5823  do {
5824  if ( $node->getName() === 'h' ) {
5825  $bits = $node->splitHeading();
5826  $curLevel = $bits['level'];
5827  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable False positive
5828  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5829  break;
5830  }
5831  }
5832  if ( $mode === 'get' ) {
5833  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5834  }
5835  $node = $node->getNextSibling();
5836  } while ( $node );
5837 
5838  # Write out the remainder (in replace mode only)
5839  if ( $mode === 'replace' ) {
5840  # Output the replacement text
5841  # Add two newlines on -- trailing whitespace in $newText is conventionally
5842  # stripped by the editor, so we need both newlines to restore the paragraph gap
5843  # Only add trailing whitespace if there is newText
5844  if ( $newText != "" ) {
5845  $outText .= $newText . "\n\n";
5846  }
5847 
5848  while ( $node ) {
5849  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5850  $node = $node->getNextSibling();
5851  }
5852  }
5853 
5854  # Re-insert stripped tags
5855  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5856 
5857  return $outText;
5858  }
5859 
5875  public function getSection( $text, $sectionId, $defaultText = '' ) {
5876  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5877  }
5878 
5892  public function replaceSection( $oldText, $sectionId, $newText ) {
5893  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5894  }
5895 
5925  public function getFlatSectionInfo( $text ) {
5926  $magicScopeVariable = $this->lock();
5927  $this->startParse(
5928  null,
5931  true
5932  );
5933  $frame = $this->getPreprocessor()->newFrame();
5934  $root = $this->preprocessToDom( $text, 0 );
5935  $node = $root->getFirstChild();
5936  $offset = 0;
5937  $currentSection = [
5938  'index' => 0,
5939  'level' => 0,
5940  'offset' => 0,
5941  'heading' => '',
5942  'text' => ''
5943  ];
5944  $sections = [];
5945 
5946  while ( $node ) {
5947  $nodeText = $frame->expand( $node, PPFrame::RECOVER_ORIG );
5948  if ( $node->getName() === 'h' ) {
5949  $bits = $node->splitHeading();
5950  $sections[] = $currentSection;
5951  $currentSection = [
5952  'index' => $bits['i'],
5953  'level' => $bits['level'],
5954  'offset' => $offset,
5955  'heading' => $nodeText,
5956  'text' => $nodeText
5957  ];
5958  } else {
5959  $currentSection['text'] .= $nodeText;
5960  }
5961  $offset += strlen( $nodeText );
5962  $node = $node->getNextSibling();
5963  }
5964  $sections[] = $currentSection;
5965  return $sections;
5966  }
5967 
5979  public function getRevisionId() {
5980  return $this->mRevisionId;
5981  }
5982 
5989  public function getRevisionRecordObject() {
5990  if ( $this->mRevisionRecordObject ) {
5991  return $this->mRevisionRecordObject;
5992  }
5993 
5994  // NOTE: try to get the RevisionRecord object even if mRevisionId is null.
5995  // This is useful when parsing a revision that has not yet been saved.
5996  // However, if we get back a saved revision even though we are in
5997  // preview mode, we'll have to ignore it, see below.
5998  // NOTE: This callback may be used to inject an OLD revision that was
5999  // already loaded, so "current" is a bit of a misnomer. We can't just
6000  // skip it if mRevisionId is set.
6001  $rev = call_user_func(
6002  $this->mOptions->getCurrentRevisionRecordCallback(),
6003  $this->getTitle(),
6004  $this
6005  );
6006 
6007  if ( !$rev ) {
6008  // The revision record callback returns `false` (not null) to
6009  // indicate that the revision is missing. (See for example
6010  // Parser::statelessFetchRevisionRecord(), the default callback.)
6011  // This API expects `null` instead. (T251952)
6012  return null;
6013  }
6014 
6015  if ( $this->mRevisionId === null && $rev->getId() ) {
6016  // We are in preview mode (mRevisionId is null), and the current revision callback
6017  // returned an existing revision. Ignore it and return null, it's probably the page's
6018  // current revision, which is not what we want here. Note that we do want to call the
6019  // callback to allow the unsaved revision to be injected here, e.g. for
6020  // self-transclusion previews.
6021  return null;
6022  }
6023 
6024  // If the parse is for a new revision, then the callback should have
6025  // already been set to force the object and should match mRevisionId.
6026  // If not, try to fetch by mRevisionId instead.
6027  if ( $this->mRevisionId && $rev->getId() != $this->mRevisionId ) {
6028  $rev = MediaWikiServices::getInstance()
6029  ->getRevisionLookup()
6030  ->getRevisionById( $this->mRevisionId );
6031  }
6032 
6033  $this->mRevisionRecordObject = $rev;
6034 
6035  return $this->mRevisionRecordObject;
6036  }
6037 
6044  public function getRevisionTimestamp() {
6045  if ( $this->mRevisionTimestamp !== null ) {
6046  return $this->mRevisionTimestamp;
6047  }
6048 
6049  # Use specified revision timestamp, falling back to the current timestamp
6050  $revObject = $this->getRevisionRecordObject();
6051  $timestamp = $revObject && $revObject->getTimestamp()
6052  ? $revObject->getTimestamp()
6053  : $this->mOptions->getTimestamp();
6054  $this->mOutput->setRevisionTimestampUsed( $timestamp ); // unadjusted time zone
6055 
6056  # The cryptic '' timezone parameter tells to use the site-default
6057  # timezone offset instead of the user settings.
6058  # Since this value will be saved into the parser cache, served
6059  # to other users, and potentially even used inside links and such,
6060  # it needs to be consistent for all visitors.
6061  $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp, '' );
6062 
6063  return $this->mRevisionTimestamp;
6064  }
6065 
6072  public function getRevisionUser(): ?string {
6073  if ( $this->mRevisionUser === null ) {
6074  $revObject = $this->getRevisionRecordObject();
6075 
6076  # if this template is subst: the revision id will be blank,
6077  # so just use the current user's name
6078  if ( $revObject && $revObject->getUser() ) {
6079  $this->mRevisionUser = $revObject->getUser()->getName();
6080  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
6081  $this->mRevisionUser = $this->getUserIdentity()->getName();
6082  } else {
6083  # Note that we fall through here with
6084  # $this->mRevisionUser still null
6085  }
6086  }
6087  return $this->mRevisionUser;
6088  }
6089 
6096  public function getRevisionSize() {
6097  if ( $this->mRevisionSize === null ) {
6098  $revObject = $this->getRevisionRecordObject();
6099 
6100  # if this variable is subst: the revision id will be blank,
6101  # so just use the parser input size, because the own substitution
6102  # will change the size.
6103  if ( $revObject ) {
6104  $this->mRevisionSize = $revObject->getSize();
6105  } else {
6106  $this->mRevisionSize = $this->mInputSize;
6107  }
6108  }
6109  return $this->mRevisionSize;
6110  }
6111 
6120  public function setDefaultSort( $sort ) {
6121  wfDeprecated( __METHOD__, '1.38' );
6122  $this->mOutput->setPageProperty( 'defaultsort', $sort );
6123  }
6124 
6138  public function getDefaultSort() {
6139  wfDeprecated( __METHOD__, '1.38' );
6140  return $this->mOutput->getPageProperty( 'defaultsort' ) ?? '';
6141  }
6142 
6152  public function getCustomDefaultSort() {
6153  wfDeprecated( __METHOD__, '1.38' );
6154  return $this->mOutput->getPageProperty( 'defaultsort' ) ?? false;
6155  }
6156 
6157  private static function getSectionNameFromStrippedText( $text ) {
6159  $text = Sanitizer::decodeCharReferences( $text );
6160  $text = self::normalizeSectionName( $text );
6161  return $text;
6162  }
6163 
6164  private static function makeAnchor( $sectionName ) {
6165  return '#' . Sanitizer::escapeIdForLink( $sectionName );
6166  }
6167 
6168  private function makeLegacyAnchor( $sectionName ) {
6169  $fragmentMode = $this->svcOptions->get( MainConfigNames::FragmentMode );
6170  if ( isset( $fragmentMode[1] ) && $fragmentMode[1] === 'legacy' ) {
6171  // ForAttribute() and ForLink() are the same for legacy encoding
6173  } else {
6174  $id = Sanitizer::escapeIdForLink( $sectionName );
6175  }
6176 
6177  return "#$id";
6178  }
6179 
6189  public function guessSectionNameFromWikiText( $text ) {
6190  # Strip out wikitext links(they break the anchor)
6191  $text = $this->stripSectionName( $text );
6192  $sectionName = self::getSectionNameFromStrippedText( $text );
6193  return self::makeAnchor( $sectionName );
6194  }
6195 
6206  public function guessLegacySectionNameFromWikiText( $text ) {
6207  # Strip out wikitext links(they break the anchor)
6208  $text = $this->stripSectionName( $text );
6209  $sectionName = self::getSectionNameFromStrippedText( $text );
6210  return $this->makeLegacyAnchor( $sectionName );
6211  }
6212 
6219  public static function guessSectionNameFromStrippedText( $text ) {
6220  $sectionName = self::getSectionNameFromStrippedText( $text );
6221  return self::makeAnchor( $sectionName );
6222  }
6223 
6230  private static function normalizeSectionName( $text ) {
6231  # T90902: ensure the same normalization is applied for IDs as to links
6233  $titleParser = MediaWikiServices::getInstance()->getTitleParser();
6234  '@phan-var MediaWikiTitleCodec $titleParser';
6235  try {
6236 
6237  $parts = $titleParser->splitTitleString( "#$text" );
6238  } catch ( MalformedTitleException $ex ) {
6239  return $text;
6240  }
6241  return $parts['fragment'];
6242  }
6243 
6259  public function stripSectionName( $text ) {
6260  # Strip internal link markup
6261  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
6262  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
6263 
6264  # Strip external link markup
6265  # @todo FIXME: Not tolerant to blank link text
6266  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
6267  # on how many empty links there are on the page - need to figure that out.
6268  $text = preg_replace(
6269  '/\[(?i:' . $this->urlUtils->validProtocols() . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
6270 
6271  # Parse wikitext quotes (italics & bold)
6272  $text = $this->doQuotes( $text );
6273 
6274  # Strip HTML tags
6275  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
6276  return $text;
6277  }
6278 
6292  private function fuzzTestSrvus( $text, PageReference $page, ParserOptions $options,
6293  $outputType = self::OT_HTML
6294  ) {
6295  $magicScopeVariable = $this->lock();
6296  $this->startParse( $page, $options, $outputType, true );
6297 
6298  $text = $this->replaceVariables( $text );
6299  $text = $this->mStripState->unstripBoth( $text );
6300  $text = Sanitizer::internalRemoveHtmlTags( $text );
6301  return $text;
6302  }
6303 
6315  private function fuzzTestPst( $text, PageReference $page, ParserOptions $options ) {
6316  return $this->preSaveTransform( $text, $page, $options->getUserIdentity(), $options );
6317  }
6318 
6330  private function fuzzTestPreprocess( $text, PageReference $page, ParserOptions $options ) {
6331  return $this->fuzzTestSrvus( $text, $page, $options, self::OT_PREPROCESS );
6332  }
6333 
6352  public function markerSkipCallback( $s, callable $callback ) {
6353  $i = 0;
6354  $out = '';
6355  while ( $i < strlen( $s ) ) {
6356  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
6357  if ( $markerStart === false ) {
6358  $out .= call_user_func( $callback, substr( $s, $i ) );
6359  break;
6360  } else {
6361  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
6362  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
6363  if ( $markerEnd === false ) {
6364  $out .= substr( $s, $markerStart );
6365  break;
6366  } else {
6367  $markerEnd += strlen( self::MARKER_SUFFIX );
6368  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
6369  $i = $markerEnd;
6370  }
6371  }
6372  }
6373  return $out;
6374  }
6375 
6383  public function killMarkers( $text ) {
6384  return $this->mStripState->killMarkers( $text );
6385  }
6386 
6397  public static function parseWidthParam( $value, $parseHeight = true ) {
6398  $parsedWidthParam = [];
6399  if ( $value === '' ) {
6400  return $parsedWidthParam;
6401  }
6402  $m = [];
6403  # (T15500) In both cases (width/height and width only),
6404  # permit trailing "px" for backward compatibility.
6405  if ( $parseHeight && preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6406  $width = intval( $m[1] );
6407  $height = intval( $m[2] );
6408  $parsedWidthParam['width'] = $width;
6409  $parsedWidthParam['height'] = $height;
6410  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6411  $width = intval( $value );
6412  $parsedWidthParam['width'] = $width;
6413  }
6414  return $parsedWidthParam;
6415  }
6416 
6426  protected function lock() {
6427  if ( $this->mInParse ) {
6428  throw new MWException( "Parser state cleared while parsing. "
6429  . "Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6430  }
6431 
6432  // Save the backtrace when locking, so that if some code tries locking again,
6433  // we can print the lock owner's backtrace for easier debugging
6434  $e = new Exception;
6435  $this->mInParse = $e->getTraceAsString();
6436 
6437  $recursiveCheck = new ScopedCallback( function () {
6438  $this->mInParse = false;
6439  } );
6440 
6441  return $recursiveCheck;
6442  }
6443 
6451  public function isLocked() {
6452  return (bool)$this->mInParse;
6453  }
6454 
6465  public static function stripOuterParagraph( $html ) {
6466  $m = [];
6467  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) && strpos( $m[1], '</p>' ) === false ) {
6468  $html = $m[1];
6469  }
6470 
6471  return $html;
6472  }
6473 
6484  public static function formatPageTitle( $nsText, $nsSeparator, $mainText ): string {
6485  $html = '';
6486  if ( $nsText !== '' ) {
6487  $html .= '<span class="mw-page-title-namespace">' . HtmlArmor::getHtml( $nsText ) . '</span>';
6488  $html .= '<span class="mw-page-title-separator">' . HtmlArmor::getHtml( $nsSeparator ) . '</span>';
6489  }
6490  $html .= '<span class="mw-page-title-main">' . HtmlArmor::getHtml( $mainText ) . '</span>';
6491  return $html;
6492  }
6493 
6505  public function getFreshParser() {
6506  if ( $this->mInParse ) {
6507  return $this->factory->create();
6508  } else {
6509  return $this;
6510  }
6511  }
6512 
6520  public function enableOOUI() {
6521  wfDeprecated( __METHOD__, '1.35' );
6523  $this->mOutput->setEnableOOUI( true );
6524  }
6525 
6532  private function setOutputFlag( string $flag, string $reason ): void {
6533  $this->mOutput->setOutputFlag( $flag );
6534  $name = $this->getTitle()->getPrefixedText();
6535  $this->logger->debug( __METHOD__ . ": set $flag flag on '$name'; $reason" );
6536  }
6537 }
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:528
if(!defined('MW_SETUP_CALLBACK'))
Definition: WebStart.php:88
static doBlockLevels( $text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
static expand(Parser $parser, string $id, ConvertibleTimestamp $ts, ServiceOptions $svcOptions, LoggerInterface $logger)
Expand the magic variable given by $index.
static register(Parser $parser, ServiceOptions $options)
static register(Parser $parser, ServiceOptions $options)
const REGISTER_OPTIONS
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:68
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().
static bcp47( $code)
Get the normalised IANA language tag See unit test for examples.
Base class for language-specific code.
Definition: Language.php:57
MediaWiki exception.
Definition: MWException.php:32
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:41
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:36
Helper class for mapping value objects representing basic entities to cache keys.
This class performs some operations related to tracking categories, such as creating a list of all su...
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:565
Factory creating MWHttpRequest objects.
Variant of the Message class.
Definition: RawMessage.php:40
An interface for creating language converters.
isConversionDisabled()
Whether to disable language variant conversion.
A service that provides utilities to do with language names and codes.
Factory to create LinkRender objects.
Class that generates HTML for internal links.
Some internal bits split of from Skin.php.
Definition: Linker.php:67
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Class for handling an array of magic words.
A factory that stores information about MagicWords, and creates them on demand with caching.
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:42
Exception representing a failure to look up a revision.
Page revision base class.
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
Factory for handling the special page list and generating SpecialPage objects.
Stub object for the user language.
Base class for HTML cleanup utilities.
Represents a title within MediaWiki.
Definition: Title.php:82
Creates User objects.
Definition: UserFactory.php:42
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:357
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:144
static plaintextParam( $plaintext)
Definition: Message.php:1267
static numParam( $num)
Definition: Message.php:1146
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:107
addTrackingCategory( $msg)
Definition: Parser.php:4127
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:1260
getTargetLanguage()
Get the target language for the content being parsed.
Definition: Parser.php:1163
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
Definition: Parser.php:6044
setDefaultSort( $sort)
Mutator for the 'defaultsort' page property.
Definition: Parser.php:6120
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6465
static normalizeLinkUrl( $url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:2296
__clone()
Allow extensions to clean up when the parser is cloned.
Definition: Parser.php:557
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4787
getMagicWordFactory()
Get the MagicWordFactory that this Parser is using.
Definition: Parser.php:1217
$mHighestExpansionDepth
Definition: Parser.php:256
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:668
getCustomDefaultSort()
Accessor for the 'defaultsort' page property.
Definition: Parser.php:6152
ParserOptions null $mOptions
Definition: Parser.php:283
getOptions()
Definition: Parser.php:1103
cleanSig( $text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4744
getStripState()
Definition: Parser.php:1331
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:6072
isCurrentRevisionOfTitleCached(LinkTarget $link)
Definition: Parser.php:3538
replaceVariables( $text, $frame=false, $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
Definition: Parser.php:2916
getFunctionSynonyms()
Definition: Parser.php:5701
extensionSubstitution(array $params, PPFrame $frame, bool $processNowiki=false)
Return the text to be used for a given extension tag.
Definition: Parser.php:3952
interwikiTransclude(LinkTarget $link, $action)
Transclude an interwiki link.
Definition: Parser.php:3813
const OT_HTML
Definition: Parser.php:140
$mHeadings
Definition: Parser.php:259
getPreloadText( $text, PageReference $page, ParserOptions $options, $params=[])
Process the wikitext for the "?preload=" feature.
Definition: Parser.php:972
setTitle(Title $t=null)
Set the context title.
Definition: Parser.php:1005
static guessSectionNameFromStrippedText( $text)
Like guessSectionNameFromWikiText(), but takes already-stripped text as input.
Definition: Parser.php:6219
const OT_WIKI
Definition: Parser.php:141
getFlatSectionInfo( $text)
Get an array of preprocessor section information.
Definition: Parser.php:5925
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:2967
modifyImageHtml(File $file, array $params, string &$html)
Give hooks a chance to modify image thumbnail HTML.
Definition: Parser.php:5609
fetchTemplateAndTitle(LinkTarget $link)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3577
Title null $mTitle
Since 1.34, leaving mTitle uninitialized or setting mTitle to null is deprecated.
Definition: Parser.php:292
setUser(?UserIdentity $user)
Set the current user.
Definition: Parser.php:994
getRevisionSize()
Get the size of the revision.
Definition: Parser.php:6096
stripSectionName( $text)
Strips a text string of wikitext for use in a section anchor.
Definition: Parser.php:6259
tagNeedsNowikiStrippedInTagPF(string $lowerTagName)
Definition: Parser.php:3926
getOutputType()
Accessor for the output type.
Definition: Parser.php:1059
fetchCurrentRevisionRecordOfTitle(LinkTarget $link)
Fetch the current revision of a given title as a RevisionRecord.
Definition: Parser.php:3508
fetchFileAndTitle(LinkTarget $link, array $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3763
recursivePreprocess( $text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:952
fetchFileNoRegister(LinkTarget $link, array $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3790
getUrlProtocols()
Definition: Parser.php:5709
setFunctionHook( $id, callable $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4971
getHookRunner()
Get a HookRunner for calling core hooks.
Definition: Parser.php:1672
getTitle()
Definition: Parser.php:1014
braceSubstitution(array $piece, PPFrame $frame)
Return the text of a template, after recursively replacing any variables or templates within the temp...
Definition: Parser.php:2992
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:1202
preprocess( $text, ?PageReference $page, ParserOptions $options, $revid=null, $frame=false)
Expand templates and variables in the text, producing valid, static wikitext.
Definition: Parser.php:925
getExternalLinkAttribs( $url)
Get an associative array of additional HTML attributes appropriate for a particular external link.
Definition: Parser.php:2265
setOutputType( $ot)
Mutator for the output type.
Definition: Parser.php:1068
getFunctionLang()
Get a language object for use in parser functions such as {{FORMATNUM:}}.
Definition: Parser.php:1150
callParserFunction(PPFrame $frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3373
makeLimitReport()
Set the limit report data in the current ParserOutput.
Definition: Parser.php:769
parseExtensionTagAsTopLevelDoc( $text)
Needed by Parsoid/PHP to ensure all the hooks for extensions are run in the right order.
Definition: Parser.php:906
transformMsg( $text, ParserOptions $options, ?PageReference $page=null)
Wrapper for preprocess()
Definition: Parser.php:4864
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:4829
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1323
setHook( $tag, callable $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4904
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition: Parser.php:1237
getTags()
Accessor.
Definition: Parser.php:5693
static statelessFetchRevisionRecord(LinkTarget $link, $parser=null)
Wrapper around RevisionLookup::getKnownCurrentRevision.
Definition: Parser.php:3554
recursiveTagParse( $text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:857
getTargetLanguageConverter()
Shorthand for getting a Language Converter for Target language.
Definition: Parser.php:1636
static replaceTableOfContentsMarker( $text, $toc)
Replace table of contents marker in parsed HTML.
Definition: Parser.php:4808
const OT_PLAIN
Definition: Parser.php:145
getSection( $text, $sectionId, $defaultText='')
This function returns the text of a section, specified by a number ($section).
Definition: Parser.php:5875
internalParse( $text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
Definition: Parser.php:1567
replaceSection( $oldText, $sectionId, $newText)
This function returns $oldtext after the content of the section specified by $section has been replac...
Definition: Parser.php:5892
setPage(?PageReference $t=null)
Set the page used as context for parsing, e.g.
Definition: Parser.php:1027
getUserIdentity()
Get a user either from the user set on Parser if it's set, or from the ParserOptions object otherwise...
Definition: Parser.php:1182
doBlockLevels( $text, $linestart)
Make lists from lines starting with ':', '*', '#', etc.
Definition: Parser.php:2810
$mPPNodeCount
Definition: Parser.php:254
getDefaultSort()
Accessor for the 'defaultsort' page property.
Definition: Parser.php:6138
const MARKER_PREFIX
Definition: Parser.php:165
isLocked()
Will entry points such as parse() throw an exception due to the parser already being active?
Definition: Parser.php:6451
getHookContainer()
Get a HookContainer capable of returning metadata about hooks or running extension hooks.
Definition: Parser.php:1660
getFunctionHooks()
Get all registered function hook identifiers.
Definition: Parser.php:5008
$mMarkerIndex
Definition: Parser.php:201
replaceLinkHolders(&$text)
Replace "<!--LINK-->" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:5019
getPage()
Returns the page used as context for parsing, e.g.
Definition: Parser.php:1050
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:2240
$mExpensiveFunctionCount
Definition: Parser.php:265
getContentLanguage()
Get the content language that this Parser is using.
Definition: Parser.php:1227
getOutput()
Definition: Parser.php:1095
Options( $x=null)
Accessor/mutator for the ParserOptions object.
Definition: Parser.php:1123
recursiveTagParseFully( $text, $frame=false)
Fully parse wikitext to fully parsed HTML.
Definition: Parser.php:881
guessSectionNameFromWikiText( $text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
Definition: Parser.php:6189
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4922
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5979
setOptions(ParserOptions $options)
Mutator for the ParserOptions object.
Definition: Parser.php:1112
static parseWidthParam( $value, $parseHeight=true)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6397
validateSig( $text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4729
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:1192
guessLegacySectionNameFromWikiText( $text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead, if possible.
Definition: Parser.php:6206
const EXT_LINK_URL_CLASS
Definition: Parser.php:119
OutputType( $x=null)
Accessor/mutator for the output type.
Definition: Parser.php:1086
const SFH_OBJECT_ARGS
Definition: Parser.php:111
__destruct()
Reduce memory usage to reduce the impact of circular references.
Definition: Parser.php:543
resetOutput()
Reset the ParserOutput.
Definition: Parser.php:645
setLinkID( $id)
Definition: Parser.php:1140
doQuotes( $text)
Helper function for handleAllQuotes()
Definition: Parser.php:1975
getTemplateDom(LinkTarget $title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3463
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:6484
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:1344
const CONSTRUCTOR_OPTIONS
Definition: Parser.php:403
const OT_MSG
Definition: Parser.php:143
incrementExpensiveFunctionCount()
Definition: Parser.php:4058
makeImage(LinkTarget $link, $options, $holders=false)
Parse image options text and use it to make an image.
Definition: Parser.php:5304
const OT_PREPROCESS
Definition: Parser.php:142
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:4660
__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:454
firstCallInit()
Used to do various kinds of initialisation on the first call of the parser.
Definition: Parser.php:587
preprocessToDom( $text, $flags=0)
Get the document object model for the given wikitext.
Definition: Parser.php:2891
markerSkipCallback( $s, callable $callback)
Call a callback function on all regions of the given text that are not inside strip markers,...
Definition: Parser.php:6352
clearState()
Clear Parser state.
Definition: Parser.php:599
killMarkers( $text)
Remove any strip markers found in the given text.
Definition: Parser.php:6383
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6520
nextLinkID()
Definition: Parser.php:1132
msg(string $msg,... $args)
Helper function to correctly set the target language and title of a message based on the parser conte...
Definition: Parser.php:4146
getRevisionRecordObject()
Get the revision record object for $this->mRevisionId.
Definition: Parser.php:5989
attributeStripCallback(&$text, $frame=false)
Callback from the Sanitizer for expanding items found in HTML attribute values, so they can be safely...
Definition: Parser.php:5680
argSubstitution(array $piece, PPFrame $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3885
const SFH_NO_HASH
Definition: Parser.php:110
renderImageGallery( $text, array $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:5058
lock()
Lock the current instance of the parser.
Definition: Parser.php:6426
static statelessFetchTemplate( $page, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3619
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:6505
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:4539
Differences from DOM schema:
const DOM_FOR_INCLUSION
Transclusion mode flag for Preprocessor::preprocessToObj()
Group all the pieces relevant to the context of a request into one instance.
setTitle(Title $title=null)
static getMain()
Get the RequestContext object associated with the main request.
static fixTagAttributes( $text, $element, $sorted=false)
Take a tag soup fragment listing an HTML element's attributes and normalize it to well-formed XML,...
Definition: Sanitizer.php:839
static cleanUrl( $url)
Definition: Sanitizer.php:1778
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:974
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:526
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:393
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:318
static normalizeSectionNameWhitespace( $section)
Normalizes whitespace in a section name, such as might be returned by Parser::stripSectionName(),...
Definition: Sanitizer.php:1240
const ID_FALLBACK
Tells escapeUrlForHtml() to encode the ID using the fallback encoding, or return false if no fallback...
Definition: Sanitizer.php:86
static decodeCharReferences( $text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string.
Definition: Sanitizer.php:1372
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:947
static stripAllTags( $html)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
Definition: Sanitizer.php:1727
static decodeTagAttributes( $text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1140
const ID_PRIMARY
Tells escapeUrlForHtml() to encode the ID using the wiki's primary encoding.
Definition: Sanitizer.php:78
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.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:71
static newFromName( $name, $validate='valid')
Definition: User.php:592
Multi-datacenter aware caching interface.
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:747
return[0=> 'ـ', 1=> ' ', 2=> '`', 3=> '´', 4=> '˜', 5=> '^', 6=> '¯', 7=> '‾', 8=> '˘', 9=> '˙', 10=> '¨', 11=> '˚', 12=> '˝', 13=> '᾽', 14=> '῝', 15=> '¸', 16=> '˛', 17=> '_', 18=> '‗', 19=> '῀', 20=> '﮲', 21=> '﮳', 22=> '﮴', 23=> '﮵', 24=> '﮶', 25=> '﮷', 26=> '﮸', 27=> '﮹', 28=> '﮺', 29=> '﮻', 30=> '﮼', 31=> '﮽', 32=> '﮾', 33=> '﮿', 34=> '﯀', 35=> '﯁', 36=> '゛', 37=> '゜', 38=> '-', 39=> '֊', 40=> '᐀', 41=> '᭠', 42=> '᠆', 43=> '᠇', 44=> '‐', 45=> '‒', 46=> '–', 47=> '—', 48=> '―', 49=> '⁓', 50=> '⸗', 51=> '゠', 52=> '・', 53=> ',', 54=> '՝', 55=> '،', 56=> '؍', 57=> '٫', 58=> '٬', 59=> '߸', 60=> '᠂', 61=> '᠈', 62=> '꓾', 63=> '꘍', 64=> '꛵', 65=> '︑', 66=> ';', 67=> '؛', 68=> '⁏', 69=> '꛶', 70=> ':', 71=> '։', 72=> '؞', 73=> '܃', 74=> '܄', 75=> '܅', 76=> '܆', 77=> '܇', 78=> '܈', 79=> '࠰', 80=> '࠱', 81=> '࠲', 82=> '࠳', 83=> '࠴', 84=> '࠵', 85=> '࠶', 86=> '࠷', 87=> '࠸', 88=> '࠹', 89=> '࠺', 90=> '࠻', 91=> '࠼', 92=> '࠽', 93=> '࠾', 94=> '፡', 95=> '፣', 96=> '፤', 97=> '፥', 98=> '፦', 99=> '᠄', 100=> '᠅', 101=> '༔', 102=> '៖', 103=> '᭝', 104=> '꧇', 105=> '᛫', 106=> '᛬', 107=> '᛭', 108=> '꛴', 109=> '!', 110=> '¡', 111=> '՜', 112=> '߹', 113=> '᥄', 114=> '?', 115=> '¿', 116=> '⸮', 117=> '՞', 118=> '؟', 119=> '܉', 120=> '፧', 121=> '᥅', 122=> '⳺', 123=> '⳻', 124=> '꘏', 125=> '꛷', 126=> '‽', 127=> '⸘', 128=> '.', 129=> '᠁', 130=> '۔', 131=> '܁', 132=> '܂', 133=> '።', 134=> '᠃', 135=> '᠉', 136=> '᙮', 137=> '᭜', 138=> '⳹', 139=> '⳾', 140=> '⸰', 141=> '꓿', 142=> '꘎', 143=> '꛳', 144=> '︒', 145=> '·', 146=> '⸱', 147=> '।', 148=> '॥', 149=> '꣎', 150=> '꣏', 151=> '᰻', 152=> '᰼', 153=> '꡶', 154=> '꡷', 155=> '᜵', 156=> '᜶', 157=> '꤯', 158=> '၊', 159=> '။', 160=> '។', 161=> '៕', 162=> '᪨', 163=> '᪩', 164=> '᪪', 165=> '᪫', 166=> '᭞', 167=> '᭟', 168=> '꧈', 169=> '꧉', 170=> '꩝', 171=> '꩞', 172=> '꩟', 173=> '꯫', 174=> '𐩖', 175=> '𐩗', 176=> '𑁇', 177=> '𑁈', 178=> '𑃀', 179=> '𑃁', 180=> '᱾', 181=> '᱿', 182=> '܀', 183=> '߷', 184=> '჻', 185=> '፠', 186=> '፨', 187=> '᨞', 188=> '᨟', 189=> '᭚', 190=> '᭛', 191=> '꧁', 192=> '꧂', 193=> '꧃', 194=> '꧄', 195=> '꧅', 196=> '꧆', 197=> '꧊', 198=> '꧋', 199=> '꧌', 200=> '꧍', 201=> '꛲', 202=> '꥟', 203=> '𐡗', 204=> '𐬺', 205=> '𐬻', 206=> '𐬼', 207=> '𐬽', 208=> '𐬾', 209=> '𐬿', 210=> '𑂾', 211=> '𑂿', 212=> '⁕', 213=> '⁖', 214=> '⁘', 215=> '⁙', 216=> '⁚', 217=> '⁛', 218=> '⁜', 219=> '⁝', 220=> '⁞', 221=> '⸪', 222=> '⸫', 223=> '⸬', 224=> '⸭', 225=> '⳼', 226=> '⳿', 227=> '⸙', 228=> '𐤿', 229=> '𐄀', 230=> '𐄁', 231=> '𐄂', 232=> '𐎟', 233=> '𐏐', 234=> '𐤟', 235=> '𒑰', 236=> '𒑱', 237=> '𒑲', 238=> '𒑳', 239=> '\'', 240=> '‘', 241=> '’', 242=> '‚', 243=> '‛', 244=> '‹', 245=> '›', 246=> '"', 247 => '“', 248 => '”', 249 => '„', 250 => '‟', 251 => '«', 252 => '»', 253 => '(', 254 => ')', 255 => '[', 256 => ']', 257 => '{', 258 => '}', 259 => '༺', 260 => '༻', 261 => '༼', 262 => '༽', 263 => '᚛', 264 => '᚜', 265 => '⁅', 266 => '⁆', 267 => '⧼', 268 => '⧽', 269 => '⦃', 270 => '⦄', 271 => '⦅', 272 => '⦆', 273 => '⦇', 274 => '⦈', 275 => '⦉', 276 => '⦊', 277 => '⦋', 278 => '⦌', 279 => '⦍', 280 => '⦎', 281 => '⦏', 282 => '⦐', 283 => '⦑', 284 => '⦒', 285 => '⦓', 286 => '⦔', 287 => '⦕', 288 => '⦖', 289 => '⦗', 290 => '⦘', 291 => '⟬', 292 => '⟭', 293 => '⟮', 294 => '⟯', 295 => '⸂', 296 => '⸃', 297 => '⸄', 298 => '⸅', 299 => '⸉', 300 => '⸊', 301 => '⸌', 302 => '⸍', 303 => '⸜', 304 => '⸝', 305 => '⸠', 306 => '⸡', 307 => '⸢', 308 => '⸣', 309 => '⸤', 310 => '⸥', 311 => '⸦', 312 => '⸧', 313 => '⸨', 314 => '⸩', 315 => '〈', 316 => '〉', 317 => '「', 318 => '」', 319 => '﹝', 320 => '﹞', 321 => '︗', 322 => '︘', 323 => '﴾', 324 => '﴿', 325 => '§', 326 => '¶', 327 => '⁋', 328 => '©', 329 => '®', 330 => '@', 331 => '*', 332 => '⁎', 333 => '⁑', 334 => '٭', 335 => '꙳', 336 => '/', 337 => '⁄', 338 => '\\', 339 => '&', 340 => '⅋', 341 => '⁊', 342 => '#', 343 => '%', 344 => '٪', 345 => '‰', 346 => '؉', 347 => '‱', 348 => '؊', 349 => '⁒', 350 => '†', 351 => '‡', 352 => '•', 353 => '‣', 354 => '‧', 355 => '⁃', 356 => '⁌', 357 => '⁍', 358 => '′', 359 => '‵', 360 => '‸', 361 => '※', 362 => '‿', 363 => '⁔', 364 => '⁀', 365 => '⁐', 366 => '⁁', 367 => '⁂', 368 => '⸀', 369 => '⸁', 370 => '⸆', 371 => '⸇', 372 => '⸈', 373 => '⸋', 374 => '⸎', 375 => '⸏', 376 => '⸐', 377 => '⸑', 378 => '⸒', 379 => '⸓', 380 => '⸔', 381 => '⸕', 382 => '⸖', 383 => '⸚', 384 => '⸛', 385 => '⸞', 386 => '⸟', 387 => '꙾', 388 => '՚', 389 => '՛', 390 => '՟', 391 => '־', 392 => '׀', 393 => '׃', 394 => '׆', 395 => '׳', 396 => '״', 397 => '܊', 398 => '܋', 399 => '܌', 400 => '܍', 401 => '࡞', 402 => '᠀', 403 => '॰', 404 => '꣸', 405 => '꣹', 406 => '꣺', 407 => '෴', 408 => '๚', 409 => '๛', 410 => '꫞', 411 => '꫟', 412 => '༄', 413 => '༅', 414 => '༆', 415 => '༇', 416 => '༈', 417 => '༉', 418 => '༊', 419 => '࿐', 420 => '࿑', 421 => '་', 422 => '།', 423 => '༎', 424 => '༏', 425 => '༐', 426 => '༑', 427 => '༒', 428 => '྅', 429 => '࿒', 430 => '࿓', 431 => '࿔', 432 => '࿙', 433 => '࿚', 434 => '᰽', 435 => '᰾', 436 => '᰿', 437 => '᥀', 438 => '၌', 439 => '၍', 440 => '၎', 441 => '၏', 442 => '႞', 443 => '႟', 444 => '꩷', 445 => '꩸', 446 => '꩹', 447 => 'ៗ', 448 => '៘', 449 => '៙', 450 => '៚', 451 => '᪠', 452 => '᪡', 453 => '᪢', 454 => '᪣', 455 => '᪤', 456 => '᪥', 457 => '᪦', 458 => '᪬', 459 => '᪭', 460 => '᙭', 461 => '⵰', 462 => '꡴', 463 => '꡵', 464 => '᯼', 465 => '᯽', 466 => '᯾', 467 => '᯿', 468 => '꤮', 469 => '꧞', 470 => '꧟', 471 => '꩜', 472 => '𑁉', 473 => '𑁊', 474 => '𑁋', 475 => '𑁌', 476 => '𑁍', 477 => '𐩐', 478 => '𐩑', 479 => '𐩒', 480 => '𐩓', 481 => '𐩔', 482 => '𐩕', 483 => '𐩘', 484 => '𐬹', 485 => '𑂻', 486 => '𑂼', 487 => 'ʹ', 488 => '͵', 489 => 'ʺ', 490 => '˂', 491 => '˃', 492 => '˄', 493 => '˅', 494 => 'ˆ', 495 => 'ˇ', 496 => 'ˈ', 497 => 'ˉ', 498 => 'ˊ', 499 => 'ˋ', 500 => 'ˌ', 501 => 'ˍ', 502 => 'ˎ', 503 => 'ˏ', 504 => '˒', 505 => '˓', 506 => '˔', 507 => '˕', 508 => '˖', 509 => '˗', 510 => '˞', 511 => '˟', 512 => '˥', 513 => '˦', 514 => '˧', 515 => '˨', 516 => '˩', 517 => '˪', 518 => '˫', 519 => 'ˬ', 520 => '˭', 521 => '˯', 522 => '˰', 523 => '˱', 524 => '˲', 525 => '˳', 526 => '˴', 527 => '˵', 528 => '˶', 529 => '˷', 530 => '˸', 531 => '˹', 532 => '˺', 533 => '˻', 534 => '˼', 535 => '˽', 536 => '˾', 537 => '˿', 538 => '᎐', 539 => '᎑', 540 => '᎒', 541 => '᎓', 542 => '᎔', 543 => '᎕', 544 => '᎖', 545 => '᎗', 546 => '᎘', 547 => '᎙', 548 => '꜀', 549 => '꜁', 550 => '꜂', 551 => '꜃', 552 => '꜄', 553 => '꜅', 554 => '꜆', 555 => '꜇', 556 => '꜈', 557 => '꜉', 558 => '꜊', 559 => '꜋', 560 => '꜌', 561 => '꜍', 562 => '꜎', 563 => '꜏', 564 => '꜐', 565 => '꜑', 566 => '꜒', 567 => '꜓', 568 => '꜔', 569 => '꜕', 570 => '꜖', 571 => 'ꜗ', 572 => 'ꜘ', 573 => 'ꜙ', 574 => 'ꜚ', 575 => 'ꜛ', 576 => 'ꜜ', 577 => 'ꜝ', 578 => 'ꜞ', 579 => 'ꜟ', 580 => '꜠', 581 => '꜡', 582 => 'ꞈ', 583 => '꞉', 584 => '꞊', 585 => '°', 586 => '҂', 587 => '؈', 588 => '؎', 589 => '؏', 590 => '۞', 591 => '۩', 592 => '﷽', 593 => '߶', 594 => '৺', 595 => '୰', 596 => '௳', 597 => '௴', 598 => '௵', 599 => '௶', 600 => '௷', 601 => '௸', 602 => '௺', 603 => '౿', 604 => '൹', 605 => '꠨', 606 => '꠩', 607 => '꠪', 608 => '꠫', 609 => '꠶', 610 => '꠷', 611 => '꠹', 612 => '๏', 613 => '༁', 614 => '༂', 615 => '༃', 616 => '༓', 617 => '༕', 618 => '༖', 619 => '༗', 620 => '༚', 621 => '༛', 622 => '༜', 623 => '༝', 624 => '༞', 625 => '༟', 626 => '༴', 627 => '༶', 628 => '༸', 629 => '྾', 630 => '྿', 631 => '࿀', 632 => '࿁', 633 => '࿂', 634 => '࿃', 635 => '࿄', 636 => '࿅', 637 => '࿇', 638 => '࿈', 639 => '࿉', 640 => '࿊', 641 => '࿋', 642 => '࿌', 643 => '࿎', 644 => '࿏', 645 => '࿕', 646 => '࿖', 647 => '࿗', 648 => '࿘', 649 => '᧠', 650 => '᧡', 651 => '᧢', 652 => '᧣', 653 => '᧤', 654 => '᧥', 655 => '᧦', 656 => '᧧', 657 => '᧨', 658 => '᧩', 659 => '᧪', 660 => '᧫', 661 => '᧬', 662 => '᧭', 663 => '᧮', 664 => '᧯', 665 => '᧰', 666 => '᧱', 667 => '᧲', 668 => '᧳', 669 => '᧴', 670 => '᧵', 671 => '᧶', 672 => '᧷', 673 => '᧸', 674 => '᧹', 675 => '᧺', 676 => '᧻', 677 => '᧼', 678 => '᧽', 679 => '᧾', 680 => '᧿', 681 => '᭡', 682 => '᭢', 683 => '᭣', 684 => '᭤', 685 => '᭥', 686 => '᭦', 687 => '᭧', 688 => '᭨', 689 => '᭩', 690 => '᭪', 691 => '᭴', 692 => '᭵', 693 => '᭶', 694 => '᭷', 695 => '᭸', 696 => '᭹', 697 => '᭺', 698 => '᭻', 699 => '᭼', 700 => '℄', 701 => '℈', 702 => '℔', 703 => '℗', 704 => '℘', 705 => '℞', 706 => '℟', 707 => '℣', 708 => '℥', 709 => '℧', 710 => '℩', 711 => '℮', 712 => '℺', 713 => '⅁', 714 => '⅂', 715 => '⅃', 716 => '⅄', 717 => '⅊', 718 => '⅌', 719 => '⅍', 720 => '⅏', 721 => '←', 722 => '→', 723 => '↑', 724 => '↓', 725 => '↔', 726 => '↕', 727 => '↖', 728 => '↗', 729 => '↘', 730 => '↙', 731 => '↜', 732 => '↝', 733 => '↞', 734 => '↟', 735 => '↠', 736 => '↡', 737 => '↢', 738 => '↣', 739 => '↤', 740 => '↥', 741 => '↦', 742 => '↧', 743 => '↨', 744 => '↩', 745 => '↪', 746 => '↫', 747 => '↬', 748 => '↭', 749 => '↯', 750 => '↰', 751 => '↱', 752 => '↲', 753 => '↳', 754 => '↴', 755 => '↵', 756 => '↶', 757 => '↷', 758 => '↸', 759 => '↹', 760 => '↺', 761 => '↻', 762 => '↼', 763 => '↽', 764 => '↾', 765 => '↿', 766 => '⇀', 767 => '⇁', 768 => '⇂', 769 => '⇃', 770 => '⇄', 771 => '⇅', 772 => '⇆', 773 => '⇇', 774 => '⇈', 775 => '⇉', 776 => '⇊', 777 => '⇋', 778 => '⇌', 779 => '⇐', 780 => '⇑', 781 => '⇒', 782 => '⇓', 783 => '⇔', 784 => '⇕', 785 => '⇖', 786 => '⇗', 787 => '⇘', 788 => '⇙', 789 => '⇚', 790 => '⇛', 791 => '⇜', 792 => '⇝', 793 => '⇞', 794 => '⇟', 795 => '⇠', 796 => '⇡', 797 => '⇢', 798 => '⇣', 799 => '⇤', 800 => '⇥', 801 => '⇦', 802 => '⇧', 803 => '⇨', 804 => '⇩', 805 => '⇪', 806 => '⇫', 807 => '⇬', 808 => '⇭', 809 => '⇮', 810 => '⇯', 811 => '⇰', 812 => '⇱', 813 => '⇲', 814 => '⇳', 815 => '⇴', 816 => '⇵', 817 => '⇶', 818 => '⇷', 819 => '⇸', 820 => '⇹', 821 => '⇺', 822 => '⇻', 823 => '⇼', 824 => '⇽', 825 => '⇾', 826 => '⇿', 827 => '∀', 828 => '∁', 829 => '∂', 830 => '∃', 831 => '∅', 832 => '∆', 833 => '∇', 834 => '∈', 835 => '∊', 836 => '∋', 837 => '∍', 838 => '϶', 839 => '∎', 840 => '∏', 841 => '∐', 842 => '∑', 843 => '+', 844 => '±', 845 => '÷', 846 => '×', 847 => '<', 848 => '=', 849 => '>', 850 => '¬', 851 => '|', 852 => '¦', 853 => '‖', 854 => '~', 855 => '−', 856 => '∓', 857 => '∔', 858 => '∕', 859 => '∖', 860 => '∗', 861 => '∘', 862 => '∙', 863 => '√', 864 => '∛', 865 => '؆', 866 => '∜', 867 => '؇', 868 => '∝', 869 => '∞', 870 => '∟', 871 => '∠', 872 => '∡', 873 => '∢', 874 => '∣', 875 => '∥', 876 => '∧', 877 => '∨', 878 => '∩', 879 => '∪', 880 => '∫', 881 => '∮', 882 => '∱', 883 => '∲', 884 => '∳', 885 => '∴', 886 => '∵', 887 => '∶', 888 => '∷', 889 => '∸', 890 => '∹', 891 => '∺', 892 => '∻', 893 => '∼', 894 => '∽', 895 => '∾', 896 => '∿', 897 => '≀', 898 => '≂', 899 => '≃', 900 => '≅', 901 => '≆', 902 => '≈', 903 => '≊', 904 => '≋', 905 => '≌', 906 => '≍', 907 => '≎', 908 => '≏', 909 => '≐', 910 => '≑', 911 => '≒', 912 => '≓', 913 => '≔', 914 => '≕', 915 => '≖', 916 => '≗', 917 => '≘', 918 => '≙', 919 => '≚', 920 => '≛', 921 => '≜', 922 => '≝', 923 => '≞', 924 => '≟', 925 => '≡', 926 => '≣', 927 => '≤', 928 => '≥', 929 => '≦', 930 => '≧', 931 => '≨', 932 => '≩', 933 => '≪', 934 => '≫', 935 => '≬', 936 => '≲', 937 => '≳', 938 => '≶', 939 => '≷', 940 => '≺', 941 => '≻', 942 => '≼', 943 => '≽', 944 => '≾', 945 => '≿', 946 => '⊂', 947 => '⊃', 948 => '⊆', 949 => '⊇', 950 => '⊊', 951 => '⊋', 952 => '⊌', 953 => '⊍', 954 => '⊎', 955 => '⊏', 956 => '⊐', 957 => '⊑', 958 => '⊒', 959 => '⊓', 960 => '⊔', 961 => '⊕', 962 => '⊖', 963 => '⊗', 964 => '⊘', 965 => '⊙', 966 => '⊚', 967 => '⊛', 968 => '⊜', 969 => '⊝', 970 => '⊞', 971 => '⊟', 972 => '⊠', 973 => '⊡', 974 => '⊢', 975 => '⊣', 976 => '⊤', 977 => '⊥', 978 => '⊦', 979 => '⊧', 980 => '⊨', 981 => '⊩', 982 => '⊪', 983 => '⊫', 984 => '⊰', 985 => '⊱', 986 => '⊲', 987 => '⊳', 988 => '⊴', 989 => '⊵', 990 => '⊶', 991 => '⊷', 992 => '⊸', 993 => '⊹', 994 => '⊺', 995 => '⊻', 996 => '⊼', 997 => '⊽', 998 => '⊾', 999 => '⊿', 1000 => '⋀', 1001 => '⋁', 1002 => '⋂', 1003 => '⋃', 1004 => '⋄', 1005 => '⋅', 1006 => '⋆', 1007 => '⋇', 1008 => '⋈', 1009 => '⋉', 1010 => '⋊', 1011 => '⋋', 1012 => '⋌', 1013 => '⋍', 1014 => '⋎', 1015 => '⋏', 1016 => '⋐', 1017 => '⋑', 1018 => '⋒', 1019 => '⋓', 1020 => '⋔', 1021 => '⋕', 1022 => '⋖', 1023 => '⋗', 1024 => '⋘', 1025 => '⋙', 1026 => '⋚', 1027 => '⋛', 1028 => '⋜', 1029 => '⋝', 1030 => '⋞', 1031 => '⋟', 1032 => '⋤', 1033 => '⋥', 1034 => '⋦', 1035 => '⋧', 1036 => '⋨', 1037 => '⋩', 1038 => '⋮', 1039 => '⋯', 1040 => '⋰', 1041 => '⋱', 1042 => '⋲', 1043 => '⋳', 1044 => '⋴', 1045 => '⋵', 1046 => '⋶', 1047 => '⋷', 1048 => '⋸', 1049 => '⋹', 1050 => '⋺', 1051 => '⋻', 1052 => '⋼', 1053 => '⋽', 1054 => '⋾', 1055 => '⋿', 1056 => '⌀', 1057 => '⌁', 1058 => '⌂', 1059 => '⌃', 1060 => '⌄', 1061 => '⌅', 1062 => '⌆', 1063 => '⌇', 1064 => '⌈', 1065 => '⌉', 1066 => '⌊', 1067 => '⌋', 1068 => '⌌', 1069 => '⌍', 1070 => '⌎', 1071 => '⌏', 1072 => '⌐', 1073 => '⌑', 1074 => '⌒', 1075 => '⌓', 1076 => '⌔', 1077 => '⌕', 1078 => '⌖', 1079 => '⌗', 1080 => '⌘', 1081 => '⌙', 1082 => '⌚', 1083 => '⌛', 1084 => '⌜', 1085 => '⌝', 1086 => '⌞', 1087 => '⌟', 1088 => '⌠', 1089 => '⌡', 1090 => '⌢', 1091 => '⌣', 1092 => '⌤', 1093 => '⌥', 1094 => '⌦', 1095 => '⌧', 1096 => '⌨', 1097 => '⌫', 1098 => '⌬', 1099 => '⌭', 1100 => '⌮', 1101 => '⌯', 1102 => '⌰', 1103 => '⌱', 1104 => '⌲', 1105 => '⌳', 1106 => '⌴', 1107 => '⌵', 1108 => '⌶', 1109 => '⌷', 1110 => '⌸', 1111 => '⌹', 1112 => '⌺', 1113 => '⌻', 1114 => '⌼', 1115 => '⌽', 1116 => '⌾', 1117 => '⌿', 1118 => '⍀', 1119 => '⍁', 1120 => '⍂', 1121 => '⍃', 1122 => '⍄', 1123 => '⍅', 1124 => '⍆', 1125 => '⍇', 1126 => '⍈', 1127 => '⍉', 1128 => '⍊', 1129 => '⍋', 1130 => '⍌', 1131 => '⍍', 1132 => '⍎', 1133 => '⍏', 1134 => '⍐', 1135 => '⍑', 1136 => '⍒', 1137 => '⍓', 1138 => '⍔', 1139 => '⍕', 1140 => '⍖', 1141 => '⍗', 1142 => '⍘', 1143 => '⍙', 1144 => '⍚', 1145 => '⍛', 1146 => '⍜', 1147 => '⍝', 1148 => '⍞', 1149 => '⍟', 1150 => '⍠', 1151 => '⍡', 1152 => '⍢', 1153 => '⍣', 1154 => '⍤', 1155 => '⍥', 1156 => '⍦', 1157 => '⍧', 1158 => '⍨', 1159 => '⍩', 1160 => '⍪', 1161 => '⍫', 1162 => '⍬', 1163 => '⍭', 1164 => '⍮', 1165 => '⍯', 1166 => '⍰', 1167 => '⍱', 1168 => '⍲', 1169 => '⍳', 1170 => '⍴', 1171 => '⍵', 1172 => '⍶', 1173 => '⍷', 1174 => '⍸', 1175 => '⍹', 1176 => '⍺', 1177 => '⍻', 1178 => '⍼', 1179 => '⍽', 1180 => '⍾', 1181 => '⍿', 1182 => '⎀', 1183 => '⎁', 1184 => '⎂', 1185 => '⎃', 1186 => '⎄', 1187 => '⎅', 1188 => '⎆', 1189 => '⎇', 1190 => '⎈', 1191 => '⎉', 1192 => '⎊', 1193 => '⎋', 1194 => '⎌', 1195 => '⎍', 1196 => '⎎', 1197 => '⎏', 1198 => '⎐', 1199 => '⎑', 1200 => '⎒', 1201 => '⎓', 1202 => '⎔', 1203 => '⎕', 1204 => '⎖', 1205 => '⎗', 1206 => '⎘', 1207 => '⎙', 1208 => '⎚', 1209 => '⎛', 1210 => '⎜', 1211 => '⎝', 1212 => '⎞', 1213 => '⎟', 1214 => '⎠', 1215 => '⎡', 1216 => '⎢', 1217 => '⎣', 1218 => '⎤', 1219 => '⎥', 1220 => '⎦', 1221 => '⎧', 1222 => '⎨', 1223 => '⎩', 1224 => '⎪', 1225 => '⎫', 1226 => '⎬', 1227 => '⎭', 1228 => '⎮', 1229 => '⎯', 1230 => '⎰', 1231 => '⎱', 1232 => '⎲', 1233 => '⎳', 1234 => '⎴', 1235 => '⎵', 1236 => '⎶', 1237 => '⎷', 1238 => '⎸', 1239 => '⎹', 1240 => '⎺', 1241 => '⎻', 1242 => '⎼', 1243 => '⎽', 1244 => '⎾', 1245 => '⎿', 1246 => '⏀', 1247 => '⏁', 1248 => '⏂', 1249 => '⏃', 1250 => '⏄', 1251 => '⏅', 1252 => '⏆', 1253 => '⏇', 1254 => '⏈', 1255 => '⏉', 1256 => '⏊', 1257 => '⏋', 1258 => '⏌', 1259 => '⏍', 1260 => '⏎', 1261 => '⏏', 1262 => '⏐', 1263 => '⏑', 1264 => '⏒', 1265 => '⏓', 1266 => '⏔', 1267 => '⏕', 1268 => '⏖', 1269 => '⏗', 1270 => '⏘', 1271 => '⏙', 1272 => '⏚', 1273 => '⏛', 1274 => '⏜', 1275 => '⏝', 1276 => '⏞', 1277 => '⏟', 1278 => '⏠', 1279 => '⏡', 1280 => '⏢', 1281 => '⏣', 1282 => '⏤', 1283 => '⏥', 1284 => '⏦', 1285 => '⏧', 1286 => '⏨', 1287 => '⏩', 1288 => '⏪', 1289 => '⏫', 1290 => '⏬', 1291 => '⏭', 1292 => '⏮', 1293 => '⏯', 1294 => '⏰', 1295 => '⏱', 1296 => '⏲', 1297 => '⏳', 1298 => '␀', 1299 => '␁', 1300 => '␂', 1301 => '␃', 1302 => '␄', 1303 => '␅', 1304 => '␆', 1305 => '␇', 1306 => '␈', 1307 => '␉', 1308 => '␊', 1309 => '␋', 1310 => '␌', 1311 => '␍', 1312 => '␎', 1313 => '␏', 1314 => '␐', 1315 => '␑', 1316 => '␒', 1317 => '␓', 1318 => '␔', 1319 => '␕', 1320 => '␖', 1321 => '␗', 1322 => '␘', 1323 => '␙', 1324 => '␚', 1325 => '␛', 1326 => '␜', 1327 => '␝', 1328 => '␞', 1329 => '␟', 1330 => '␠', 1331 => '␡', 1332 => '␢', 1333 => '␣', 1334 => '␤', 1335 => '␥', 1336 => '␦', 1337 => '⑀', 1338 => '⑁', 1339 => '⑂', 1340 => '⑃', 1341 => '⑄', 1342 => '⑅', 1343 => '⑆', 1344 => '⑇', 1345 => '⑈', 1346 => '⑉', 1347 => '⑊', 1348 => '─', 1349 => '━', 1350 => '│', 1351 => '┃', 1352 => '┄', 1353 => '┅', 1354 => '┆', 1355 => '┇', 1356 => '┈', 1357 => '┉', 1358 => '┊', 1359 => '┋', 1360 => '┌', 1361 => '┍', 1362 => '┎', 1363 => '┏', 1364 => '┐', 1365 => '┑', 1366 => '┒', 1367 => '┓', 1368 => '└', 1369 => '┕', 1370 => '┖', 1371 => '┗', 1372 => '┘', 1373 => '┙', 1374 => '┚', 1375 => '┛', 1376 => '├', 1377 => '┝', 1378 => '┞', 1379 => '┟', 1380 => '┠', 1381 => '┡', 1382 => '┢', 1383 => '┣', 1384 => '┤', 1385 => '┥', 1386 => '┦', 1387 => '┧', 1388 => '┨', 1389 => '┩', 1390 => '┪', 1391 => '┫', 1392 => '┬', 1393 => '┭', 1394 => '┮', 1395 => '┯', 1396 => '┰', 1397 => '┱', 1398 => '┲', 1399 => '┳', 1400 => '┴', 1401 => '┵', 1402 => '┶', 1403 => '┷', 1404 => '┸', 1405 => '┹', 1406 => '┺', 1407 => '┻', 1408 => '┼', 1409 => '┽', 1410 => '┾', 1411 => '┿', 1412 => '╀', 1413 => '╁', 1414 => '╂', 1415 => '╃', 1416 => '╄', 1417 => '╅', 1418 => '╆', 1419 => '╇', 1420 => '╈', 1421 => '╉', 1422 => '╊', 1423 => '╋', 1424 => '╌', 1425 => '╍', 1426 => '╎', 1427 => '╏', 1428 => '═', 1429 => '║', 1430 => '╒', 1431 => '╓', 1432 => '╔', 1433 => '╕', 1434 => '╖', 1435 => '╗', 1436 => '╘', 1437 => '╙', 1438 => '╚', 1439 => '╛', 1440 => '╜', 1441 => '╝', 1442 => '╞', 1443 => '╟', 1444 => '╠', 1445 => '╡', 1446 => '╢', 1447 => '╣', 1448 => '╤', 1449 => '╥', 1450 => '╦', 1451 => '╧', 1452 => '╨', 1453 => '╩', 1454 => '╪', 1455 => '╫', 1456 => '╬', 1457 => '╭', 1458 => '╮', 1459 => '╯', 1460 => '╰', 1461 => '╱', 1462 => '╲', 1463 => '╳', 1464 => '╴', 1465 => '╵', 1466 => '╶', 1467 => '╷', 1468 => '╸', 1469 => '╹', 1470 => '╺', 1471 => '╻', 1472 => '╼', 1473 => '╽', 1474 => '╾', 1475 => '╿', 1476 => '▀', 1477 => '▁', 1478 => '▂', 1479 => '▃', 1480 => '▄', 1481 => '▅', 1482 => '▆', 1483 => '▇', 1484 => '█', 1485 => '▉', 1486 => '▊', 1487 => '▋', 1488 => '▌', 1489 => '▍', 1490 => '▎', 1491 => '▏', 1492 => '▐', 1493 => '░', 1494 => '▒', 1495 => '▓', 1496 => '▔', 1497 => '▕', 1498 => '▖', 1499 => '▗', 1500 => '▘', 1501 => '▙', 1502 => '▚', 1503 => '▛', 1504 => '▜', 1505 => '▝', 1506 => '▞', 1507 => '▟', 1508 => '■', 1509 => '□', 1510 => '▢', 1511 => '▣', 1512 => '▤', 1513 => '▥', 1514 => '▦', 1515 => '▧', 1516 => '▨', 1517 => '▩', 1518 => '▪', 1519 => '▫', 1520 => '▬', 1521 => '▭', 1522 => '▮', 1523 => '▯', 1524 => '▰', 1525 => '▱', 1526 => '▲', 1527 => '△', 1528 => '▴', 1529 => '▵', 1530 => '▶', 1531 => '▷', 1532 => '▸', 1533 => '▹', 1534 => '►', 1535 => '▻', 1536 => '▼', 1537 => '▽', 1538 => '▾', 1539 => '▿', 1540 => '◀', 1541 => '◁', 1542 => '◂', 1543 => '◃', 1544 => '◄', 1545 => '◅', 1546 => '◆', 1547 => '◇', 1548 => '◈', 1549 => '◉', 1550 => '◊', 1551 => '○', 1552 => '◌', 1553 => '◍', 1554 => '◎', 1555 => '●', 1556 => '◐', 1557 => '◑', 1558 => '◒', 1559 => '◓', 1560 => '◔', 1561 => '◕', 1562 => '◖', 1563 => '◗', 1564 => '◘', 1565 => '◙', 1566 => '◚', 1567 => '◛', 1568 => '◜', 1569 => '◝', 1570 => '◞', 1571 => '◟', 1572 => '◠', 1573 => '◡', 1574 => '◢', 1575 => '◣', 1576 => '◤', 1577 => '◥', 1578 => '◦', 1579 => '◧', 1580 => '◨', 1581 => '◩', 1582 => '◪', 1583 => '◫', 1584 => '◬', 1585 => '◭', 1586 => '◮', 1587 => '◯', 1588 => '◰', 1589 => '◱', 1590 => '◲', 1591 => '◳', 1592 => '◴', 1593 => '◵', 1594 => '◶', 1595 => '◷', 1596 => '◸', 1597 => '◹', 1598 => '◺', 1599 => '◻', 1600 => '◼', 1601 => '◽', 1602 => '◾', 1603 => '◿', 1604 => '☀', 1605 => '☁', 1606 => '☂', 1607 => '☃', 1608 => '☄', 1609 => '★', 1610 => '☆', 1611 => '☇', 1612 => '☈', 1613 => '☉', 1614 => '☊', 1615 => '☋', 1616 => '☌', 1617 => '☍', 1618 => '☎', 1619 => '☏', 1620 => '☐', 1621 => '☑', 1622 => '☒', 1623 => '☓', 1624 => '☔', 1625 => '☕', 1626 => '☖', 1627 => '☗', 1628 => '☘', 1629 => '☙', 1630 => '☚', 1631 => '☛', 1632 => '☜', 1633 => '☝', 1634 => '☞', 1635 => '☟', 1636 => '☠', 1637 => '☡', 1638 => '☢', 1639 => '☣', 1640 => '☤', 1641 => '☥', 1642 => '☦', 1643 => '☧', 1644 => '☨', 1645 => '☩', 1646 => '☪', 1647 => '☫', 1648 => '☬', 1649 => '☭', 1650 => '☮', 1651 => '☯', 1652 => '☸', 1653 => '☹', 1654 => '☺', 1655 => '☻', 1656 => '☼', 1657 => '☽', 1658 => '☾', 1659 => '☿', 1660 => '♀', 1661 => '♁', 1662 => '♂', 1663 => '♃', 1664 => '♄', 1665 => '♅', 1666 => '♆', 1667 => '♇', 1668 => '♈', 1669 => '♉', 1670 => '♊', 1671 => '♋', 1672 => '♌', 1673 => '♍', 1674 => '♎', 1675 => '♏', 1676 => '♐', 1677 => '♑', 1678 => '♒', 1679 => '♓', 1680 => '♔', 1681 => '♕', 1682 => '♖', 1683 => '♗', 1684 => '♘', 1685 => '♙', 1686 => '♚', 1687 => '♛', 1688 => '♜', 1689 => '♝', 1690 => '♞', 1691 => '♟', 1692 => '♠', 1693 => '♡', 1694 => '♢', 1695 => '♣', 1696 => '♤', 1697 => '♥', 1698 => '♦', 1699 => '♧', 1700 => '♨', 1701 => '♩', 1702 => '♪', 1703 => '♫', 1704 => '♬', 1705 => '♰', 1706 => '♱', 1707 => '♲', 1708 => '♳', 1709 => '♴', 1710 => '♵', 1711 => '♶', 1712 => '♷', 1713 => '♸', 1714 => '♹', 1715 => '♺', 1716 => '♻', 1717 => '♼', 1718 => '♽', 1719 => '♾', 1720 => '♿', 1721 => '⚀', 1722 => '⚁', 1723 => '⚂', 1724 => '⚃', 1725 => '⚄', 1726 => '⚅', 1727 => '⚆', 1728 => '⚇', 1729 => '⚈', 1730 => '⚉', 1731 => '⚐', 1732 => '⚑', 1733 => '⚒', 1734 => '⚓', 1735 => '⚔', 1736 => '⚕', 1737 => '⚖', 1738 => '⚗', 1739 => '⚘', 1740 => '⚙', 1741 => '⚚', 1742 => '⚛', 1743 => '⚜', 1744 => '⚝', 1745 => '⚞', 1746 => '⚟', 1747 => '⚠', 1748 => '⚡', 1749 => '⚢', 1750 => '⚣', 1751 => '⚤', 1752 => '⚥', 1753 => '⚦', 1754 => '⚧', 1755 => '⚨', 1756 => '⚩', 1757 => '⚪', 1758 => '⚫', 1759 => '⚬', 1760 => '⚭', 1761 => '⚮', 1762 => '⚯', 1763 => '⚰', 1764 => '⚱', 1765 => '⚲', 1766 => '⚳', 1767 => '⚴', 1768 => '⚵', 1769 => '⚶', 1770 => '⚷', 1771 => '⚸', 1772 => '⚹', 1773 => '⚺', 1774 => '⚻', 1775 => '⚼', 1776 => '⚽', 1777 => '⚾', 1778 => '⚿', 1779 => '⛀', 1780 => '⛁', 1781 => '⛂', 1782 => '⛃', 1783 => '⛄', 1784 => '⛅', 1785 => '⛆', 1786 => '⛇', 1787 => '⛈', 1788 => '⛉', 1789 => '⛊', 1790 => '⛋', 1791 => '⛌', 1792 => '⛍', 1793 => '⛎', 1794 => '⛏', 1795 => '⛐', 1796 => '⛑', 1797 => '⛒', 1798 => '⛓', 1799 => '⛔', 1800 => '⛕', 1801 => '⛖', 1802 => '⛗', 1803 => '⛘', 1804 => '⛙', 1805 => '⛚', 1806 => '⛛', 1807 => '⛜', 1808 => '⛝', 1809 => '⛞', 1810 => '⛟', 1811 => '⛠', 1812 => '⛡', 1813 => '⛢', 1814 => '⛣', 1815 => '⛤', 1816 => '⛥', 1817 => '⛦', 1818 => '⛧', 1819 => '⛨', 1820 => '⛩', 1821 => '⛪', 1822 => '⛫', 1823 => '⛬', 1824 => '⛭', 1825 => '⛮', 1826 => '⛯', 1827 => '⛰', 1828 => '⛱', 1829 => '⛲', 1830 => '⛳', 1831 => '⛴', 1832 => '⛵', 1833 => '⛶', 1834 => '⛷', 1835 => '⛸', 1836 => '⛹', 1837 => '⛺', 1838 => '⛻', 1839 => '⛼', 1840 => '⛽', 1841 => '⛾', 1842 => '⛿', 1843 => '✁', 1844 => '✂', 1845 => '✃', 1846 => '✄', 1847 => '✅', 1848 => '✆', 1849 => '✇', 1850 => '✈', 1851 => '✉', 1852 => '✊', 1853 => '✋', 1854 => '✌', 1855 => '✍', 1856 => '✎', 1857 => '✏', 1858 => '✐', 1859 => '✑', 1860 => '✒', 1861 => '✓', 1862 => '✔', 1863 => '✕', 1864 => '✖', 1865 => '✗', 1866 => '✘', 1867 => '✙', 1868 => '✚', 1869 => '✛', 1870 => '✜', 1871 => '✝', 1872 => '✞', 1873 => '✟', 1874 => '✠', 1875 => '✡', 1876 => '✢', 1877 => '✣', 1878 => '✤', 1879 => '✥', 1880 => '✦', 1881 => '✧', 1882 => '✨', 1883 => '✩', 1884 => '✪', 1885 => '✫', 1886 => '✬', 1887 => '✭', 1888 => '✮', 1889 => '✯', 1890 => '✰', 1891 => '✱', 1892 => '✲', 1893 => '✳', 1894 => '✴', 1895 => '✵', 1896 => '✶', 1897 => '✷', 1898 => '✸', 1899 => '✹', 1900 => '✺', 1901 => '✻', 1902 => '✼', 1903 => '✽', 1904 => '✾', 1905 => '✿', 1906 => '❀', 1907 => '❁', 1908 => '❂', 1909 => '❃', 1910 => '❄', 1911 => '❅', 1912 => '❆', 1913 => '❇', 1914 => '❈', 1915 => '❉', 1916 => '❊', 1917 => '❋', 1918 => '❌', 1919 => '❍', 1920 => '❎', 1921 => '❏', 1922 => '❐', 1923 => '❑', 1924 => '❒', 1925 => '❓', 1926 => '❔', 1927 => '❕', 1928 => '❖', 1929 => '❗', 1930 => '❘', 1931 => '❙', 1932 => '❚', 1933 => '❛', 1934 => '❜', 1935 => '❝', 1936 => '❞', 1937 => '❟', 1938 => '❠', 1939 => '❡', 1940 => '❢', 1941 => '❣', 1942 => '❤', 1943 => '❥', 1944 => '❦', 1945 => '❧', 1946 => '❨', 1947 => '❩', 1948 => '❪', 1949 => '❫', 1950 => '❬', 1951 => '❭', 1952 => '❮', 1953 => '❯', 1954 => '❰', 1955 => '❱', 1956 => '❲', 1957 => '❳', 1958 => '❴', 1959 => '❵', 1960 => '➔', 1961 => '➕', 1962 => '➖', 1963 => '➗', 1964 => '➘', 1965 => '➙', 1966 => '➚', 1967 => '➛', 1968 => '➜', 1969 => '➝', 1970 => '➞', 1971 => '➟', 1972 => '➠', 1973 => '➡', 1974 => '➢', 1975 => '➣', 1976 => '➤', 1977 => '➥', 1978 => '➦', 1979 => '➧', 1980 => '➨', 1981 => '➩', 1982 => '➪', 1983 => '➫', 1984 => '➬', 1985 => '➭', 1986 => '➮', 1987 => '➯', 1988 => '➰', 1989 => '➱', 1990 => '➲', 1991 => '➳', 1992 => '➴', 1993 => '➵', 1994 => '➶', 1995 => '➷', 1996 => '➸', 1997 => '➹', 1998 => '➺', 1999 => '➻', 2000 => '➼', 2001 => '➽', 2002 => '➾', 2003 => '➿', 2004 => '⟀', 2005 => '⟁', 2006 => '⟂', 2007 => '⟃', 2008 => '⟄', 2009 => '⟅', 2010 => '⟆', 2011 => '⟇', 2012 => '⟈', 2013 => '⟉', 2014 => '⟊', 2015 => '⟌', 2016 => '⟎', 2017 => '⟏', 2018 => '⟐', 2019 => '⟑', 2020 => '⟒', 2021 => '⟓', 2022 => '⟔', 2023 => '⟕', 2024 => '⟖', 2025 => '⟗', 2026 => '⟘', 2027 => '⟙', 2028 => '⟚', 2029 => '⟛', 2030 => '⟜', 2031 => '⟝', 2032 => '⟞', 2033 => '⟟', 2034 => '⟠', 2035 => '⟡', 2036 => '⟢', 2037 => '⟣', 2038 => '⟤', 2039 => '⟥', 2040 => '⟦', 2041 => '⟧', 2042 => '⟨', 2043 => '⟩', 2044 => '⟪', 2045 => '⟫', 2046 => '⟰', 2047 => '⟱', 2048 => '⟲', 2049 => '⟳', 2050 => '⟴', 2051 => '⟵', 2052 => '⟶', 2053 => '⟷', 2054 => '⟸', 2055 => '⟹', 2056 => '⟺', 2057 => '⟻', 2058 => '⟼', 2059 => '⟽', 2060 => '⟾', 2061 => '⟿', 2062 => '⤀', 2063 => '⤁', 2064 => '⤂', 2065 => '⤃', 2066 => '⤄', 2067 => '⤅', 2068 => '⤆', 2069 => '⤇', 2070 => '⤈', 2071 => '⤉', 2072 => '⤊', 2073 => '⤋', 2074 => '⤌', 2075 => '⤍', 2076 => '⤎', 2077 => '⤏', 2078 => '⤐', 2079 => '⤑', 2080 => '⤒', 2081 => '⤓', 2082 => '⤔', 2083 => '⤕', 2084 => '⤖', 2085 => '⤗', 2086 => '⤘', 2087 => '⤙', 2088 => '⤚', 2089 => '⤛', 2090 => '⤜', 2091 => '⤝', 2092 => '⤞', 2093 => '⤟', 2094 => '⤠', 2095 => '⤡', 2096 => '⤢', 2097 => '⤣', 2098 => '⤤', 2099 => '⤥', 2100 => '⤦', 2101 => '⤧', 2102 => '⤨', 2103 => '⤩', 2104 => '⤪', 2105 => '⤫', 2106 => '⤬', 2107 => '⤭', 2108 => '⤮', 2109 => '⤯', 2110 => '⤰', 2111 => '⤱', 2112 => '⤲', 2113 => '⤳', 2114 => '⤴', 2115 => '⤵', 2116 => '⤶', 2117 => '⤷', 2118 => '⤸', 2119 => '⤹', 2120 => '⤺', 2121 => '⤻', 2122 => '⤼', 2123 => '⤽', 2124 => '⤾', 2125 => '⤿', 2126 => '⥀', 2127 => '⥁', 2128 => '⥂', 2129 => '⥃', 2130 => '⥄', 2131 => '⥅', 2132 => '⥆', 2133 => '⥇', 2134 => '⥈', 2135 => '⥉', 2136 => '⥊', 2137 => '⥋', 2138 => '⥌', 2139 => '⥍', 2140 => '⥎', 2141 => '⥏', 2142 => '⥐', 2143 => '⥑', 2144 => '⥒', 2145 => '⥓', 2146 => '⥔', 2147 => '⥕', 2148 => '⥖', 2149 => '⥗', 2150 => '⥘', 2151 => '⥙', 2152 => '⥚', 2153 => '⥛', 2154 => '⥜', 2155 => '⥝', 2156 => '⥞', 2157 => '⥟', 2158 => '⥠', 2159 => '⥡', 2160 => '⥢', 2161 => '⥣', 2162 => '⥤', 2163 => '⥥', 2164 => '⥦', 2165 => '⥧', 2166 => '⥨', 2167 => '⥩', 2168 => '⥪', 2169 => '⥫', 2170 => '⥬', 2171 => '⥭', 2172 => '⥮', 2173 => '⥯', 2174 => '⥰', 2175 => '⥱', 2176 => '⥲', 2177 => '⥳', 2178 => '⥴', 2179 => '⥵', 2180 => '⥶', 2181 => '⥷', 2182 => '⥸', 2183 => '⥹', 2184 => '⥺', 2185 => '⥻', 2186 => '⥼', 2187 => '⥽', 2188 => '⥾', 2189 => '⥿', 2190 => '⦀', 2191 => '⦁', 2192 => '⦂', 2193 => '⦙', 2194 => '⦚', 2195 => '⦛', 2196 => '⦜', 2197 => '⦝', 2198 => '⦞', 2199 => '⦟', 2200 => '⦠', 2201 => '⦡', 2202 => '⦢', 2203 => '⦣', 2204 => '⦤', 2205 => '⦥', 2206 => '⦦', 2207 => '⦧', 2208 => '⦨', 2209 => '⦩', 2210 => '⦪', 2211 => '⦫', 2212 => '⦬', 2213 => '⦭', 2214 => '⦮', 2215 => '⦯', 2216 => '⦰', 2217 => '⦱', 2218 => '⦲', 2219 => '⦳', 2220 => '⦴', 2221 => '⦵', 2222 => '⦶', 2223 => '⦷', 2224 => '⦸', 2225 => '⦹', 2226 => '⦺', 2227 => '⦻', 2228 => '⦼', 2229 => '⦽', 2230 => '⦾', 2231 => '⦿', 2232 => '⧀', 2233 => '⧁', 2234 => '⧂', 2235 => '⧃', 2236 => '⧄', 2237 => '⧅', 2238 => '⧆', 2239 => '⧇', 2240 => '⧈', 2241 => '⧉', 2242 => '⧊', 2243 => '⧋', 2244 => '⧌', 2245 => '⧍', 2246 => '⧎', 2247 => '⧏', 2248 => '⧐', 2249 => '⧑', 2250 => '⧒', 2251 => '⧓', 2252 => '⧔', 2253 => '⧕', 2254 => '⧖', 2255 => '⧗', 2256 => '⧘', 2257 => '⧙', 2258 => '⧚', 2259 => '⧛', 2260 => '⧜', 2261 => '⧝', 2262 => '⧞', 2263 => '⧟', 2264 => '⧠', 2265 => '⧡', 2266 => '⧢', 2267 => '⧣', 2268 => '⧤', 2269 => '⧥', 2270 => '⧦', 2271 => '⧧', 2272 => '⧨', 2273 => '⧩', 2274 => '⧪', 2275 => '⧫', 2276 => '⧬', 2277 => '⧭', 2278 => '⧮', 2279 => '⧯', 2280 => '⧰', 2281 => '⧱', 2282 => '⧲', 2283 => '⧳', 2284 => '⧴', 2285 => '⧵', 2286 => '⧶', 2287 => '⧷', 2288 => '⧸', 2289 => '⧹', 2290 => '⧺', 2291 => '⧻', 2292 => '⧾', 2293 => '⧿', 2294 => '⨀', 2295 => '⨁', 2296 => '⨂', 2297 => '⨃', 2298 => '⨄', 2299 => '⨅', 2300 => '⨆', 2301 => '⨇', 2302 => '⨈', 2303 => '⨉', 2304 => '⨊', 2305 => '⨋', 2306 => '⨍', 2307 => '⨎', 2308 => '⨏', 2309 => '⨐', 2310 => '⨑', 2311 => '⨒', 2312 => '⨓', 2313 => '⨔', 2314 => '⨕', 2315 => '⨖', 2316 => '⨗', 2317 => '⨘', 2318 => '⨙', 2319 => '⨚', 2320 => '⨛', 2321 => '⨜', 2322 => '⨝', 2323 => '⨞', 2324 => '⨟', 2325 => '⨠', 2326 => '⨡', 2327 => '⨢', 2328 => '⨣', 2329 => '⨤', 2330 => '⨥', 2331 => '⨦', 2332 => '⨧', 2333 => '⨨', 2334 => '⨩', 2335 => '⨪', 2336 => '⨫', 2337 => '⨬', 2338 => '⨭', 2339 => '⨮', 2340 => '⨯', 2341 => '⨰', 2342 => '⨱', 2343 => '⨲', 2344 => '⨳', 2345 => '⨴', 2346 => '⨵', 2347 => '⨶', 2348 => '⨷', 2349 => '⨸', 2350 => '⨹', 2351 => '⨺', 2352 => '⨻', 2353 => '⨼', 2354 => '⨽', 2355 => '⨾', 2356 => '⨿', 2357 => '⩀', 2358 => '⩁', 2359 => '⩂', 2360 => '⩃', 2361 => '⩄', 2362 => '⩅', 2363 => '⩆', 2364 => '⩇', 2365 => '⩈', 2366 => '⩉', 2367 => '⩊', 2368 => '⩋', 2369 => '⩌', 2370 => '⩍', 2371 => '⩎', 2372 => '⩏', 2373 => '⩐', 2374 => '⩑', 2375 => '⩒', 2376 => '⩓', 2377 => '⩔', 2378 => '⩕', 2379 => '⩖', 2380 => '⩗', 2381 => '⩘', 2382 => '⩙', 2383 => '⩚', 2384 => '⩛', 2385 => '⩜', 2386 => '⩝', 2387 => '⩞', 2388 => '⩟', 2389 => '⩠', 2390 => '⩡', 2391 => '⩢', 2392 => '⩣', 2393 => '⩤', 2394 => '⩥', 2395 => '⩦', 2396 => '⩧', 2397 => '⩨', 2398 => '⩩', 2399 => '⩪', 2400 => '⩫', 2401 => '⩬', 2402 => '⩭', 2403 => '⩮', 2404 => '⩯', 2405 => '⩰', 2406 => '⩱', 2407 => '⩲', 2408 => '⩳', 2409 => '⩷', 2410 => '⩸', 2411 => '⩹', 2412 => '⩺', 2413 => '⩻', 2414 => '⩼', 2415 => '⩽', 2416 => '⩾', 2417 => '⩿', 2418 => '⪀', 2419 => '⪁', 2420 => '⪂', 2421 => '⪃', 2422 => '⪄', 2423 => '⪅', 2424 => '⪆', 2425 => '⪇', 2426 => '⪈', 2427 => '⪉', 2428 => '⪊', 2429 => '⪋', 2430 => '⪌', 2431 => '⪍', 2432 => '⪎', 2433 => '⪏', 2434 => '⪐', 2435 => '⪑', 2436 => '⪒', 2437 => '⪓', 2438 => '⪔', 2439 => '⪕', 2440 => '⪖', 2441 => '⪗', 2442 => '⪘', 2443 => '⪙', 2444 => '⪚', 2445 => '⪛', 2446 => '⪜', 2447 => '⪝', 2448 => '⪞', 2449 => '⪟', 2450 => '⪠', 2451 => '⪡', 2452 => '⪢', 2453 => '⪣', 2454 => '⪤', 2455 => '⪥', 2456 => '⪦', 2457 => '⪧', 2458 => '⪨', 2459 => '⪩', 2460 => '⪪', 2461 => '⪫', 2462 => '⪬', 2463 => '⪭', 2464 => '⪮', 2465 => '⪯', 2466 => '⪰', 2467 => '⪱', 2468 => '⪲', 2469 => '⪳', 2470 => '⪴', 2471 => '⪵', 2472 => '⪶', 2473 => '⪷', 2474 => '⪸', 2475 => '⪹', 2476 => '⪺', 2477 => '⪻', 2478 => '⪼', 2479 => '⪽', 2480 => '⪾', 2481 => '⪿', 2482 => '⫀', 2483 => '⫁', 2484 => '⫂', 2485 => '⫃', 2486 => '⫄', 2487 => '⫅', 2488 => '⫆', 2489 => '⫇', 2490 => '⫈', 2491 => '⫉', 2492 => '⫊', 2493 => '⫋', 2494 => '⫌', 2495 => '⫍', 2496 => '⫎', 2497 => '⫏', 2498 => '⫐', 2499 => '⫑', 2500 => '⫒', 2501 => '⫓', 2502 => '⫔', 2503 => '⫕', 2504 => '⫖', 2505 => '⫗', 2506 => '⫘', 2507 => '⫙', 2508 => '⫚', 2509 => '⫛', 2510 => '⫝', 2511 => '⫞', 2512 => '⫟', 2513 => '⫠', 2514 => '⫡', 2515 => '⫢', 2516 => '⫣', 2517 => '⫤', 2518 => '⫥', 2519 => '⫦', 2520 => '⫧', 2521 => '⫨', 2522 => '⫩', 2523 => '⫪', 2524 => '⫫', 2525 => '⫬', 2526 => '⫭', 2527 => '⫮', 2528 => '⫯', 2529 => '⫰', 2530 => '⫱', 2531 => '⫲', 2532 => '⫳', 2533 => '⫴', 2534 => '⫵', 2535 => '⫶', 2536 => '⫷', 2537 => '⫸', 2538 => '⫹', 2539 => '⫺', 2540 => '⫻', 2541 => '⫼', 2542 => '⫽', 2543 => '⫾', 2544 => '⫿', 2545 => '⬀', 2546 => '⬁', 2547 => '⬂', 2548 => '⬃', 2549 => '⬄', 2550 => '⬅', 2551 => '⬆', 2552 => '⬇', 2553 => '⬈', 2554 => '⬉', 2555 => '⬊', 2556 => '⬋', 2557 => '⬌', 2558 => '⬍', 2559 => '⬎', 2560 => '⬏', 2561 => '⬐', 2562 => '⬑', 2563 => '⬒', 2564 => '⬓', 2565 => '⬔', 2566 => '⬕', 2567 => '⬖', 2568 => '⬗', 2569 => '⬘', 2570 => '⬙', 2571 => '⬚', 2572 => '⬛', 2573 => '⬜', 2574 => '⬝', 2575 => '⬞', 2576 => '⬟', 2577 => '⬠', 2578 => '⬡', 2579 => '⬢', 2580 => '⬣', 2581 => '⬤', 2582 => '⬥', 2583 => '⬦', 2584 => '⬧', 2585 => '⬨', 2586 => '⬩', 2587 => '⬪', 2588 => '⬫', 2589 => '⬬', 2590 => '⬭', 2591 => '⬮', 2592 => '⬯', 2593 => '⬰', 2594 => '⬱', 2595 => '⬲', 2596 => '⬳', 2597 => '⬴', 2598 => '⬵', 2599 => '⬶', 2600 => '⬷', 2601 => '⬸', 2602 => '⬹', 2603 => '⬺', 2604 => '⬻', 2605 => '⬼', 2606 => '⬽', 2607 => '⬾', 2608 => '⬿', 2609 => '⭀', 2610 => '⭁', 2611 => '⭂', 2612 => '⭃', 2613 => '⭄', 2614 => '⭅', 2615 => '⭆', 2616 => '⭇', 2617 => '⭈', 2618 => '⭉', 2619 => '⭊', 2620 => '⭋', 2621 => '⭌', 2622 => '⭐', 2623 => '⭑', 2624 => '⭒', 2625 => '⭓', 2626 => '⭔', 2627 => '⭕', 2628 => '⭖', 2629 => '⭗', 2630 => '⭘', 2631 => '⭙', 2632 => '⳥', 2633 => '⳦', 2634 => '⳧', 2635 => '⳨', 2636 => '⳩', 2637 => '⳪', 2638 => '⠀', 2639 => '⠁', 2640 => '⠂', 2641 => '⠃', 2642 => '⠄', 2643 => '⠅', 2644 => '⠆', 2645 => '⠇', 2646 => '⠈', 2647 => '⠉', 2648 => '⠊', 2649 => '⠋', 2650 => '⠌', 2651 => '⠍', 2652 => '⠎', 2653 => '⠏', 2654 => '⠐', 2655 => '⠑', 2656 => '⠒', 2657 => '⠓', 2658 => '⠔', 2659 => '⠕', 2660 => '⠖', 2661 => '⠗', 2662 => '⠘', 2663 => '⠙', 2664 => '⠚', 2665 => '⠛', 2666 => '⠜', 2667 => '⠝', 2668 => '⠞', 2669 => '⠟', 2670 => '⠠', 2671 => '⠡', 2672 => '⠢', 2673 => '⠣', 2674 => '⠤', 2675 => '⠥', 2676 => '⠦', 2677 => '⠧', 2678 => '⠨', 2679 => '⠩', 2680 => '⠪', 2681 => '⠫', 2682 => '⠬', 2683 => '⠭', 2684 => '⠮', 2685 => '⠯', 2686 => '⠰', 2687 => '⠱', 2688 => '⠲', 2689 => '⠳', 2690 => '⠴', 2691 => '⠵', 2692 => '⠶', 2693 => '⠷', 2694 => '⠸', 2695 => '⠹', 2696 => '⠺', 2697 => '⠻', 2698 => '⠼', 2699 => '⠽', 2700 => '⠾', 2701 => '⠿', 2702 => '⡀', 2703 => '⡁', 2704 => '⡂', 2705 => '⡃', 2706 => '⡄', 2707 => '⡅', 2708 => '⡆', 2709 => '⡇', 2710 => '⡈', 2711 => '⡉', 2712 => '⡊', 2713 => '⡋', 2714 => '⡌', 2715 => '⡍', 2716 => '⡎', 2717 => '⡏', 2718 => '⡐', 2719 => '⡑', 2720 => '⡒', 2721 => '⡓', 2722 => '⡔', 2723 => '⡕', 2724 => '⡖', 2725 => '⡗', 2726 => '⡘', 2727 => '⡙', 2728 => '⡚', 2729 => '⡛', 2730 => '⡜', 2731 => '⡝', 2732 => '⡞', 2733 => '⡟', 2734 => '⡠', 2735 => '⡡', 2736 => '⡢', 2737 => '⡣', 2738 => '⡤', 2739 => '⡥', 2740 => '⡦', 2741 => '⡧', 2742 => '⡨', 2743 => '⡩', 2744 => '⡪', 2745 => '⡫', 2746 => '⡬', 2747 => '⡭', 2748 => '⡮', 2749 => '⡯', 2750 => '⡰', 2751 => '⡱', 2752 => '⡲', 2753 => '⡳', 2754 => '⡴', 2755 => '⡵', 2756 => '⡶', 2757 => '⡷', 2758 => '⡸', 2759 => '⡹', 2760 => '⡺', 2761 => '⡻', 2762 => '⡼', 2763 => '⡽', 2764 => '⡾', 2765 => '⡿', 2766 => '⢀', 2767 => '⢁', 2768 => '⢂', 2769 => '⢃', 2770 => '⢄', 2771 => '⢅', 2772 => '⢆', 2773 => '⢇', 2774 => '⢈', 2775 => '⢉', 2776 => '⢊', 2777 => '⢋', 2778 => '⢌', 2779 => '⢍', 2780 => '⢎', 2781 => '⢏', 2782 => '⢐', 2783 => '⢑', 2784 => '⢒', 2785 => '⢓', 2786 => '⢔', 2787 => '⢕', 2788 => '⢖', 2789 => '⢗', 2790 => '⢘', 2791 => '⢙', 2792 => '⢚', 2793 => '⢛', 2794 => '⢜', 2795 => '⢝', 2796 => '⢞', 2797 => '⢟', 2798 => '⢠', 2799 => '⢡', 2800 => '⢢', 2801 => '⢣', 2802 => '⢤', 2803 => '⢥', 2804 => '⢦', 2805 => '⢧', 2806 => '⢨', 2807 => '⢩', 2808 => '⢪', 2809 => '⢫', 2810 => '⢬', 2811 => '⢭', 2812 => '⢮', 2813 => '⢯', 2814 => '⢰', 2815 => '⢱', 2816 => '⢲', 2817 => '⢳', 2818 => '⢴', 2819 => '⢵', 2820 => '⢶', 2821 => '⢷', 2822 => '⢸', 2823 => '⢹', 2824 => '⢺', 2825 => '⢻', 2826 => '⢼', 2827 => '⢽', 2828 => '⢾', 2829 => '⢿', 2830 => '⣀', 2831 => '⣁', 2832 => '⣂', 2833 => '⣃', 2834 => '⣄', 2835 => '⣅', 2836 => '⣆', 2837 => '⣇', 2838 => '⣈', 2839 => '⣉', 2840 => '⣊', 2841 => '⣋', 2842 => '⣌', 2843 => '⣍', 2844 => '⣎', 2845 => '⣏', 2846 => '⣐', 2847 => '⣑', 2848 => '⣒', 2849 => '⣓', 2850 => '⣔', 2851 => '⣕', 2852 => '⣖', 2853 => '⣗', 2854 => '⣘', 2855 => '⣙', 2856 => '⣚', 2857 => '⣛', 2858 => '⣜', 2859 => '⣝', 2860 => '⣞', 2861 => '⣟', 2862 => '⣠', 2863 => '⣡', 2864 => '⣢', 2865 => '⣣', 2866 => '⣤', 2867 => '⣥', 2868 => '⣦', 2869 => '⣧', 2870 => '⣨', 2871 => '⣩', 2872 => '⣪', 2873 => '⣫', 2874 => '⣬', 2875 => '⣭', 2876 => '⣮', 2877 => '⣯', 2878 => '⣰', 2879 => '⣱', 2880 => '⣲', 2881 => '⣳', 2882 => '⣴', 2883 => '⣵', 2884 => '⣶', 2885 => '⣷', 2886 => '⣸', 2887 => '⣹', 2888 => '⣺', 2889 => '⣻', 2890 => '⣼', 2891 => '⣽', 2892 => '⣾', 2893 => '⣿', 2894 => '⚊', 2895 => '⚋', 2896 => '⚌', 2897 => '⚍', 2898 => '⚎', 2899 => '⚏', 2900 => '☰', 2901 => '☱', 2902 => '☲', 2903 => '☳', 2904 => '☴', 2905 => '☵', 2906 => '☶', 2907 => '☷', 2908 => '䷀', 2909 => '䷁', 2910 => '䷂', 2911 => '䷃', 2912 => '䷄', 2913 => '䷅', 2914 => '䷆', 2915 => '䷇', 2916 => '䷈', 2917 => '䷉', 2918 => '䷊', 2919 => '䷋', 2920 => '䷌', 2921 => '䷍', 2922 => '䷎', 2923 => '䷏', 2924 => '䷐', 2925 => '䷑', 2926 => '䷒', 2927 => '䷓', 2928 => '䷔', 2929 => '䷕', 2930 => '䷖', 2931 => '䷗', 2932 => '䷘', 2933 => '䷙', 2934 => '䷚', 2935 => '䷛', 2936 => '䷜', 2937 => '䷝', 2938 => '䷞', 2939 => '䷟', 2940 => '䷠', 2941 => '䷡', 2942 => '䷢', 2943 => '䷣', 2944 => '䷤', 2945 => '䷥', 2946 => '䷦', 2947 => '䷧', 2948 => '䷨', 2949 => '䷩', 2950 => '䷪', 2951 => '䷫', 2952 => '䷬', 2953 => '䷭', 2954 => '䷮', 2955 => '䷯', 2956 => '䷰', 2957 => '䷱', 2958 => '䷲', 2959 => '䷳', 2960 => '䷴', 2961 => '䷵', 2962 => '䷶', 2963 => '䷷', 2964 => '䷸', 2965 => '䷹', 2966 => '䷺', 2967 => '䷻', 2968 => '䷼', 2969 => '䷽', 2970 => '䷾', 2971 => '䷿', 2972 => '𝌀', 2973 => '𝌁', 2974 => '𝌂', 2975 => '𝌃', 2976 => '𝌄', 2977 => '𝌅', 2978 => '𝌆', 2979 => '𝌇', 2980 => '𝌈', 2981 => '𝌉', 2982 => '𝌊', 2983 => '𝌋', 2984 => '𝌌', 2985 => '𝌍', 2986 => '𝌎', 2987 => '𝌏', 2988 => '𝌐', 2989 => '𝌑', 2990 => '𝌒', 2991 => '𝌓', 2992 => '𝌔', 2993 => '𝌕', 2994 => '𝌖', 2995 => '𝌗', 2996 => '𝌘', 2997 => '𝌙', 2998 => '𝌚', 2999 => '𝌛', 3000 => '𝌜', 3001 => '𝌝', 3002 => '𝌞', 3003 => '𝌟', 3004 => '𝌠', 3005 => '𝌡', 3006 => '𝌢', 3007 => '𝌣', 3008 => '𝌤', 3009 => '𝌥', 3010 => '𝌦', 3011 => '𝌧', 3012 => '𝌨', 3013 => '𝌩', 3014 => '𝌪', 3015 => '𝌫', 3016 => '𝌬', 3017 => '𝌭', 3018 => '𝌮', 3019 => '𝌯', 3020 => '𝌰', 3021 => '𝌱', 3022 => '𝌲', 3023 => '𝌳', 3024 => '𝌴', 3025 => '𝌵', 3026 => '𝌶', 3027 => '𝌷', 3028 => '𝌸', 3029 => '𝌹', 3030 => '𝌺', 3031 => '𝌻', 3032 => '𝌼', 3033 => '𝌽', 3034 => '𝌾', 3035 => '𝌿', 3036 => '𝍀', 3037 => '𝍁', 3038 => '𝍂', 3039 => '𝍃', 3040 => '𝍄', 3041 => '𝍅', 3042 => '𝍆', 3043 => '𝍇', 3044 => '𝍈', 3045 => '𝍉', 3046 => '𝍊', 3047 => '𝍋', 3048 => '𝍌', 3049 => '𝍍', 3050 => '𝍎', 3051 => '𝍏', 3052 => '𝍐', 3053 => '𝍑', 3054 => '𝍒', 3055 => '𝍓', 3056 => '𝍔', 3057 => '𝍕', 3058 => '𝍖', 3059 => '꒐', 3060 => '꒑', 3061 => '꒒', 3062 => '꒓', 3063 => '꒔', 3064 => '꒕', 3065 => '꒖', 3066 => '꒗', 3067 => '꒘', 3068 => '꒙', 3069 => '꒚', 3070 => '꒛', 3071 => '꒜', 3072 => '꒝', 3073 => '꒞', 3074 => '꒟', 3075 => '꒠', 3076 => '꒡', 3077 => '꒢', 3078 => '꒣', 3079 => '꒤', 3080 => '꒥', 3081 => '꒦', 3082 => '꒧', 3083 => '꒨', 3084 => '꒩', 3085 => '꒪', 3086 => '꒫', 3087 => '꒬', 3088 => '꒭', 3089 => '꒮', 3090 => '꒯', 3091 => '꒰', 3092 => '꒱', 3093 => '꒲', 3094 => '꒳', 3095 => '꒴', 3096 => '꒵', 3097 => '꒶', 3098 => '꒷', 3099 => '꒸', 3100 => '꒹', 3101 => '꒺', 3102 => '꒻', 3103 => '꒼', 3104 => '꒽', 3105 => '꒾', 3106 => '꒿', 3107 => '꓀', 3108 => '꓁', 3109 => '꓂', 3110 => '꓃', 3111 => '꓄', 3112 => '꓅', 3113 => '꓆', 3114 => '𐄷', 3115 => '𐄸', 3116 => '𐄹', 3117 => '𐄺', 3118 => '𐄻', 3119 => '𐄼', 3120 => '𐄽', 3121 => '𐄾', 3122 => '𐄿', 3123 => '𐅹', 3124 => '𐅺', 3125 => '𐅻', 3126 => '𐅼', 3127 => '𐅽', 3128 => '𐅾', 3129 => '𐅿', 3130 => '𐆀', 3131 => '𐆁', 3132 => '𐆂', 3133 => '𐆃', 3134 => '𐆄', 3135 => '𐆅', 3136 => '𐆆', 3137 => '𐆇', 3138 => '𐆈', 3139 => '𐆉', 3140 => '𐆐', 3141 => '𐆑', 3142 => '𐆒', 3143 => '𐆓', 3144 => '𐆔', 3145 => '𐆕', 3146 => '𐆖', 3147 => '𐆗', 3148 => '𐆘', 3149 => '𐆙', 3150 => '𐆚', 3151 => '𐆛', 3152 => '𐇐', 3153 => '𐇑', 3154 => '𐇒', 3155 => '𐇓', 3156 => '𐇔', 3157 => '𐇕', 3158 => '𐇖', 3159 => '𐇗', 3160 => '𐇘', 3161 => '𐇙', 3162 => '𐇚', 3163 => '𐇛', 3164 => '𐇜', 3165 => '𐇝', 3166 => '𐇞', 3167 => '𐇟', 3168 => '𐇠', 3169 => '𐇡', 3170 => '𐇢', 3171 => '𐇣', 3172 => '𐇤', 3173 => '𐇥', 3174 => '𐇦', 3175 => '𐇧', 3176 => '𐇨', 3177 => '𐇩', 3178 => '𐇪', 3179 => '𐇫', 3180 => '𐇬', 3181 => '𐇭', 3182 => '𐇮', 3183 => '𐇯', 3184 => '𐇰', 3185 => '𐇱', 3186 => '𐇲', 3187 => '𐇳', 3188 => '𐇴', 3189 => '𐇵', 3190 => '𐇶', 3191 => '𐇷', 3192 => '𐇸', 3193 => '𐇹', 3194 => '𐇺', 3195 => '𐇻', 3196 => '𐇼', 3197 => '𝀀', 3198 => '𝀁', 3199 => '𝀂', 3200 => '𝀃', 3201 => '𝀄', 3202 => '𝀅', 3203 => '𝀆', 3204 => '𝀇', 3205 => '𝀈', 3206 => '𝀉', 3207 => '𝀊', 3208 => '𝀋', 3209 => '𝀌', 3210 => '𝀍', 3211 => '𝀎', 3212 => '𝀏', 3213 => '𝀐', 3214 => '𝀑', 3215 => '𝀒', 3216 => '𝀓', 3217 => '𝀔', 3218 => '𝀕', 3219 => '𝀖', 3220 => '𝀗', 3221 => '𝀘', 3222 => '𝀙', 3223 => '𝀚', 3224 => '𝀛', 3225 => '𝀜', 3226 => '𝀝', 3227 => '𝀞', 3228 => '𝀟', 3229 => '𝀠', 3230 => '𝀡', 3231 => '𝀢', 3232 => '𝀣', 3233 => '𝀤', 3234 => '𝀥', 3235 => '𝀦', 3236 => '𝀧', 3237 => '𝀨', 3238 => '𝀩', 3239 => '𝀪', 3240 => '𝀫', 3241 => '𝀬', 3242 => '𝀭', 3243 => '𝀮', 3244 => '𝀯', 3245 => '𝀰', 3246 => '𝀱', 3247 => '𝀲', 3248 => '𝀳', 3249 => '𝀴', 3250 => '𝀵', 3251 => '𝀶', 3252 => '𝀷', 3253 => '𝀸', 3254 => '𝀹', 3255 => '𝀺', 3256 => '𝀻', 3257 => '𝀼', 3258 => '𝀽', 3259 => '𝀾', 3260 => '𝀿', 3261 => '𝁀', 3262 => '𝁁', 3263 => '𝁂', 3264 => '𝁃', 3265 => '𝁄', 3266 => '𝁅', 3267 => '𝁆', 3268 => '𝁇', 3269 => '𝁈', 3270 => '𝁉', 3271 => '𝁊', 3272 => '𝁋', 3273 => '𝁌', 3274 => '𝁍', 3275 => '𝁎', 3276 => '𝁏', 3277 => '𝁐', 3278 => '𝁑', 3279 => '𝁒', 3280 => '𝁓', 3281 => '𝁔', 3282 => '𝁕', 3283 => '𝁖', 3284 => '𝁗', 3285 => '𝁘', 3286 => '𝁙', 3287 => '𝁚', 3288 => '𝁛', 3289 => '𝁜', 3290 => '𝁝', 3291 => '𝁞', 3292 => '𝁟', 3293 => '𝁠', 3294 => '𝁡', 3295 => '𝁢', 3296 => '𝁣', 3297 => '𝁤', 3298 => '𝁥', 3299 => '𝁦', 3300 => '𝁧', 3301 => '𝁨', 3302 => '𝁩', 3303 => '𝁪', 3304 => '𝁫', 3305 => '𝁬', 3306 => '𝁭', 3307 => '𝁮', 3308 => '𝁯', 3309 => '𝁰', 3310 => '𝁱', 3311 => '𝁲', 3312 => '𝁳', 3313 => '𝁴', 3314 => '𝁵', 3315 => '𝁶', 3316 => '𝁷', 3317 => '𝁸', 3318 => '𝁹', 3319 => '𝁺', 3320 => '𝁻', 3321 => '𝁼', 3322 => '𝁽', 3323 => '𝁾', 3324 => '𝁿', 3325 => '𝂀', 3326 => '𝂁', 3327 => '𝂂', 3328 => '𝂃', 3329 => '𝂄', 3330 => '𝂅', 3331 => '𝂆', 3332 => '𝂇', 3333 => '𝂈', 3334 => '𝂉', 3335 => '𝂊', 3336 => '𝂋', 3337 => '𝂌', 3338 => '𝂍', 3339 => '𝂎', 3340 => '𝂏', 3341 => '𝂐', 3342 => '𝂑', 3343 => '𝂒', 3344 => '𝂓', 3345 => '𝂔', 3346 => '𝂕', 3347 => '𝂖', 3348 => '𝂗', 3349 => '𝂘', 3350 => '𝂙', 3351 => '𝂚', 3352 => '𝂛', 3353 => '𝂜', 3354 => '𝂝', 3355 => '𝂞', 3356 => '𝂟', 3357 => '𝂠', 3358 => '𝂡', 3359 => '𝂢', 3360 => '𝂣', 3361 => '𝂤', 3362 => '𝂥', 3363 => '𝂦', 3364 => '𝂧', 3365 => '𝂨', 3366 => '𝂩', 3367 => '𝂪', 3368 => '𝂫', 3369 => '𝂬', 3370 => '𝂭', 3371 => '𝂮', 3372 => '𝂯', 3373 => '𝂰', 3374 => '𝂱', 3375 => '𝂲', 3376 => '𝂳', 3377 => '𝂴', 3378 => '𝂵', 3379 => '𝂶', 3380 => '𝂷', 3381 => '𝂸', 3382 => '𝂹', 3383 => '𝂺', 3384 => '𝂻', 3385 => '𝂼', 3386 => '𝂽', 3387 => '𝂾', 3388 => '𝂿', 3389 => '𝃀', 3390 => '𝃁', 3391 => '𝃂', 3392 => '𝃃', 3393 => '𝃄', 3394 => '𝃅', 3395 => '𝃆', 3396 => '𝃇', 3397 => '𝃈', 3398 => '𝃉', 3399 => '𝃊', 3400 => '𝃋', 3401 => '𝃌', 3402 => '𝃍', 3403 => '𝃎', 3404 => '𝃏', 3405 => '𝃐', 3406 => '𝃑', 3407 => '𝃒', 3408 => '𝃓', 3409 => '𝃔', 3410 => '𝃕', 3411 => '𝃖', 3412 => '𝃗', 3413 => '𝃘', 3414 => '𝃙', 3415 => '𝃚', 3416 => '𝃛', 3417 => '𝃜', 3418 => '𝃝', 3419 => '𝃞', 3420 => '𝃟', 3421 => '𝃠', 3422 => '𝃡', 3423 => '𝃢', 3424 => '𝃣', 3425 => '𝃤', 3426 => '𝃥', 3427 => '𝃦', 3428 => '𝃧', 3429 => '𝃨', 3430 => '𝃩', 3431 => '𝃪', 3432 => '𝃫', 3433 => '𝃬', 3434 => '𝃭', 3435 => '𝃮', 3436 => '𝃯', 3437 => '𝃰', 3438 => '𝃱', 3439 => '𝃲', 3440 => '𝃳', 3441 => '𝃴', 3442 => '𝃵', 3443 => '𝄀', 3444 => '𝄁', 3445 => '𝄂', 3446 => '𝄃', 3447 => '𝄄', 3448 => '𝄅', 3449 => '𝄆', 3450 => '𝄇', 3451 => '𝄈', 3452 => '𝄉', 3453 => '𝄊', 3454 => '𝄋', 3455 => '𝄌', 3456 => '𝄍', 3457 => '𝄎', 3458 => '𝄏', 3459 => '𝄐', 3460 => '𝄑', 3461 => '𝄒', 3462 => '𝄓', 3463 => '𝄔', 3464 => '𝄕', 3465 => '𝄖', 3466 => '𝄗', 3467 => '𝄘', 3468 => '𝄙', 3469 => '𝄚', 3470 => '𝄛', 3471 => '𝄜', 3472 => '𝄝', 3473 => '𝄞', 3474 => '𝄟', 3475 => '𝄠', 3476 => '𝄡', 3477 => '𝄢', 3478 => '𝄣', 3479 => '𝄤', 3480 => '𝄥', 3481 => '𝄦', 3482 => '♭', 3483 => '♮', 3484 => '♯', 3485 => '𝄪', 3486 => '𝄫', 3487 => '𝄬', 3488 => '𝄭', 3489 => '𝄮', 3490 => '𝄯', 3491 => '𝄰', 3492 => '𝄱', 3493 => '𝄲', 3494 => '𝄳', 3495 => '𝄴', 3496 => '𝄵', 3497 => '𝄶', 3498 => '𝄷', 3499 => '𝄸', 3500 => '𝄹', 3501 => '𝄩', 3502 => '𝄺', 3503 => '𝄻', 3504 => '𝄼', 3505 => '𝄽', 3506 => '𝄾', 3507 => '𝄿', 3508 => '𝅀', 3509 => '𝅁', 3510 => '𝅂', 3511 => '𝅃', 3512 => '𝅄', 3513 => '𝅅', 3514 => '𝅆', 3515 => '𝅇', 3516 => '𝅈', 3517 => '𝅉', 3518 => '𝅊', 3519 => '𝅋', 3520 => '𝅌', 3521 => '𝅍', 3522 => '𝅎', 3523 => '𝅏', 3524 => '𝅐', 3525 => '𝅑', 3526 => '𝅒', 3527 => '𝅓', 3528 => '𝅔', 3529 => '𝅕', 3530 => '𝅖', 3531 => '𝅗', 3532 => '𝅘', 3533 => '𝅙', 3534 => '𝅚', 3535 => '𝅛', 3536 => '𝅜', 3537 => '𝅝', 3538 => '𝅪', 3539 => '𝅫', 3540 => '𝅬', 3541 => '𝆃', 3542 => '𝆄', 3543 => '𝆌', 3544 => '𝆍', 3545 => '𝆎', 3546 => '𝆏', 3547 => '𝆐', 3548 => '𝆑', 3549 => '𝆒', 3550 => '𝆓', 3551 => '𝆔', 3552 => '𝆕', 3553 => '𝆖', 3554 => '𝆗', 3555 => '𝆘', 3556 => '𝆙', 3557 => '𝆚', 3558 => '𝆛', 3559 => '𝆜', 3560 => '𝆝', 3561 => '𝆞', 3562 => '𝆟', 3563 => '𝆠', 3564 => '𝆡', 3565 => '𝆢', 3566 => '𝆣', 3567 => '𝆤', 3568 => '𝆥', 3569 => '𝆦', 3570 => '𝆧', 3571 => '𝆨', 3572 => '𝆩', 3573 => '𝆮', 3574 => '𝆯', 3575 => '𝆰', 3576 => '𝆱', 3577 => '𝆲', 3578 => '𝆳', 3579 => '𝆴', 3580 => '𝆵', 3581 => '𝆶', 3582 => '𝆷', 3583 => '𝆸', 3584 => '𝆹', 3585 => '𝆺', 3586 => '𝇁', 3587 => '𝇂', 3588 => '𝇃', 3589 => '𝇄', 3590 => '𝇅', 3591 => '𝇆', 3592 => '𝇇', 3593 => '𝇈', 3594 => '𝇉', 3595 => '𝇊', 3596 => '𝇋', 3597 => '𝇌', 3598 => '𝇍', 3599 => '𝇎', 3600 => '𝇏', 3601 => '𝇐', 3602 => '𝇑', 3603 => '𝇒', 3604 => '𝇓', 3605 => '𝇔', 3606 => '𝇕', 3607 => '𝇖', 3608 => '𝇗', 3609 => '𝇘', 3610 => '𝇙', 3611 => '𝇚', 3612 => '𝇛', 3613 => '𝇜', 3614 => '𝇝', 3615 => '𝈀', 3616 => '𝈁', 3617 => '𝈂', 3618 => '𝈃', 3619 => '𝈄', 3620 => '𝈅', 3621 => '𝈆', 3622 => '𝈇', 3623 => '𝈈', 3624 => '𝈉', 3625 => '𝈊', 3626 => '𝈋', 3627 => '𝈌', 3628 => '𝈍', 3629 => '𝈎', 3630 => '𝈏', 3631 => '𝈐', 3632 => '𝈑', 3633 => '𝈒', 3634 => '𝈓', 3635 => '𝈔', 3636 => '𝈕', 3637 => '𝈖', 3638 => '𝈗', 3639 => '𝈘', 3640 => '𝈙', 3641 => '𝈚', 3642 => '𝈛', 3643 => '𝈜', 3644 => '𝈝', 3645 => '𝈞', 3646 => '𝈟', 3647 => '𝈠', 3648 => '𝈡', 3649 => '𝈢', 3650 => '𝈣', 3651 => '𝈤', 3652 => '𝈥', 3653 => '𝈦', 3654 => '𝈧', 3655 => '𝈨', 3656 => '𝈩', 3657 => '𝈪', 3658 => '𝈫', 3659 => '𝈬', 3660 => '𝈭', 3661 => '𝈮', 3662 => '𝈯', 3663 => '𝈰', 3664 => '𝈱', 3665 => '𝈲', 3666 => '𝈳', 3667 => '𝈴', 3668 => '𝈵', 3669 => '𝈶', 3670 => '𝈷', 3671 => '𝈸', 3672 => '𝈹', 3673 => '𝈺', 3674 => '𝈻', 3675 => '𝈼', 3676 => '𝈽', 3677 => '𝈾', 3678 => '𝈿', 3679 => '𝉀', 3680 => '𝉁', 3681 => '𝉅', 3682 => '🀀', 3683 => '🀁', 3684 => '🀂', 3685 => '🀃', 3686 => '🀄', 3687 => '🀅', 3688 => '🀆', 3689 => '🀇', 3690 => '🀈', 3691 => '🀉', 3692 => '🀊', 3693 => '🀋', 3694 => '🀌', 3695 => '🀍', 3696 => '🀎', 3697 => '🀏', 3698 => '🀐', 3699 => '🀑', 3700 => '🀒', 3701 => '🀓', 3702 => '🀔', 3703 => '🀕', 3704 => '🀖', 3705 => '🀗', 3706 => '🀘', 3707 => '🀙', 3708 => '🀚', 3709 => '🀛', 3710 => '🀜', 3711 => '🀝', 3712 => '🀞', 3713 => '🀟', 3714 => '🀠', 3715 => '🀡', 3716 => '🀢', 3717 => '🀣', 3718 => '🀤', 3719 => '🀥', 3720 => '🀦', 3721 => '🀧', 3722 => '🀨', 3723 => '🀩', 3724 => '🀪', 3725 => '🀫', 3726 => '🀰', 3727 => '🀱', 3728 => '🀲', 3729 => '🀳', 3730 => '🀴', 3731 => '🀵', 3732 => '🀶', 3733 => '🀷', 3734 => '🀸', 3735 => '🀹', 3736 => '🀺', 3737 => '🀻', 3738 => '🀼', 3739 => '🀽', 3740 => '🀾', 3741 => '🀿', 3742 => '🁀', 3743 => '🁁', 3744 => '🁂', 3745 => '🁃', 3746 => '🁄', 3747 => '🁅', 3748 => '🁆', 3749 => '🁇', 3750 => '🁈', 3751 => '🁉', 3752 => '🁊', 3753 => '🁋', 3754 => '🁌', 3755 => '🁍', 3756 => '🁎', 3757 => '🁏', 3758 => '🁐', 3759 => '🁑', 3760 => '🁒', 3761 => '🁓', 3762 => '🁔', 3763 => '🁕', 3764 => '🁖', 3765 => '🁗', 3766 => '🁘', 3767 => '🁙', 3768 => '🁚', 3769 => '🁛', 3770 => '🁜', 3771 => '🁝', 3772 => '🁞', 3773 => '🁟', 3774 => '🁠', 3775 => '🁡', 3776 => '🁢', 3777 => '🁣', 3778 => '🁤', 3779 => '🁥', 3780 => '🁦', 3781 => '🁧', 3782 => '🁨', 3783 => '🁩', 3784 => '🁪', 3785 => '🁫', 3786 => '🁬', 3787 => '🁭', 3788 => '🁮', 3789 => '🁯', 3790 => '🁰', 3791 => '🁱', 3792 => '🁲', 3793 => '🁳', 3794 => '🁴', 3795 => '🁵', 3796 => '🁶', 3797 => '🁷', 3798 => '🁸', 3799 => '🁹', 3800 => '🁺', 3801 => '🁻', 3802 => '🁼', 3803 => '🁽', 3804 => '🁾', 3805 => '🁿', 3806 => '🂀', 3807 => '🂁', 3808 => '🂂', 3809 => '🂃', 3810 => '🂄', 3811 => '🂅', 3812 => '🂆', 3813 => '🂇', 3814 => '🂈', 3815 => '🂉', 3816 => '🂊', 3817 => '🂋', 3818 => '🂌', 3819 => '🂍', 3820 => '🂎', 3821 => '🂏', 3822 => '🂐', 3823 => '🂑', 3824 => '🂒', 3825 => '🂓', 3826 => '🂠', 3827 => '🂡', 3828 => '🂢', 3829 => '🂣', 3830 => '🂤', 3831 => '🂥', 3832 => '🂦', 3833 => '🂧', 3834 => '🂨', 3835 => '🂩', 3836 => '🂪', 3837 => '🂫', 3838 => '🂬', 3839 => '🂭', 3840 => '🂮', 3841 => '🂱', 3842 => '🂲', 3843 => '🂳', 3844 => '🂴', 3845 => '🂵', 3846 => '🂶', 3847 => '🂷', 3848 => '🂸', 3849 => '🂹', 3850 => '🂺', 3851 => '🂻', 3852 => '🂼', 3853 => '🂽', 3854 => '🂾', 3855 => '🃁', 3856 => '🃂', 3857 => '🃃', 3858 => '🃄', 3859 => '🃅', 3860 => '🃆', 3861 => '🃇', 3862 => '🃈', 3863 => '🃉', 3864 => '🃊', 3865 => '🃋', 3866 => '🃌', 3867 => '🃍', 3868 => '🃎', 3869 => '🃏', 3870 => '🃑', 3871 => '🃒', 3872 => '🃓', 3873 => '🃔', 3874 => '🃕', 3875 => '🃖', 3876 => '🃗', 3877 => '🃘', 3878 => '🃙', 3879 => '🃚', 3880 => '🃛', 3881 => '🃜', 3882 => '🃝', 3883 => '🃞', 3884 => '🃟', 3885 => '🌀', 3886 => '🌁', 3887 => '🌂', 3888 => '🌃', 3889 => '🌄', 3890 => '🌅', 3891 => '🌆', 3892 => '🌇', 3893 => '🌈', 3894 => '🌉', 3895 => '🌊', 3896 => '🌋', 3897 => '🌌', 3898 => '🌍', 3899 => '🌎', 3900 => '🌏', 3901 => '🌐', 3902 => '🌑', 3903 => '🌒', 3904 => '🌓', 3905 => '🌔', 3906 => '🌕', 3907 => '🌖', 3908 => '🌗', 3909 => '🌘', 3910 => '🌙', 3911 => '🌚', 3912 => '🌛', 3913 => '🌜', 3914 => '🌝', 3915 => '🌞', 3916 => '🌟', 3917 => '🌠', 3918 => '🌰', 3919 => '🌱', 3920 => '🌲', 3921 => '🌳', 3922 => '🌴', 3923 => '🌵', 3924 => '🌷', 3925 => '🌸', 3926 => '🌹', 3927 => '🌺', 3928 => '🌻', 3929 => '🌼', 3930 => '🌽', 3931 => '🌾', 3932 => '🌿', 3933 => '🍀', 3934 => '🍁', 3935 => '🍂', 3936 => '🍃', 3937 => '🍄', 3938 => '🍅', 3939 => '🍆', 3940 => '🍇', 3941 => '🍈', 3942 => '🍉', 3943 => '🍊', 3944 => '🍋', 3945 => '🍌', 3946 => '🍍', 3947 => '🍎', 3948 => '🍏', 3949 => '🍐', 3950 => '🍑', 3951 => '🍒', 3952 => '🍓', 3953 => '🍔', 3954 => '🍕', 3955 => '🍖', 3956 => '🍗', 3957 => '🍘', 3958 => '🍙', 3959 => '🍚', 3960 => '🍛', 3961 => '🍜', 3962 => '🍝', 3963 => '🍞', 3964 => '🍟', 3965 => '🍠', 3966 => '🍡', 3967 => '🍢', 3968 => '🍣', 3969 => '🍤', 3970 => '🍥', 3971 => '🍦', 3972 => '🍧', 3973 => '🍨', 3974 => '🍩', 3975 => '🍪', 3976 => '🍫', 3977 => '🍬', 3978 => '🍭', 3979 => '🍮', 3980 => '🍯', 3981 => '🍰', 3982 => '🍱', 3983 => '🍲', 3984 => '🍳', 3985 => '🍴', 3986 => '🍵', 3987 => '🍶', 3988 => '🍷', 3989 => '🍸', 3990 => '🍹', 3991 => '🍺', 3992 => '🍻', 3993 => '🍼', 3994 => '🎀', 3995 => '🎁', 3996 => '🎂', 3997 => '🎃', 3998 => '🎄', 3999 => '🎅', 4000 => '🎆', 4001 => '🎇', 4002 => '🎈', 4003 => '🎉', 4004 => '🎊', 4005 => '🎋', 4006 => '🎌', 4007 => '🎍', 4008 => '🎎', 4009 => '🎏', 4010 => '🎐', 4011 => '🎑', 4012 => '🎒', 4013 => '🎓', 4014 => '🎠', 4015 => '🎡', 4016 => '🎢', 4017 => '🎣', 4018 => '🎤', 4019 => '🎥', 4020 => '🎦', 4021 => '🎧', 4022 => '🎨', 4023 => '🎩', 4024 => '🎪', 4025 => '🎫', 4026 => '🎬', 4027 => '🎭', 4028 => '🎮', 4029 => '🎯', 4030 => '🎰', 4031 => '🎱', 4032 => '🎲', 4033 => '🎳', 4034 => '🎴', 4035 => '🎵', 4036 => '🎶', 4037 => '🎷', 4038 => '🎸', 4039 => '🎹', 4040 => '🎺', 4041 => '🎻', 4042 => '🎼', 4043 => '🎽', 4044 => '🎾', 4045 => '🎿', 4046 => '🏀', 4047 => '🏁', 4048 => '🏂', 4049 => '🏃', 4050 => '🏄', 4051 => '🏆', 4052 => '🏇', 4053 => '🏈', 4054 => '🏉', 4055 => '🏊', 4056 => '🏠', 4057 => '🏡', 4058 => '🏢', 4059 => '🏣', 4060 => '🏤', 4061 => '🏥', 4062 => '🏦', 4063 => '🏧', 4064 => '🏨', 4065 => '🏩', 4066 => '🏪', 4067 => '🏫', 4068 => '🏬', 4069 => '🏭', 4070 => '🏮', 4071 => '🏯', 4072 => '🏰', 4073 => '🐀', 4074 => '🐁', 4075 => '🐂', 4076 => '🐃', 4077 => '🐄', 4078 => '🐅', 4079 => '🐆', 4080 => '🐇', 4081 => '🐈', 4082 => '🐉', 4083 => '🐊', 4084 => '🐋', 4085 => '🐌', 4086 => '🐍', 4087 => '🐎', 4088 => '🐏', 4089 => '🐐', 4090 => '🐑', 4091 => '🐒', 4092 => '🐓', 4093 => '🐔', 4094 => '🐕', 4095 => '🐖', 4096 => '🐗', 4097 => '🐘', 4098 => '🐙', 4099 => '🐚', 4100 => '🐛', 4101 => '🐜', 4102 => '🐝', 4103 => '🐞', 4104 => '🐟', 4105 => '🐠', 4106 => '🐡', 4107 => '🐢', 4108 => '🐣', 4109 => '🐤', 4110 => '🐥', 4111 => '🐦', 4112 => '🐧', 4113 => '🐨', 4114 => '🐩', 4115 => '🐪', 4116 => '🐫', 4117 => '🐬', 4118 => '🐭', 4119 => '🐮', 4120 => '🐯', 4121 => '🐰', 4122 => '🐱', 4123 => '🐲', 4124 => '🐳', 4125 => '🐴', 4126 => '🐵', 4127 => '🐶', 4128 => '🐷', 4129 => '🐸', 4130 => '🐹', 4131 => '🐺', 4132 => '🐻', 4133 => '🐼', 4134 => '🐽', 4135 => '🐾', 4136 => '👀', 4137 => '👂', 4138 => '👃', 4139 => '👄', 4140 => '👅', 4141 => '👆', 4142 => '👇', 4143 => '👈', 4144 => '👉', 4145 => '👊', 4146 => '👋', 4147 => '👌', 4148 => '👍', 4149 => '👎', 4150 => '👏', 4151 => '👐', 4152 => '👑', 4153 => '👒', 4154 => '👓', 4155 => '👔', 4156 => '👕', 4157 => '👖', 4158 => '👗', 4159 => '👘', 4160 => '👙', 4161 => '👚', 4162 => '👛', 4163 => '👜', 4164 => '👝', 4165 => '👞', 4166 => '👟', 4167 => '👠', 4168 => '👡', 4169 => '👢', 4170 => '👣', 4171 => '👤', 4172 => '👥', 4173 => '👦', 4174 => '👧', 4175 => '👨', 4176 => '👩', 4177 => '👪', 4178 => '👫', 4179 => '👬', 4180 => '👭', 4181 => '👮', 4182 => '👯', 4183 => '👰', 4184 => '👱', 4185 => '👲', 4186 => '👳', 4187 => '👴', 4188 => '👵', 4189 => '👶', 4190 => '👷', 4191 => '👸', 4192 => '👹', 4193 => '👺', 4194 => '👻', 4195 => '👼', 4196 => '👽', 4197 => '👾', 4198 => '👿', 4199 => '💀', 4200 => '💁', 4201 => '💂', 4202 => '💃', 4203 => '💄', 4204 => '💅', 4205 => '💆', 4206 => '💇', 4207 => '💈', 4208 => '💉', 4209 => '💊', 4210 => '💋', 4211 => '💌', 4212 => '💍', 4213 => '💎', 4214 => '💏', 4215 => '💐', 4216 => '💑', 4217 => '💒', 4218 => '💓', 4219 => '💔', 4220 => '💕', 4221 => '💖', 4222 => '💗', 4223 => '💘', 4224 => '💙', 4225 => '💚', 4226 => '💛', 4227 => '💜', 4228 => '💝', 4229 => '💞', 4230 => '💟', 4231 => '💠', 4232 => '💡', 4233 => '💢', 4234 => '💣', 4235 => '💤', 4236 => '💥', 4237 => '💦', 4238 => '💧', 4239 => '💨', 4240 => '💩', 4241 => '💪', 4242 => '💫', 4243 => '💬', 4244 => '💭', 4245 => '💮', 4246 => '💯', 4247 => '💰', 4248 => '💱', 4249 => '💲', 4250 => '💳', 4251 => '💴', 4252 => '💵', 4253 => '💶', 4254 => '💷', 4255 => '💸', 4256 => '💹', 4257 => '💺', 4258 => '💻', 4259 => '💼', 4260 => '💽', 4261 => '💾', 4262 => '💿', 4263 => '📀', 4264 => '📁', 4265 => '📂', 4266 => '📃', 4267 => '📄', 4268 => '📅', 4269 => '📆', 4270 => '📇', 4271 => '📈', 4272 => '📉', 4273 => '📊', 4274 => '📋', 4275 => '📌', 4276 => '📍', 4277 => '📎', 4278 => '📏', 4279 => '📐', 4280 => '📑', 4281 => '📒', 4282 => '📓', 4283 => '📔', 4284 => '📕', 4285 => '📖', 4286 => '📗', 4287 => '📘', 4288 => '📙', 4289 => '📚', 4290 => '📛', 4291 => '📜', 4292 => '📝', 4293 => '📞', 4294 => '📟', 4295 => '📠', 4296 => '📡', 4297 => '📢', 4298 => '📣', 4299 => '📤', 4300 => '📥', 4301 => '📦', 4302 => '📧', 4303 => '📨', 4304 => '📩', 4305 => '📪', 4306 => '📫', 4307 => '📬', 4308 => '📭', 4309 => '📮', 4310 => '📯', 4311 => '📰', 4312 => '📱', 4313 => '📲', 4314 => '📳', 4315 => '📴', 4316 => '📵', 4317 => '📶', 4318 => '📷', 4319 => '📹', 4320 => '📺', 4321 => '📻', 4322 => '📼', 4323 => '🔀', 4324 => '🔁', 4325 => '🔂', 4326 => '🔃', 4327 => '🔄', 4328 => '🔅', 4329 => '🔆', 4330 => '🔇', 4331 => '🔈', 4332 => '🔉', 4333 => '🔊', 4334 => '🔋', 4335 => '🔌', 4336 => '🔍', 4337 => '🔎', 4338 => '🔏', 4339 => '🔐', 4340 => '🔑', 4341 => '🔒', 4342 => '🔓', 4343 => '🔔', 4344 => '🔕', 4345 => '🔖', 4346 => '🔗', 4347 => '🔘', 4348 => '🔙', 4349 => '🔚', 4350 => '🔛', 4351 => '🔜', 4352 => '🔝', 4353 => '🔞', 4354 => '🔟', 4355 => '🔠', 4356 => '🔡', 4357 => '🔢', 4358 => '🔣', 4359 => '🔤', 4360 => '🔥', 4361 => '🔦', 4362 => '🔧', 4363 => '🔨', 4364 => '🔩', 4365 => '🔪', 4366 => '🔫', 4367 => '🔬', 4368 => '🔭', 4369 => '🔮', 4370 => '🔯', 4371 => '🔰', 4372 => '🔱', 4373 => '🔲', 4374 => '🔳', 4375 => '🔴', 4376 => '🔵', 4377 => '🔶', 4378 => '🔷', 4379 => '🔸', 4380 => '🔹', 4381 => '🔺', 4382 => '🔻', 4383 => '🔼', 4384 => '🔽', 4385 => '🕐', 4386 => '🕑', 4387 => '🕒', 4388 => '🕓', 4389 => '🕔', 4390 => '🕕', 4391 => '🕖', 4392 => '🕗', 4393 => '🕘', 4394 => '🕙', 4395 => '🕚', 4396 => '🕛', 4397 => '🕜', 4398 => '🕝', 4399 => '🕞', 4400 => '🕟', 4401 => '🕠', 4402 => '🕡', 4403 => '🕢', 4404 => '🕣', 4405 => '🕤', 4406 => '🕥', 4407 => '🕦', 4408 => '🕧', 4409 => '🗻', 4410 => '🗼', 4411 => '🗽', 4412 => '🗾', 4413 => '🗿', 4414 => '😁', 4415 => '😂', 4416 => '😃', 4417 => '😄', 4418 => '😅', 4419 => '😆', 4420 => '😇', 4421 => '😈', 4422 => '😉', 4423 => '😊', 4424 => '😋', 4425 => '😌', 4426 => '😍', 4427 => '😎', 4428 => '😏', 4429 => '😐', 4430 => '😒', 4431 => '😓', 4432 => '😔', 4433 => '😖', 4434 => '😘', 4435 => '😚', 4436 => '😜', 4437 => '😝', 4438 => '😞', 4439 => '😠', 4440 => '😡', 4441 => '😢', 4442 => '😣', 4443 => '😤', 4444 => '😥', 4445 => '😨', 4446 => '😩', 4447 => '😪', 4448 => '😫', 4449 => '😭', 4450 => '😰', 4451 => '😱', 4452 => '😲', 4453 => '😳', 4454 => '😵', 4455 => '😶', 4456 => '😷', 4457 => '😸', 4458 => '😹', 4459 => '😺', 4460 => '😻', 4461 => '😼', 4462 => '😽', 4463 => '😾', 4464 => '😿', 4465 => '🙀', 4466 => '🙅', 4467 => '🙆', 4468 => '🙇', 4469 => '🙈', 4470 => '🙉', 4471 => '🙊', 4472 => '🙋', 4473 => '🙌', 4474 => '🙍', 4475 => '🙎', 4476 => '🙏', 4477 => '🚀', 4478 => '🚁', 4479 => '🚂', 4480 => '🚃', 4481 => '🚄', 4482 => '🚅', 4483 => '🚆', 4484 => '🚇', 4485 => '🚈', 4486 => '🚉', 4487 => '🚊', 4488 => '🚋', 4489 => '🚌', 4490 => '🚍', 4491 => '🚎', 4492 => '🚏', 4493 => '🚐', 4494 => '🚑', 4495 => '🚒', 4496 => '🚓', 4497 => '🚔', 4498 => '🚕', 4499 => '🚖', 4500 => '🚗', 4501 => '🚘', 4502 => '🚙', 4503 => '🚚', 4504 => '🚛', 4505 => '🚜', 4506 => '🚝', 4507 => '🚞', 4508 => '🚟', 4509 => '🚠', 4510 => '🚡', 4511 => '🚢', 4512 => '🚣', 4513 => '🚤', 4514 => '🚥', 4515 => '🚦', 4516 => '🚧', 4517 => '🚨', 4518 => '🚩', 4519 => '🚪', 4520 => '🚫', 4521 => '🚬', 4522 => '🚭', 4523 => '🚮', 4524 => '🚯', 4525 => '🚰', 4526 => '🚱', 4527 => '🚲', 4528 => '🚳', 4529 => '🚴', 4530 => '🚵', 4531 => '🚶', 4532 => '🚷', 4533 => '🚸', 4534 => '🚹', 4535 => '🚺', 4536 => '🚻', 4537 => '🚼', 4538 => '🚽', 4539 => '🚾', 4540 => '🚿', 4541 => '🛀', 4542 => '🛁', 4543 => '🛂', 4544 => '🛃', 4545 => '🛄', 4546 => '🛅', 4547 => '🜀', 4548 => '🜁', 4549 => '🜂', 4550 => '🜃', 4551 => '🜄', 4552 => '🜅', 4553 => '🜆', 4554 => '🜇', 4555 => '🜈', 4556 => '🜉', 4557 => '🜊', 4558 => '🜋', 4559 => '🜌', 4560 => '🜍', 4561 => '🜎', 4562 => '🜏', 4563 => '🜐', 4564 => '🜑', 4565 => '🜒', 4566 => '🜓', 4567 => '🜔', 4568 => '🜕', 4569 => '🜖', 4570 => '🜗', 4571 => '🜘', 4572 => '🜙', 4573 => '🜚', 4574 => '🜛', 4575 => '🜜', 4576 => '🜝', 4577 => '🜞', 4578 => '🜟', 4579 => '🜠', 4580 => '🜡', 4581 => '🜢', 4582 => '🜣', 4583 => '🜤', 4584 => '🜥', 4585 => '🜦', 4586 => '🜧', 4587 => '🜨', 4588 => '🜩', 4589 => '🜪', 4590 => '🜫', 4591 => '🜬', 4592 => '🜭', 4593 => '🜮', 4594 => '🜯', 4595 => '🜰', 4596 => '🜱', 4597 => '🜲', 4598 => '🜳', 4599 => '🜴', 4600 => '🜵', 4601 => '🜶', 4602 => '🜷', 4603 => '🜸', 4604 => '🜹', 4605 => '🜺', 4606 => '🜻', 4607 => '🜼', 4608 => '🜽', 4609 => '🜾', 4610 => '🜿', 4611 => '🝀', 4612 => '🝁', 4613 => '🝂', 4614 => '🝃', 4615 => '🝄', 4616 => '🝅', 4617 => '🝆', 4618 => '🝇', 4619 => '🝈', 4620 => '🝉', 4621 => '🝊', 4622 => '🝋', 4623 => '🝌', 4624 => '🝍', 4625 => '🝎', 4626 => '🝏', 4627 => '🝐', 4628 => '🝑', 4629 => '🝒', 4630 => '🝓', 4631 => '🝔', 4632 => '🝕', 4633 => '🝖', 4634 => '🝗', 4635 => '🝘', 4636 => '🝙', 4637 => '🝚', 4638 => '🝛', 4639 => '🝜', 4640 => '🝝', 4641 => '🝞', 4642 => '🝟', 4643 => '🝠', 4644 => '🝡', 4645 => '🝢', 4646 => '🝣', 4647 => '🝤', 4648 => '🝥', 4649 => '🝦', 4650 => '🝧', 4651 => '🝨', 4652 => '🝩', 4653 => '🝪', 4654 => '🝫', 4655 => '🝬', 4656 => '🝭', 4657 => '🝮', 4658 => '🝯', 4659 => '🝰', 4660 => '🝱', 4661 => '🝲', 4662 => '🝳', 4663 => '㆐', 4664 => '㆑', 4665 => '', 4666 => '�', 4667 => '৴', 4668 => '৵', 4669 => '৶', 4670 => '৷', 4671 => '৸', 4672 => '৹', 4673 => '୲', 4674 => '୳', 4675 => '୴', 4676 => '୵', 4677 => '୶', 4678 => '୷', 4679 => '꠰', 4680 => '꠱', 4681 => '꠲', 4682 => '꠳', 4683 => '꠴', 4684 => '꠵', 4685 => '௰', 4686 => '௱', 4687 => '௲', 4688 => '൰', 4689 => '൱', 4690 => '൲', 4691 => '൳', 4692 => '൴', 4693 => '൵', 4694 => '፲', 4695 => '፳', 4696 => '፴', 4697 => '፵', 4698 => '፶', 4699 => '፷', 4700 => '፸', 4701 => '፹', 4702 => '፺', 4703 => '፻', 4704 => '፼', 4705 => 'ↀ', 4706 => 'ↁ', 4707 => 'ↂ', 4708 => 'ↆ', 4709 => 'ↇ', 4710 => 'ↈ', 4711 => '𐹩', 4712 => '𐹪', 4713 => '𐹫', 4714 => '𐹬', 4715 => '𐹭', 4716 => '𐹮', 4717 => '𐹯', 4718 => '𐹰', 4719 => '𐹱', 4720 => '𐹲', 4721 => '𐹳', 4722 => '𐹴', 4723 => '𐹵', 4724 => '𐹶', 4725 => '𐹷', 4726 => '𐹸', 4727 => '𐹹', 4728 => '𐹺', 4729 => '𐹻', 4730 => '𐹼', 4731 => '𐹽', 4732 => '𐹾', 4733 => '⳽', 4734 => '𐌢', 4735 => '𐌣', 4736 => '𐄐', 4737 => '𐄑', 4738 => '𐄒', 4739 => '𐄓', 4740 => '𐄔', 4741 => '𐄕', 4742 => '𐄖', 4743 => '𐄗', 4744 => '𐄘', 4745 => '𐄙', 4746 => '𐄚', 4747 => '𐄛', 4748 => '𐄜', 4749 => '𐄝', 4750 => '𐄞', 4751 => '𐄟', 4752 => '𐄠', 4753 => '𐄡', 4754 => '𐄢', 4755 => '𐄣', 4756 => '𐄤', 4757 => '𐄥', 4758 => '𐄦', 4759 => '𐄧', 4760 => '𐄨', 4761 => '𐄩', 4762 => '𐄪', 4763 => '𐄫', 4764 => '𐄬', 4765 => '𐄭', 4766 => '𐄮', 4767 => '𐄯', 4768 => '𐄰', 4769 => '𐄱', 4770 => '𐄲', 4771 => '𐄳', 4772 => '𐅀', 4773 => '𐅁', 4774 => '𐅄', 4775 => '𐅅', 4776 => '𐅆', 4777 => '𐅇', 4778 => '𐅉', 4779 => '𐅊', 4780 => '𐅋', 4781 => '𐅌', 4782 => '𐅍', 4783 => '𐅎', 4784 => '𐅐', 4785 => '𐅑', 4786 => '𐅒', 4787 => '𐅓', 4788 => '𐅔', 4789 => '𐅕', 4790 => '𐅖', 4791 => '𐅗', 4792 => '𐅠', 4793 => '𐅡', 4794 => '𐅢', 4795 => '𐅣', 4796 => '𐅤', 4797 => '𐅥', 4798 => '𐅦', 4799 => '𐅧', 4800 => '𐅨', 4801 => '𐅩', 4802 => '𐅪', 4803 => '𐅫', 4804 => '𐅬', 4805 => '𐅭', 4806 => '𐅮', 4807 => '𐅯', 4808 => '𐅰', 4809 => '𐅱', 4810 => '𐅲', 4811 => '𐅴', 4812 => '𐅵', 4813 => '𐅶', 4814 => '𐅷', 4815 => '𐅸', 4816 => '𐏓', 4817 => '𐏔', 4818 => '𐏕', 4819 => '𐩾', 4820 => '𐩿', 4821 => '𐤗', 4822 => '𐤘', 4823 => '𐤙', 4824 => '𐡛', 4825 => '𐡜', 4826 => '𐡝', 4827 => '𐡞', 4828 => '𐡟', 4829 => '𐭜', 4830 => '𐭝', 4831 => '𐭞', 4832 => '𐭟', 4833 => '𐭼', 4834 => '𐭽', 4835 => '𐭾', 4836 => '𐭿', 4837 => '𑁛', 4838 => '𑁜', 4839 => '𑁝', 4840 => '𑁞', 4841 => '𑁟', 4842 => '𑁠', 4843 => '𑁡', 4844 => '𑁢', 4845 => '𑁣', 4846 => '𑁤', 4847 => '𑁥', 4848 => '𐩄', 4849 => '𐩅', 4850 => '𐩆', 4851 => '𐩇', 4852 => '𒐲', 4853 => '𒐳', 4854 => '𒑖', 4855 => '𒑗', 4856 => '𒑚', 4857 => '𒑛', 4858 => '𒑜', 4859 => '𒑝', 4860 => '𒑞', 4861 => '𒑟', 4862 => '𒑠', 4863 => '𒑡', 4864 => '𒑢', 4865 => '𝍩', 4866 => '𝍪', 4867 => '𝍫', 4868 => '𝍬', 4869 => '𝍭', 4870 => '𝍮', 4871 => '𝍯', 4872 => '𝍰', 4873 => '𝍱', 4874 => 'ː', 4875 => 'ˑ', 4876 => 'ॱ', 4877 => 'ๆ', 4878 => 'ໆ', 4879 => 'ᪧ', 4880 => 'ꧏ', 4881 => 'ꩰ', 4882 => 'ꫝ', 4883 => 'ゝ', 4884 => 'ー', 4885 => 'ヽ', 4886 => '¤', 4887 => '¢', 4888 => '$', 4889 => '£', 4890 => '¥', 4891 => '؋', 4892 => '৲', 4893 => '৳', 4894 => '৻', 4895 => '૱', 4896 => '꠸', 4897 => '௹', 4898 => '฿', 4899 => '៛', 4900 => '₠', 4901 => '₡', 4902 => '₢', 4903 => '₣', 4904 => '₤', 4905 => '₥', 4906 => '₦', 4907 => '₧', 4908 => '₩', 4909 => '₪', 4910 => '₫', 4911 => '€', 4912 => '₭', 4913 => '₮', 4914 => '₯', 4915 => '₰', 4916 => '₱', 4917 => '₲', 4918 => '₳', 4919 => '₴', 4920 => '₵', 4921 => '₶', 4922 => '₷', 4923 => '₸', 4924 => '₹', 4925 => '0', 4926 => '1', 4927 => '2', 4928 => '3', 4929 => '4', 4930 => '5', 4931 => '6', 4932 => '7', 4933 => '8', 4934 => '9', 4935 => 'A', 4936 => 'ᴀ', 4937 => 'Ⱥ', 4938 => 'ᶏ', 4939 => 'ᴁ', 4940 => 'ᴂ', 4941 => 'Ɐ', 4942 => 'Ɑ', 4943 => 'ᶐ', 4944 => 'Ɒ', 4945 => 'B', 4946 => 'ʙ', 4947 => 'Ƀ', 4948 => 'ᴯ', 4949 => 'ᴃ', 4950 => 'ᵬ', 4951 => 'ᶀ', 4952 => 'Ɓ', 4953 => 'Ƃ', 4954 => 'C', 4955 => 'ᴄ', 4956 => 'Ȼ', 4957 => 'Ƈ', 4958 => 'ɕ', 4959 => 'Ↄ', 4960 => 'Ꜿ', 4961 => 'D', 4962 => 'ᴅ', 4963 => 'ᴆ', 4964 => 'ᵭ', 4965 => 'ᶁ', 4966 => 'Ɖ', 4967 => 'Ɗ', 4968 => 'ᶑ', 4969 => 'Ƌ', 4970 => 'ȡ', 4971 => 'ꝱ', 4972 => 'ẟ', 4973 => 'E', 4974 => 'ᴇ', 4975 => 'Ɇ', 4976 => 'ᶒ', 4977 => 'ⱸ', 4978 => 'Ǝ', 4979 => 'ⱻ', 4980 => 'Ə', 4981 => 'ᶕ', 4982 => 'Ɛ', 4983 => 'ᶓ', 4984 => 'ɘ', 4985 => 'ɚ', 4986 => 'ɜ', 4987 => 'ᶔ', 4988 => 'ᴈ', 4989 => 'ɝ', 4990 => 'ɞ', 4991 => 'ʚ', 4992 => 'ɤ', 4993 => 'F', 4994 => 'ꜰ', 4995 => 'ᵮ', 4996 => 'ᶂ', 4997 => 'Ƒ', 4998 => 'Ⅎ', 4999 => 'ꟻ', 5000 => 'G', 5001 => 'ɡ', 5002 => 'ɢ', 5003 => 'Ǥ', 5004 => 'ᶃ', 5005 => 'Ɠ', 5006 => 'ʛ', 5007 => 'ᵷ', 5008 => 'Ꝿ', 5009 => 'Ɣ', 5010 => 'Ƣ', 5011 => 'H', 5012 => 'ʜ', 5013 => 'Ƕ', 5014 => 'ɦ', 5015 => 'Ⱨ', 5016 => 'Ⱶ', 5017 => 'Ꜧ', 5018 => 'ɧ', 5019 => 'ʻ', 5020 => 'ʽ', 5021 => 'I', 5022 => 'ı', 5023 => 'ɪ', 5024 => 'ꟾ', 5025 => 'ᴉ', 5026 => 'Ɨ', 5027 => 'ᵻ', 5028 => 'ᶖ', 5029 => 'Ɩ', 5030 => 'ᵼ', 5031 => 'J', 5032 => 'ȷ', 5033 => 'ᴊ', 5034 => 'Ɉ', 5035 => 'ʝ', 5036 => 'ɟ', 5037 => 'ʄ', 5038 => 'K', 5039 => 'ᴋ', 5040 => 'ᶄ', 5041 => 'Ƙ', 5042 => 'Ⱪ', 5043 => 'Ꝁ', 5044 => 'Ꝃ', 5045 => 'Ꝅ', 5046 => 'ʞ', 5047 => 'L', 5048 => 'ʟ', 5049 => 'Ꝇ', 5050 => 'ᴌ', 5051 => 'Ꝉ', 5052 => 'Ƚ', 5053 => 'Ⱡ', 5054 => 'Ɫ', 5055 => 'ɬ', 5056 => 'ᶅ', 5057 => 'ɭ', 5058 => 'ꞎ', 5059 => 'ȴ', 5060 => 'ꝲ', 5061 => 'ɮ', 5062 => 'Ꞁ', 5063 => 'ƛ', 5064 => 'ʎ', 5065 => 'M', 5066 => 'ᴍ', 5067 => 'ᵯ', 5068 => 'ᶆ', 5069 => 'Ɱ', 5070 => 'ꟽ', 5071 => 'ꟿ', 5072 => 'ꝳ', 5073 => 'N', 5074 => 'ɴ', 5075 => 'ᴻ', 5076 => 'ᴎ', 5077 => 'ᵰ', 5078 => 'Ɲ', 5079 => 'Ƞ', 5080 => 'Ꞑ', 5081 => 'ᶇ', 5082 => 'ɳ', 5083 => 'ȵ', 5084 => 'ꝴ', 5085 => 'Ŋ', 5086 => 'O', 5087 => 'ᴏ', 5088 => 'ᴑ', 5089 => 'ɶ', 5090 => 'ᴔ', 5091 => 'ᴓ', 5092 => 'Ɔ', 5093 => 'ᴐ', 5094 => 'ᴒ', 5095 => 'ᶗ', 5096 => 'Ꝍ', 5097 => 'ᴖ', 5098 => 'ᴗ', 5099 => 'ⱺ', 5100 => 'Ɵ', 5101 => 'Ꝋ', 5102 => 'ɷ', 5103 => 'Ȣ', 5104 => 'ᴕ', 5105 => 'P', 5106 => 'ᴘ', 5107 => 'Ᵽ', 5108 => 'Ꝑ', 5109 => 'ᵱ', 5110 => 'ᶈ', 5111 => 'Ƥ', 5112 => 'Ꝓ', 5113 => 'Ꝕ', 5114 => 'ꟼ', 5115 => 'ɸ', 5116 => 'ⱷ', 5117 => 'Q', 5118 => 'Ꝗ', 5119 => 'Ꝙ', 5120 => 'ʠ', 5121 => 'Ɋ', 5122 => 'ĸ', 5123 => 'R', 5124 => 'Ʀ', 5125 => 'Ꝛ', 5126 => 'ᴙ', 5127 => 'Ɍ', 5128 => 'ᵲ', 5129 => 'ɹ', 5130 => 'ᴚ', 5131 => 'ɺ', 5132 => 'ᶉ', 5133 => 'ɻ', 5134 => 'ⱹ', 5135 => 'ɼ', 5136 => 'Ɽ', 5137 => 'ɾ', 5138 => 'ᵳ', 5139 => 'ɿ', 5140 => 'ʁ', 5141 => 'ꝵ', 5142 => 'ꝶ', 5143 => 'Ꝝ', 5144 => 'S', 5145 => 'ꜱ', 5146 => 'ᵴ', 5147 => 'ᶊ', 5148 => 'ʂ', 5149 => 'Ȿ', 5150 => 'ẜ', 5151 => 'ẝ', 5152 => 'Ʃ', 5153 => 'ᶋ', 5154 => 'ƪ', 5155 => 'ʅ', 5156 => 'ᶘ', 5157 => 'ʆ', 5158 => 'T', 5159 => 'ᴛ', 5160 => 'Ŧ', 5161 => 'Ⱦ', 5162 => 'ᵵ', 5163 => 'ƫ', 5164 => 'Ƭ', 5165 => 'Ʈ', 5166 => 'ȶ', 5167 => 'ꝷ', 5168 => 'ʇ', 5169 => 'U', 5170 => 'ᴜ', 5171 => 'ᴝ', 5172 => 'ᴞ', 5173 => 'ᵫ', 5174 => 'Ʉ', 5175 => 'ᵾ', 5176 => 'ᶙ', 5177 => 'Ɥ', 5178 => 'ʮ', 5179 => 'ʯ', 5180 => 'Ɯ', 5181 => 'ꟺ', 5182 => 'ᴟ', 5183 => 'ɰ', 5184 => 'Ʊ', 5185 => 'ᵿ', 5186 => 'V', 5187 => 'ᴠ', 5188 => 'Ꝟ', 5189 => 'ᶌ', 5190 => 'Ʋ', 5191 => 'ⱱ', 5192 => 'ⱴ', 5193 => 'Ỽ', 5194 => 'Ʌ', 5195 => 'W', 5196 => 'ᴡ', 5197 => 'Ⱳ', 5198 => 'ʍ', 5199 => 'X', 5200 => 'ᶍ', 5201 => 'Y', 5202 => 'ʏ', 5203 => 'Ɏ', 5204 => 'Ƴ', 5205 => 'Ỿ', 5206 => 'Z', 5207 => 'ᴢ', 5208 => 'Ƶ', 5209 => 'ᵶ', 5210 => 'ᶎ', 5211 => 'Ȥ', 5212 => 'ʐ', 5213 => 'ʑ', 5214 => 'Ɀ', 5215 => 'Ⱬ', 5216 => 'Ꝣ', 5217 => 'Ʒ', 5218 => 'ᴣ', 5219 => 'Ƹ', 5220 => 'ᶚ', 5221 => 'ƺ', 5222 => 'ʓ', 5223 => 'Ȝ', 5224 => 'Þ', 5225 => 'Ꝥ', 5226 => 'Ꝧ', 5227 => 'Ƿ', 5228 => 'Ꝩ', 5229 => 'Ꝫ', 5230 => 'Ꝭ', 5231 => 'Ꝯ', 5232 => 'ꝸ', 5233 => 'ƻ', 5234 => 'Ꜫ', 5235 => 'Ꜭ', 5236 => 'Ꜯ', 5237 => 'Ƨ', 5238 => 'Ƽ', 5239 => 'Ƅ', 5240 => 'ʔ', 5241 => 'Ɂ', 5242 => 'ˀ', 5243 => 'ʼ', 5244 => 'ˮ', 5245 => 'ʾ', 5246 => 'Ꜣ', 5247 => 'Ꞌ', 5248 => 'ʕ', 5249 => 'ʿ', 5250 => 'ˁ', 5251 => 'ᴤ', 5252 => 'ᴥ', 5253 => 'Ꜥ', 5254 => 'ʡ', 5255 => 'ʢ', 5256 => 'ʖ', 5257 => 'ǀ', 5258 => 'ǁ', 5259 => 'ǂ', 5260 => 'ǃ', 5261 => 'ʗ', 5262 => 'ʘ', 5263 => 'ʬ', 5264 => 'ʭ', 5265 => 'Α', 5266 => 'Β', 5267 => 'Γ', 5268 => 'ᴦ', 5269 => 'Δ', 5270 => 'Ε', 5271 => 'Ϝ', 5272 => 'Ͷ', 5273 => 'Ϛ', 5274 => 'Ζ', 5275 => 'Ͱ', 5276 => 'Η', 5277 => 'Θ', 5278 => 'Ι', 5279 => 'ϳ', 5280 => 'Κ', 5281 => 'Λ', 5282 => 'ᴧ', 5283 => 'Μ', 5284 => 'Ν', 5285 => 'Ξ', 5286 => 'Ο', 5287 => 'Π', 5288 => 'ᴨ', 5289 => 'Ϻ', 5290 => 'Ϟ', 5291 => 'Ϙ', 5292 => 'Ρ', 5293 => 'ᴩ', 5294 => 'ϼ', 5295 => 'Σ', 5296 => 'Ͼ', 5297 => 'Ͻ', 5298 => 'Ͽ', 5299 => 'Τ', 5300 => 'Υ', 5301 => 'Φ', 5302 => 'Χ', 5303 => 'Ψ', 5304 => 'ᴪ', 5305 => 'Ω', 5306 => 'Ϡ', 5307 => 'Ͳ', 5308 => 'Ϸ', 5309 => 'Ⲁ', 5310 => 'Ⲃ', 5311 => 'Ⲅ', 5312 => 'Ⲇ', 5313 => 'Ⲉ', 5314 => 'Ⲷ', 5315 => 'Ⲋ', 5316 => 'Ⲍ', 5317 => 'Ⲏ', 5318 => 'Ⲑ', 5319 => 'Ⲓ', 5320 => 'Ⲕ', 5321 => 'Ⲹ', 5322 => 'Ⲗ', 5323 => 'Ⲙ', 5324 => 'Ⲛ', 5325 => 'Ⲻ', 5326 => 'Ⲽ', 5327 => 'Ⲝ', 5328 => 'Ⲟ', 5329 => 'Ⲡ', 5330 => 'Ⲣ', 5331 => 'Ⲥ', 5332 => 'Ⲧ', 5333 => 'Ⲩ', 5334 => 'Ⲫ', 5335 => 'Ⲭ', 5336 => 'Ⲯ', 5337 => 'Ⲱ', 5338 => 'Ⲿ', 5339 => 'Ⳁ', 5340 => 'Ϣ', 5341 => 'Ⳬ', 5342 => 'Ⳃ', 5343 => 'Ⳅ', 5344 => 'Ⳇ', 5345 => 'Ϥ', 5346 => 'Ϧ', 5347 => 'Ⳉ', 5348 => 'Ϩ', 5349 => 'Ⳋ', 5350 => 'Ⳍ', 5351 => 'Ⳏ', 5352 => 'Ⳑ', 5353 => 'Ⳓ', 5354 => 'Ⳕ', 5355 => 'Ϫ', 5356 => 'Ⳮ', 5357 => 'Ⳗ', 5358 => 'Ϭ', 5359 => 'Ⳙ', 5360 => 'Ⳛ', 5361 => 'Ⳝ', 5362 => 'Ϯ', 5363 => 'Ⲳ', 5364 => 'Ⲵ', 5365 => 'Ⳟ', 5366 => 'Ⳡ', 5367 => 'Ⳣ', 5368 => 'А', 5369 => 'Ӑ', 5370 => 'Ӓ', 5371 => 'Ә', 5372 => 'Ӛ', 5373 => 'Ӕ', 5374 => 'Б', 5375 => 'В', 5376 => 'Г', 5377 => 'Ғ', 5378 => 'Ӻ', 5379 => 'Ҕ', 5380 => 'Ӷ', 5381 => 'Д', 5382 => 'Ԁ', 5383 => 'Ꚁ', 5384 => 'Ђ', 5385 => 'Ꙣ', 5386 => 'Ԃ', 5387 => 'Ѓ', 5388 => 'Ҙ', 5389 => 'Е', 5390 => 'Ӗ', 5391 => 'Є', 5392 => 'Ж', 5393 => 'Ꚅ', 5394 => 'Ӝ', 5395 => 'Җ', 5396 => 'З', 5397 => 'Ꙁ', 5398 => 'Ԅ', 5399 => 'Ԑ', 5400 => 'Ӟ', 5401 => 'Ꙃ', 5402 => 'Ѕ', 5403 => 'Ꙅ', 5404 => 'Ӡ', 5405 => 'Ꚉ', 5406 => 'Ԇ', 5407 => 'Ꚃ', 5408 => 'И', 5409 => 'Ҋ', 5410 => 'Ӥ', 5411 => 'І', 5412 => 'Ꙇ', 5413 => 'Ї', 5414 => 'Й', 5415 => 'Ј', 5416 => 'Ꙉ', 5417 => 'К', 5418 => 'Қ', 5419 => 'Ӄ', 5420 => 'Ҡ', 5421 => 'Ҟ', 5422 => 'Ҝ', 5423 => 'Ԟ', 5424 => 'Ԛ', 5425 => 'Л', 5426 => 'ᴫ', 5427 => 'Ӆ', 5428 => 'Ԓ', 5429 => 'Ԡ', 5430 => 'Љ', 5431 => 'Ꙥ', 5432 => 'Ԉ', 5433 => 'Ԕ', 5434 => 'М', 5435 => 'Ӎ', 5436 => 'Ꙧ', 5437 => 'Н', 5438 => 'Ӊ', 5439 => 'Ң', 5440 => 'Ӈ', 5441 => 'Ԣ', 5442 => 'Ҥ', 5443 => 'Њ', 5444 => 'Ԋ', 5445 => 'О', 5446 => 'Ӧ', 5447 => 'Ө', 5448 => 'Ӫ', 5449 => 'П', 5450 => 'Ԥ', 5451 => 'Ҧ', 5452 => 'Ҁ', 5453 => 'Р', 5454 => 'Ҏ', 5455 => 'Ԗ', 5456 => 'С', 5457 => 'Ԍ', 5458 => 'Ҫ', 5459 => 'Т', 5460 => 'Ꚍ', 5461 => 'Ԏ', 5462 => 'Ҭ', 5463 => 'Ꚋ', 5464 => 'Ћ', 5465 => 'Ќ', 5466 => 'У', 5467 => 'Ў', 5468 => 'Ӱ', 5469 => 'Ӳ', 5470 => 'Ү', 5471 => 'Ұ', 5472 => 'Ꙋ', 5473 => 'Ѹ', 5474 => 'Ф', 5475 => 'Х', 5476 => 'Ӽ', 5477 => 'Ӿ', 5478 => 'Ҳ', 5479 => 'Һ', 5480 => 'Ԧ', 5481 => 'Ꚕ', 5482 => 'Ѡ', 5483 => 'Ѿ', 5484 => 'Ꙍ', 5485 => 'Ѽ', 5486 => 'Ѻ', 5487 => 'Ц', 5488 => 'Ꙡ', 5489 => 'Ꚏ', 5490 => 'Ҵ', 5491 => 'Ꚑ', 5492 => 'Ч', 5493 => 'Ꚓ', 5494 => 'Ӵ', 5495 => 'Ҷ', 5496 => 'Ӌ', 5497 => 'Ҹ', 5498 => 'Ꚇ', 5499 => 'Ҽ', 5500 => 'Ҿ', 5501 => 'Џ', 5502 => 'Ш', 5503 => 'Ꚗ', 5504 => 'Щ', 5505 => 'Ꙏ', 5506 => 'ⸯ', 5507 => 'ꙿ', 5508 => 'Ъ', 5509 => 'Ꙑ', 5510 => 'Ы', 5511 => 'Ӹ', 5512 => 'Ь', 5513 => 'Ҍ', 5514 => 'Ѣ', 5515 => 'Ꙓ', 5516 => 'Э', 5517 => 'Ӭ', 5518 => 'Ю', 5519 => 'Ꙕ', 5520 => 'Ꙗ', 5521 => 'Я', 5522 => 'Ԙ', 5523 => 'Ѥ', 5524 => 'Ѧ', 5525 => 'Ꙙ', 5526 => 'Ѫ', 5527 => 'Ꙛ', 5528 => 'Ѩ', 5529 => 'Ꙝ', 5530 => 'Ѭ', 5531 => 'Ѯ', 5532 => 'Ѱ', 5533 => 'Ѳ', 5534 => 'Ѵ', 5535 => 'Ѷ', 5536 => 'Ꙟ', 5537 => 'Ҩ', 5538 => 'Ԝ', 5539 => 'Ӏ', 5540 => 'Ⰰ', 5541 => 'Ⰱ', 5542 => 'Ⰲ', 5543 => 'Ⰳ', 5544 => 'Ⰴ', 5545 => 'Ⰵ', 5546 => 'Ⰶ', 5547 => 'Ⰷ', 5548 => 'Ⰸ', 5549 => 'Ⰹ', 5550 => 'Ⰺ', 5551 => 'Ⰻ', 5552 => 'Ⰼ', 5553 => 'Ⰽ', 5554 => 'Ⰾ', 5555 => 'Ⰿ', 5556 => 'Ⱀ', 5557 => 'Ⱁ', 5558 => 'Ⱂ', 5559 => 'Ⱃ', 5560 => 'Ⱄ', 5561 => 'Ⱅ', 5562 => 'Ⱆ', 5563 => 'Ⱇ', 5564 => 'Ⱈ', 5565 => 'Ⱉ', 5566 => 'Ⱊ', 5567 => 'Ⱋ', 5568 => 'Ⱌ', 5569 => 'Ⱍ', 5570 => 'Ⱎ', 5571 => 'Ⱏ', 5572 => 'Ⱐ', 5573 => 'Ⱑ', 5574 => 'Ⱒ', 5575 => 'Ⱓ', 5576 => 'Ⱔ', 5577 => 'Ⱕ', 5578 => 'Ⱖ', 5579 => 'Ⱗ', 5580 => 'Ⱘ', 5581 => 'Ⱙ', 5582 => 'Ⱚ', 5583 => 'Ⱛ', 5584 => 'Ⱜ', 5585 => 'Ⱝ', 5586 => 'Ⱞ', 5587 => 'ა', 5588 => 'Ⴀ', 5589 => 'ბ', 5590 => 'Ⴁ', 5591 => 'გ', 5592 => 'Ⴂ', 5593 => 'დ', 5594 => 'Ⴃ', 5595 => 'ე', 5596 => 'Ⴄ', 5597 => 'ვ', 5598 => 'Ⴅ', 5599 => 'ზ', 5600 => 'Ⴆ', 5601 => 'ჱ', 5602 => 'Ⴡ', 5603 => 'თ', 5604 => 'Ⴇ', 5605 => 'ი', 5606 => 'Ⴈ', 5607 => 'კ', 5608 => 'Ⴉ', 5609 => 'ლ', 5610 => 'Ⴊ', 5611 => 'მ', 5612 => 'Ⴋ', 5613 => 'ნ', 5614 => 'Ⴌ', 5615 => 'ჲ', 5616 => 'Ⴢ', 5617 => 'ო', 5618 => 'Ⴍ', 5619 => 'პ', 5620 => 'Ⴎ', 5621 => 'ჟ', 5622 => 'Ⴏ', 5623 => 'რ', 5624 => 'Ⴐ', 5625 => 'ს', 5626 => 'Ⴑ', 5627 => 'ტ', 5628 => 'Ⴒ', 5629 => 'ჳ', 5630 => 'Ⴣ', 5631 => 'უ', 5632 => 'Ⴓ', 5633 => 'ფ', 5634 => 'Ⴔ', 5635 => 'ქ', 5636 => 'Ⴕ', 5637 => 'ღ', 5638 => 'Ⴖ', 5639 => 'ყ', 5640 => 'Ⴗ', 5641 => 'შ', 5642 => 'Ⴘ', 5643 => 'ჩ', 5644 => 'Ⴙ', 5645 => 'ც', 5646 => 'Ⴚ', 5647 => 'ძ', 5648 => 'Ⴛ', 5649 => 'წ', 5650 => 'Ⴜ', 5651 => 'ჭ', 5652 => 'Ⴝ', 5653 => 'ხ', 5654 => 'Ⴞ', 5655 => 'ჴ', 5656 => 'Ⴤ', 5657 => 'ჯ', 5658 => 'Ⴟ', 5659 => 'ჰ', 5660 => 'Ⴠ', 5661 => 'ჵ', 5662 => 'Ⴥ', 5663 => 'ჶ', 5664 => 'ჷ', 5665 => 'ჸ', 5666 => 'ჹ', 5667 => 'ჺ', 5668 => 'Ա', 5669 => 'Բ', 5670 => 'Գ', 5671 => 'Դ', 5672 => 'Ե', 5673 => 'Զ', 5674 => 'Է', 5675 => 'Ը', 5676 => 'Թ', 5677 => 'Ժ', 5678 => 'Ի', 5679 => 'Լ', 5680 => 'Խ', 5681 => 'Ծ', 5682 => 'Կ', 5683 => 'Հ', 5684 => 'Ձ', 5685 => 'Ղ', 5686 => 'Ճ', 5687 => 'Մ', 5688 => 'Յ', 5689 => 'Ն', 5690 => 'Շ', 5691 => 'Ո', 5692 => 'Չ', 5693 => 'Պ', 5694 => 'Ջ', 5695 => 'Ռ', 5696 => 'Ս', 5697 => 'Վ', 5698 => 'Տ', 5699 => 'Ր', 5700 => 'Ց', 5701 => 'Ւ', 5702 => 'Փ', 5703 => 'Ք', 5704 => 'Օ', 5705 => 'Ֆ', 5706 => 'ՙ', 5707 => 'א', 5708 => 'ב', 5709 => 'ג', 5710 => 'ד', 5711 => 'ה', 5712 => 'ו', 5713 => 'ז', 5714 => 'ח', 5715 => 'ט', 5716 => 'י', 5717 => 'ך', 5718 => 'ל', 5719 => 'ם', 5720 => 'ן', 5721 => 'ס', 5722 => 'ע', 5723 => 'ף', 5724 => 'ץ', 5725 => 'ק', 5726 => 'ר', 5727 => 'ש', 5728 => 'ת', 5729 => '𐤀', 5730 => '𐤁', 5731 => '𐤂', 5732 => '𐤃', 5733 => '𐤄', 5734 => '𐤅', 5735 => '𐤆', 5736 => '𐤇', 5737 => '𐤈', 5738 => '𐤉', 5739 => '𐤊', 5740 => '𐤋', 5741 => '𐤌', 5742 => '𐤍', 5743 => '𐤎', 5744 => '𐤏', 5745 => '𐤐', 5746 => '𐤑', 5747 => '𐤒', 5748 => '𐤓', 5749 => '𐤔', 5750 => '𐤕', 5751 => 'ࠀ', 5752 => 'ࠁ', 5753 => 'ࠂ', 5754 => 'ࠃ', 5755 => 'ࠄ', 5756 => 'ࠅ', 5757 => 'ࠆ', 5758 => 'ࠇ', 5759 => 'ࠈ', 5760 => 'ࠉ', 5761 => 'ࠊ', 5762 => 'ࠋ', 5763 => 'ࠌ', 5764 => 'ࠍ', 5765 => 'ࠎ', 5766 => 'ࠏ', 5767 => 'ࠐ', 5768 => 'ࠑ', 5769 => 'ࠒ', 5770 => 'ࠓ', 5771 => 'ࠔ', 5772 => 'ࠕ', 5773 => 'ࠚ', 5774 => 'ء', 5775 => 'آ', 5776 => 'أ', 5777 => 'ٲ', 5778 => 'ٱ', 5779 => 'ؤ', 5780 => 'إ', 5781 => 'ٳ', 5782 => 'ݳ', 5783 => 'ݴ', 5784 => 'ئ', 5785 => 'ا', 5786 => 'ٮ', 5787 => 'ب', 5788 => 'ٻ', 5789 => 'پ', 5790 => 'ڀ', 5791 => 'ݐ', 5792 => 'ݑ', 5793 => 'ݒ', 5794 => 'ݓ', 5795 => 'ݔ', 5796 => 'ݕ', 5797 => 'ݖ', 5798 => 'ة', 5799 => 'ت', 5800 => 'ث', 5801 => 'ٹ', 5802 => 'ٺ', 5803 => 'ټ', 5804 => 'ٽ', 5805 => 'ٿ', 5806 => 'ج', 5807 => 'ڃ', 5808 => 'ڄ', 5809 => 'چ', 5810 => 'ڿ', 5811 => 'ڇ', 5812 => 'ح', 5813 => 'خ', 5814 => 'ځ', 5815 => 'ڂ', 5816 => 'څ', 5817 => 'ݗ', 5818 => 'ݘ', 5819 => 'ݮ', 5820 => 'ݯ', 5821 => 'ݲ', 5822 => 'ݼ', 5823 => 'د', 5824 => 'ذ', 5825 => 'ڈ', 5826 => 'ډ', 5827 => 'ڊ', 5828 => 'ڋ', 5829 => 'ڌ', 5830 => 'ڍ', 5831 => 'ڎ', 5832 => 'ڏ', 5833 => 'ڐ', 5834 => 'ۮ', 5835 => 'ݙ', 5836 => 'ݚ', 5837 => 'ر', 5838 => 'ز', 5839 => 'ڑ', 5840 => 'ڒ', 5841 => 'ړ', 5842 => 'ڔ', 5843 => 'ڕ', 5844 => 'ږ', 5845 => 'ڗ', 5846 => 'ژ', 5847 => 'ڙ', 5848 => 'ۯ', 5849 => 'ݛ', 5850 => 'ݫ', 5851 => 'ݬ', 5852 => 'ݱ', 5853 => 'س', 5854 => 'ش', 5855 => 'ښ', 5856 => 'ڛ', 5857 => 'ڜ', 5858 => 'ۺ', 5859 => 'ݜ', 5860 => 'ݭ', 5861 => 'ݰ', 5862 => 'ݽ', 5863 => 'ݾ', 5864 => 'ص', 5865 => 'ض', 5866 => 'ڝ', 5867 => 'ڞ', 5868 => 'ۻ', 5869 => 'ط', 5870 => 'ظ', 5871 => 'ڟ', 5872 => 'ع', 5873 => 'غ', 5874 => 'ڠ', 5875 => 'ۼ', 5876 => 'ݝ', 5877 => 'ݞ', 5878 => 'ݟ', 5879 => 'ف', 5880 => 'ڡ', 5881 => 'ڢ', 5882 => 'ڣ', 5883 => 'ڤ', 5884 => 'ڥ', 5885 => 'ڦ', 5886 => 'ݠ', 5887 => 'ݡ', 5888 => 'ٯ', 5889 => 'ق', 5890 => 'ڧ', 5891 => 'ڨ', 5892 => 'ك', 5893 => 'ک', 5894 => 'ڪ', 5895 => 'ګ', 5896 => 'ڬ', 5897 => 'ݿ', 5898 => 'ڭ', 5899 => 'ڮ', 5900 => 'گ', 5901 => 'ڰ', 5902 => 'ڱ', 5903 => 'ڲ', 5904 => 'ڳ', 5905 => 'ڴ', 5906 => 'ݢ', 5907 => 'ػ', 5908 => 'ؼ', 5909 => 'ݣ', 5910 => 'ݤ', 5911 => 'ل', 5912 => 'ڵ', 5913 => 'ڶ', 5914 => 'ڷ', 5915 => 'ڸ', 5916 => 'ݪ', 5917 => 'م', 5918 => 'ݥ', 5919 => 'ݦ', 5920 => 'ن', 5921 => 'ں', 5922 => 'ڻ', 5923 => 'ڼ', 5924 => 'ڽ', 5925 => 'ڹ', 5926 => 'ݧ', 5927 => 'ݨ', 5928 => 'ݩ', 5929 => 'ه', 5930 => 'ھ', 5931 => 'ہ', 5932 => 'ۃ', 5933 => 'ۿ', 5934 => 'ە', 5935 => 'و', 5936 => 'ۄ', 5937 => 'ۅ', 5938 => 'ۆ', 5939 => 'ۇ', 5940 => 'ۈ', 5941 => 'ۉ', 5942 => 'ۊ', 5943 => 'ۋ', 5944 => 'ۏ', 5945 => 'ݸ', 5946 => 'ݹ', 5947 => 'ى', 5948 => 'ي', 5949 => 'ی', 5950 => 'ۍ', 5951 => 'ێ', 5952 => 'ې', 5953 => 'ۑ', 5954 => 'ؽ', 5955 => 'ؾ', 5956 => 'ؿ', 5957 => 'ؠ', 5958 => 'ݵ', 5959 => 'ݶ', 5960 => 'ݷ', 5961 => 'ے', 5962 => 'ݺ', 5963 => 'ݻ', 5964 => 'ܐ', 5965 => 'ܒ', 5966 => 'ܓ', 5967 => 'ܖ', 5968 => 'ܕ', 5969 => 'ܗ', 5970 => 'ܘ', 5971 => 'ܙ', 5972 => 'ݍ', 5973 => 'ܚ', 5974 => 'ܛ', 5975 => 'ܝ', 5976 => 'ܞ', 5977 => 'ܟ', 5978 => 'ݎ', 5979 => 'ܠ', 5980 => 'ܡ', 5981 => 'ܢ', 5982 => 'ܣ', 5983 => 'ܥ', 5984 => 'ܦ', 5985 => 'ݏ', 5986 => 'ܨ', 5987 => 'ܩ', 5988 => 'ܪ', 5989 => 'ܫ', 5990 => 'ܬ', 5991 => 'ࡀ', 5992 => 'ࡁ', 5993 => 'ࡂ', 5994 => 'ࡃ', 5995 => 'ࡄ', 5996 => 'ࡅ', 5997 => 'ࡆ', 5998 => 'ࡇ', 5999 => 'ࡈ', 6000 => 'ࡉ', 6001 => 'ࡊ', 6002 => 'ࡋ', 6003 => 'ࡌ', 6004 => 'ࡍ', 6005 => 'ࡎ', 6006 => 'ࡏ', 6007 => 'ࡐ', 6008 => 'ࡑ', 6009 => 'ࡒ', 6010 => 'ࡓ', 6011 => 'ࡔ', 6012 => 'ࡕ', 6013 => 'ࡖ', 6014 => 'ࡗ', 6015 => 'ࡘ', 6016 => 'ހ', 6017 => 'ޙ', 6018 => 'ޚ', 6019 => 'ށ', 6020 => 'ނ', 6021 => 'ރ', 6022 => 'ޜ', 6023 => 'ބ', 6024 => 'ޅ', 6025 => 'ކ', 6026 => 'އ', 6027 => 'ޢ', 6028 => 'ޣ', 6029 => 'ވ', 6030 => 'ޥ', 6031 => 'މ', 6032 => 'ފ', 6033 => 'ދ', 6034 => 'ޛ', 6035 => 'ތ', 6036 => 'ޘ', 6037 => 'ޠ', 6038 => 'ޡ', 6039 => 'ލ', 6040 => 'ގ', 6041 => 'ޤ', 6042 => 'ޏ', 6043 => 'ސ', 6044 => 'ޝ', 6045 => 'ޞ', 6046 => 'ޟ', 6047 => 'ޑ', 6048 => 'ޒ', 6049 => 'ޓ', 6050 => 'ޔ', 6051 => 'ޕ', 6052 => 'ޖ', 6053 => 'ޗ', 6054 => 'ޱ', 6055 => 'ߊ', 6056 => 'ߋ', 6057 => 'ߌ', 6058 => 'ߍ', 6059 => 'ߎ', 6060 => 'ߏ', 6061 => 'ߐ', 6062 => 'ߑ', 6063 => 'ߒ', 6064 => 'ߓ', 6065 => 'ߔ', 6066 => 'ߕ', 6067 => 'ߖ', 6068 => 'ߗ', 6069 => 'ߘ', 6070 => 'ߙ', 6071 => 'ߚ', 6072 => 'ߛ', 6073 => 'ߜ', 6074 => 'ߝ', 6075 => 'ߞ', 6076 => 'ߟ', 6077 => 'ߠ', 6078 => 'ߡ', 6079 => 'ߢ', 6080 => 'ߣ', 6081 => 'ߤ', 6082 => 'ߥ', 6083 => 'ߦ', 6084 => 'ߧ', 6085 => 'ߴ', 6086 => 'ߵ', 6087 => 'ⴰ', 6088 => 'ⴱ', 6089 => 'ⴲ', 6090 => 'ⴳ', 6091 => 'ⴴ', 6092 => 'ⴵ', 6093 => 'ⴶ', 6094 => 'ⴷ', 6095 => 'ⴸ', 6096 => 'ⴹ', 6097 => 'ⴺ', 6098 => 'ⴻ', 6099 => 'ⴼ', 6100 => 'ⴽ', 6101 => 'ⴾ', 6102 => 'ⴿ', 6103 => 'ⵀ', 6104 => 'ⵁ', 6105 => 'ⵂ', 6106 => 'ⵃ', 6107 => 'ⵄ', 6108 => 'ⵅ', 6109 => 'ⵆ', 6110 => 'ⵇ', 6111 => 'ⵈ', 6112 => 'ⵉ', 6113 => 'ⵊ', 6114 => 'ⵋ', 6115 => 'ⵌ', 6116 => 'ⵍ', 6117 => 'ⵎ', 6118 => 'ⵏ', 6119 => 'ⵐ', 6120 => 'ⵑ', 6121 => 'ⵒ', 6122 => 'ⵓ', 6123 => 'ⵔ', 6124 => 'ⵕ', 6125 => 'ⵖ', 6126 => 'ⵗ', 6127 => 'ⵘ', 6128 => 'ⵙ', 6129 => 'ⵚ', 6130 => 'ⵛ', 6131 => 'ⵜ', 6132 => 'ⵝ', 6133 => 'ⵞ', 6134 => 'ⵟ', 6135 => 'ⵠ', 6136 => 'ⵡ', 6137 => 'ⵢ', 6138 => 'ⵣ', 6139 => 'ⵤ', 6140 => 'ⵥ', 6141 => 'ⵯ', 6142 => 'ሀ', 6143 => 'ሁ', 6144 => 'ሂ', 6145 => 'ሃ', 6146 => 'ሄ', 6147 => 'ህ', 6148 => 'ሆ', 6149 => 'ሇ', 6150 => 'ለ', 6151 => 'ሉ', 6152 => 'ሊ', 6153 => 'ላ', 6154 => 'ሌ', 6155 => 'ል', 6156 => 'ሎ', 6157 => 'ሏ', 6158 => 'ⶀ', 6159 => 'ሐ', 6160 => 'ሑ', 6161 => 'ሒ', 6162 => 'ሓ', 6163 => 'ሔ', 6164 => 'ሕ', 6165 => 'ሖ', 6166 => 'ሗ', 6167 => 'መ', 6168 => 'ሙ', 6169 => 'ሚ', 6170 => 'ማ', 6171 => 'ሜ', 6172 => 'ም', 6173 => 'ሞ', 6174 => 'ሟ', 6175 => 'ᎀ', 6176 => 'ᎁ', 6177 => 'ᎂ', 6178 => 'ᎃ', 6179 => 'ⶁ', 6180 => 'ሠ', 6181 => 'ሡ', 6182 => 'ሢ', 6183 => 'ሣ', 6184 => 'ሤ', 6185 => 'ሥ', 6186 => 'ሦ', 6187 => 'ሧ', 6188 => 'ረ', 6189 => 'ሩ', 6190 => 'ሪ', 6191 => 'ራ', 6192 => 'ሬ', 6193 => 'ር', 6194 => 'ሮ', 6195 => 'ሯ', 6196 => 'ⶂ', 6197 => 'ሰ', 6198 => 'ሱ', 6199 => 'ሲ', 6200 => 'ሳ', 6201 => 'ሴ', 6202 => 'ስ', 6203 => 'ሶ', 6204 => 'ሷ', 6205 => 'ⶃ', 6206 => 'ꬁ', 6207 => 'ꬂ', 6208 => 'ꬃ', 6209 => 'ꬄ', 6210 => 'ꬅ', 6211 => 'ꬆ', 6212 => 'ሸ', 6213 => 'ሹ', 6214 => 'ሺ', 6215 => 'ሻ', 6216 => 'ሼ', 6217 => 'ሽ', 6218 => 'ሾ', 6219 => 'ሿ', 6220 => 'ⶄ', 6221 => 'ቀ', 6222 => 'ቁ', 6223 => 'ቂ', 6224 => 'ቃ', 6225 => 'ቄ', 6226 => 'ቅ', 6227 => 'ቆ', 6228 => 'ቇ', 6229 => 'ቈ', 6230 => 'ቊ', 6231 => 'ቋ', 6232 => 'ቌ', 6233 => 'ቍ', 6234 => 'ቐ', 6235 => 'ቑ', 6236 => 'ቒ', 6237 => 'ቓ', 6238 => 'ቔ', 6239 => 'ቕ', 6240 => 'ቖ', 6241 => 'ቘ', 6242 => 'ቚ', 6243 => 'ቛ', 6244 => 'ቜ', 6245 => 'ቝ', 6246 => 'በ', 6247 => 'ቡ', 6248 => 'ቢ', 6249 => 'ባ', 6250 => 'ቤ', 6251 => 'ብ', 6252 => 'ቦ', 6253 => 'ቧ', 6254 => 'ᎄ', 6255 => 'ᎅ', 6256 => 'ᎆ', 6257 => 'ᎇ', 6258 => 'ⶅ', 6259 => 'ቨ', 6260 => 'ቩ', 6261 => 'ቪ', 6262 => 'ቫ', 6263 => 'ቬ', 6264 => 'ቭ', 6265 => 'ቮ', 6266 => 'ቯ', 6267 => 'ተ', 6268 => 'ቱ', 6269 => 'ቲ', 6270 => 'ታ', 6271 => 'ቴ', 6272 => 'ት', 6273 => 'ቶ', 6274 => 'ቷ', 6275 => 'ⶆ', 6276 => 'ቸ', 6277 => 'ቹ', 6278 => 'ቺ', 6279 => 'ቻ', 6280 => 'ቼ', 6281 => 'ች', 6282 => 'ቾ', 6283 => 'ቿ', 6284 => 'ⶇ', 6285 => 'ኀ', 6286 => 'ኁ', 6287 => 'ኂ', 6288 => 'ኃ', 6289 => 'ኄ', 6290 => 'ኅ', 6291 => 'ኆ', 6292 => 'ኇ', 6293 => 'ኈ', 6294 => 'ኊ', 6295 => 'ኋ', 6296 => 'ኌ', 6297 => 'ኍ', 6298 => 'ነ', 6299 => 'ኑ', 6300 => 'ኒ', 6301 => 'ና', 6302 => 'ኔ', 6303 => 'ን', 6304 => 'ኖ', 6305 => 'ኗ', 6306 => 'ⶈ', 6307 => 'ኘ', 6308 => 'ኙ', 6309 => 'ኚ', 6310 => 'ኛ', 6311 => 'ኜ', 6312 => 'ኝ', 6313 => 'ኞ', 6314 => 'ኟ', 6315 => 'ⶉ', 6316 => 'አ', 6317 => 'ኡ', 6318 => 'ኢ', 6319 => 'ኣ', 6320 => 'ኤ', 6321 => 'እ', 6322 => 'ኦ', 6323 => 'ኧ', 6324 => 'ⶊ', 6325 => 'ከ', 6326 => 'ኩ', 6327 => 'ኪ', 6328 => 'ካ', 6329 => 'ኬ', 6330 => 'ክ', 6331 => 'ኮ', 6332 => 'ኯ', 6333 => 'ኰ', 6334 => 'ኲ', 6335 => 'ኳ', 6336 => 'ኴ', 6337 => 'ኵ', 6338 => 'ኸ', 6339 => 'ኹ', 6340 => 'ኺ', 6341 => 'ኻ', 6342 => 'ኼ', 6343 => 'ኽ', 6344 => 'ኾ', 6345 => 'ዀ', 6346 => 'ዂ', 6347 => 'ዃ', 6348 => 'ዄ', 6349 => 'ዅ', 6350 => 'ወ', 6351 => 'ዉ', 6352 => 'ዊ', 6353 => 'ዋ', 6354 => 'ዌ', 6355 => 'ው', 6356 => 'ዎ', 6357 => 'ዏ', 6358 => 'ዐ', 6359 => 'ዑ', 6360 => 'ዒ', 6361 => 'ዓ', 6362 => 'ዔ', 6363 => 'ዕ', 6364 => 'ዖ', 6365 => 'ዘ', 6366 => 'ዙ', 6367 => 'ዚ', 6368 => 'ዛ', 6369 => 'ዜ', 6370 => 'ዝ', 6371 => 'ዞ', 6372 => 'ዟ', 6373 => 'ⶋ', 6374 => 'ꬑ', 6375 => 'ꬒ', 6376 => 'ꬓ', 6377 => 'ꬔ', 6378 => 'ꬕ', 6379 => 'ꬖ', 6380 => 'ዠ', 6381 => 'ዡ', 6382 => 'ዢ', 6383 => 'ዣ', 6384 => 'ዤ', 6385 => 'ዥ', 6386 => 'ዦ', 6387 => 'ዧ', 6388 => 'የ', 6389 => 'ዩ', 6390 => 'ዪ', 6391 => 'ያ', 6392 => 'ዬ', 6393 => 'ይ', 6394 => 'ዮ', 6395 => 'ዯ', 6396 => 'ደ', 6397 => 'ዱ', 6398 => 'ዲ', 6399 => 'ዳ', 6400 => 'ዴ', 6401 => 'ድ', 6402 => 'ዶ', 6403 => 'ዷ', 6404 => 'ⶌ', 6405 => 'ꬉ', 6406 => 'ꬊ', 6407 => 'ꬋ', 6408 => 'ꬌ', 6409 => 'ꬍ', 6410 => 'ꬎ', 6411 => 'ዸ', 6412 => 'ዹ', 6413 => 'ዺ', 6414 => 'ዻ', 6415 => 'ዼ', 6416 => 'ዽ', 6417 => 'ዾ', 6418 => 'ዿ', 6419 => 'ⶍ', 6420 => 'ጀ', 6421 => 'ጁ', 6422 => 'ጂ', 6423 => 'ጃ', 6424 => 'ጄ', 6425 => 'ጅ', 6426 => 'ጆ', 6427 => 'ጇ', 6428 => 'ⶎ', 6429 => 'ገ', 6430 => 'ጉ', 6431 => 'ጊ', 6432 => 'ጋ', 6433 => 'ጌ', 6434 => 'ግ', 6435 => 'ጎ', 6436 => 'ጏ', 6437 => 'ጐ', 6438 => 'ጒ', 6439 => 'ጓ', 6440 => 'ጔ', 6441 => 'ጕ', 6442 => 'ጘ', 6443 => 'ጙ', 6444 => 'ጚ', 6445 => 'ጛ', 6446 => 'ጜ', 6447 => 'ጝ', 6448 => 'ጞ', 6449 => 'ጟ', 6450 => 'ⶓ', 6451 => 'ⶔ', 6452 => 'ⶕ', 6453 => 'ⶖ', 6454 => 'ጠ', 6455 => 'ጡ', 6456 => 'ጢ', 6457 => 'ጣ', 6458 => 'ጤ', 6459 => 'ጥ', 6460 => 'ጦ', 6461 => 'ጧ', 6462 => 'ⶏ', 6463 => 'ጨ', 6464 => 'ጩ', 6465 => 'ጪ', 6466 => 'ጫ', 6467 => 'ጬ', 6468 => 'ጭ', 6469 => 'ጮ', 6470 => 'ጯ', 6471 => 'ⶐ', 6472 => 'ꬠ', 6473 => 'ꬡ', 6474 => 'ꬢ', 6475 => 'ꬣ', 6476 => 'ꬤ', 6477 => 'ꬥ', 6478 => 'ꬦ', 6479 => 'ጰ', 6480 => 'ጱ', 6481 => 'ጲ', 6482 => 'ጳ', 6483 => 'ጴ', 6484 => 'ጵ', 6485 => 'ጶ', 6486 => 'ጷ', 6487 => 'ⶑ', 6488 => 'ጸ', 6489 => 'ጹ', 6490 => 'ጺ', 6491 => 'ጻ', 6492 => 'ጼ', 6493 => 'ጽ', 6494 => 'ጾ', 6495 => 'ጿ', 6496 => 'ꬨ', 6497 => 'ꬩ', 6498 => 'ꬪ', 6499 => 'ꬫ', 6500 => 'ꬬ', 6501 => 'ꬭ', 6502 => 'ꬮ', 6503 => 'ፀ', 6504 => 'ፁ', 6505 => 'ፂ', 6506 => 'ፃ', 6507 => 'ፄ', 6508 => 'ፅ', 6509 => 'ፆ', 6510 => 'ፇ', 6511 => 'ፈ', 6512 => 'ፉ', 6513 => 'ፊ', 6514 => 'ፋ', 6515 => 'ፌ', 6516 => 'ፍ', 6517 => 'ፎ', 6518 => 'ፏ', 6519 => 'ᎈ', 6520 => 'ᎉ', 6521 => 'ᎊ', 6522 => 'ᎋ', 6523 => 'ፐ', 6524 => 'ፑ', 6525 => 'ፒ', 6526 => 'ፓ', 6527 => 'ፔ', 6528 => 'ፕ', 6529 => 'ፖ', 6530 => 'ፗ', 6531 => 'ᎌ', 6532 => 'ᎍ', 6533 => 'ᎎ', 6534 => 'ᎏ', 6535 => 'ⶒ', 6536 => 'ፘ', 6537 => 'ፙ', 6538 => 'ፚ', 6539 => 'ⶠ', 6540 => 'ⶡ', 6541 => 'ⶢ', 6542 => 'ⶣ', 6543 => 'ⶤ', 6544 => 'ⶥ', 6545 => 'ⶦ', 6546 => 'ⶨ', 6547 => 'ⶩ', 6548 => 'ⶪ', 6549 => 'ⶫ', 6550 => 'ⶬ', 6551 => 'ⶭ', 6552 => 'ⶮ', 6553 => 'ⶰ', 6554 => 'ⶱ', 6555 => 'ⶲ', 6556 => 'ⶳ', 6557 => 'ⶴ', 6558 => 'ⶵ', 6559 => 'ⶶ', 6560 => 'ⶸ', 6561 => 'ⶹ', 6562 => 'ⶺ', 6563 => 'ⶻ', 6564 => 'ⶼ', 6565 => 'ⶽ', 6566 => 'ⶾ', 6567 => 'ⷀ', 6568 => 'ⷁ', 6569 => 'ⷂ', 6570 => 'ⷃ', 6571 => 'ⷄ', 6572 => 'ⷅ', 6573 => 'ⷆ', 6574 => 'ⷈ', 6575 => 'ⷉ', 6576 => 'ⷊ', 6577 => 'ⷋ', 6578 => 'ⷌ', 6579 => 'ⷍ', 6580 => 'ⷎ', 6581 => 'ⷐ', 6582 => 'ⷑ', 6583 => 'ⷒ', 6584 => 'ⷓ', 6585 => 'ⷔ', 6586 => 'ⷕ', 6587 => 'ⷖ', 6588 => 'ⷘ', 6589 => 'ⷙ', 6590 => 'ⷚ', 6591 => 'ⷛ', 6592 => 'ⷜ', 6593 => 'ⷝ', 6594 => 'ⷞ', 6595 => 'ॐ', 6596 => 'ॲ', 6597 => 'ऄ', 6598 => 'अ', 6599 => 'आ', 6600 => 'ॳ', 6601 => 'ॴ', 6602 => 'ॵ', 6603 => 'ॶ', 6604 => 'ॷ', 6605 => 'इ', 6606 => 'ई', 6607 => 'उ', 6608 => 'ऊ', 6609 => 'ऋ', 6610 => 'ॠ', 6611 => 'ऌ', 6612 => 'ॡ', 6613 => 'ऍ', 6614 => 'ऎ', 6615 => 'ए', 6616 => 'ऐ', 6617 => 'ऑ', 6618 => 'ऒ', 6619 => 'ओ', 6620 => 'औ', 6621 => 'क', 6622 => 'ख', 6623 => 'ग', 6624 => 'ॻ', 6625 => 'घ', 6626 => 'ङ', 6627 => 'च', 6628 => 'छ', 6629 => 'ज', 6630 => 'ॹ', 6631 => 'ॼ', 6632 => 'झ', 6633 => 'ञ', 6634 => 'ट', 6635 => 'ठ', 6636 => 'ड', 6637 => 'ॾ', 6638 => 'ढ', 6639 => 'ण', 6640 => 'त', 6641 => 'थ', 6642 => 'द', 6643 => 'ध', 6644 => 'न', 6645 => 'प', 6646 => 'फ', 6647 => 'ब', 6648 => 'ॿ', 6649 => 'भ', 6650 => 'म', 6651 => 'य', 6652 => 'ॺ', 6653 => 'र', 6654 => 'ल', 6655 => 'ळ', 6656 => 'व', 6657 => 'श', 6658 => 'ष', 6659 => 'स', 6660 => 'ह', 6661 => 'ऽ', 6662 => 'ॽ', 6663 => 'ᳩ', 6664 => 'ꣲ', 6665 => 'ꣻ', 6666 => 'অ', 6667 => 'আ', 6668 => 'ই', 6669 => 'ঈ', 6670 => 'উ', 6671 => 'ঊ', 6672 => 'ঋ', 6673 => 'ৠ', 6674 => 'ঌ', 6675 => 'ৡ', 6676 => 'এ', 6677 => 'ঐ', 6678 => 'ও', 6679 => 'ঔ', 6680 => 'ক', 6681 => 'খ', 6682 => 'গ', 6683 => 'ঘ', 6684 => 'ঙ', 6685 => 'চ', 6686 => 'ছ', 6687 => 'জ', 6688 => 'ঝ', 6689 => 'ঞ', 6690 => 'ট', 6691 => 'ঠ', 6692 => 'ড', 6693 => 'ঢ', 6694 => 'ণ', 6695 => 'ত', 6696 => 'থ', 6697 => 'দ', 6698 => 'ধ', 6699 => 'ন', 6700 => 'প', 6701 => 'ফ', 6702 => 'ব', 6703 => 'ভ', 6704 => 'ম', 6705 => 'য', 6706 => 'র', 6707 => 'ৰ', 6708 => 'ল', 6709 => 'ৱ', 6710 => 'শ', 6711 => 'ষ', 6712 => 'স', 6713 => 'হ', 6714 => 'ঽ', 6715 => 'ੴ', 6716 => 'ੳ', 6717 => 'ਉ', 6718 => 'ਊ', 6719 => 'ਓ', 6720 => 'ਅ', 6721 => 'ਆ', 6722 => 'ਐ', 6723 => 'ਔ', 6724 => 'ੲ', 6725 => 'ਇ', 6726 => 'ਈ', 6727 => 'ਏ', 6728 => 'ਸ', 6729 => 'ਹ', 6730 => 'ਕ', 6731 => 'ਖ', 6732 => 'ਗ', 6733 => 'ਘ', 6734 => 'ਙ', 6735 => 'ਚ', 6736 => 'ਛ', 6737 => 'ਜ', 6738 => 'ਝ', 6739 => 'ਞ', 6740 => 'ਟ', 6741 => 'ਠ', 6742 => 'ਡ', 6743 => 'ਢ', 6744 => 'ਣ', 6745 => 'ਤ', 6746 => 'ਥ', 6747 => 'ਦ', 6748 => 'ਧ', 6749 => 'ਨ', 6750 => 'ਪ', 6751 => 'ਫ', 6752 => 'ਬ', 6753 => 'ਭ', 6754 => 'ਮ', 6755 => 'ਯ', 6756 => 'ਰ', 6757 => 'ਲ', 6758 => 'ਵ', 6759 => 'ੜ', 6760 => 'ૐ', 6761 => 'અ', 6762 => 'આ', 6763 => 'ઇ', 6764 => 'ઈ', 6765 => 'ઉ', 6766 => 'ઊ', 6767 => 'ઋ', 6768 => 'ૠ', 6769 => 'ઌ', 6770 => 'ૡ', 6771 => 'ઍ', 6772 => 'એ', 6773 => 'ઐ', 6774 => 'ઑ', 6775 => 'ઓ', 6776 => 'ઔ', 6777 => 'ક', 6778 => 'ખ', 6779 => 'ગ', 6780 => 'ઘ', 6781 => 'ઙ', 6782 => 'ચ', 6783 => 'છ', 6784 => 'જ', 6785 => 'ઝ', 6786 => 'ઞ', 6787 => 'ટ', 6788 => 'ઠ', 6789 => 'ડ', 6790 => 'ઢ', 6791 => 'ણ', 6792 => 'ત', 6793 => 'થ', 6794 => 'દ', 6795 => 'ધ', 6796 => 'ન', 6797 => 'પ', 6798 => 'ફ', 6799 => 'બ', 6800 => 'ભ', 6801 => 'મ', 6802 => 'ય', 6803 => 'ર', 6804 => 'લ', 6805 => 'વ', 6806 => 'શ', 6807 => 'ષ', 6808 => 'સ', 6809 => 'હ', 6810 => 'ળ', 6811 => 'ઽ', 6812 => 'ଅ', 6813 => 'ଆ', 6814 => 'ଇ', 6815 => 'ଈ', 6816 => 'ଉ', 6817 => 'ଊ', 6818 => 'ଋ', 6819 => 'ୠ', 6820 => 'ଌ', 6821 => 'ୡ', 6822 => 'ଏ', 6823 => 'ଐ', 6824 => 'ଓ', 6825 => 'ଔ', 6826 => 'କ', 6827 => 'ଖ', 6828 => 'ଗ', 6829 => 'ଘ', 6830 => 'ଙ', 6831 => 'ଚ', 6832 => 'ଛ', 6833 => 'ଜ', 6834 => 'ଝ', 6835 => 'ଞ', 6836 => 'ଟ', 6837 => 'ଠ', 6838 => 'ଡ', 6839 => 'ଢ', 6840 => 'ଣ', 6841 => 'ତ', 6842 => 'ଥ', 6843 => 'ଦ', 6844 => 'ଧ', 6845 => 'ନ', 6846 => 'ପ', 6847 => 'ଫ', 6848 => 'ବ', 6849 => 'ଭ', 6850 => 'ମ', 6851 => 'ଯ', 6852 => 'ୟ', 6853 => 'ର', 6854 => 'ଲ', 6855 => 'ଳ', 6856 => 'ଵ', 6857 => 'ୱ', 6858 => 'ଶ', 6859 => 'ଷ', 6860 => 'ସ', 6861 => 'ହ', 6862 => 'ଽ', 6863 => 'ௐ', 6864 => 'அ', 6865 => 'ஆ', 6866 => 'இ', 6867 => 'ஈ', 6868 => 'உ', 6869 => 'ஊ', 6870 => 'எ', 6871 => 'ஏ', 6872 => 'ஐ', 6873 => 'ஒ', 6874 => 'ஓ', 6875 => 'ஔ', 6876 => 'ஃ', 6877 => 'க', 6878 => 'ங', 6879 => 'ச', 6880 => 'ஞ', 6881 => 'ட', 6882 => 'ண', 6883 => 'த', 6884 => 'ந', 6885 => 'ப', 6886 => 'ம', 6887 => 'ய', 6888 => 'ர', 6889 => 'ல', 6890 => 'வ', 6891 => 'ழ', 6892 => 'ள', 6893 => 'ற', 6894 => 'ன', 6895 => 'ஜ', 6896 => 'ஶ', 6897 => 'ஷ', 6898 => 'ஸ', 6899 => 'ஹ', 6900 => 'అ', 6901 => 'ఆ', 6902 => 'ఇ', 6903 => 'ఈ', 6904 => 'ఉ', 6905 => 'ఊ', 6906 => 'ఋ', 6907 => 'ౠ', 6908 => 'ఌ', 6909 => 'ౡ', 6910 => 'ఎ', 6911 => 'ఏ', 6912 => 'ఐ', 6913 => 'ఒ', 6914 => 'ఓ', 6915 => 'ఔ', 6916 => 'క', 6917 => 'ఖ', 6918 => 'గ', 6919 => 'ఘ', 6920 => 'ఙ', 6921 => 'చ', 6922 => 'ౘ', 6923 => 'ఛ', 6924 => 'జ', 6925 => 'ౙ', 6926 => 'ఝ', 6927 => 'ఞ', 6928 => 'ట', 6929 => 'ఠ', 6930 => 'డ', 6931 => 'ఢ', 6932 => 'ణ', 6933 => 'త', 6934 => 'థ', 6935 => 'ద', 6936 => 'ధ', 6937 => 'న', 6938 => 'ప', 6939 => 'ఫ', 6940 => 'బ', 6941 => 'భ', 6942 => 'మ', 6943 => 'య', 6944 => 'ర', 6945 => 'ఱ', 6946 => 'ల', 6947 => 'వ', 6948 => 'శ', 6949 => 'ష', 6950 => 'స', 6951 => 'హ', 6952 => 'ళ', 6953 => 'ఽ', 6954 => 'ಅ', 6955 => 'ಆ', 6956 => 'ಇ', 6957 => 'ಈ', 6958 => 'ಉ', 6959 => 'ಊ', 6960 => 'ಋ', 6961 => 'ೠ', 6962 => 'ಌ', 6963 => 'ೡ', 6964 => 'ಎ', 6965 => 'ಏ', 6966 => 'ಐ', 6967 => 'ಒ', 6968 => 'ಓ', 6969 => 'ಔ', 6970 => 'ಕ', 6971 => 'ಖ', 6972 => 'ಗ', 6973 => 'ಘ', 6974 => 'ಙ', 6975 => 'ಚ', 6976 => 'ಛ', 6977 => 'ಜ', 6978 => 'ಝ', 6979 => 'ಞ', 6980 => 'ಟ', 6981 => 'ಠ', 6982 => 'ಡ', 6983 => 'ಢ', 6984 => 'ಣ', 6985 => 'ತ', 6986 => 'ಥ', 6987 => 'ದ', 6988 => 'ಧ', 6989 => 'ನ', 6990 => 'ಪ', 6991 => 'ಫ', 6992 => 'ಬ', 6993 => 'ಭ', 6994 => 'ಮ', 6995 => 'ಯ', 6996 => 'ರ', 6997 => 'ಱ', 6998 => 'ಲ', 6999 => 'ವ', 7000 => 'ಶ', 7001 => 'ಷ', 7002 => 'ಸ', 7003 => 'ಹ', 7004 => 'ಳ', 7005 => 'ೞ', 7006 => 'ಽ', 7007 => 'ೱ', 7008 => 'ೲ', 7009 => 'അ', 7010 => 'ആ', 7011 => 'ഇ', 7012 => 'ഈ', 7013 => 'ഉ', 7014 => 'ഊ', 7015 => 'ഋ', 7016 => 'ൠ', 7017 => 'ഌ', 7018 => 'ൡ', 7019 => 'എ', 7020 => 'ഏ', 7021 => 'ഐ', 7022 => 'ഒ', 7023 => 'ഓ', 7024 => 'ഔ', 7025 => 'ക', 7026 => 'ഖ', 7027 => 'ഗ', 7028 => 'ഘ', 7029 => 'ങ', 7030 => 'ച', 7031 => 'ഛ', 7032 => 'ജ', 7033 => 'ഝ', 7034 => 'ഞ', 7035 => 'ട', 7036 => 'ഠ', 7037 => 'ഡ', 7038 => 'ഢ', 7039 => 'ണ', 7040 => 'ത', 7041 => 'ഥ', 7042 => 'ദ', 7043 => 'ധ', 7044 => 'ന', 7045 => 'ഩ', 7046 => 'പ', 7047 => 'ഫ', 7048 => 'ബ', 7049 => 'ഭ', 7050 => 'മ', 7051 => 'യ', 7052 => 'ര', 7053 => 'ല', 7054 => 'വ', 7055 => 'ശ', 7056 => 'ഷ', 7057 => 'സ', 7058 => 'ഹ', 7059 => 'ള', 7060 => 'ഴ', 7061 => 'റ', 7062 => 'ഺ', 7063 => 'ഽ', 7064 => 'අ', 7065 => 'ආ', 7066 => 'ඇ', 7067 => 'ඈ', 7068 => 'ඉ', 7069 => 'ඊ', 7070 => 'උ', 7071 => 'ඌ', 7072 => 'ඍ', 7073 => 'ඎ', 7074 => 'ඏ', 7075 => 'ඐ', 7076 => 'එ', 7077 => 'ඒ', 7078 => 'ඓ', 7079 => 'ඔ', 7080 => 'ඕ', 7081 => 'ඖ', 7082 => 'ක', 7083 => 'ඛ', 7084 => 'ග', 7085 => 'ඝ', 7086 => 'ඞ', 7087 => 'ඟ', 7088 => 'ච', 7089 => 'ඡ', 7090 => 'ජ', 7091 => 'ඣ', 7092 => 'ඤ', 7093 => 'ඥ', 7094 => 'ඦ', 7095 => 'ට', 7096 => 'ඨ', 7097 => 'ඩ', 7098 => 'ඪ', 7099 => 'ණ', 7100 => 'ඬ', 7101 => 'ත', 7102 => 'ථ', 7103 => 'ද', 7104 => 'ධ', 7105 => 'න', 7106 => 'ඳ', 7107 => 'ප', 7108 => 'ඵ', 7109 => 'බ', 7110 => 'භ', 7111 => 'ම', 7112 => 'ඹ', 7113 => 'ය', 7114 => 'ර', 7115 => 'ල', 7116 => 'ව', 7117 => 'ශ', 7118 => 'ෂ', 7119 => 'ස', 7120 => 'හ', 7121 => 'ළ', 7122 => 'ෆ', 7123 => 'ꯀ', 7124 => 'ꯁ', 7125 => 'ꯂ', 7126 => 'ꯃ', 7127 => 'ꯄ', 7128 => 'ꯅ', 7129 => 'ꯆ', 7130 => 'ꯇ', 7131 => 'ꯈ', 7132 => 'ꯉ', 7133 => 'ꯊ', 7134 => 'ꯋ', 7135 => 'ꯌ', 7136 => 'ꯍ', 7137 => 'ꯎ', 7138 => 'ꯏ', 7139 => 'ꯐ', 7140 => 'ꯑ', 7141 => 'ꯒ', 7142 => 'ꯓ', 7143 => 'ꯔ', 7144 => 'ꯕ', 7145 => 'ꯖ', 7146 => 'ꯗ', 7147 => 'ꯘ', 7148 => 'ꯙ', 7149 => 'ꯚ', 7150 => 'ꯛ', 7151 => 'ꯜ', 7152 => 'ꯝ', 7153 => 'ꯞ', 7154 => 'ꯟ', 7155 => 'ꯠ', 7156 => 'ꯡ', 7157 => 'ꯢ', 7158 => 'ꠀ', 7159 => 'ꠁ', 7160 => 'ꠃ', 7161 => 'ꠄ', 7162 => 'ꠅ', 7163 => 'ꠇ', 7164 => 'ꠈ', 7165 => 'ꠉ', 7166 => 'ꠊ', 7167 => 'ꠌ', 7168 => 'ꠍ', 7169 => 'ꠎ', 7170 => 'ꠏ', 7171 => 'ꠐ', 7172 => 'ꠑ', 7173 => 'ꠒ', 7174 => 'ꠓ', 7175 => 'ꠔ', 7176 => 'ꠕ', 7177 => 'ꠖ', 7178 => 'ꠗ', 7179 => 'ꠘ', 7180 => 'ꠙ', 7181 => 'ꠚ', 7182 => 'ꠛ', 7183 => 'ꠜ', 7184 => 'ꠝ', 7185 => 'ꠞ', 7186 => 'ꠟ', 7187 => 'ꠠ', 7188 => 'ꠡ', 7189 => 'ꠢ', 7190 => 'ꢂ', 7191 => 'ꢃ', 7192 => 'ꢄ', 7193 => 'ꢅ', 7194 => 'ꢆ', 7195 => 'ꢇ', 7196 => 'ꢈ', 7197 => 'ꢉ', 7198 => 'ꢊ', 7199 => 'ꢋ', 7200 => 'ꢌ', 7201 => 'ꢍ', 7202 => 'ꢎ', 7203 => 'ꢏ', 7204 => 'ꢐ', 7205 => 'ꢑ', 7206 => 'ꢒ', 7207 => 'ꢓ', 7208 => 'ꢔ', 7209 => 'ꢕ', 7210 => 'ꢖ', 7211 => 'ꢗ', 7212 => 'ꢘ', 7213 => 'ꢙ', 7214 => 'ꢚ', 7215 => 'ꢛ', 7216 => 'ꢜ', 7217 => 'ꢝ', 7218 => 'ꢞ', 7219 => 'ꢟ', 7220 => 'ꢠ', 7221 => 'ꢡ', 7222 => 'ꢢ', 7223 => 'ꢣ', 7224 => 'ꢤ', 7225 => 'ꢥ', 7226 => 'ꢦ', 7227 => 'ꢧ', 7228 => 'ꢨ', 7229 => 'ꢩ', 7230 => 'ꢪ', 7231 => 'ꢫ', 7232 => 'ꢬ', 7233 => 'ꢭ', 7234 => 'ꢮ', 7235 => 'ꢯ', 7236 => 'ꢰ', 7237 => 'ꢱ', 7238 => 'ꢲ', 7239 => 'ꢳ', 7240 => '𑂃', 7241 => '𑂄', 7242 => '𑂅', 7243 => '𑂆', 7244 => '𑂇', 7245 => '𑂈', 7246 => '𑂉', 7247 => '𑂊', 7248 => '𑂋', 7249 => '𑂌', 7250 => '𑂍', 7251 => '𑂎', 7252 => '𑂏', 7253 => '𑂐', 7254 => '𑂑', 7255 => '𑂒', 7256 => '𑂓', 7257 => '𑂔', 7258 => '𑂕', 7259 => '𑂖', 7260 => '𑂗', 7261 => '𑂘', 7262 => '𑂙', 7263 => '𑂛', 7264 => '𑂝', 7265 => '𑂞', 7266 => '𑂟', 7267 => '𑂠', 7268 => '𑂡', 7269 => '𑂢', 7270 => '𑂣', 7271 => '𑂤', 7272 => '𑂥', 7273 => '𑂦', 7274 => '𑂧', 7275 => '𑂨', 7276 => '𑂩', 7277 => '𑂪', 7278 => '𑂬', 7279 => '𑂭', 7280 => '𑂮', 7281 => '𑂯', 7282 => 'ᮃ', 7283 => 'ᮄ', 7284 => 'ᮅ', 7285 => 'ᮆ', 7286 => 'ᮇ', 7287 => 'ᮈ', 7288 => 'ᮉ', 7289 => 'ᮊ', 7290 => 'ᮮ', 7291 => 'ᮋ', 7292 => 'ᮌ', 7293 => 'ᮍ', 7294 => 'ᮎ', 7295 => 'ᮏ', 7296 => 'ᮐ', 7297 => 'ᮑ', 7298 => 'ᮒ', 7299 => 'ᮓ', 7300 => 'ᮔ', 7301 => 'ᮕ', 7302 => 'ᮖ', 7303 => 'ᮗ', 7304 => 'ᮘ', 7305 => 'ᮙ', 7306 => 'ᮚ', 7307 => 'ᮛ', 7308 => 'ᮜ', 7309 => 'ᮝ', 7310 => 'ᮞ', 7311 => 'ᮟ', 7312 => 'ᮯ', 7313 => 'ᮠ', 7314 => '𑀅', 7315 => '𑀆', 7316 => '𑀇', 7317 => '𑀈', 7318 => '𑀉', 7319 => '𑀊', 7320 => '𑀋', 7321 => '𑀌', 7322 => '𑀍', 7323 => '𑀎', 7324 => '𑀏', 7325 => '𑀐', 7326 => '𑀑', 7327 => '𑀒', 7328 => '𑀓', 7329 => '𑀔', 7330 => '𑀕', 7331 => '𑀖', 7332 => '𑀗', 7333 => '𑀘', 7334 => '𑀙', 7335 => '𑀚', 7336 => '𑀛', 7337 => '𑀜', 7338 => '𑀝', 7339 => '𑀞', 7340 => '𑀟', 7341 => '𑀠', 7342 => '𑀡', 7343 => '𑀢', 7344 => '𑀣', 7345 => '𑀤', 7346 => '𑀥', 7347 => '𑀦', 7348 => '𑀧', 7349 => '𑀨', 7350 => '𑀩', 7351 => '𑀪', 7352 => '𑀫', 7353 => '𑀬', 7354 => '𑀭', 7355 => '𑀮', 7356 => '𑀯', 7357 => '𑀰', 7358 => '𑀱', 7359 => '𑀲', 7360 => '𑀳', 7361 => '𑀃', 7362 => '𑀄', 7363 => '𑀴', 7364 => '𑀵', 7365 => '𑀶', 7366 => '𑀷', 7367 => '𐨀', 7368 => '𐨐', 7369 => '𐨑', 7370 => '𐨒', 7371 => '𐨓', 7372 => '𐨕', 7373 => '𐨖', 7374 => '𐨗', 7375 => '𐨙', 7376 => '𐨚', 7377 => '𐨛', 7378 => '𐨜', 7379 => '𐨝', 7380 => '𐨞', 7381 => '𐨟', 7382 => '𐨠', 7383 => '𐨡', 7384 => '𐨢', 7385 => '𐨣', 7386 => '𐨤', 7387 => '𐨥', 7388 => '𐨦', 7389 => '𐨧', 7390 => '𐨨', 7391 => '𐨩', 7392 => '𐨪', 7393 => '𐨫', 7394 => '𐨬', 7395 => '𐨭', 7396 => '𐨮', 7397 => '𐨯', 7398 => '𐨰', 7399 => '𐨱', 7400 => '𐨲', 7401 => '𐨳', 7402 => 'ก', 7403 => 'ข', 7404 => 'ฃ', 7405 => 'ค', 7406 => 'ฅ', 7407 => 'ฆ', 7408 => 'ง', 7409 => 'จ', 7410 => 'ฉ', 7411 => 'ช', 7412 => 'ซ', 7413 => 'ฌ', 7414 => 'ญ', 7415 => 'ฎ', 7416 => 'ฏ', 7417 => 'ฐ', 7418 => 'ฑ', 7419 => 'ฒ', 7420 => 'ณ', 7421 => 'ด', 7422 => 'ต', 7423 => 'ถ', 7424 => 'ท', 7425 => 'ธ', 7426 => 'น', 7427 => 'บ', 7428 => 'ป', 7429 => 'ผ', 7430 => 'ฝ', 7431 => 'พ', 7432 => 'ฟ', 7433 => 'ภ', 7434 => 'ม', 7435 => 'ย', 7436 => 'ร', 7437 => 'ฤ', 7438 => 'ล', 7439 => 'ฦ', 7440 => 'ว', 7441 => 'ศ', 7442 => 'ษ', 7443 => 'ส', 7444 => 'ห', 7445 => 'ฬ', 7446 => 'อ', 7447 => 'ฮ', 7448 => 'ฯ', 7449 => 'ะ', 7450 => 'า', 7451 => 'ำ', 7452 => 'เ', 7453 => 'แ', 7454 => 'โ', 7455 => 'ใ', 7456 => 'ไ', 7457 => 'ๅ', 7458 => 'ກ', 7459 => 'ຂ', 7460 => 'ຄ', 7461 => 'ງ', 7462 => 'ຈ', 7463 => 'ສ', 7464 => 'ຊ', 7465 => 'ຍ', 7466 => 'ດ', 7467 => 'ຕ', 7468 => 'ຖ', 7469 => 'ທ', 7470 => 'ນ', 7471 => 'ບ', 7472 => 'ປ', 7473 => 'ຜ', 7474 => 'ຝ', 7475 => 'ພ', 7476 => 'ຟ', 7477 => 'ມ', 7478 => 'ຢ', 7479 => 'ຣ', 7480 => 'ລ', 7481 => 'ວ', 7482 => 'ຫ', 7483 => 'ອ', 7484 => 'ຮ', 7485 => 'ຯ', 7486 => 'ະ', 7487 => 'າ', 7488 => 'ຳ', 7489 => 'ຽ', 7490 => 'ເ', 7491 => 'ແ', 7492 => 'ໂ', 7493 => 'ໃ', 7494 => 'ໄ', 7495 => 'ꪀ', 7496 => 'ꪁ', 7497 => 'ꪂ', 7498 => 'ꪃ', 7499 => 'ꪄ', 7500 => 'ꪅ', 7501 => 'ꪆ', 7502 => 'ꪇ', 7503 => 'ꪈ', 7504 => 'ꪉ', 7505 => 'ꪊ', 7506 => 'ꪋ', 7507 => 'ꪌ', 7508 => 'ꪍ', 7509 => 'ꪎ', 7510 => 'ꪏ', 7511 => 'ꪐ', 7512 => 'ꪑ', 7513 => 'ꪒ', 7514 => 'ꪓ', 7515 => 'ꪔ', 7516 => 'ꪕ', 7517 => 'ꪖ', 7518 => 'ꪗ', 7519 => 'ꪘ', 7520 => 'ꪙ', 7521 => 'ꪚ', 7522 => 'ꪛ', 7523 => 'ꪜ', 7524 => 'ꪝ', 7525 => 'ꪞ', 7526 => 'ꪟ', 7527 => 'ꪠ', 7528 => 'ꪡ', 7529 => 'ꪢ', 7530 => 'ꪣ', 7531 => 'ꪤ', 7532 => 'ꪥ', 7533 => 'ꪦ', 7534 => 'ꪧ', 7535 => 'ꪨ', 7536 => 'ꪩ', 7537 => 'ꪪ', 7538 => 'ꪫ', 7539 => 'ꪬ', 7540 => 'ꪭ', 7541 => 'ꪮ', 7542 => 'ꪯ', 7543 => 'ꪱ', 7544 => 'ꪵ', 7545 => 'ꪶ', 7546 => 'ꪹ', 7547 => 'ꪺ', 7548 => 'ꪻ', 7549 => 'ꪼ', 7550 => 'ꪽ', 7551 => 'ꫀ', 7552 => 'ꫂ', 7553 => 'ꫛ', 7554 => 'ꫜ', 7555 => 'ཀ', 7556 => 'ཫ', 7557 => 'ཁ', 7558 => 'ག', 7559 => 'ང', 7560 => 'ཅ', 7561 => 'ཆ', 7562 => 'ཇ', 7563 => 'ཉ', 7564 => 'ཊ', 7565 => 'ཋ', 7566 => 'ཌ', 7567 => 'ཎ', 7568 => 'ཏ', 7569 => 'ཐ', 7570 => 'ད', 7571 => 'ན', 7572 => 'པ', 7573 => 'ཕ', 7574 => 'བ', 7575 => 'མ', 7576 => 'ཙ', 7577 => 'ཚ', 7578 => 'ཛ', 7579 => 'ཝ', 7580 => 'ཞ', 7581 => 'ཟ', 7582 => 'འ', 7583 => 'ཡ', 7584 => 'ར', 7585 => 'ཬ', 7586 => 'ལ', 7587 => 'ཤ', 7588 => 'ཥ', 7589 => 'ས', 7590 => 'ཧ', 7591 => 'ཨ', 7592 => 'ྈ', 7593 => 'ྉ', 7594 => 'ྌ', 7595 => 'ྊ', 7596 => 'ྋ', 7597 => 'ᰀ', 7598 => 'ᰁ', 7599 => 'ᰂ', 7600 => 'ᰃ', 7601 => 'ᰄ', 7602 => 'ᰅ', 7603 => 'ᰆ', 7604 => 'ᰇ', 7605 => 'ᰈ', 7606 => 'ᰉ', 7607 => 'ᱍ', 7608 => 'ᱎ', 7609 => 'ᱏ', 7610 => 'ᰊ', 7611 => 'ᰋ', 7612 => 'ᰌ', 7613 => 'ᰍ', 7614 => 'ᰎ', 7615 => 'ᰏ', 7616 => 'ᰐ', 7617 => 'ᰑ', 7618 => 'ᰒ', 7619 => 'ᰓ', 7620 => 'ᰔ', 7621 => 'ᰕ', 7622 => 'ᰖ', 7623 => 'ᰗ', 7624 => 'ᰘ', 7625 => 'ᰙ', 7626 => 'ᰚ', 7627 => 'ᰛ', 7628 => 'ᰜ', 7629 => 'ᰝ', 7630 => 'ᰞ', 7631 => 'ᰟ', 7632 => 'ᰠ', 7633 => 'ᰡ', 7634 => 'ᰢ', 7635 => 'ᰣ', 7636 => 'ꡀ', 7637 => 'ꡁ', 7638 => 'ꡂ', 7639 => 'ꡃ', 7640 => 'ꡄ', 7641 => 'ꡅ', 7642 => 'ꡆ', 7643 => 'ꡇ', 7644 => 'ꡩ', 7645 => 'ꡪ', 7646 => 'ꡫ', 7647 => 'ꡬ', 7648 => 'ꡈ', 7649 => 'ꡉ', 7650 => 'ꡊ', 7651 => 'ꡋ', 7652 => 'ꡌ', 7653 => 'ꡍ', 7654 => 'ꡎ', 7655 => 'ꡏ', 7656 => 'ꡐ', 7657 => 'ꡑ', 7658 => 'ꡒ', 7659 => 'ꡓ', 7660 => 'ꡧ', 7661 => 'ꡔ', 7662 => 'ꡕ', 7663 => 'ꡖ', 7664 => 'ꡗ', 7665 => 'ꡨ', 7666 => 'ꡭ', 7667 => 'ꡘ', 7668 => 'ꡱ', 7669 => 'ꡲ', 7670 => 'ꡙ', 7671 => 'ꡚ', 7672 => 'ꡮ', 7673 => 'ꡛ', 7674 => 'ꡜ', 7675 => 'ꡯ', 7676 => 'ꡰ', 7677 => 'ꡝ', 7678 => 'ꡢ', 7679 => 'ꡣ', 7680 => 'ꡤ', 7681 => 'ꡥ', 7682 => 'ꡞ', 7683 => 'ꡟ', 7684 => 'ꡠ', 7685 => 'ꡡ', 7686 => 'ꡦ', 7687 => 'ꡳ', 7688 => 'ᤀ', 7689 => 'ᤁ', 7690 => 'ᤂ', 7691 => 'ᤃ', 7692 => 'ᤄ', 7693 => 'ᤅ', 7694 => 'ᤆ', 7695 => 'ᤇ', 7696 => 'ᤈ', 7697 => 'ᤉ', 7698 => 'ᤊ', 7699 => 'ᤋ', 7700 => 'ᤌ', 7701 => 'ᤍ', 7702 => 'ᤎ', 7703 => 'ᤏ', 7704 => 'ᤐ', 7705 => 'ᤑ', 7706 => 'ᤒ', 7707 => 'ᤓ', 7708 => 'ᤔ', 7709 => 'ᤕ', 7710 => 'ᤖ', 7711 => 'ᤗ', 7712 => 'ᤘ', 7713 => 'ᤙ', 7714 => 'ᤚ', 7715 => 'ᤛ', 7716 => 'ᤜ', 7717 => 'ᜀ', 7718 => 'ᜁ', 7719 => 'ᜂ', 7720 => 'ᜃ', 7721 => 'ᜄ', 7722 => 'ᜅ', 7723 => 'ᜆ', 7724 => 'ᜇ', 7725 => 'ᜈ', 7726 => 'ᜉ', 7727 => 'ᜊ', 7728 => 'ᜋ', 7729 => 'ᜌ', 7730 => 'ᜎ', 7731 => 'ᜏ', 7732 => 'ᜐ', 7733 => 'ᜑ', 7734 => 'ᜠ', 7735 => 'ᜡ', 7736 => 'ᜢ', 7737 => 'ᜣ', 7738 => 'ᜤ', 7739 => 'ᜥ', 7740 => 'ᜦ', 7741 => 'ᜧ', 7742 => 'ᜨ', 7743 => 'ᜩ', 7744 => 'ᜪ', 7745 => 'ᜫ', 7746 => 'ᜬ', 7747 => 'ᜭ', 7748 => 'ᜮ', 7749 => 'ᜯ', 7750 => 'ᜰ', 7751 => 'ᜱ', 7752 => 'ᝀ', 7753 => 'ᝁ', 7754 => 'ᝂ', 7755 => 'ᝃ', 7756 => 'ᝄ', 7757 => 'ᝅ', 7758 => 'ᝆ', 7759 => 'ᝇ', 7760 => 'ᝈ', 7761 => 'ᝉ', 7762 => 'ᝊ', 7763 => 'ᝋ', 7764 => 'ᝌ', 7765 => 'ᝍ', 7766 => 'ᝎ', 7767 => 'ᝏ', 7768 => 'ᝐ', 7769 => 'ᝑ', 7770 => 'ᝠ', 7771 => 'ᝡ', 7772 => 'ᝢ', 7773 => 'ᝣ', 7774 => 'ᝤ', 7775 => 'ᝥ', 7776 => 'ᝦ', 7777 => 'ᝧ', 7778 => 'ᝨ', 7779 => 'ᝩ', 7780 => 'ᝪ', 7781 => 'ᝫ', 7782 => 'ᝬ', 7783 => 'ᝮ', 7784 => 'ᝯ', 7785 => 'ᝰ', 7786 => 'ᨀ', 7787 => 'ᨁ', 7788 => 'ᨂ', 7789 => 'ᨃ', 7790 => 'ᨄ', 7791 => 'ᨅ', 7792 => 'ᨆ', 7793 => 'ᨇ', 7794 => 'ᨈ', 7795 => 'ᨉ', 7796 => 'ᨊ', 7797 => 'ᨋ', 7798 => 'ᨌ', 7799 => 'ᨍ', 7800 => 'ᨎ', 7801 => 'ᨏ', 7802 => 'ᨐ', 7803 => 'ᨑ', 7804 => 'ᨒ', 7805 => 'ᨓ', 7806 => 'ᨔ', 7807 => 'ᨕ', 7808 => 'ᨖ', 7809 => 'ᯀ', 7810 => 'ᯂ', 7811 => 'ᯅ', 7812 => 'ᯇ', 7813 => 'ᯉ', 7814 => 'ᯋ', 7815 => 'ᯎ', 7816 => 'ᯐ', 7817 => 'ᯑ', 7818 => 'ᯒ', 7819 => 'ᯔ', 7820 => 'ᯖ', 7821 => 'ᯘ', 7822 => 'ᯛ', 7823 => 'ᯝ', 7824 => 'ᯞ', 7825 => 'ᯠ', 7826 => 'ᯡ', 7827 => 'ᯢ', 7828 => 'ᯣ', 7829 => 'ᯤ', 7830 => 'ᯥ', 7831 => 'ꤰ', 7832 => 'ꤱ', 7833 => 'ꤲ', 7834 => 'ꤳ', 7835 => 'ꤴ', 7836 => 'ꤵ', 7837 => 'ꤶ', 7838 => 'ꤷ', 7839 => 'ꤸ', 7840 => 'ꤹ', 7841 => 'ꤺ', 7842 => 'ꤻ', 7843 => 'ꤼ', 7844 => 'ꤽ', 7845 => 'ꤾ', 7846 => 'ꤿ', 7847 => 'ꥀ', 7848 => 'ꥁ', 7849 => 'ꥂ', 7850 => 'ꥃ', 7851 => 'ꥄ', 7852 => 'ꥅ', 7853 => 'ꥆ', 7854 => 'ꤊ', 7855 =&